程序中什么时候打印什么级别的⽇志
⽇志的打印在软件开发过程中必不可少,⼀般分为两个⼤类:
操作⽇志
系统⽇志
操作⽇志,主要针对的是⽤户,例如在Photoshop软件中会记录⾃⼰操作的步骤,便于⽤户⾃⼰查看。
系统⽇志,主要针对的是软件开发⼈员(包括测试、维护⼈员),也就是说这部分的⽇志⽤户是看不到的,也就是我们通常所说的debug⽇志。
在⼤学中所谓的实践项⽬或者⽼师布置的作⽤中,通常是不会在意⽇志,除⾮在作业中有特别的需要,往往在开发过程中直接打印控制台语句来调试程序,这是极为不专业的调试开发过程。所以这也就导致了⼀个问题,⼤学毕业和⼯作时衔接不上最⼤的问题不在于技术上的难度,⽽是⽇志打印的问题。这个看似不起眼的问题对于应届⽣来说往往是“恶梦”,操作⽇志相对⽐较好理解,⽤户做了什么就记录什么;⽽打印系统⽇志则⽆从下⼿,往往⼀般有下⾯⼏个⽅⾯——3W:
Where:不清楚在何处打印⽇志
Who:不清楚打印什么级别的⽇志
What:不清楚⽇志应该包含什么内容
本篇着重讲解系统⽇志,所以以下“⽇志”均为“系统⽇志”的简称。我将针对这⼏个⽅⾯对系统⽇志的打印做⼀个简要的总结。另外对Java中常⽤的⽇志打印框架(log4j)的⼏种使⽤⽅式做⼀个⽰范。
WHERE
1.程序⼊⼝
在⼊⼝打印⽇志是因为这个时候传递进来的参数没有经过任何处理,将它打印在⽇志⽂件中能⼀眼就知道程序的原始数据是否符合我们的预期,是不是传递进来的原始数据就出现的问题。
2.异常捕获
在异常打印出详细的⽇志能让你快速定位错误在哪⾥,例如在程序抛出异常捕获时,在平时我们经常就是直接在控制台打印出堆栈信息e.printStackTrace(),但在实际的⽣产环境更加艰苦,更别说有IDE来让你查看控制台信息,此时就需要我们将堆栈信息记录在⽇志中,以便发⽣异常时我们能准确定位程序在哪⾥出错。
3.重要信息
这⼀点可能很宽泛,因为不同的业务逻辑重点可能并不⼀样,例如在有的重要参数不能为空,此时就需要判断是否为空,如果为空则记录到⽇志中;还有的例如传递进来的参数经过⼀系列的算法处理过后,此时也需要打印⽇志来查看是否计算正确。但切记,尽量不要直接在for循环中打印⽇志,特别是for循环特别⼤时,这样你的⽇志可能分分钟被冲得不见踪迹,甚⾄带来性能上的影响。
WHO
⽇志打印通常有四种级别,从⾼到底分别是:ERROR、WARN、INFO、DEBUG。应该选⽤哪种级别就是个很重要的问题。
⾸先明确⽇志级别中的优先级是什么意思,在你的系统中如果开启了某⼀级别的⽇志后,就不会打印⽐它级别低的⽇志。例如,程序如果开启了INFO级别⽇志,DEBUG⽇志就不会打印,但不打印不代表不产⽣,这在后⾯会提到。通常在⽣产环境中开启INFO⽇志。
那么应该打印什么级别的⽇志呢?⾸先我们应该明确谁在看⽇志。
通常来说,系统出了问题客户不会进到系统对着⿊黢黢的控制台查看⽇志输出,所以⽇志所⾯对的主体对象必然是软件开发⼈员(包括测试测试、维护⼈员)。
下⾯我们假设⼏种场景来帮助我们理解⽇志级别。
⾸先,程序开发结束后交由给测试⼈员进⾏测试,测试⼈员根据测试⽤例发现某个⽤例的输出和预期不符,此时他的第⼀反应该是查看⽇志。此时的⽇志是INFO级别⽇志不会出现DEBUG级别的⽇志,现在就需要根据⽇志打印分为两种情况决定他下⼀步操作:
1. 通过查看INFO⽇志发现是由于⾃⼰操作失误,造成了程序结果和预期不符合,这种情况不是程序出错,所以并不是bug,不需要开发
⼈员到场。
2. 通过查看INFO⽇志发现⾃⼰的操作正确,根据INFO⽇志查看并不是操作失误造成,这个时候就需要开发⼈员到场确认。
3. 以上两种情况是理想情况,测试⼈员仅根据INFO级别的⽇志就能判断出程序的输出结果与预期不符是因为⾃⼰操作失误还是程序
幼儿涂色bug。更为现实的情况实际是测试⼈员并不能根据INFO级别的⽇志判断是否是⾃⼰失误还是程序bug。
综上,INFO级别的⽇志应该是能帮助测试⼈员判断这是否是⼀个真正的bug,⽽不是⾃⼰操作失误造成的。
假设测试⼈员现在已经初步判断这是⼀个bug,并且这个bug不那么明显,此时就需要开发⼈员到场确认。
开发⼈员到达现场后,第⼀步应该是查看INFO⽇志初步作初步判断验证测试⼈员的看法,接着如果不能判断出问题所在则应该是将⽇志级别调整⾄DEBUG级别,打印出DEBUG级别的⽇志,通过DEBUG⽇志来分析定位bug出在哪⾥。
所以,DEBUG级别的⽇志应该是能帮助开发⼈员分析定位bug所在的位置。
ERROR和WARN的级别都⽐INFO要⾼,所以在设定⽇志级别在INFO时,这两者的⽇志也会被打印。根据上⾯INFO和DEBUG级别的区别以及适⽤⼈员可以知道,ERROR和WARN是同时给测试和开发观察的。
WARN级别称之为“警告”,这个“警告”实际上就有点含糊了,它不算错,你可以选择忽视它,但也可以选择重视它。例如,现在⼀个WARN⽇志打出这么⼀条⽇志“系统有崩溃的风险”,这个时候就需要引起⾜够的重视,它代表现在不会崩溃,但是它有崩溃的风险。或者出现“某⽤户在短时间内将密码输
出很多次过后才进⼊了系统”,这个时候是不是系统被暴⼒破解了呢?等等,这个级别⽇志如同它的字⾯含义,给你⼀个警告,你可以选择忽视,也可以重视,但⾄少它现在不会给系统带来其他影响。
ERROR级别称之为“错误”,这个含义就更明显了,就是系统出现了错误,需要处理。最为常见的就是捕获异常时所打印的⽇志。
上⾯我们介绍了四种⽇志级别的区别,特别需要注意的是INFO级别和DEBUG级别所适⽤的⼈员。那么我们该如何选择哪个级别的⽇志输出呢?
以下是我的个⼈理解:
INFO
1. 程序⼊⼝,这能让开发⼈员确认参数是否为⾃⼰所为。
2. 计算结果,测试关⼼的程序的输出结果是否符合预期,那么对于计算过程不应该关⼼,仅给出计算结果就能判断是否符合预期。
DEBUG
对于DEBUG级别,我认为更关⼼的是过程,以及更为具体的相关信息,因为帮助它的定位在于帮助开发⼈员定位bug,定位bug就需要较为详细的参数信息才能定位。例如对于某个具体的算法过程,可以使⽤DEBUG打印,开发⼈员不仅关⼼结果,同时在结果不正确时应该能根据DEBUG⽇志查询计算过程是否出现偏差
WARN
某个不常⾛到的分⽀,对于常规的操作是不应该打印WARN⽇志的,只有在满⾜某个条件才能⾛到的分⽀,且这个分⽀引起了“警觉”,此时就应该打印WARN⽇志。
ERROR
毫⽆疑问出现错误,程序不能继续运⾏下去就应该打印ERROR⽇志,这个错误并不是业务上的错误。例如,新增某个⽤户发现已经存在时,此时虽然新增失败,但不能说程序出现错误就打印ERROR⽇志;在删除某个⽤户发现⽤户已经被锁定时,此时也不能说因为程序不能按照删除的逻辑继续运⾏下去就应该打印ERROR⽇志。
WHAT
应该打印什么内容?打印的内容⼀定要从实际出发。也就是说如果在实际的⽣产环境中,你的⽤户
九湖乡量很⼤,⽇志在不停地刷新,如何定位某个⽤户的整个登录以及后续的操作呢?当然就是根据⽤户名来跟踪。所以打印内容的第⼀要素就是要能便于定位;定位过后也许⽤户在好⼏个模板中进⾏操作,还是定位,这个时候定的是模块的位;还有⼀点当然就是⽤户操作时的具体参数;最后⼀点就是⽤户⼲了什么。
总结就是,[id, module, params, content](关键字,模块,参数,内容)。
以上就是对⽇志打印的⼏点建议,说的不全⾯,抛砖引⽟。下⾯是对⽇志打印框架(log4j)的⾮最佳实践。
在resources(IDEA中resources就是classpath路径)中新建⼀个log4j.properties⽂件,如下所⽰:
#⽇志输出到控制台
新规则log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.ConversionLayout = %d [%t] %-5p %c - %m%n
#⽇志输出到⽂件
log4j.appender.logfile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File = /Urs/yulinfeng/Log/log
普拉提的好处及作用log4j.appender.logfile.maxFileSize=10240KB #⽇志的最⼤容量为10M
log4j.appender.logfile.Append = true #是否追加写进⽂件
log4j.appender.logfile.Threshold = DEBUG #输出DEBUG级别⽇志到⽂件中
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %d [%t] %-5p %c - %m%n
第1⾏,Logger=INFO, stdout, logfile。这是log4j的根配置,第⼀个参数表⽰输出什么级别的⽇志,后⾯的参数表⽰输出的位置,位置可以是控制台,也可以是⽂件,语法为Logger=[level], appendername……,在这⾥定义了两个输出位置,名字⽆所谓取
设么,有意义即可。⽇志级别从⾼到低分别是:OFF、FATAL、ERROR、WARN、INFO、DEBUG、
ALL,log4j建议只使⽤ERROR、WARN、INFO、DEBUG四个级别,也就是也就是在上⾯提到过的。
第3、7⾏就分别指定了stdout和logfile⽇志的输出位置,log4j⼀共提供了5个。
org.apache.log4j.ConsoleAppender(控制台)
org.apache.log4j.FileAppender(⽂件)
org.apache.log4j.DailyRollingFileAppender(每天产⽣⼀个⽇志⽂件)
org.apache.log4j.RollingFileAppender(⽂件⼤⼩达到⼀定⼤⼩后产⽣⼀个新的⽂件)
org.apache.log4j.WriterAppender(将⽇志信息以流格式发送到任意位置)
第4⾏表⽰⽇志信息的格式,⼀共有以下⼏种。
org.apache.log4j.HTMLLayout(以HTML表格输出)
org.apache.log4j.PatternLayout(灵活的⾃定义格式输出)
org.apache.log4j.SimpleLayout(简单的格式输出,只包括⽇志级别和⽇志信息的字符串)
宠物种类
org.apache.log4j.TTCCLayout(包含线程、⽇志级别、⽇志所在类和⽇志信息的字符串)
通常为了更为灵活的打印⽇志,我们会选择PatternLayout布局的⽇志,同时通过ConversionPattern⾃定义输出格式。
按照上⾯的配置,我们就可以在代码中进⾏⽇志的输出了。由于是在Spring框架下使⽤log4j,所以就要使⽤Spring对log4j进⾏初始化,在l中对log4j进⾏初始化。
大蒜功效<web-app>
<display-name>log web</display-name>
<context-param>
<param-name>log4jConfigurationLocation</param-name>
<param-value>classpath*:log4j.properties</param-value>
</context-param>
<!--每隔60s扫描log4j的配置⽂件,这⾥配置的log4jRefreshInterval参数表⽰能不⽤重启web服务器就
能动态更改log4j⽇志级别,这也是和Spring整合的⼀⼤好处-->
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>
<listener>
<!--从spring4.2.1开始Log4jConfigListener已经被废弃,最好使⽤log4j2对应的org.apache.logging.log4j.web.Log4jServletContextListener
.Log4jServletContextListener-->
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
</web-app>
private Logger log = Logger(Test.class);
log.info(“test info”);
上⾯是所有⽇志⽂件都输出到⼀个⽂件的情况,在实际中我们很有可能针对不同的模块输出到不同到⽇志⽂件。
修改log4j.properties:
#输出到控制台
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.ConversionLayout = %d [%t] %-5p %c - %m%n
#模块1输出的⽇志⽂件
dule1 = org.apache.log4j.DailyRollingFileAppender
dule1.File = /Urs/yulinfeng/Log/module1
dule1.maxFileSize=10240KB #⽇志的最⼤容量为10M
dule1.Append = true #是否追加写进⽂件
dule1.Threshold = DEBUG #输出DEBUG级别⽇志到⽂件中鸡毛蒜皮的意思
dule1.layout = org.apache.log4j.PatternLayout
dule1.layout.ConversionPattern = %d [%t] %-5p %c - %m%n
#模块2输出的⽇志⽂件
dule2 = org.apache.log4j.DailyRollingFileAppender
dule2.File = /Urs/yulinfeng/Log/module2
dule2.maxFileSize=10240KB #⽇志的最⼤容量为10M
dule2.Append = true #是否追加写进⽂件
dule2.Threshold = DEBUG #输出DEBUG级别⽇志到⽂件中
dule2.layout = org.apache.log4j.PatternLayout
dule2.layout.ConversionPattern = %d [%t] %-5p %c - %m%n
在模块1中输出⽇志⽂件时其实就是参数不同⽽已:
private Logger log = Logger(“module1”);电影赏析
log.info(“test info”);
在模块2中:
private Logger log = Logger(“module2”);
log.info(“test info”);
以上就是在Spring中使⽤log4j⽇志框架的⾮最佳实践。
最后,还要介绍另外⼀种打印⽇志的⽅式,上⾯的⽅式将会在每个类中都定义⼀个Logger对象,这
样的代码相对于业务逻辑来说实际是不想关,此时就可以利⽤Spring中的AOP⾯向切⾯编程打印⽇志。这⾥可能不是所有的⼈都能接触到利⽤AOP来打印⽇志,这⾥暂时不做详细介绍。