选择日志工具/日志库
slf4j其实不算一个工具,它只是一个标准的接口。接口中按照不同级别定义了非常丰富的日志输出方式。
至于真正的日志输出工具可以根据系统的需求、公司要求、个人喜好来使用。
目前我所了解到的有java.util.logging、common-logging、Log4j、Log4j2、logback
日志级别以及使用
在slf4j中日志级别顺序是这样的:trace > debug > info > warn > error
从左到右日志输出级别是包含的关系。比如日志配置设定的是debug级别,那么使用日志时候使用debug、info、warn、error这四种级别都会输出,但是使用trace级别则不会输出。
很多人容易记混,其实理解级别的含义就好了,如果你真的无法理解含义那就写个测试用例自己试一下背下来也行,背不下来打印到一张纸上,放到办公桌上每天看看。
trace:说真的我还没用过(给自己一巴掌)
debug:调试模式。我个人常用在开发模式中,这种模式常在开发期间、联调期间使用,可打印出更多有帮助的日志信息,比如在计算用户积分时使用了哪种策略、某个内部逻辑输入参数和最终计算结果。
info:正常信息输出、业务信息输出。这个级别可以忽略掉调试时候需要看的信息,一般我用于接口输入和输出、关键业务信息输出。
warn:警告。它属于一种业务系统的警告类信息,但它又不属于异常。比如在根据某些条件到存储/缓存中查询,理论上存储/缓存应该返回结果,但经过非空判断后发现并不存在,这时候我一般使用警告.
error:异常。这个就不用说了,非常非常有用的信息,它放到了日志级别的最后一位也是因为日志里什么都可以不输出,但是异常必须要输出。这也是为了让你的系统提供更稳定的服务。low一点的开发每天早中晚会去线上看异常日志,之后去解决异常。再懒一点的可以通过监控等手段给自己发邮件、发短信、发微信等等。各位自己YY吧,我相信开发人员都是“懒惰”的。
日志的副作用
- 首先想到的是性能问题,虽然一般情况下系统不用考虑日志带来的影响,但是这确实是其中一个点或者说一个副作用。同步写日志会影响性能。如果对性能要求很高考虑异步日志吧。Log4j2
- 很多朋友打印日志喜欢使用字符串拼接,建议多使用日志库提供的格式化方式,因为拼接的方式不管日志是否会输出都会执行拼接操作,而格式化方式在需要输出时候才进行格式化。如果日志库不支持格式化那么请在日志 输出前加一个判断吧,比如LOG.isInfoEnabled();
- 可以使用类名来命名Logger,日志配置中取消class输出
- LOG.isXXXEnabled的另外一个作用可以防止懒加载的对象无辜因为日志输出导致加载或异常.
我的日志工具类
下面这个类是我自己做的一个日志工具类包装,它干了这么几件事
- 包装LOG.isDebugEnabled、LOG.isInfoEnabled、LOG.isWarnEnabled、LOG.isErrorEnabled
- 将slf4j的格式化形式"{}"换成key、value方式.
- 输出线程日志追踪号可以看到getTrackLogValue()方法这里返回空串,其实可以在日志配置中使用MDC。关于MDC的使用可以看http://www.cnblogs.com/sealedbook/p/6227452.html
package com.xx.xx.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; /** * 日志登记工具. * * @author shitianshu on 17/3/2 下午6:43. */ public class LogUtils { /** * info级别日志登记. * * @param LOG {@link org.slf4j.Logger}对象 * @param title 日志登记标题,描述日志意图 * @param params 日志参数名+参数内容,2位一组,第一位是参数名,第二位是参数内容.<BR/> * [param1name,param1value,param2name,param2value.....paramXname,paramXvalue] */ public static void info(final Logger LOG, String title, Object... params) { if(LOG.isInfoEnabled()) { LOG.info(LOG_FORMAT, getTrackLogValue(), title, formatParam(params)); } } /** * debug级别日志登记 * * @param LOG {@link org.slf4j.Logger}对象 * @param title 日志登记标题,描述日志意图 * @param params 日志参数名+参数内容,2位一组,第一位是参数名,第二位是参数内容.<BR/> * [param1name,param1value,param2name,param2value.....paramXname,paramXvalue] */ public static void debug(final Logger LOG, String title, Object... params) { if(LOG.isDebugEnabled()) { LOG.debug(LOG_FORMAT, getTrackLogValue(), title, formatParam(params)); } } /** * warn级别日志登记 * * @param LOG {@link org.slf4j.Logger}对象 * @param title 日志登记标题,描述日志意图 * @param params 日志参数名+参数内容,2位一组,第一位是参数名,第二位是参数内容.<BR/> * [param1name,param1value,param2name,param2value.....paramXname,paramXvalue] */ public static void warn(final Logger LOG, String title, Object... params) { if(LOG.isWarnEnabled()) { LOG.warn(LOG_FORMAT, getTrackLogValue(), title, formatParam(params)); } } /** * error级别日志登记 * * @param LOG {@link org.slf4j.Logger}对象 * @param title 日志登记标题,描述日志意图 * @param params 日志参数名+参数内容,2位一组,第一位是参数名,第二位是参数内容.<BR/> * [param1name,param1value,param2name,param2value.....paramXname,paramXvalue] */ public static void error(final Logger LOG, String title, Object... params) { if(LOG.isErrorEnabled()) { LOG.error(LOG_FORMAT, getTrackLogValue(), title, formatParam(params)); } } /** * error级别日志登记 * * @param LOG {@link org.slf4j.Logger}对象 * @param e {@link java.lang.Throwable}对象 * @param title 日志登记标题,描述日志意图 * @param params 日志参数名+参数内容,2位一组,第一位是参数名,第二位是参数内容.<BR/> * [param1name,param1value,param2name,param2value.....paramXname,paramXvalue] */ public static void error(final Logger LOG, Throwable e, String title, Object... params) { if(LOG.isErrorEnabled()) { LOG.error(LOG_FORMAT, getTrackLogValue(), title, formatParam(params), e); } } /** * 参数格式化. * * @param params 参数名+参数内容集合. * @return 格式化后的参数列表 日志参数名+参数内容,2位一组,第一位是参数名,第二位是参数内容.[param1name,param1value,param2name,param2value.....paramXname,paramXvalue] */ private static StringBuilder formatParam(Object... params) { StringBuilder param = new StringBuilder(); if(null == params || params.length <= 0) { return param; } for(int index = 0; index < params.length; index += 2) { Object paramName = ""; Object paramValue = ""; try { paramName = params[index]; paramValue = params[index + 1]; } catch(IndexOutOfBoundsException e) { /** 数组越界什么都不做,继续打印. */ } param.append(paramName).append(":[").append(paramValue).append("]."); } return param; } /** * 获得日志跟踪号 * 也可以直接通过日志配置文件配置. * * @return */ private static Object getTrackLogValue() { // String diagnosticValue = MDC.get(AD_TRACKING_LOG_TRACK_KEY); // return null == diagnosticValue ? "" : diagnosticValue; return ""; } /** 日志组件,这个组件可以在error时候直接用LOG_UTIL_LOG.error(),日志文件中拦截LogUtils输出到error.log。比较方便. */ private static final Logger LOG_UTIL_LOG = LoggerFactory.getLogger(LogUtils.class); /** 日志格式. */ private static final String LOG_FORMAT = "{}[{}]#{}"; /** AD Tracking系统日志跟踪KEY */ public static final String AD_TRACKING_LOG_TRACK_KEY = "AD_TRACKING_LOG_TRACK_KEY"; }
这个工具这样使用:
private static final Logger LOG = LoggerFactory.getLogger("name"); LogUtils.info(LOG, "这段日志记录什么", "param1", "value1", "param2", "value2"); try { /** 模拟一个异常 */ String number = null; number.toString(); } catch(NullPointerException e) { LogUtils.error(LOG, e, "为什么异常了"); LogUtils.error(LOG, e, "为什么异常了", "param1", "value1"); }
这个工具还有个问题,就是key和value必须成对儿出现,不然会报数组越界错误(fornatParam导致,因为一直是自己使用,还没有改造)。
最后我想说别小看日志,它真的很重要
下面是一些参考,我觉得将的都不错
http://macrochen.iteye.com/blog/1399082
http://developer.51cto.com/art/201512/503099.htm
http://www.infoq.com/cn/articles/why-and-how-log
http://slf4j.org/faq.html#trace