天天看点

log4j+slf4j日志管理系统背景log4j日志管理框架slf4j简单日志门面spring项目引入Log4j后记

背景

当我们在项目中想使用日志系统帮我们进行日志记录管理时可以使用log4j+slf4j,这两个日志框架(slf4j其实并不是日志框架,而是一个日志facade)。

log4j日志管理框架

log4j是Apache下面的日志记录管理框架,它可以将日志输出到控制台、文件、数据库、邮件等等。这些只需要简单的配置一下配置文件即可完成。log4j有两种配置文件格式:log4j.xml和log4j.properties,使用log4j.properties可以简单的使用键值对形式进行配置,但是功能实现来说相对log4j.xml差一些。比如级别过滤,所以当两种配置文件同时存在的时候以log4.xml为准。log4j优于System.out.println()是在于他在禁止一些信息输出的同时不会妨碍其他的输出能力,他根据开发者的配置进行输出。

log4j重要组件

log4j从功能实现来说,主要由三大组件组成:Logger、Appender、Layout。 他们的关系如下:

log4j+slf4j日志管理系统背景log4j日志管理框架slf4j简单日志门面spring项目引入Log4j后记

Logger是日志记录器(也可以说是日志级别)、Appender是日志输出目的地、Layout是日志输出格式控制器,一个日志记录器可以对应多个输出目的地,每个输出目的地有特定的输出格式。

Logger使用

log4j级别

log4j提供了7种级别:ALL<DEBUG<INFO<WARN<ERROR<FATAL<OFF,建议使用DEBUG到ERROR,实际项目中也是基本使用这5个级别。log4j级别原则是只要大于指定优先级就进行输出。

代码中使用Logger

private static final Logger logger = Logger.getLogger(ClassName.Class);
logger.debug("debug information");
logger.info("info information");
logger.warn("warn information");
logger.error("error information");
           

实际ClassName.class可以自定义任意字符串,但是建议使用类名。

配置文件log4j.xml定义logger

<logger name="loggerName" additivity="true|false">
	<appender-ref ref="appenderName1"/>
	<appender-ref ref="appenderName1"/>
</logger>
           

Appender

log4j将日志输出源称为Appender,每个输出源一个Appender,一个logger可以对应多个Appender。

常见Appender

org.apache.log4j.ConsoleAppender:控制台输出 org.apache.log4j.FileAppender:输出到文件 org.apache.log4j.DailyRollingFileAppender:每天产生日志输出到文件 org.apache.log4j.RollingFileAppender:文件达到指定大小产生新文件 org.apache.log4j.WriterAppender:以流格式发送到指定位置 org.apache.log4j.jdbc.JDBCAppender:输出到数据库

配置文件log4j.xml定义Appender

<appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
	<layout class="org.apache.log4j.PatternLayout">
		<param name="ConversionPattern"  value="%d{yyyy-MM-dd HH:mm:dd,SSS} [%t] [%c:%L] [%p] - %m%n"/>
	</layout>
	<filter class="org.apache.log4j.varia.LevelRangeFilter">
		<param  name="LevelMin" value="INFO"/>
		<param  neme="LevelMax" value="ERROR"/>
	</filter>
</appender>
           

Layout

每个Appender可以指定特定的输出格式,log4j将输出格式称为Layout。

常见Layout

org.apache.log4j.HTMLLayout:HTML格式Layout org.apache.log4j.PatternLayout:自定义格式Layout(常用) org.apache.log4j.SimpleLayout:包含日志信息级别及信息 org.apache.log4j.TTCCLayout:包含生产时间、类别、级别等

常用PatternLayout

通常PatternLayout最为常用,可以根据自己的需求自定义输出格式,使用格式化符指代特定信息: %m 输出代码中指定的消息 

%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL 

%r 输出自应用启动到输出该log信息耗费的毫秒数 

%c 输出所属的类目,通常就是所在类的全名 

%t 输出产生该日志事件的线程名 

%n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n” 

%-x 左对其信息

log4j.properties配置

#定义根Logger,后面为根Logger定义的级别,再后面为Appender名称,可以有多个,根logger对应的多个appender。每个Appender再在后面定义
log4j.rootLogger=INFO, AppenderName1,AppenderName2
log4j.additivity.org.apache=false:表示logger不会继承父Logger的appender输出,默认为true及输出

#定义输出控制台的Appender
log4j.appender.AppenderName1=org.apache.log4j.ConsoleAppender		#指定输出源
log4j.appender.AppenderName1.layout=org.apache.log4j.PatternLayout	#指定输出样式
log4j.appender.Threshold=WARN		#设置该appender的最低级别
log4j.appender.AppenderName1.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n	#设置输出样式
log4j.appender.AppenderName2=org.apache.log4j.DailyRollingFileAppender
log4j.appender.AppenderName2.file=xxx.log
log4j.appender.AppenderName2.append=true	#false为覆盖制定文件内容,true为每天的日志追加到指定文件
log4j.appender.AppenderName2.DatePattern='.'yyyy-MM	#每月滚动一次日志文件,及每月产生一个日志文件,当月名称为xxx.log,上个月的为xxx.log.yyy-MM,这个也可以设置为:'.'yyyy-MM 每月、'.'yyyy-ww 每周、'.'yyyy-MM-dd 每天、'.'yyyy-MM-dd-a每天两次、'.'yyyy-MM-dd-HH 每小时、'.'yyyy-MM-dd-HH-mm 每分钟

log4j.appender.AppenderName2.layout=org.apache.log4j.PatternLayout
log4j.appender.AppenderName2.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n

log4j.logger.xxx=INFO,AppenderName2     #指定logger的appender
           

log4j.xml配置

理解了log4j.properties中的含义,在看 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="CONSOLE" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c:%L] [%p] - %m%n"/>
        </layout>
        <!-- 过滤设置输出级别 -->
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="LevelMin" value="INFO" />
            <param name="LevelMax" value="ERROR" />
        </filter>
    </appender>

    <!-- 错误日志Appender-->
    <appender name="error" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="/opt/yjz/server/jetty-6.1.22/logs/index_error.log"/>
        <param name="Append" value="true"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c:%L] [%p] - %m%n"/>
        </layout>
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="LevelMin" value="ERROR"/>
            <param name="LevelMax" value="ERROR"/>
        </filter>
    </appender>

    <!-- INFO到ERROR级别Appender -->
    <appender name="index" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="/opt/yjz/server/jetty-6.1.22/logs/index_all.log"/>
        <param name="Append" value="true"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c:%L] [%p] - %m%n"/>
        </layout>
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="LevelMin" value="INFO"/>
            <param name="LevelMax" value="ERROR"/>
        </filter>
    </appender>

    <!-- INFO到WARN级别Appender-->
    <appender name="info" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="/opt/yjz/server/jetty-6.1.22/logs/info.log"/>
        <param name="Append" value="true"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c:%L] [%p] - %m%n"/>
        </layout>
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="LevelMin" value="INFO"/>
            <param name="LevelMax" value="WARN"/>
        </filter>
    </appender>

    <appender name="debug" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="/opt/yjz/server/jetty-6.1.22/logs/debug.log"/>
        <param name="Append" value="true"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c:%L] [%p] - %m%n"/>
        </layout>
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="LevelMin" value="DEBUG"/>
            <param name="LevelMax" value="DEBUG"/>
        </filter>
    </appender>

    <!-- additivity是否继承父Logger的appender,设置为false,Logger只会在自己的appender输出,如果是true,则会在父Appender输出 -->
    <logger name="com.yjz" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>

    <logger name="com.yjz.child" additivity="false">
        <appender-ref ref="info"/>
        <appender-ref ref="error"/>
    </logger>

    <root>
        <level value="debug"/>
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="index" />
    </root>

</log4j:configuration>
           

级别优化判断

if(logger.isDebugEnable()){
	logger.debug("debug inforamtion"+name);
}
           

我们在一些成熟框架中经常看到这种判断,不是说我们在配置文件中已经过滤了输出文件吗,为什么这里还要判断?原因在于优化,字符串的拼接操作也会影响运行的效率,如果先判断logger的级别,如果该级别已经过滤,就省去了字符串拼接操作了。

slf4j简单日志门面

简单日志门面(simple logging Facade for java),slf4j并不是一个完整的日志框架,他只是为各种日志框架提供统一的API接口,及根据slf4j提供的接口规则使用Logger,而在部署的时候根据自己的需求配置自己希望使用的日志系统。可以说他是将各个日志系统接口进行了抽象,抽象为统一的slf4j接口,这样只要项目中使用slf4j API,我们可以随时更换日志系统,而不用更改代码。

slf4j的jar包

当我们使用slf4j的时候,需要加入他的jar包slf4j-api.xxx.jar,如果日志系统使用的是log4j,则在加入slf4j-log4j.xxx.jar,如果使用JDK自带的logging日志系统只需将slf4j-log4j.xxx.jar替换为slf4j-jdkxx.xxx.jar

slf4j门面原理

log4j+slf4j日志管理系统背景log4j日志管理框架slf4j简单日志门面spring项目引入Log4j后记

(图片来源于网上)

Logger API

private static final Logger logger = LoggerFactory.getLogger(ClassName.class);
logger.info("info information");
           

Java中Logger如何匹配Appender

当我们在代码中定义logger的时候,getLogger通过ClassName定义的logger(其实可以自定义),但是我们在log4j.xml并没有看到以这个名字定义的logger,那么这个日志信息会输出在哪里?原来这里是有一个继承的问题,我们通过ClassName.class获取的是类名称,比如为com.yjz.xxx.yyyy,首先要知道,所有的logger都继承自root,所以他们的信息会在rootLogger定义的Appender中输出,还有就是我们在log4j.xml中定义了com.yjz这个logger,这样属于com.yjz包下的类都会继承这个logger,所以会在这个logger定义的appender输出。为了不重复输出我们使用additivity="false",这样只会输出一遍。

spring项目引入Log4j

在web.xml中引入

<context-param>
	<param-name>log4jConfigLocation</param-name>
	<param-value>/WEB-INF/conf/log4j.xml</param-value>
</context-param>
<listener>
	<listerner-class>org.springframework.web.util.Log4jConfigListener</listerner-class>
</listener>
           

后记

这里只是这两天简单的应用了log4j+slf4j日志管理系统,对于其更深入的学习待有时间详细了解。