日志的使用

Apache Logging Services

背景可维护性及不得不涉及到系统监控和Bug的快速定位。在开发阶段还比较容易对系统进行监控,一般都会在本机上对系统的运行进行实时监控。而对于bug的定位,开发者都会熟练使用debug功能进行bug定位,更有甚者通过多年的开发经验根据系统的异常信息直接能分析出来Bug产生的原因、位置以及解决方案。但是,系统毕竟是人开发的,我们无法预料到在运行中会出行什么想不到的问题,即使在各种测试中没有出现,但是也无法保证不会出现一些意想不到的问题。那么在系统运行期间如果产生问题出现异常且无法在测试环境中重现,我们又该如何快速、准确地对bug进行定位分析和解决呢?举个亲身的例子吧,公司一套设备监控系统,用来对上万个节点进行实时监控,如果该节点有异常(比如温度过高、电压过高等)则向系统进行发出告警信息。在开发环境中只有五十多个设备被安全(不会产生什么告警)的放在机房中供开发和测试使用,这种测试环境根本无法模拟实际环境。系统在测试中没有出现过什么大的问题。然而,在实际的运行环境中,偶尔发现系统的一个模块功能会丧失,失去告警接收的功能。在本地测试的时候从来没有发现过类似问题,但是部署在实际环境中就会有发生。我们不可能实时的24小时对系统进行人工监控,那么该如何定位功能丧失的原因呢?这时,对于系统日志来说就“是时候表演真正的技术了”。后来通过系统日志分析之后发现功能的丧失是由于大量的告警同时上报导致数据库链接不够用产生大量connectionTimeout进而导致OOM异常,这个模块彻底死了。可见,日志对于运行环境中系统的监控和问题定位是至关重要的,在系统设计、开发和实现的过程中必须时刻注意着log的输出,这将会对于日后的系统监控和异常分析起至关重要的作用!

系统的哪些运行信息需要进行日志记录?

功能模块的启动和结束

完整的系统由多个功能模块组成,每个模块负责不同的功能,因此需要对模块的启动和结束进行监控。是否在需要的时机正常加载该模块?又是否在退出结束的时候正常完成结束操作,正常退出?

用户的登录和退出

哪位用户在什么时间通过什么IP登录或退出了系统

系统的关键性操作

数据库链接信息、网络通信的成功与失败等

系统运行期间的异常信息

NPE、OOM以及其他的超时、转换异常等

关键性方法的进入和退出

一些重要业务处理的方法,在进入和结束的时候需要有日志信息进行输出

什么是日志框架

日志框架的能力:定制输出目标输出到文件数据库、通过配置文件定制输出格式、日志携带的上下文信息如:类路径,线程,调用对象…、运行时选择性输出、灵活的配置、优异的性能

常见的日志框架

  • JCL 日志门面 Apache自带common-logging
  • SLF4J 日志门面 LogBack和SLF4J是同一个作者,而Spring boot用的 就是SLF4J + LogBack
  • jboss-logging 日志门面 服务大众的

  • JUL 日志实现 JDK自带的logging ,实现太过简陋

  • Log4j 日志实现 LogBack是Log4j的升级版
  • Log4j2 日志实现 LogBack才是真正意义上的Log4J2,Apache只是借了个名字,Log4J2太先进,很多框架对它的支持还不够完善。Log4J2优于Logback的超高性能,但是这种Logback已经足够优秀了
  • LogBack 日志实现

日志级别

  • ERROR level指出虽然发生错误事件,但仍然不影响系统的继续运行。
  • WARN level表明会出现潜在错误的情形。
  • INFO level表明 消息在粗粒度级别上突出强调应用程序的运行过程。
  • DEBUG Level指出细粒度信息事件对调试应用程序是非常有帮助的。
  • TRACE Level很低的日志级别,一般不会使用。

日志的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?xml version="1.0" encoding="UTF-8"?>
<configuration >

<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<!--展示的配置-->
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式-->
<pattern>
%d - %msg%n
</pattern>
</layout>
</appender>

<!--输出的info日志文件-->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志过滤-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">

<level>
ERROR
</level>
<!--匹配就禁止-->
<onMatch>DENY</onMatch>
<!--否则就输出-->
<onMismatch>ACCEPT</onMismatch>
</filter>
<!--输出格式-->
<encoder>
<pattern>
%d - %msg%n
</pattern>
</encoder>
<!--滚动策略 按照时间-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>
/Users/wenqi/data/log/tomcat/sell/info.%d.log
</fileNamePattern>
</rollingPolicy>
</appender>


<!--输出的error日志文件-->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">

<!--只拦截-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!--只要error级别的日志-->
<level>
ERROR
</level>
</filter>
<encoder>
<pattern>
%d - %msg%n
</pattern>
</encoder>
<!--滚动策略 按照时间-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>
/Users/wenqi/data/log/tomcat/sell/error.%d.log
</fileNamePattern>
</rollingPolicy>
</appender>

<!--输出日志到MySQL-->
<appender name="projectLog" class="ch.qos.logback.classic.db.DBAppender">
<!--数据库连接管理-->
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
<driverClass>net.sf.log4jdbc.DriverSpy</driverClass>
<url>jdbc:log4jdbc:mysql://127.0.0.1:3306/sell_log</url>
<user>sell</user>
<password>sell</password>
<sqlDialect class="ch.qos.logback.core.db.dialect.MySQLDialect" />
</dataSource>
</connectionSource>
</appender>

<!--日志级别和用在哪?-->
<root level="info">

<!--控制台的输出-->
<appender-ref ref="consoleLog"/>
<!--输出到info日志文件-->
<appender-ref ref="fileInfoLog"/>
<!--输出到error日志文件-->
<appender-ref ref="fileErrorLog"/>
<!--输出到数据库-->
<appender-ref ref="projectLog"/>
</root>


</configuration>

创建LogBack的数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT;

BEGIN;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
COMMIT;

BEGIN;
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;


BEGIN;
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;