package evanq.game.trace;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import evanq.game.helper.New;
import evanq.game.utils.ExceptionUtils;
import evanq.game.utils.IOUtils;
/**
* The trace mechanism is the logging facility of this database. There is
* usually one trace system per database. It is called 'trace' because the term
* 'log' is already used in the database domain and means 'transaction log'. It
* is possible to write after close was called, but that means for each write
* the file will be opened and closed again (which is slower).
*/
public class LogSystem implements LogWriter {
/**
* The default level for system out trace messages.
*/
public static final int DEFAULT_TRACE_LEVEL_SYSTEM_OUT = LogLevel.OFF;
/**
* The default level for file trace messages.
*/
public static final int DEFAULT_TRACE_LEVEL_FILE = LogLevel.ERROR;
/**
* The default maximum trace file size. It is currently 64 MB. Additionally,
* there could be a .old file of the same size.
*/
private static final int DEFAULT_MAX_FILE_SIZE = 64 * 1024 * 1024;
/**
* 限制每次写入的数据大小
*/
private static final int CHECK_SIZE_EACH_WRITES = 128;
/**
* 输出到System.out的级别
*/
private int levelSystemOut = DEFAULT_TRACE_LEVEL_SYSTEM_OUT;
/**
* 输出到文件的级别
*/
private int logLevel = DEFAULT_TRACE_LEVEL_FILE;
private int levelMax;
private int maxFileSize = DEFAULT_MAX_FILE_SIZE;
private String fileName;
private HashMap<String, Trace> traces;
private SimpleDateFormat dateFormat;
//用于将日志输出到指定的文件
private Writer fileWriter;
private PrintWriter printWriter;
private int checkSize;
private boolean closed;
private boolean writingErrorLogged;
private LogWriter writer = this;
private PrintStream sysOut = System.out;
/**
* 日志所属模块
*/
private String module;
private boolean delegateSlf4j;
private boolean appendClassName= false;
/**
* create a new module log system
* @param module
*/
public LogSystem(String module) {
if(null == module){
throw new NullPointerException("module");
}
setName(module);
updateLogLevel();
this.delegateSlf4j = false;
}
/**
*
* @param module
* @param logFileName
*/
public LogSystem(String module,String logFileName){
this(module);
if(null == logFileName){
throw new NullPointerException("logFileName");
}
//日志文件输出
this.fileName = logFileName;
}
public LogSystem(String module,boolean delegateSlf4j){
this(module);
this.delegateSlf4j = delegateSlf4j;
}
public void setLogLevel(int level){
this.logLevel = level;
this.levelSystemOut = level;
updateLogLevel();
}
private void updateLogLevel() {
levelMax = Math.max(levelSystemOut, logLevel);
}
/**
* Set the print stream to use instead of System.out.
*
* @param out the new print stream
*/
public void setSysOut(PrintStream out) {
this.sysOut = out;
}
/**
* Write the exception to the driver manager log writer if configured.
*
* @param e the exception
*/
public static void traceThrowable(Throwable e) {
//TODO 将异常重定向到制定的日志writer
ExceptionUtils.printStackTrace(e);
}
/**
* Get or create a trace object for this module. Trace modules with names
* such as "JDBC[1]" are not cached (modules where the name ends with "]").
* All others are cached.
*
* @param module the module name
* @return the trace object
*/
public synchronized Trace getTrace(String module) {
if (module.endsWith("]")) {
return new Trace(writer, module);
}
if (traces == null) {
traces = New.hashMap(16);
}
Trace t = traces.get(module);
if (t == null) {
t = new Trace(writer, module);
traces.put(module, t);
}
return t;
}
public synchronized Trace getTrace(Class<?> clazz){
if(delegateSlf4j){
LogWriterAdapter adaptor = new LogWriterAdapter(clazz);
return new Trace(adaptor, module);
}
if (appendClassName) {
StringBuffer b = new StringBuffer() ;
b.append(module).append("[").append(clazz.getName()).append("]");
return getTrace(b.toString());
}
return getTrace(module);
}
private static LogSystem LOGSYSTEM;
/**
* 如果横向跟踪 Class<?>
* @param clazz
* @return
*/
public static Trace getDefaultTrace(Class<?> clazz){
boolean useSLF4J = true;
if(null == LOGSYSTEM ){
LOGSYSTEM = new LogSystem(TraceConstant.GAME_SYSTEM,useSLF4J);
LOGSYSTEM.setLogLevel(LogLevel.INFO);
}
if(null !=LOGSYSTEM && !LOGSYSTEM.delegateSlf4j){
LOGSYSTEM = new LogSystem(TraceConstant.GAME_SYSTEM,true);
LOGSYSTEM.setLogLevel(LogLevel.INFO);
}
return LOGSYSTEM.getTrace(clazz );
}
/**
* 如果纵向跟踪对象(connection[1234])或者类
* @param module
* @return
*/
public static Trace getDefaultTrace(String module){
if(null == LOGSYSTEM){
LOGSYSTEM = new LogSystem(TraceConstant.GAME_SYSTEM);
LOGSYSTEM.setLogLevel(LogLevel.INFO);
}
return LOGSYSTEM.getTrace(module);
}
@Override
public boolean isEnabled(int level) {
return level <= this.levelMax;
}
/**
* Set the maximum trace file size in bytes.
*
* @param max the maximum size
*/
public void setMaxFileSize(int max) {
this.maxFileSize = max;
}
/**
* Set the trace level to use for System.out
*
* @param level the new level
*/
public void setLevelSystemOut(int level) {
levelSystemOut = level;
updateLogLevel();
}
private synchronized String format(int level,String module, String s) {
if (dateFormat == null) {//15:08:32.205
dateFormat = new SimpleDateFormat("HH:mm:ss.S");
}
String i = "";
switch(level){
case LogLevel.DEBUG:
i="DEBUG";break;
case LogLevel.ERROR:
i="ERROR";break;
case LogLevel.INFO:
i="INFO";break;
case LogLevel.WARN:
i="WARN";break;
}
String nameOfThread = Thread.currentThread().getName();
StringBuffer sb = new StringBuffer();
sb.append(dateFormat.format(new Date()) );
sb.append(" [").append(nameOfThread).append("]");
sb.append(" ").append(i).append(" " ).append(module).append(" - ").append(s);
return sb.toString();
}
@Override
public void write(int level, String module, String s) {
if (level <= levelSystemOut || level > this.levelMax) {
// level <= levelSystemOut: the system out level is set higher
// level > this.level: the level for this module is set higher
sysOut.println(format(level,module, s));
}
if (fileName != null) {
if (level <= logLevel) {
writeFile(format(level,module, s));
}
}
}
@Override
public void write(int level,String module, String format, Object... objects) {
if (level <= levelSystemOut || level > this.levelMax) {
// level <= levelSystemOut: the system out level is set higher
// level > this.level: the level for this module is set higher
FormattingTuple format2 = MessageFormatter.arrayFormat(format, objects);
sysOut.println(format(level,module, format2.getMessage()));
}
if (fileName != null) {
if (level <= logLevel) {
FormattingTuple format2 = MessageFormatter.arrayFormat(format, objects);
writeFile(format(level,module, format2.getMessage()));
}
}
}
/**
* 将目标字符串写入文件
* @param s
* @param t
*/
private synchronized void writeFile(String s) {
try {
//TODO 记录日志的条数
if (checkSize++ >= CHECK_SIZE_EACH_WRITES) {
checkSize = 0;
closeWriter();
File file = new File(fileName);
if (maxFileSize > 0 && file.length() > maxFileSize) {
String old = fileName + ".old";
//删除旧文件
File fileOld = new File(old);
if(fileOld.exists())fileOld.delete();
//MOVE TO
file.renameTo(fileOld);
}
}
if (!openWriter()) {
return;
}
printWriter.println(s);
printWriter.flush();
if (closed) {
closeWriter();
}
} catch (Exception e) {
logWritingError(e);
}
}
private void logWritingError(Exception e) {
if (writingErrorLogged) {
return;
}
writingErrorLogged = true;
//Exception se = DbException.get(ErrorCode.TRACE_FILE_ERROR_2, e, fileName, e.toString());
// print this error only once
fileName = null;
sysOut.println(e);
e.printStackTrace();
}
private boolean openWriter() {
if (printWriter == null) {
//建立文件
try {
File file = new File(fileName);
if( file.exists() && ! file.canWrite() ){
return false;
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileWriter = IOUtils.getBufferedWriter(fileOutputStream);
printWriter = new PrintWriter(fileWriter, true);
} catch (Exception e) {
logWritingError(e);
return false;
}
}
return true;
}
private synchronized void closeWriter() {
if (printWriter != null) {
printWriter.flush();
printWriter.close();
printWriter = null;
}
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
// ignore
}
fileWriter = null;
}
}
/**
* Close the writers, and the files if required. It is still possible to
* write after closing, however after each write the file is closed again
* (slowing down tracing).
*/
public void close() {
closeWriter();
closed = true;
}
@Override
public void setName(String name) {
this.module = name;
}
}