目录
简介
任何系统都需要记录日志,实现一个日志系统既要分析需要记录什么的日志,也需要考虑实现方式以及便捷性和扩展性。
需要记录什么指的是日志内容。而一些现成的框架可以实现日志记录操作。
日志内容
从内容方面,跟用户关联性由弱到强,这些日志内容分为:系统日志,用户操作日志,业务操作日志,详细如下:
- 系统日志:记录系统的运行日志,A:框架日志,标识系统运行的情况。B:为实现业务的流程日志,为了调试和日志解决问题使用。
- 用户操作日志:这部分是记录用户的行为日志,这部分日志是为了记录用户操作的行为的,这部分日志大部分是系统提供者自行记录的日志,用户并看不到。
- 业务操作日志:同样是用户行为日志,但更偏向于业务流程操作。比如:转账记录,浏览记录,审批记录等等。这部分日志是需要从业务角度记录的,大部分情况下用户是直接可以查询的记录。
日志框架介绍
现有大部分框架都是正对系统日志进行的,部分技术也会用在“用户操作日志”上,用来发掘用户的行为。
现有的框架大部分记录的都是非结构的数据,这部分数据用来发现系统问题和挖掘用户行为是可以行的,但记录“业务操作日志”就显得有点任性。
用于做构成记录日志代码包括:“提供一组日志接口”,“实际记录日志组件”,“日志的收集和分析框架”。所以这个叫法就没法统一。
很多组织提供了不同的记录日志的组件,比如:jul(jdk自带),log4j,logbak 等等。如果一个系统直接依赖这些组件,那么更换组件的时候就需要改动左右记录日志的代码,这个影响是不可估量也是不可接受的。
所以人们就想统一出一套接口规范来,比如 Sef4j,Commons Logging 都是提供记录规范,但不记录实际日志的框架,Self4j整合不同的组件就可以使用不同的组件记录日志而不用修改代码。这个模式成为“外观设计模式”。
随着分布式和大数据的发展,需要记录将分布式系统上的日志收集和分析的需求被提出来。就产生了Flume(日志分析系统),Logstash(利用Logstash,你可以对日志进行传输、处理、管理和检索,并且提供Web接口以便开发者统计和查询日志信息),以及ELK这样的综合解决方案。
框架列举
JDKLog:日志小刀
JDKLog是JDK官方提供的一个记录日志的方式,直接在JDK中就可以使用。
JDKLog 的有点是使用非常简单,直接在 JDK 中就可以使用。但 JDKLog 功能比较太过于简单,不支持占位符显示,拓展性比较差,所以现在用的人也很少。
import java.util.logging.Logger;
/****
** JDKLog Demo
**/
public class JDKLog
{
public static void main( String[] args )
{
Logger logger = Logger.getLogger("JDKLog");
logger.info("Hello World.");
}
}
Log4J:日志大炮
Log4J 有 1.X 版本和 2.X 版本,现在官方推荐使用 2.X 版本,2.X 版本在架构上进行了一些升级,配置文件也发生了一些变化。
依赖包:
<!-- Log4J -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>
配置文件:
log4j.rootLogger=INFO,console
#console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=INFO
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%-5p] %l %rms: %m%n
日志记录:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/****
** Log4J Demo
**/
public class Log4jLog {
public static void main(String args[]) {
Logger logger = LogManager.getLogger(Log4jLog.class);
logger.debug("Debug Level");
logger.info("Info Level");
logger.warn("Warn Level");
logger.error("Error Level");
}
}
LogBack:日志火箭
LogBack 除了具备 Log4j 的所有优点之外,还解决了 Log4J 不能使用占位符的问题。
依赖:
<!-- LogBack -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
配置文件:logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<logger name="com.chanshuyi" level="TRACE"/>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
日志记录:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/****
** LogBack Demo
**/
public class LogBack {
static final Logger logger = LoggerFactory.getLogger(LogBack.class);
public static void main(String[] args) {
logger.trace("Trace Level.");
logger.debug("Debug Level.");
logger.info("Info Level.");
logger.warn("Warn Level.");
logger.error("Error Level.{}", "123");
}
}
注意:
- Logback使用的是self4j作为门户
- 解决了占位符问题
SLF4J:适配器
共性,都是使用sef4j的api进行调用,系统根据添加的jar包不同调用不同的日志组件。所以下边只记录jar包和配置文件。
SLF4J+JDKLog
POM:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.21</version>
</dependency>
SLF4J+LOG4J
POM:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' >
<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" />
</layout>
<!--过滤器设置输出的级别-->
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="levelMin" value="debug" />
<param name="levelMax" value="error" />
<param name="AcceptOnMatch" value="true" />
</filter>
</appender>
<!-- 根logger的设置-->
<root>
<priority value ="debug"/>
<appender-ref ref="myConsole"/>
</root>
</log4j:configuration>
或者:log4j.properties
log4j.rootLogger=INFO,console
#console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=INFO
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%-5p] %l %rms: %m%n
SLF4J+LogBack
POM:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
配置:logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<logger name="com.chanshuyi" level="TRACE"/>
<root level="warn">
<appender-ref ref="STDOUT" />
</root>
</configuration>
日志桥接
一个系统使用不同的日志组件,我们可以把不同的组件都桥接到SEF4J上。同样也有SEF4J到不同日志组件的桥接。
桥接的具体使用时通过添加jar实现的,并不需要进行配置,所以可能有循环桥接的问题,需要注意。图示:
SEF4J-To-Other
jar包名 |
说明 |
slf4j-log4j12-1.7.13.jar |
log4j1.2版本的桥接器,你需要将log4j.jar加入classpath。 |
slf4j-jdk14-1.7.13.jar |
java.util.logging的桥接器,JDK原生日志框架。 |
slf4j-nop-1.7.13.jar |
NOP桥接器,默默丢弃一切日志。 |
slf4j-simple-1.7.13.jar |
一个简单实现的桥接器,该实现输出所有事件到System.err. 只有INFO以及高于该级别的消息被打印,在小型应用中它也许是有用的。 |
slf4j-jcl-1.7.13.jar |
Jakarta Commons Logging 的桥接器. 这个桥接器将SLF4j所有日志委派给JCL。 |
logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar) |
slf4j的原生实现,logback直接实现了slf4j的接口,因此使用slf4j与 logback的结合使用也意味更小的内存与计算开销 |
Other-To- SEF4J
在实际环境中我们经常会遇到不同的组件使用的日志框架不同的情况,例如Spring Framework使用的是日志组件是Commons logging,XSocket依赖的则是Java Util Logging。当我们在同一项目中使用不同的组件时应该如果解决不同组件依赖的日志组件不一致的情况呢?现在我们需要统一日志方案,统一使用SLF4J,把他们的日志输出重定向到SLF4J,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。Slf4j带有几个桥接模块,可以重定向log4j,JCL和java.util.logging中的API到Slf4j。
jar包名 |
作用 |
log4j-over-slf4j-version.jar |
将log4j重定向到slf4j |
jcl-over-slf4j-version.jar |
将commos logging里的Simple Logger重定向到slf4j |
jul-to-slf4j-version.jar |
将Java Util Logging重定向到slf4j |
循环桥接
在使用slf4j桥接时要注意避免形成死循环,在项目依赖的jar包中不要存在以下情况。
多个日志jar包形成死循环的条件 |
产生原因 |
log4j-over-slf4j.jar和slf4j-log4j12.jar同时存在 |
由于slf4j-log4j12.jar的存在会将所有日志调用委托给log4j。但由于同时由于log4j-over-slf4j.jar的存在,会将所有对log4j api的调用委托给相应等值的slf4j,所以log4j-over-slf4j.jar和slf4j-log4j12.jar同时存在会形成死循环 |
jul-to-slf4j.jar和slf4j-jdk14.jar同时存在 |
由于slf4j-jdk14.jar的存在会将所有日志调用委托给jdk的log。但由于同时jul-to-slf4j.jar的存在,会将所有对jul api的调用委托给相应等值的slf4j,所以jul-to-slf4j.jar和slf4j-jdk14.jar同时存在会形成死循环 |
日志框架ELK
ELK是Elasticsearch、Logstash、Kibana的简称,这三者是核心套件,但并非全部。
Elasticsearch是实时全文搜索和分析引擎,提供搜集、分析、存储数据三大功能;是一套开放REST和JAVA API等结构提供高效搜索功能,可扩展的分布式系统。它构建于Apache Lucene搜索引擎库之上。
Logstash是一个用来搜集、分析、过滤日志的工具。它支持几乎任何类型的日志,包括系统日志、错误日志和自定义应用程序日志。它可以从许多来源接收日志,这些来源包括 syslog、消息传递(例如 RabbitMQ)和JMX,它能够以多种方式输出数据,包括电子邮件、websockets和Elasticsearch。
Kibana是一个基于Web的图形界面,用于搜索、分析和可视化存储在 Elasticsearch指标中的日志数据。它利用Elasticsearch的REST接口来检索数据,不仅允许用户创建他们自己的数据的定制仪表板视图,还允许他们以特殊的方式查询和过滤数据
图示:
附录
Commons Logging与Slf4j实现机制对比
Commons logging实现机制
Commons logging是通过动态查找机制,在程序运行时,使用自己的ClassLoader寻找和载入本地具体的实现。详细策略可以查看commons-logging-*.jar包中的org.apache.commons.logging.impl.LogFactoryImpl.java文件。由于OSGi不同的插件使用独立的ClassLoader,OSGI的这种机制保证了插件互相独立, 其机制限制了commons logging在OSGi中的正常使用。
Slf4j实现机制
Slf4j在编译期间,静态绑定本地的LOG库,因此可以在OSGi中正常使用。它是通过查找类路径下org.slf4j.impl.StaticLoggerBinder,然后绑定工作都在这类里面进。