/**----------------------------------------------------------------------------
 * GreasySpoon
 * Copyright (C) 2008 Karel Mittig
 *-----------------------------------------------------------------------------
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  Please refer to the LICENSE.txt file that comes along with this source file
 *  or to http://www.gnu.org/licenses/gpl.txt for a full version of the license.
 *
 *-----------------------------------------------------------------------------
 * For any comment, question, suggestion, bugfix or code contribution please
 * contact Karel Mittig : karel [dot] mittig [at] gmail [dot] com
 * Created     : 2/06/01
 *--------------------------------------------------------*/
 
package tools.logger;

///////////////////////////////////
// 	Import
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.zip.*;
import java.util.logging.Level;
///////////////////////////////////

//-----------------------------------------------------------------------------

/**
 * Threaded server allowing to store messages in logs file with log rotation, 
 * zip compression of rotated files and files deletion if file number exceeds 
 * a defined limit.<br>
 * Started before Apache log4j, kept because it still provides features not
 * directly embedded in Log4j: compression, files rotation and overwriting. 
 * @version 1.0, 6/08/03
 * @author Karel Mittig 
 */
public class Log extends Level{ 

	//final private static boolean debug = false; 
	private static final long serialVersionUID = 7129015785377379428L;

	//Log file in which to store messages
    /**Redirect messages in access log file (normally used to trace applicative traffic)*/
	public final static short ACCESS		= 0;
	/**Redirect messages  in server log file (normally used to trace server configuration/administration)*/
	public final static short ERROR 		= 1;
	/**Redirect messages in debug log file (normally used for debugging)*/
	public final static short TRACE			= 2;
	/**Redirect messages in service log file (normally used for service operations)*/
	public final static short SERVICE		= 3;
	
	/**Redirect messages  in error log file (normally used to trace server configuration/errors)*/	
	public final static short ADMIN 		= 4;
	
	/**flag used to force logs rotation / cleaning */
    public final static short MAINTENANCE       = 255;
	
	/**logs directory*/
	protected static String logPath = "./";
	/**file extension for log files*/
	protected static String extension = ".log";

	/**Supported log files*/
	protected static File debugLog;
	protected static File errorLog;
	protected static File adminLog;
	protected static File accessLog;
	protected static File serviceLog;

	
	/**Thread used to compress rotated log files (under zip format)*/
	protected Zipper zipper;
	
	/**
	* Threshold entries limit in after which a log file is rotated
	* Default value is set to 100000, which corresponds to an average log file size of
	* 0->8000 kB, and zipped files of 125 kB.
	*/
	protected static int maxentries = 10000;
	
	/**
	* Log files Threshold after which oldest files are deleted.
	* Default value is set to 100 (of each log file type)
    * Set to -1 for unlimited files
	* If threshold is reached, oldest file is deleted before log rotation
	*/
	protected static int maxfiles = 100;
	
	/**
	* Set if disk access must be buffered or not
	* Buffered mode must be preferred for "high performance" solutions, while
    * unbuffered access must be used for real time log processing.
	* Disk access is unbuffered by default.
	*/
	protected final static boolean isBuffered = false;
	
	
	/**internal log entries counters.*/
	private static int accessEntriesCounter = 0;
	private static int errorEntriesCounter = 0;
	private static int adminEntriesCounter = 0;
	private static int debugEntriesCounter = 0;
	private static int serviceEntriesCounter = 0;

	/**Internal debug level value.*/
	private static Level loglevel = Level.INFO;
	
	/**Log files names*/
	protected static String debugName	= "debug";
	protected static String errorName 	= "error";
	protected static String adminName 	= "admin";
	protected static String accessName 	= "access";
	protected static String serviceName = "service";
	
	/**Internal flows*/
	private static BufferedOutputStream accessBufferedOutputStream;
	private static BufferedOutputStream adminBufferedOutputStream;
	private static BufferedOutputStream errorBufferedOutputStream;
	private static BufferedOutputStream debugBufferedOutputStream;
	private static BufferedOutputStream serviceBufferedOutputStream;

	/**Variable interne.*/
	private static FileOutputStream accessFileoutput;
	private static FileOutputStream debugFileoutput;
	private static FileOutputStream errorFileoutput;
	private static FileOutputStream adminFileoutput;
	private static FileOutputStream serviceFileoutput;

	/**Static class instance for static access*/
	private static Log logger = new Log();
    /**Platform line separator*/
	public static final String returnchar = System.getProperty("line.separator");
	
	private static boolean enabled = false;
	private static boolean debugEnabled = true;
	private static boolean errorEnabled = true;
	private static boolean adminEnabled = true;
	private static boolean accessEnabled = true;
	private static boolean serviceEnabled = true;
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/**Constructor, initialize logs files, archive existing logs if not empty*/
private Log() {
    super("Rotation log",Level.INFO.intValue());
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/**
 * Modify log repository<br>
 * <b>Provided directory MUST exist</b>
 * @param path the new Path where logs must be stored
 */
public static void setLogPath(String path){
    File f= new File(path);
    if (!f.isDirectory()){
    	System.err.println("Invalid log directory ["+path+"]");
    	return;
    }
    if (logPath.equals(f.getAbsolutePath()+File.separator)) return;
 	logPath = f.getAbsolutePath()+File.separator;
	if (enabled) {
		disable();
		enable();
	}
 	Log.error(Log.CONFIG,"Log directory set to ["+logPath+"]");
}

/**
 * @return directory in which log files are stored
 */
public static  String getLogPath(){return logPath;}
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
/**
 * Initialize and enable the Log process.
 * Create a static log object that can be used by any class to store log infos. 
 * This method MUST be called before generating logs (orelse log messages will be discarded).
 */
public static void enable(){
	logger.init();
	enabled = true;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/**
 * @return true if logs are enabled
 */
public static boolean isEnable(){
	return enabled;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/**
 * Stop the Log process
 */
public static void disable(){
	logger.finalize();
	enabled = false;
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/**
 * Initialize the different logs files (access, error, debug)
 * Note: debug file is only initialized if debug > NONE (0)
 */
private void init(){
	try {
			if (accessBufferedOutputStream==null){
				accessLog = new File(logPath+accessName+extension);
			  	accessFileoutput = new FileOutputStream(accessLog,true);
				accessBufferedOutputStream = new BufferedOutputStream(accessFileoutput);
			}
			if (errorBufferedOutputStream==null){
				errorLog = new File(logPath+errorName+extension);
		   		errorFileoutput = new FileOutputStream(errorLog,true);
				errorBufferedOutputStream = new BufferedOutputStream(errorFileoutput);
			}
			if (loglevel!=Level.OFF &&debugBufferedOutputStream==null) {
				debugLog = new File(logPath+debugName+extension);
		   		debugFileoutput = new FileOutputStream(debugLog,true);
				debugBufferedOutputStream = new BufferedOutputStream(debugFileoutput);
			}
			if (serviceBufferedOutputStream==null) {
				serviceLog = new File(logPath+serviceName+extension);
		   		serviceFileoutput = new FileOutputStream(serviceLog,true);
				serviceBufferedOutputStream = new BufferedOutputStream(serviceFileoutput);
			}
			if (adminBufferedOutputStream==null) {
				adminLog = new File(logPath+adminName+extension);
		   		adminFileoutput = new FileOutputStream(adminLog,true);
		   		adminBufferedOutputStream = new BufferedOutputStream(adminFileoutput);
			}
	} catch(Exception e){
			System.err.println("Cannot initialize Log: " + e.toString());
	}// try&catch
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/**On stop, close opened streams*/
protected void finalize() {
	if (accessBufferedOutputStream!=null)  {
		closeStream(accessBufferedOutputStream);
		accessBufferedOutputStream=null;
	}
	if (errorBufferedOutputStream!=null) {
		closeStream(errorBufferedOutputStream);
		errorBufferedOutputStream=null;
	}
	if (debugBufferedOutputStream!=null)  {
		closeStream(debugBufferedOutputStream);
		debugBufferedOutputStream=null;
	}
	if (serviceBufferedOutputStream!=null)  {
		closeStream(serviceBufferedOutputStream);
		serviceBufferedOutputStream=null;
	}
	if (adminBufferedOutputStream!=null)  {
		closeStream(adminBufferedOutputStream);
		adminBufferedOutputStream=null;
	}
}
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
private void closeStream(BufferedOutputStream stream){
	try {
		stream.flush();
		stream.close();
	} catch(Exception e){
		Log.error(Level.SEVERE, "Log error ==> Unable to close stream: ", e);
	}
}
//-----------------------------------------------------------------------------


//--------------------------------------------------------------------------------------
/**
* Log Rotation method (compress current log file).
* This method is called when internal log counter reaches defined entries threshold
* TODO: Add a scheduled rotation (every hours, ...)
* Compressed file is stored using [log file name] + [absolute time in milliseconds].
* This name format allows to easily find the oldest file to suppress if needed
* @param filename The log filename (prefix) to rotate
*/
public final synchronized void rotateLog(String filename) {
	File fichierLog = new File(logPath+filename+extension);
	if (!fichierLog.exists() || fichierLog.length()==0) {
		return;
	}

	//Check if zipped files are exceeding max threshold. If yes delete oldest one.
	if (maxfiles>0){
		try{
		    //get zipped files list
	        File profilesDirectory = new File(logPath);
	        Filter logfilter = retrieveFilter(profilesDirectory,filename,".zip");
	        //filter contains zip files number and oldest one
	        if (logfilter.oldestfile!=null && logfilter.size>=maxfiles){
	            //System.out.println("Rotation: Too much files. Deleting "+logfilter.oldestfile);
	            new File(logPath+logfilter.oldestfile).delete();
	        }
	    }catch (Exception e){
	    }
	}

	try {
		String nameToZip = filename+System.currentTimeMillis();
		File rotatefile = new File(nameToZip+extension);
		if (fichierLog.renameTo(new File(nameToZip+extension))) {
			fichierLog.delete();
			new Zipper(rotatefile, nameToZip);
		} 
	}catch (Exception e) {
		Log.error(Level.SEVERE, "Error while rotating log: " + filename, e);
	}
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Retrieve file list from given directory starting with given prefix and ending with given suffix 
 * @param directory Directory to search files in
 * @param prefix Prefix to filter
 * @param suffix Suffix to filter
 * @return Files matching the criteria
 */
public File[] filter(File directory, String prefix, String suffix){
    Filter filter = new Filter(prefix, suffix);
    return directory.listFiles(filter);
}
/**
 * Create a file filter from given directory starting with given prefix and ending with given suffix 
 * @param directory Directory to search files in
 * @param prefix Prefix to filter
 * @param suffix Suffix to filter
 * @return Associated Filter based on given criteria
 */
public Filter retrieveFilter(File directory, String prefix, String suffix){
    Filter filter = new Filter(prefix, suffix);
    directory.listFiles(filter);
    return filter;
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Internal class used to retrieve oldest compressed log file
 */
private class Filter implements FilenameFilter{
    String prefix, suffix, oldestfile;
    int size = 0;
    /**
     * Filter files starting with given prefix and ending with given suffix
     * @param _prefix Log filename (service, server, debug, access)
     * @param _suffix (.log, .zip, ...)
     */
    public Filter(String _prefix, String _suffix){
        this.prefix = _prefix;
        this.suffix = _suffix;
    }
    /**@return the oldest file matching this filter*/
    //public String getOldestfile(){return oldestfile;}
    /** @return number of files matching this filter*/
    //public int getSize(){return size;}
    
    /**
     * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
     */
    public boolean accept(File dir,String name){
        if (name.startsWith(prefix) && name.endsWith(suffix)){
            if (oldestfile==null) oldestfile = name;
            else oldestfile = (oldestfile.compareTo(name)<0)?oldestfile:name;
            size++;
            return true;
        }
        return false;
    }
}
//--------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------
/**
* Internal Thread used to compress log files
* Compression level is set to maximum (=9).
*/
private class Zipper extends Thread  {
	File fileToZip;
	String nameToZip;
	static final int BUFFER = 2048;
	byte data[] = new byte[BUFFER];
	/**
	 * Create a thread that will compress given file  
	 * @param _fileToZip file to compress
	 * @param _nameToZip name of the compressed file
	 */
	public Zipper(File _fileToZip, String _nameToZip) {
		super("Zipper");
		this.setPriority(Thread.MIN_PRIORITY);
		fileToZip = _fileToZip;
		nameToZip = _nameToZip;
		start();
	}

	/**
	 * @see java.lang.Thread#run()
	 */
	public final void run() {
		try  {
		   BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileToZip), BUFFER);
			ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(logPath+nameToZip+".zip",true)));
			zos.setLevel(6);
			zos.putNextEntry(new ZipEntry(nameToZip+extension));
			int readedbytes;
			while (( readedbytes = bis.read(data, 0,BUFFER)) != -1 ){
				zos.write(data, 0, readedbytes);
			}//End while readLine
			bis.close();
			zos.close();
			fileToZip.delete();
		}catch (Exception e) {
			error(Level.SEVERE, "Error while zipping the log : ",e);
		}
	}//End run
}//End Thread Zipper
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Store a message in ACCESS log.<br>
 * All accesses should be logged (so no Level is asked) 
 * @param message Message content
 */
public final static synchronized void access(String message) {
	if (!accessEnabled || loglevel == Level.OFF ) return;
	logger.store(ACCESS, null, new StringBuilder(message));
}
/**
 * Store a message in ACCESS log.<br>
 * All accesses should be logued (so no Level is asked)
 * @param message Message content
 */
public final static synchronized void access(StringBuilder message) {
	if (!accessEnabled || loglevel == Level.OFF ) return;
    logger.store(ACCESS, null, message);
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Store a message in SERVICE log
 * @param level Message Level
 * @param message Message content
 */
public final static synchronized void service(Level level, String message) {
	if (!serviceEnabled || level.intValue()<loglevel.intValue()) return;
	logger.store(SERVICE, level, new StringBuilder(message));
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Store a message in SERVER log
 * @param level Message Level
 * @param message Message content
 */
public final static synchronized void error(Level level, String message) {
	if (!errorEnabled || level.intValue()<loglevel.intValue()) return;
	logger.store(ERROR, level, new StringBuilder(message));
}
//--------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------
/**
 * Store a message in SERVER log
 * @param level Message Level
 * @param message Message content
 * @param exception Associated exception
 */
public final static synchronized void error(Level level,String message, Exception exception) {
	if (!errorEnabled || level.intValue()<loglevel.intValue()) return;
	logger.store(ERROR, level,new StringBuilder(message).append("\t").append(exception.getMessage()));
}
/**
 * Store a message in SERVER log
 * @param level Message Level
 * @param message Message content
 * @param throwable Associated exception
 */
public final static synchronized void error(Level level,String message, Throwable throwable) {
	if (!errorEnabled || level.intValue()<loglevel.intValue()) return;
	logger.store(ERROR, level,new StringBuilder(message).append("\t").append(throwable.toString()));
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Store a message in SERVER log
 * @param level Message Level
 * @param message Message content
 */
public final static synchronized void admin(Level level, String message) {
	if (!adminEnabled || level.intValue()<loglevel.intValue()) return;
	logger.store(ADMIN, level, new StringBuilder(message));
}
//--------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------
/**
 * Store a message in SERVER log
 * @param level Message Level
 * @param message Message content
 * @param exception Associated exception
 */
public final static synchronized void admin(Level level,String message, Exception exception) {
	if (!adminEnabled || level.intValue()<loglevel.intValue()) return;
	logger.store(ADMIN, level,new StringBuilder(message).append("\t").append(exception.getMessage()));
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Store a message in debug log
 * @param level	the message level
 * @param message Message content	
 */
public final static synchronized void trace(Level level, String message) {
	if (!debugEnabled  || level.intValue()<loglevel.intValue()) return;
	logger.store(TRACE,level, new StringBuilder(message));
}
//--------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------
/**
 * Store a message in debug log with given level
 * Exceptions are logged if level is >= FINE
 * @param level Message level
 * @param exception Exception that will be used as message content
 */
public final static synchronized void trace(Level level, Exception exception) {
	if (!debugEnabled) return;
	if (level.intValue()<loglevel.intValue()) return;
	if (loglevel.intValue()>Level.INFO.intValue()) {
		logger.store(TRACE, level, new StringBuilder(exception.getMessage()));
	} else {
		StackTraceElement[]  trace = exception.getStackTrace();
		for (int i=0; i<trace.length;i++) {
			logger.store(TRACE, level, new StringBuilder(trace[i].toString()));
		}
	}//Endif
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Direct message log method.<br> 
 * Messages are filtered depending of their level compared to the current setted Log level.<br>
 * If log level is >= FINE, Exception stack trace is added to the log file.
 * @param file The log file in which to store message
 * @param level	the Message level
 * @param exception Exception to store	
 */
public final static synchronized void log(int file, Level level, Exception exception) {
    if (!enabled) return;
    if (level.intValue()<loglevel.intValue()) return;
    if (loglevel.intValue()>Level.INFO.intValue()) {
        logger.store(file,level, new StringBuilder(exception.getMessage()));
    } else {
        StackTraceElement[]  trace = exception.getStackTrace();
        for (int i=0; i<trace.length;i++) {
            logger.store(file,level, new StringBuilder(trace[i].toString()));
        }
    }//Endif
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Direct message log method. Messages are filtered depending of their level compared to the current setted Log level.<br>
 * @param file The log file in which to store message
 * @param level	the Message level
 * @param message Message content	
 */
public final static synchronized void log(int file, Level level, String message) {
    if (!enabled) return;
    if (level.intValue()<loglevel.intValue()) return;
    logger.store(file,level, new StringBuilder(message));
}
//--------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------
/**
 * Store message in DEBUG log file.<br>
 * Messages are filtered depending of their level compared to the current setted Log level.
 * If log level is >= FINE, Exception stack trace is added to the log file.
 * @param level  Message level
 * @param message Message content	
 * @param exception	Associated exception (if any)
 */
public final static synchronized void trace(Level level, String message, Exception exception) {
	if (!debugEnabled) return;
	if (level.intValue()<loglevel.intValue()) return;
	if (loglevel.intValue()>Level.FINE.intValue()) {
		logger.store(TRACE,level, new StringBuilder(message).append("\t").append(exception.getMessage()));
	} else {
		logger.store(TRACE, level,new StringBuilder(message).append("\t").append(exception.getMessage()));
		StackTraceElement[]  trace = exception.getStackTrace();
		for (int i=0; i<trace.length;i++) {
			logger.store(TRACE,level, new StringBuilder(trace[i].toString()));
		}
	}//Endif
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**
 * Static method calling class Logger in order to store information (call to store in background).<br>
 * Messages are filtered depending of their level compared to the current setted Log level.
 * If log level is >= FINE, Exception stack trace is added to the log file.
 * @param logtype Log file in which to store info
 * @param level   Message level		
 * @param message Message content	
 * @param exception	Associated exception (if any)
 */
public final static synchronized void println(int logtype, Level level, String message,Exception exception) {
	if (!enabled) return;
	if (level.intValue()<loglevel.intValue()) return;
	if (loglevel.intValue()>Level.FINE.intValue()) {
        if(exception!=null) logger.store(logtype, level,new StringBuilder(message).append("\t").append(exception.getMessage()));
        else logger.store(logtype,level, new StringBuilder(message));
	} else {
        if(exception!=null) {
            logger.store(logtype,level, new StringBuilder(message).append("\t").append(exception.getMessage()));
    		StackTraceElement[]  trace = exception.getStackTrace();
    		for (int i=0; i<trace.length;i++) {
    			logger.store(logtype,level, new StringBuilder(trace[i].toString()));
    		}
        }
        else logger.store(logtype, level,new StringBuilder(message));
	}//Endif
}
//--------------------------------------------------------------------------------------

/**
 * Force logs rotation even if capacity threshold has not been reached
 */
public final static void forceRotation(){
    logger.store(MAINTENANCE, Level.INFO,new StringBuilder(""));
}

//--------------------------------------------------------------------------------------
/**Formatter for log dates (use [dd/MMM/yyyy:HH:mm:ss Z], ex:[17/Aug/2005:10:22:42 +0200])*/
static SimpleDateFormat formatter = new SimpleDateFormat("[dd/MMM/yyyy:HH:mm:ss Z]",Locale.US);
static{formatter.setTimeZone(TimeZone.getDefault());}
static Date recdate = new Date();

/**
 * Store received messages into file log.
 * If log file entries exceed a specified threshold, (default:10000),
 * create a zipper thread that rotate and compress current log.
 * Note: information received is automatically prepended with date (universal time)
 */
 private final synchronized void store(int logtype, Level level, StringBuilder message) {
	if (!enabled) return;
	try {
        //fast retrieve of current time
        long absoluteTime = System.currentTimeMillis();
        //update static date object
        recdate.setTime(absoluteTime);
        //Generate log line
        if (level!=null) {
        	message.insert(0,String.format("%1$s \t[%2$-10s",formatter.format(recdate), level.getName()+']'));
        	//message.insert(0, String.format("   %1$-7s", level.getName()));
        } else {
        	message.insert(0,String.format("%1$s \t",formatter.format(recdate)));
        	//message.insert(0," - ");
        }
        //message.insert(0,String.format("%1$s   %2$s   %3$-7s",formatter.format(recdate), absoluteTime,level.getName()));
        //message.insert(0,"   ").insert(0, absoluteTime).insert(0, "   ").insert(0, formatter.format(recdate));
         message.append(returnchar);
		//message = formatter.format(recdate)+" \t" +absoluteTime+" \t" + message+returnchar;
        
        //Store log line in requested file
		switch (logtype){
			case ACCESS:
					accessEntriesCounter++;
					accessBufferedOutputStream.write(message.toString().getBytes());
					if (!isBuffered) accessBufferedOutputStream.flush();
					if (accessEntriesCounter>=maxentries) {
					    	closeStream(accessBufferedOutputStream);
							accessFileoutput.close();
							rotateLog(accessName);
							accessFileoutput = new FileOutputStream(accessLog,true);
							accessBufferedOutputStream = new BufferedOutputStream(accessFileoutput);
							accessEntriesCounter=0; 
					}
					break;
			case SERVICE:
					serviceEntriesCounter++;
					serviceBufferedOutputStream.write(message.toString().getBytes());
					if (!isBuffered) serviceBufferedOutputStream.flush();
					if (serviceEntriesCounter>=maxentries) {
				    		closeStream(serviceBufferedOutputStream);
							serviceFileoutput.close();
							rotateLog(serviceName);
							serviceFileoutput = new FileOutputStream(serviceLog,true);
							serviceBufferedOutputStream = new BufferedOutputStream(serviceFileoutput);
							serviceEntriesCounter=0; 
					}
					break;
			case ERROR:
				errorEntriesCounter++;
				errorBufferedOutputStream.write(message.toString().getBytes());
				if (!isBuffered) errorBufferedOutputStream.flush();
				if (errorEntriesCounter>=maxentries) {
		    			closeStream(errorBufferedOutputStream);					    
						errorFileoutput.close();
						rotateLog(errorName);
						errorFileoutput = new FileOutputStream(errorLog,true);
						errorBufferedOutputStream = new BufferedOutputStream(errorFileoutput);
						errorEntriesCounter=0; 
				}
				break;					
			case ADMIN:
					adminEntriesCounter++;
					adminBufferedOutputStream.write(message.toString().getBytes());
					if (!isBuffered) adminBufferedOutputStream.flush();
					if (adminEntriesCounter>=maxentries) {
			    			closeStream(adminBufferedOutputStream);					    
			    			adminFileoutput.close();
							rotateLog(adminName);
							adminFileoutput = new FileOutputStream(adminLog,true);
							adminBufferedOutputStream = new BufferedOutputStream(adminFileoutput);
							adminEntriesCounter=0; 
					}
					break;
			case TRACE:
					debugEntriesCounter++;
					debugBufferedOutputStream.write(message.toString().getBytes());
					if (!isBuffered) debugBufferedOutputStream.flush();
					if (debugEntriesCounter>=maxentries) {
					    	closeStream(debugBufferedOutputStream);
							debugFileoutput.close();
							rotateLog(debugName);
							debugFileoutput = new FileOutputStream(debugLog,true);
							debugBufferedOutputStream = new BufferedOutputStream(debugFileoutput);
							debugEntriesCounter = 0;
					}
					break;
                    
            case MAINTENANCE:
	                closeStream(adminBufferedOutputStream);
	                adminFileoutput.close();
	                rotateLog(adminName);
	                adminFileoutput = new FileOutputStream(adminLog,true);
	                adminBufferedOutputStream = new BufferedOutputStream(adminFileoutput);
	                adminEntriesCounter = 0;
                    closeStream(debugBufferedOutputStream);
                    debugFileoutput.close();
                    rotateLog(debugName);
                    debugFileoutput = new FileOutputStream(debugLog,true);
                    debugBufferedOutputStream = new BufferedOutputStream(debugFileoutput);
                    debugEntriesCounter = 0;
                    closeStream(errorBufferedOutputStream);                     
                    errorFileoutput.close();
                    rotateLog(errorName);
                    errorFileoutput = new FileOutputStream(errorLog,true);
                    errorBufferedOutputStream = new BufferedOutputStream(errorFileoutput);
                    errorEntriesCounter=0; 
                    closeStream(serviceBufferedOutputStream);
                    serviceFileoutput.close();
                    rotateLog(serviceName);
                    serviceFileoutput = new FileOutputStream(serviceLog,true);
                    serviceBufferedOutputStream = new BufferedOutputStream(serviceFileoutput);
                    serviceEntriesCounter=0; 
                    closeStream(accessBufferedOutputStream);
                    accessFileoutput.close();
                    rotateLog(accessName);
                    accessFileoutput = new FileOutputStream(accessLog,true);
                    accessBufferedOutputStream = new BufferedOutputStream(accessFileoutput);
                    accessEntriesCounter=0; 
                break;
			}//End switch
	} catch(Exception e){
		System.err.println("[GREASYSPOON FATAL ERROR] Loging facility - Log file cannot be created, is locked by ADMIN/ROOT or disk space full");
	}// catch
}//End store
//--------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------
/**
 * @return current log level
 */
public static Level getLogLevel(){
	return Log.loglevel;
}

/**
 * Set log debug level
 * Messages sent to Log with a greater debug level than this level 
 * are silently discarded
 * @param _newLogLevel The debug level to support
 */
public static void setLogLevel(Level _newLogLevel){
	if (_newLogLevel==loglevel && enabled) return;
	try{
		if (_newLogLevel!=Level.OFF && loglevel==Level.OFF) {
				debugLog = new File(logPath+debugName+extension);
			   	debugFileoutput = new FileOutputStream(logPath+debugName+extension,true);
				debugBufferedOutputStream = new BufferedOutputStream(debugFileoutput);
                if (Log.config())error(Level.CONFIG, "Debug Mode Enable - Level:" + _newLogLevel);
		} else if (_newLogLevel == Level.OFF){
				debugBufferedOutputStream.flush();
				debugBufferedOutputStream.close();
				debugFileoutput.close();
                if (Log.config())error(Level.CONFIG,"Debug Mode disabled.");
                
		}//Endif
	} catch (Exception e){
	}
	if ( (Log.loglevel == Level.OFF || !enabled) &&  _newLogLevel!= Level.OFF ){
		Log.enable();
		Log.error(Log.CONFIG,"Logs activated.");
	} else if (Log.loglevel != Level.OFF &&  _newLogLevel == Level.OFF){
		Log.error(Log.CONFIG,"Logs disabled.");
		Log.disable();
	} 
	Log.loglevel = _newLogLevel;
}
/**
 * Update log level to given value
 * @param levelName log level provided as upper case string 
 */
public static void setLogLevel(String levelName){
    try{
        setLogLevel(Level.parse(levelName));
    }catch (Exception e){
        if (Log.config())Log.error(Log.CONFIG, "Invalid log level: "+levelName);
    }
}
//--------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------
/**@return true if log level is set to FINEST or higher level */
public final static boolean finest(){if (loglevel.intValue()<Level.FINER.intValue()) return true;return false;}
/**@return true if log level is set to FINER or higher level */
public final static boolean finer(){if (loglevel.intValue()<Level.FINE.intValue()) return true;return false;}
/**@return true if log level is set to FINE or higher level */
public final static boolean fine(){if (loglevel.intValue()<Level.CONFIG.intValue()) return true;return false;}
/**@return true if log level is set to CONFIG or higher level */
public final static boolean config(){if (loglevel.intValue()<Level.INFO.intValue()) return true;return false;}
/**@return true if log level is set to INFO or higher level */
public final static boolean info(){if (loglevel.intValue()<Level.WARNING.intValue()) return true;return false;}
/**@return true if log level is set to WARNING or higher level */
public final static boolean warning(){if (loglevel.intValue()<Level.SEVERE.intValue()) return true;return false;}
/**@return true if log level is set to SEVERE or higher level */
public final static boolean severe(){if (loglevel.intValue()<Level.OFF.intValue()) return true;return false;}
//--------------------------------------------------------------------------------------

/**
 * @return The maximum entries in log file before rotation
 */
public static int getMaxentries() {
	return maxentries;
}

/**
 * @param maxentries Set the maximum entries in log file before rotation
 */
public static void setMaxentries(int maxentries) {
	Log.maxentries = maxentries;
}

/**
 * @return the maximum log files before deletion
 */
public static int getMaxfiles() {
	return maxfiles;
}

/**
 * @param maxfiles set the maximum log files before deletion
 */
public static void setMaxfiles(int maxfiles) {
	Log.maxfiles = maxfiles;
}

/**
 * @return Returns the debugEnabled.
 */
public static boolean isDebugEnabled() {
	return debugEnabled;
}

/**
 * @param debugEnabled The debugEnabled to set.
 */
public static void setDebugEnabled(boolean debugEnabled) {
	Log.debugEnabled = debugEnabled;
}

/**
 * @return Returns the errorEnabled.
 */
public static boolean isErrorEnabled() {
	return errorEnabled;
}

/**
 * @param errorEnabled The errorEnabled to set.
 */
public static void setErrorEnabled(boolean errorEnabled) {
	Log.errorEnabled = errorEnabled;
}

/**
 * @return Returns the adminEnabled.
 */
public static boolean isAdminEnabled() {
	return adminEnabled;
}

/**
 * @param adminEnabled The adminEnabled to set.
 */
public static void setAdminEnabled(boolean adminEnabled) {
	Log.adminEnabled = adminEnabled;
}

/**
 * @return Returns the accessEnabled.
 */
public static boolean isAccessEnabled() {
	return accessEnabled;
}

/**
 * @param accessEnabled The accessEnabled to set.
 */
public static void setAccessEnabled(boolean accessEnabled) {
	Log.accessEnabled = accessEnabled;
}

/**
 * @return Returns the serviceEnabled.
 */
public static boolean isServiceEnabled() {
	return serviceEnabled;
}

/**
 * @param serviceEnabled The serviceEnabled to set.
 */
public static void setServiceEnabled(boolean serviceEnabled) {
	Log.serviceEnabled = serviceEnabled;
}

}