作者:手机用户2502853267 | 来源:互联网 | 2023-08-30 11:01
为何要log如何写好log,是程序员的必修课。比较通用的方式是Apache的log4j2,和第一个版本log4j相比,log的等级更多,可以准实时在运行过程中修改log登记,适合在发现问题时提升等
为何要log
如何写好log,是程序员的必修课。比较通用的方式是Apache的log4j2,和第一个版本log4j相比,log的等级更多,可以准实时在运行过程中修改log登记,适合在发现问题时提升等级,获取更详细的log,而平时采用一般等级。
不推荐使用System.out的方式:
- 没有等级区分,很可能会影响程序的性能
- 不能提供类名,代码行(一般错误定位才打开,生产环节中缺省不打开)的额外信息
- System.out将输出在catalina.out中,如果tomcat运行若干个web app,这些信息会混在一起。
我会要求在提交版本时,禁止System.out的方式。另外应该log哪些信息也很重要,有些开发人员会log某个对象的值,这些应该是开发调测时跟踪代码用的,在开发时可以使用IDE的断点调测,记录log或者System.out也可以,但是作为提交的版本,这就不是关键信息。再优秀的程序员有会犯错的,生产环节碰到的奇奇怪怪的情况,有时很难在代码阶段想到。不要将log等同于开发调测,还用于版本上线的异常/错误跟踪、重要数据变更记录(如配置变更),性能跟踪,记录与其他模块/接口的交换信息(这个很重要,常碰到一类开发人员,总认为自己的代码不会有错,错的都是别人,我会要求给出抓包证明,或者log信息,根据交互数据定位错误。)。
要log什么
要log哪些信息,看项目需求,但是有些基本原则:
- web app崩溃或者异常退出需要尽可能详细的信息,如 messages, stack traces, thread dumps, 甚至heap dumps;但问题在于如果crash了,我们加在程序中的log也就写不出来,这时需要JVM的工具。以OpenJDK JVM为例,可以在在运行命令中加入一下参数,例如
-XX:+HeapDumpOnOutOfMemoryError
,记录内存溢出的错误。
- 出现error时,应记录error有关的任何信息(type,message, stack trace, app对error的处理)。注意,所有的重要信息记录在log,而显示给用户只是最小的错误信息,告知出错即可,不要泄漏任何的系统信息,如文件路径,SQL查询语句等等。Tomcat中500会给出详细的错误信息,这对我们调测很有作用,但是作为生产系统,hackers可以重中获取敏感信息。
- 有时问题不会影响处理,可以继续,但需要提醒注意,作为warn,记录相关的状态,信息,和app的处理,甚至包括stack trace。
- 重要事件,例如创建实体,启动部件,用户成功登陆,系统状态等等。
- 当跟踪某个问题是,可能会希望看到相关的执行方法,参数等,但需要注意,我们并不需要总能看到这些信息,产生这些信息会影响性能。(这就是运行时可修改显示级别的意义,也就是为何log4j的版本为2。)极端的情况下,对每个方法的输入和输出进行尽量,一旦出现loop,就是极大的性能负担,我们应该减少这类log。
- 审计(例如syslog)就是某种方式的log。
Log在哪里
log不一定在文件,还有很多方式,也可以同时log在多个地方,采用同步或者异步的方式,甚至有时如果log失败就不继续执行动作。Log输出一般有以下几种方式(我们可以同时采用某些):
- Console:实时调测机制,输出在stdout,等同System.out.println(),在tomcat中,也同时输出到catalina.out文件。就单纯console而言,console buffer满后,相关信息我们就看不到,因此通常在开发过程中使用,又或者其他的log输出方式失效时,即在log故障时采用。
- Flat Files:可以看历史记录,但是很难做信息过滤或筛选。这些记录格式可以采用XML(但会造成文件很大),JSON,syslog(在Windows则为Windows Event Log)。一般文件可以滚动存放,以tomcat自己的log为例,按日期存储。
- Sockets:比较少用,通过socket输出到其他地方。(以前一个项目通过socket直接输出到图形界面中,提供给运维人员,通常只限于交互消息或协议消息跟踪,例如SIP)。
- SMTP和SMS:通过邮件和短信发送。在之前一个项目中,申请了一个微信公众号,将认为重要的告警信息推送到公众号上,通知运维人员赶紧上去看看出啥事,另外也定期将业务的统计信息推送过来。我是觉得挺方便的。
- 数据库:是一种常用方式,优点是容易进行过滤,缺点是写数据库比写文件性能要差点,存储要多点,如果部署在云端,云数据库要贵点,为了log不太值得,解决这些缺点,可以采用非关系型数据库,例如MongoDB,以二进制JSON(BSON)格式来节省空间。此外MongoDB以牺牲读性能(在大量数据中过滤慢)来换取高的写性能(insert数据比SQL要快),减少对程序性能的影响。
Log等级和类别
log等级相当于相对重要程度或者详细程度,log类型则与内容有关,我们不仅需要等级,很多时候也会需要类别对log内容进行筛选。
Log level
log等级定义log的相对重要程度。有时是整数,有时是明确语义的字符串。等级的定义并没有什么标准,下面是java.util.logging.Level的规定:
常用名字 |
Level常量 |
说明 |
Fatal Error / Crash |
-- |
最严重的错误。通常那些可以导致崩溃或者过早中断程序 |
Error |
SEVERE |
表明有严重问题的产生 |
Warning |
WARNING |
表明有某事件发生,有可能导致问题,需要查看 |
Information |
INFO |
对程序监控或者调测有用的信息。 |
Configuration Details |
CONFIG |
详细的配置信息,通常在应用或者器件启动时出现。 |
Debug |
FINE |
给出调测信息,通常包括变量的值 |
Trace Level 1 Trace Level 2 |
FINER FINEST |
不通级别的应用跟踪。很多log框架只有1个trace级别。 例如FINER用来记录执行SQL语句,而FINEST用来记录调用的方法或方法结束。 |
在Apache HTTPD 2.2中log等级为emerg, alert, crit, error, warn, notice, info和debug,在2.4中增加trace1~trace8。如何划分等级视乎项目的需要。
Log分类和筛选
在绝大部的java log场景中,log分类同时就是log实例的名字,例如java class的名字,一个类一个log实例。使用不同的log分类可以有不同的log呈现级别,例如一个是trace,一个是warn。
对于一些log系统,可以有层次结构,一个log可以继承另一个log,添加一些级别。
和log分类的比较相似,log筛选将不同类型和产生源的时间可以log在不同的位置。而log分类通常定义不同的log级别而非不同的位置。其实也没有必要对着两者太严格区分,都是有助于我们过滤。
选择Logging framework
如果我们不打算重新造轮子,可以选择第三方的logging framework。我们希望能有灵活的输出,例如不需要改动我们的程序,就可以将输出从文件变更为数据库,换言之,API固定,而可以有不同的实现。java.util.logging就是这样的好例子,可以写文件,流,socket,console,设置memory。然而我们很少使用java.util.logging,它最大的缺点是,配置是通过系统,而不是在classpath,这导致在同一web容器的两个web app不能有不同的log设置,除非浏览器扩展了基础的log实现。Tomcat进行了扩展,但是不是所有的容器都这样,但我们最好能在自己的app中解决问题。
选择的另一个考虑是性能。调用一个debug的log不应消耗毫秒级时间,而应该是纳秒级别,我们可以通过关闭所有的log,来审视性能的影响。
有两个比较好的开源logging framework:
- Log4j:http://commons.apache.org/proper/commons-logging/ 或者http://logging.apache.org/log4j/2.x/ ,Apache Commons Logging
- Slf4j:http://www.slf4j.org/ ,Simple Logging Facade for java。
对于Maven,建议对API使用compile,而实现使用runtime,这样避免应用直接使用底层的实现,在运行时,根据配置采用具体的实现。
相关链接: 我的Professional Java for Web Applications相关文章