本文是结合项目中使用Log4j总结的最佳实践,非转载。网上能够找到的是这一篇《Log4j最佳实践》。原本Log4j使用是很是简单的,无需多介绍其用法,这只是在小型项目中;但在大型的项目中使用log4j不太同样。大型项目很是依赖日志,由于解决线上问题必须依靠log,依靠大量的日志!线上出现问题每每不能重现,并且没法调试,log是必须中的必须,解决线上问题全靠它。本文内容:html
在大型项目中使用Log4j要注意下面几点:java
例以下面这个启动日志包含了版本号、耗费时间、userID等等丰富的信息:git
为系统性能考虑,使用Log4j注意下列几点:github
当配置文件中的配置项包含Location信息时候会很是昂贵,所以,须要避免'C', 'F', 'L' 'M' 等位置信息的记录(参数配置项详细说明)。sql
(注意:当配置为异步输出的时候,以上位置信息可能会显示为问号?,由于是在另一个线程记录的调用信息。此时,咱们可使用下面的方法来获取类名和函数名:数据库
StackTraceElement se = Thread.currentThread().getStackTrace()[2]; String msg = se.getClassName() + "-[" + se.getMethodName() + "] " + errorMessage;
)apache
Log4j异步写可使用默认的appender:org.apache.log4j.AsyncAppender,配置文件log4j.xml样例:api
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' debug="false"> <appender name="DAILY_FILE" class="org.apache.log4j.DailyRollingFileAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c %x - %m%n"/> </layout> <param name="File" value="log/log4j.log"/> <param name="DatePattern" value="'.'yyyy-MM-dd"/> </appender> <appender name="ASYNC_FILE" class="org.apache.log4j.AsyncAppender"> <param name="BufferSize" value="10000"/> <param name="Blocking" value="false"/> <appender-ref ref="DAILY_FILE"/> </appender> <appender name="DB_OUT" class="org.apache.log4j.jdbc.JDBCAppender"> <param name="URL" value="jdbc:postgresql://192.168.1.34:5432/myDB" /> <param name="Driver" value="org.postgresql.Driver"/> <param name="User" value="aaa"/> <param name="Password" value="bbb"/> <param name="Sql" value="INSERT INTO tracelog (ModuleName ,LoginID,UserName,Class, Method,createTime,LogLevel,MSG) values ('%c', '','','','','%d{yyyy-MM-dd HH:mm:ss,SSS}','%p','%m')"/> </appender> <appender name="ASYNC_DB" class="org.apache.log4j.AsyncAppender"> <param name="BufferSize" value="10000"/> <param name="Blocking" value="false"/> <appender-ref ref="DB_OUT"/> </appender> <root> <level value="info"/> <appender-ref ref="ASYNC_DB" /> <appender-ref ref="ASYNC_FILE" /> </root> <logger name="PACKAGE_1" additivity="false"> <level value="info"/> <appender-ref ref="ASYNC_DB" /> <appender-ref ref="ASYNC_FILE" /> </logger> <logger name="PACKAGE_2" additivity="false"> <level value="info"/> <appender-ref ref="ASYNC_DB" /> <appender-ref ref="ASYNC_FILE" /> </logger> </log4j:configuration>
上面的配置文件包含异步写文件和异步写入postgreSQL数据库的配置,默认是root,也有各个Package的配置。用的时候能够写一个logUtil的类来初始化这个log4j.xml:服务器
package com.ibm; import org.apache.log4j.Logger; import org.apache.log4j.xml.DOMConfigurator; public class LogUtil implements ILogUtil { public static LogUtil getInstance() { if (instance == null) instance = new LogUtil(); return instance; } @Override public void init() { //PropertyConfigurator.configure("conf/log4j.properties"); DOMConfigurator.configure("conf/log4j.xml"); } @Override public void close() { } @Override public void logError(String errorMessage) { logger.error(errorMessage.replace("'", "''")); } @Override public void logWarn(String warnMessage) { logger.warn(warnMessage.replace("'", "''")); } @Override public void logInfo(String infoMessage) { logger.info(infoMessage.replace("'", "''")); } @Override public void logDebug(String debugMessage) { logger.debug(debugMessage.replace("'", "''")); } @Override public void logFatal(String fatalMessage) { logger.fatal(fatalMessage.replace("'", "''")); } private LogUtil() { } private static Logger getPackageLogger(String packageName){ if(packageName.equals(PackageName.PACKAGE_1.toString())) return Logger.getLogger(PackageName.PACKAGE_1.toString()); else if(packageName.equals(PackageName.PACKAGE_2.toString())) return Logger.getLogger(PackageName.PACKAGE_2.toString()); else return Logger.getRootLogger(); } @Override public void logError(String packageName, String errorMessage) { getPackageLogger(packageName).error(errorMessage.replace("'", "''")); } @Override public void logError(String packageName, String errorMessage, Throwable exception) { getPackageLogger(packageName).error(errorMessage.replace("'", "''"), exception); } @Override public void logWarn(String packageName, String warnMessage) { getPackageLogger(packageName).warn(warnMessage.replace("'", "''")); } @Override public void logWarn(String packageName, String warnMessage, Throwable exception) { getPackageLogger(packageName).warn(warnMessage.replace("'", "''"), exception); } @Override public void logInfo(String packageName, String infoMessage) { getPackageLogger(packageName).info(infoMessage.replace("'", "''")); } @Override public void logInfo(String packageName, String infoMessage, Throwable exception) { getPackageLogger(packageName).info(infoMessage.replace("'", "''"), exception); } @Override public void logDebug(String packageName, String debugMessage) { getPackageLogger(packageName).debug(debugMessage.replace("'", "''")); } @Override public void logDebug(String packageName, String debugMessage, Throwable exception) { getPackageLogger(packageName).debug(debugMessage.replace("'", "''"), exception); } @Override public void logFatal(String packageName, String fatalMessage) { getPackageLogger(packageName).fatal(fatalMessage.replace("'", "''")); } @Override public void logFatal(String packageName, String fatalMessage, Throwable exception) { getPackageLogger(packageName).fatal(fatalMessage.replace("'", "''"), exception); } private static Logger logger = Logger.getRootLogger(); private static LogUtil instance; }
具体各个Package能够调用:网络
LogUtil.getInstance().logError("PACKAGE_1", "error message....", e);
(注意:写数据库的时候配置文件log4j.xml里面有一菊SQL,这个SQL在写的message包含单引号或双引号的时候会爆异常,因此须要把单引号或双引号转义为两个单引号;咱们本身的log能够控制,若是是例如Tomcat/jBoss写的log的message包含单引号或双引号的时候会写数据库异常,具体作法能够自定义JDBCAppender,参考这一片文章。自定义字段可使用MDC和%X,参考这一片文章。)
上面的配置文件已经根据各个Package配置单独的log输出,能够配置为写某个文件,或单独写数据库,或是组合,均可以灵活根据本身的须要配置。
(AsyncAppender中BufferSize/默认128的含义:the number of messages allowed in the event buffer before the calling thread is blocked (if blocking is true) or until messages are summarized and discarded.)
JDBCAppender存在没有数据库链接池的问题,能够扩展一下JDBCAppender,引入第三方链接池例如C3P0:
package com.ibm.log4j.jdbcplus; import org.apache.log4j.jdbc.JDBCAppender; import org.apache.log4j.spi.LoggingEvent; import java.sql.Connection; import java.sql.SQLException; import org.apache.log4j.spi.ErrorCode; import com.codestudio.sql.PoolMan; public class DBAppender extends JDBCAppender { /**经过 PoolMan 获取数据库链接对象的 jndiName 属性*/ protected String jndiName; /**数据库链接对象*/ protected Connection connection = null; public DBAppender() { super(); } @Override protected void closeConnection(Connection con) { try { if (connection != null && !connection.isClosed()) connection.close(); } catch (SQLException e) { errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE); } } @Override protected Connection getConnection() throws SQLException { try { //经过 PoolMan 获取数据库链接对象(http://nchc.dl.sourceforge.net/project/poolman/PoolMan/poolman-2.1-b1/poolman-2.1-b1.zip) Class.forName("com.codestudio.sql.PoolMan"); connection= PoolMan.connect("jdbc:poolman://" + getJndiName()); } catch (Exception e) { System.out.println(e.getMessage()); } return connection; } /** * @return the jndiName */ public String getJndiName() { return jndiName; } /** * @param jndiName the jndiName to set */ public void setJndiName(String jndiName) { this.jndiName = jndiName; } @Override public void append(LoggingEvent event) { if (event.getMessage() != null) event.getMessage().toString().replace("'", "''"); // if (event.getThrowableInformation() != null) // event.getThrowableInformation().toString().replace("'", "''"); buffer.add(event); if (buffer.size() >= bufferSize) flushBuffer(); } }
若是你的日志级别是INFO,想把ERROR log输出到单独的文件,能够这样配置:
<appender name="ERROR_FILE"> <param name="Threshold" value="ERROR"/> </appender> <appender name="GENERAL"> <param name="Threshold" value="INFO"/> </appender> <logger name="com.acme"> <level value="INFO"/> <appender-ref ref="ERROR_FILE"/> <appender-ref ref="GENERAL"/> </logger>
最后要注意的是,若是你把写日志这部分封装到一个独立的jar包模块里面(在基类或者静态类里面写日志),就会致使输出的类名、函数名都是基类的类名和函数名,这将是重大的错误。由于下面的这行:
private static Logger log = Logger.getLogger( MyClass.class );
若是你得到的是基类的logger那就永远是基类的logger。这一点须要注意.
若是你对Log4j基础不熟悉,建议你学习一下什么是log4j里面的logger, root logger, appender, configuration、Additivity和layout.
除了AsyncAppender,你还可使用SocketAppender, JMSAppender...和其它各类log4j的appender。固然,除了log4j,你也能够转到slf4j, logBack.
Log4j的异步appender也就是AsyncAppender存在性能问题(如今Log4j 2.0 RC提供了一种新的异步写log的机制(基于disruptor)来试图解决问题),问题是什么呢?异步写log有一个buffer的设置,也就是当队列中多少个日志的时候就flush到文件或数据库,当配置为blocking=true的时候,当你的应用写日志很快,log4j的缓冲队列将很快充满,当它批量flush到磁盘文件的时候,你的磁盘写入速度很慢,会发生什么状况?是的,队列阻塞,写不进去了,整个log4j阻塞了,始终等待队列写入磁盘/DB,整个异步线程死了变成同步的了?而当配置为blocking=false的时候,不会阻塞但会扔出异常并丢弃消息。你是但愿log4j死掉,仍是但愿后续消息被丢弃?都是问题。
固然,一个办法是把缓冲bufferSize设大一点。最好的解决办法:一、本身实现消息队列和自定义的AsyncAppender; 2. 等log4j 2.0 成熟发布。
(注:log4j 2 因为采用了LMAX Disruptor,性能超过原来AsyncAppender几个数量级,支持每秒并发写入1800万条日志)