- 日志的作用
在日常开发和维护中
1.需要知道程序在运行中的运行状态
2.出问题后快速定位当前的问题
3.调试分析代码
4.对产生的日志进行处理和分析 - 什么是slf4j
slf4j(全称是Simple Loging Facade For Java)是一个为Java程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就好像我们经常使用的JDBC一样,只是一种规则而已。因此单独的slf4j是不能工作的,它必须搭配其他具体的日志实现方案,比如apache的org.apache.log4j.Logger,jdk自带的java.util.logging.Logger等等。
slf4j是门面模式的典型应用
slf4j只做两件事:
1.提供日志接口
2.提供获取具体日志对象的方法 - slf4j原理
//获取Logger接口
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
// ILoggerFactory接口,具体日志系统必须实现此接口和Logger接口,
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name); //具体日志系统的实现
}
//此方法会去绑定具体日志实现,根据绑定成功与否设置不同的标志state,再根据不同的标志state返回具体ILoggerFactory的实现类
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == 0) { //进来的时候肯定是0,只有在绑定过程中才会改变INITIALIZATION_STATE的值
INITIALIZATION_STATE = 1;
performInitialization(); //初始化过程
}
//根据INITIALIZATION_STATE的值做不同的逻辑处理
switch(INITIALIZATION_STATE) {
case 1: // 即SubstituteLoggerFactory 空实现
return TEMP_FACTORY;
case 2: //初始化失败,报错
throw new IllegalStateException("org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
case 3: //初始化成功,返回具体实现类
return StaticLoggerBinder.getSingleton().getLoggerFactory(); //StaticLoggerBinder这个类就是一个中间类,它用来将抽象的slf4j变成具体的日志系统类
case 4: // NOPLoggerFactory 空实现,方法不做任何逻辑
return NOP_FALLBACK_FACTORY;
default: //抛出异常
throw new IllegalStateException("Unreachable code");
}
}
//bind方法会进行绑定 如果绑定成功即state为3 还会进行版本号验证
private static final void performInitialization() {
bind(); //绑定过程
if (INITIALIZATION_STATE == 3) {
versionSanityCheck(); //版本号验证
}
}
//绑定方法具体逻辑
private static final void bind() {
String msg;
try {
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); //根据类加载器查询org/slf4j/impl/StaticLoggerBinder.class所在的path路径
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); //检查是否引入多个日志系统实现,打印提示
StaticLoggerBinder.getSingleton(); //初始化,如果失败 异常被捕获,设置state值
INITIALIZATION_STATE = 3; //标志初始化成功
reportActualBinding(staticLoggerBinderPathSet); //如果多个日志系统 打印真实绑定成功的日志系统信息提示
emitSubstituteLoggerWarning(); //打印提示
} catch (NoClassDefFoundError var2) {
msg = var2.getMessage();
if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
failedBinding(var2);
throw var2;
}
INITIALIZATION_STATE = 4;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
} catch (NoSuchMethodError var3) {
msg = var3.getMessage();
if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
INITIALIZATION_STATE = 2;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw var3;
} catch (Exception var4) {
failedBinding(var4);
throw new IllegalStateException("Unexpected initialization failure", var4);
}
}
//classpath查找具体的实现类,这里可以看出,如果系统引入了多个日志系统,并不会报错,而是会把所有的日志系统实现放入一个Set集合中
private static Set findPossibleStaticLoggerBinderPathSet() {
LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();
//STATIC_LOGGER_BINDER_PATH 为org/slf4j/impl/StaticLoggerBinder.class
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while(paths.hasMoreElements()) {
URL path = (URL)paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException var4) {
Util.report("Error getting resources from path", var4);
}
return staticLoggerBinderPathSet;
}
//获取 new StaticLoggerBinder()
public static StaticLoggerBinder getSingleton() {
return SINGLETON; // new StaticLoggerBinder(); 获取内部LoggerContext,此类实现了ILoggerFactory
}
- 正确的打日志姿势
1.基本格式
正例:logger.warn(“user is exist: name {}” ,userName);
反例:logger.warn("user is exist: name " + userName);
原因:使用参数化信息的方式,不会产生多余的String,占用空间,同时对象过多,容易gc
2.debug打印先做判断
//slf4j已经自行做了判断
if (logger.isDebugEnabled()) {
logger.debug(”xxx”);
}
原因:debug会产生大量日志信息,因此需谨慎使用,生产环境一般设置级别为error或者info
3.对于不同程度的日志做不同级别的日志打印
4.不要吞并异常
try {
//do something
}catch (Exception e){
}
原因:捕获异常,如果不做任何业务处理,请加日志打印,做到异常可寻
- 日志输出性能优化
1.日志输出到控制台比输出到文件慢
2.日志输出格式不一样也会对性能产生影响
3.日志级别越低,输出的日志内容越多
4.日志输出方式不同,日志输出方式可以选择异步方式或同步方式
5.每次接收到日志event就进行输出比当日志内容达到一定大小才进行输出性能要低
解决方法:
1.采用正确的输出格式
2.只记录必要的关键日志信息,减少不必要的日志输出
3.采用正确的日志级别
4.输出采用缓存方式,当缓存到一定大小才进行输出
5.采用异步方式进行日志输出 - 日志打印模型
异步打印日志维护的一个堵塞队列,当队列中的消息数过多,超过阈值,则会进行TRACE, DEBUG or INFO的日志的丢弃,但是就算这样,如果error级别的消息过多,还是会堵塞,因为默认使用的put()方法,如果希望队列不堵塞,可以通过设置neverBlock=true,则会通过offer()方法而不是put()方法,由此可见,异步打印日志是会存在日志消息丢失的可能性,因此对队列容量大小需要有一个合理的设置,才会达到很好的效果。
参考文章:
异步打印日志的一点事
Java日志框架:slf4j