日志输出规范

1. 组件

日志组件有很多,日志门面的选择

  • Slf4j
  • Apache Commons Logging

日志的实现有:

  • log4j
  • logback
  • log4j2
  • Java Util Logging(jul)
  • Jboss Logging

建议统一使用Slf4j作为日志门面,logback作为日志实现。

2. 级别

日志的级别有很多,我们一般只用四个。日志级别由低到高DEBUG - INFO - WARN - ERROR。

  • DEBUG(调试):开发调试日志。一般来说,在系统实际运行过程中,不会输出该级别的日志。
  • INFO(通知):INFO日志级别主要用于记录系统运行状态等关联信息。在这里输出的信息,应该对最终用户具有实际意义。
  • WARN(警告):WARN日志常用来表示系统模块发生问题,但并不影响系统运行。虽然不需要马上处理,但是也需要引起重视。某些用户危险的操作,例如一直采用错误密码尝试登陆管理员账号的行为,可以提升到WARN日志级别记录。
  • ERROR(错误):此信息输出后,主体系统核心模块需要修复才能正常工作。系统中出现该级别日志应该及时解决。

3. 场景

在我们的系统中,不同的日志级别的打印场景大致如下:

日志级别 打印场景
DEBUG 调试日志。没有严格要求。
INFO 业务日志。用来记录业务的主流程的走向。
WARN 警告日志。一般来说,发生对整个系统没什么影响的异常时,可以打印该级别的日志。
ERROR 错误日志。级别比较高,一般如果发生一些异常,都需要打印。

4. 使用方式

编写位置

  1. 系统/应用启动和参数变更。当系统启动时,可以将相关的参数信息进行打印,以便出现问题时,更准确地查询原因;参数信息可能并不存储在本地,需要通过配置中心获取,而参数信息有变更时,也需要将变更后的内容输出在日志中。
  2. 关键操作节点。最典型的就是在关键位置添加日志,记录用户进行的某个操作。
  3. 大型任务进度上报。当系统在处理某个比较大型的任务时,可以在这个过程中增加相关的日志来表明任务处理的进度,防止因为长时间没有处理而无法得知程序执行的状态,比如在定时任务中,增加当前任务完成百分比。
  4. 异常。如果是通过 try-catch 处理,需要注意日志编写的位置。如果需要将日志在本层抛出,则不需要进行日志记录,否则会出现日志重复的问题。
  5. 批量操作。涉及到数量的操作要打印log,比如查询数据库和批量拷贝文件、上传下载、批量格式转换等批量操作,设计到的数量要打印出来。

格式

日志的格式布局会影响日志内容收集与管理的效率。日志编写时需要注意的事项:

  1. 系统之间格式保持一致:每个应用在日志格式上尽量保持统一,这样,在进行日志收集时,就可以采用统一的日志模板来收集和格式化内容。
  2. 不编写多行的日志内容:除了异常堆栈信息,不对日志内容进行多行的输出,多行的内容十分不便于数据解析,可能会出现生成多条日志的情况。
  3. 不要使用日志中的常见内容来分割:如果采用日志中常见的内容来分割,会在格式解析时出现问题,比如用户 ID 中的空格就是比较常见的内容。

面向接口

private static final Logger logger = LoggerFactory.getLogger(AuthAspect.class);

使用时应面向接口编程,要求使用slf4j日志门面,LOGGER/LoggerFactory应该都是slf4j的API。严禁直接使用日志实现包。原因大致有两点,第一是面向接口编程更优雅,这点不必说明;第二,便于切换具体的日志实现方式,举个例子,因为log4j已经几年不更新,老的项目可能使用了log4j,现在想要换用logback或者log4j2,如果是直接使用log4j的API,不利于选型更换与API的统一。

禁止

  • 禁止字符串拼接

例如:

LOGGER.debug("当前用户是:" + user + ",传入参数是:" + userId);

严禁使用字符串拼接的方式打印日志,可读性、可维护性都比较差。
建议的写法如下:

LOGGER.debug("当前用户是:{},传入参数是:{},返回值是:{},用户信息:{}", new Object[]{token, userId, userInfo, authcInfo});

Slf4j有占位符填充的功能。多个占位符可放在Object数组中。
占位符优点:

  1. 节约性能。不停叠加字符串而占用过多的内存、CPU 资源,导致性能浪费。
  2. 便于编写。先确认日志所想要表达的内容,再确认你所需要编写的参数,这样在写日志时,目的也会更加明确。
  3. 便于查看。在代码 review 时,更方便查看日志想表达的意思,而不会被日志的参数打乱。
  • 无需使用级别判断

不优雅示例:

if (LOGGER.isDebugEnabled()) {
    LOGGER.debug("当前用户是{}", token);
}

应直接使用如下形式就OK了。

LOGGER.debug("当前用户是{}", token);

或者输出对象

LOGGER.debug("当前用户是{}", JSONObject.toJSONString(userInfo));

反例

//重复
//直接打印堆栈
try {...}
catch (Exception e) {
    logger.error("getTerminalDetailsBatch() 发生异常",e);
    e.printStackTrace();
}

//System.out.println
//拼接
System.out.println("收到:"+iccids);

//向上抛出异常又打印日志
LOG.info("智能诊断{},查产品资料失败,接口返回空:{}", cardEntity.getIccidMark(), responseStr);
throw new Exception("接口返回空");

//没有参数、状态、耗时、结果
@Scheduled(cron = "0 0 */1 * * ?")
public void stopCard() {
    this.logger.info("=============开始停机=================");
    this.cardViewComp.stopCard();
    this.logger.info("=============结束停机=================");
}

//暴露敏感信息
log.info(userName + " 登录 原始密码[" + password + "] pwd[" + pwd + "] 数据库中密码[" + entity.getPassword() + "]" );

不建议用System.out
System.out日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分
尽量不用e.printStackTrace
主要原因有以下几点:
1、占用太多内存,造成锁死
要打印字符串输出到控制台上,需要字符串常量池所在的内存块有足够的空间。然而,因为e.printStackTrace() 语句要产生的字符串记录的是堆栈信息,太长太多,内存有可能被填满了,此时大量线程产出字符串产出到一半,等待有内存被释放,就锁死了,整个应用都会挂掉。
2、日志交错混合,不易读
printStackTrace()默认使用了System.err输出流进行输出,与System.out是两个不同的输出流,那么在打印时自然就形成了交叉。再就是输出流是有缓冲区的,所以对于什么时候具体输出也形成了随机。
日志的频繁打印会对模块的性能产生极大的影响
当日志产生的速度大于日志文件写磁盘的速度,会导致日志内容积压在内存中,导致内存泄漏。
一般不在循环里打印日志
不打无用的、无意义、不完全的日志

if(message instanceof TextMessage){
    //do sth
}else{    
    log.warn("Unknown message type");
}

日志打印的时候不能因为打印日志引入新的异常,例如空指针异常

log.debug("Processing request with id: {}", request.getId());
//request对象如果是null,那么就会出现NPE。

输出参数不宜过大
不对大字段、无用字段输出,因为这很影响程序执行效率和日志的内容。比如大段的Html,或者大段的对象信息,甚至整个图片流信息。

建议

如果你在方法的开始和结束都记录了日志,那么你可以人工找出效率不高的代码,也可以用切面实现。
因为打印的日志有时间戳,你可以根据开始和结束时间获得执行时间,如果执行时间较长,可能就是代码效率有问题。

在日志中增加以下信息提高可读性

  1. 会话标识:当前操作的用户和与当前请求相关的信息,类似 Session。当出现问题/查看行为时,可以根据这个值来快速识别到相关的日志。
  2. 请求标识:每个请求都拥有一个唯一的标识,这样在查看问题时,我们只需要查看这一个请求中的所有日志即可。一般我们会配合链路追踪系统一同使用,因为后者可以实现跨应用的日志追踪,从而帮助我们过滤掉不相关的信息。
  3. 参数信息:在日志中增加参数信息能帮你了解到,是什么情况下产生的问题,这样你也很容易复现问题,以及辨别错误的原因。
  4. 发生数据的结果:和参数信息相互对应,一个是执行前,一个是执行后。发生数据的结果可以帮你了解程序执行的结果。

日志 review
好的日志不是一次就能写好的,而是通过代码评审、不断迭代等方式进行完善的。之前因为定位问题打的日志,若无必要也可以删除,避免过多无用日志。(矛盾:出了问题才知道日志有没有用,多打点也无妨)

日志归档

一般情况下,我们会对日志按照日期来归档,每天生成一个日志文件,这样在日志备份和恢复时,可以按照日期来进行。
目前系统两个地方有日志

  • /data/tomcat/logs目录,四个模块,分error和info两种,info包含error,按天分割文件。
  • tomcat的logs目录下的catalina.out文件,包含所有info级别以上信息,文件较大,需要定时清理。建议取消打印控制台日志,避免冗余。
image.png
image.png

**

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,717评论 1 9
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,532评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 2,597评论 2 9
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,187评论 4 8