关于Java程序的日志处理

本文最后更新于:8 个月前

日志记录这块,简单的控制台打印也是日志,输出log文件也是日志,跨服务器输出日志,也是日志。这还没说复杂的日志监控,日志监控,需要对代码打印日志的标准有要求。这个就比较复杂了,

首先我们要知道,日志是有级别的,log4j,我记得好像有7个级别,slf4j,有五个级别。首先我们得知道log4j和slf4j是什么东西,为啥把这两个放在一起说。题外话说一下命名,这个4j应该不少人有疑问,因为这东西不符合Java的驼峰命名规则啊,为啥要起这个名呢,好吧,其实就是For Java的音译加缩写。据说是传统,比如什么dom4j等等。这个级别是用来控制日志输出内容的,你设置的级别越高,打印的日志就越少,你配置级别最低,则打印全部级别的日志。准确来说是包含关系,就像一个金字塔结构一样。

还有Log4j和Slf4j这两个并不负责输出日志,准确的说这两个是Java日志的门面,又或者是标准,门面是什么意思,就是服务员,你去吃饭,只需要和服务员说,我要一碗蛋炒饭。至于服务员去找哪个厨师帮你做,你不用管,这两个东西也是一样,你和slf4j说,我要打印日志,slf4j得去找实现给你打印日志,但是只有slf4j是没有办法给你打印日志的。这个时候你就需要去找一个厨师,就是实现。logback是直接实现了slf4j的一个日志实现。好了。应该说明白了。

实际上logback官网上是有十分详细的介绍,尬就尬在,是英文文档。

依赖

普通的Java项目

implementation 'ch.qos.logback:logback-classic:1.2.10'
implementation 'ch.qos.logback:logback-core:1.2.10'
implementation 'org.slf4j:slf4j-api:1.7.32'

springboot中无需此依赖,默认的日志就是lagback。

配置文件介绍

<?xml version="1.0" encoding="UTF-8" ?>
<!--
    configuration节点有三个属性
    scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
    scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位默认单位是毫秒,当scan为true时此属性生效,默认时间间隔为1分钟
    debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态,默认值为false
 -->
<configuration scan="true" scanPeriod="2 seconds" debug="false">

    <!-- 
        设置上下文名称,每个<logger>都关联到<logger>上下文,默认上下文名称为default,但可以使用设置成其他名字,用于区分不同应用程序的记录,一旦设置,不能修改,可以通过 %contextName 来打印日志上下文名称,一般来说我们不用这个属性,可有可无。
     -->
    <contextName>demo</contextName>
    

    <!-- 
        用来定义变量的节点,定义变量后,可以使${}来使用变量,两个属性
        这个属性只有在springboot环境中使用才可以,且配置文件名要为logback-spring.xml才可正常使用。
        name: 和下文中的name一样
        scope:一般就填context就行了,具体配置可查看官网
        source: 读取application.yml或application.properties文件中的配置,
     -->
    <springProperty scope="context" name="app_name" source="logging.file.name"/>

    <!-- 
        用来定义变量的节点,定义变量后,可以使${}来使用变量,两个属性,当定义了多个<appender>的时候还是很有用的:
        name:变量名
        value:变量值
     -->
    <property name="log.dir" value="logs"/>
    <!--
    定义滚动记录文件appender 作用:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件
    RollingFileAppender class="ch.qos.logback.core.rolling.RollingFileAppender"
        参数:
        <append>:如果是true日志被追加到文件结尾,如果是false清空现存文件,默认是true
        <fileNamePattern>:被写入的文件名,可以是相对目录也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值
        <rollingPolicy>:当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名
        <triggeringPolicy>:告知RollingFileAppender合适激活滚动
        <prudent>:当为true时不支持FixedWindowRollingPolicy支持TimeBasedRollingPolicy,但是有两个限制:1不支持也不允许文件压缩,2不能设置file属性必须留空
    -->
    <appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 如果是true,日志被追加到文件结尾,如果是false,清空现存文件.默认是true -->
        <append>true</append>
        <prudent>false</prudent>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天滚动一次的日志 只保留30天内的日志文件 -->
            <fileNamePattern>logs/%d{yyyy-MM-dd}/info%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 对日志进行格式化 -->
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>

    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <prudent>true</prudent>

        <!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
            如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
            的日志改名为今天的日期。即,<File> 的日志都是当天的。
        -->
        <File>logs/error.log</File>
        <append>true</append>
        <!-- 
            当为true时不支持FixedWindowRollingPolicy支持TimeBasedRollingPolicy,但是有两个限制:1不支持也不允许文件压缩,2不能设置file属性必须留空
         -->
        <prudent>false</prudent>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/%d{yyyy-MM-dd}/error%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--
             配置日志级别过滤器 作用:根据日志级别进行过滤,如果日志级别等于配置级别过滤器会根据onMath和onMismatch接收或拒绝日志
             参数:
             <level>:设置过滤级别
             <onMatch>:用于配置符合过滤条件的操作
             <onMismatch>:用于配置不符合过滤条件的操作
                                       此处配置为只接收ERROR日志级别信息
         -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 定义控制台appender 作用:把日志输出到控制台 class="ch.qos.logback.core.ConsoleAppender" -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
        </layout>
    </appender>

    <!--
       logger用来设置某一个包的日志打印级别
       <loger> 仅有一个name属性,一个可选的level和一个可选的addtivity属性
               name:用来指定受此loger约束的某一个包或者具体的某一个类
               level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
               addtivity:是否向上级loger传递打印信息。默认是true,会将信息输入到root配置指定的地方,可以包含多个appender-ref,标识这个appender会添加到这个logger
   -->
    <!--    <logger name="com.xcbeyond.springboot" level="debug"/>-->
    <!-- 将root的打印级别设置为"error",指定了名字为"console","fileAppender","errorAppender"的appender -->

    <!-- 
        springProfile标签用来处理开发环境和生产环境的不同配置
        name: 读取spring.profiles.active的值
        profile即根据不同的环境使用不同的日志策略,这里举例开发和生产环境:
     -->

     <!-- 开发环境输出到控制台 -->
    <springProfile  name="dev">
        <root level="DEBUG">
            <appender-ref ref="STDOUT" />
        </root>
    </springProfile>

    <!-- 生产环境输出到文件 -->
    <springProfile  name="prod">
        <root level="INFO">
            <appender-ref ref="FILE_LOG" />
        </root>
    </springProfile>   


    <root level="debug">
        <appender-ref ref="console"/>
        <appender-ref ref="fileAppender"/>
        <appender-ref ref="errorAppender"/>
    </root>
</configuration>

应该是介绍的比较详细了,因为这东西平时很少有人去专门去记一下,我也是弄好一套,然后CV一下就可以了,注意上面的配置只是用来演示的,并不能直接CV下来用。下面我配置几种常用的配置,可以直接CV下来用的。

全部日志一个文件

控制台打印日志,并输出日志文件。

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
    <!-- 
        输出日志到控制台
    -->
    <appender name="soutAppender" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
        </layout>
    </appender>

    <!-- 
        输出日志到文件
     -->
    <appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <prudent>false</prudent>
        <File>logs/log.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/%d{yyyy-MM-dd}/log%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单个日志文件最大体积 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 对日志进行格式化 -->
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>

    <root level="all">
        <appender-ref ref="soutAppender"/>
        <appender-ref ref="fileAppender"/>
    </root>
</configuration>

不同级别不同文件

控制台打印太基础我就不说了,加强主要针对log文件,这个配置我们按日志级别输出对应级别的日志。方便我们定位和查找。一般来说,我们只需要info和error级别的日志即可。

<configuration>
    <!-- 
        输出日志到控制台
    -->
    <appender name="soutAppender" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
        </layout>
    </appender>

    <!-- 
        输出日志到文件
     -->
    <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 如果是true,日志被追加到文件结尾,如果是false,清空现存文件.默认是true -->
        <append>true</append>
        <prudent>false</prudent>
        <File>logs/info.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/%d{yyyy-MM-dd}/info%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单个日志文件最大体积 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 对日志进行格式化 -->
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 两个过滤器过滤warn和error基本的日志 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--过滤 WARN-->
            <level>WARN</level>
            <!--匹配到就禁止-->
            <onMatch>DENY</onMatch>
            <!--没有匹配到就允许-->
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--过滤 Error-->
            <level>ERROR</level>
            <!--匹配到就禁止-->
            <onMatch>DENY</onMatch>
            <!--没有匹配到就允许-->
            <onMismatch>ACCEPT</onMismatch>
        </filter>
    </appender>


    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 如果是true,日志被追加到文件结尾,如果是false,清空现存文件.默认是true -->
        <append>true</append>
        <prudent>false</prudent>
        <File>logs/error.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/%d{yyyy-MM-dd}/error%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单个日志文件最大体积 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 对日志进行格式化 -->
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 
            过滤日志,只包含warn以及warn以上级别的日志也就是error的日志
         -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

    <root level="all">
        <appender-ref ref="soutAppender"/>
        <appender-ref ref="infoAppender"/>
        <appender-ref ref="errorAppender"/>
    </root>
</configuration>

最终结果是会输出两个日志文件,info.logerror.log, info包括info级别以及以下级别的日志,error.log包括warnerror两个级别的日志。

不同包不同日志文件

我们一般的系统基本都有几层架构,有controller层,以及service层,logback可以根据不同的包来输出日志,也可以精确到具体的某一个类名。主要使用logger这个标签来完成。这个没有办法CV,因为不同的项目包名不一样,根据自己的项目需要修改一下

<configuration>
    <!--
        输出日志到控制台
    -->
    <appender name="soutAppender" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
        </layout>
    </appender>

    <!--
        输出日志到文件
     -->
    <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 如果是true,日志被追加到文件结尾,如果是false,清空现存文件.默认是true -->
        <append>true</append>
        <prudent>false</prudent>
        <File>logs/info.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/%d{yyyy-MM-dd}/info%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单个日志文件最大体积 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 对日志进行格式化 -->
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>

        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--过滤 WARN-->
            <level>WARN</level>
            <!--匹配到就禁止-->
            <onMatch>DENY</onMatch>
            <!--没有匹配到就允许-->
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--过滤 Error-->
            <level>ERROR</level>
            <!--匹配到就禁止-->
            <onMatch>DENY</onMatch>
            <!--没有匹配到就允许-->
            <onMismatch>ACCEPT</onMismatch>
        </filter>
    </appender>


    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 如果是true,日志被追加到文件结尾,如果是false,清空现存文件.默认是true -->
        <append>true</append>
        <prudent>false</prudent>
        <File>logs/error.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/%d{yyyy-MM-dd}/error%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单个日志文件最大体积 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 对日志进行格式化 -->
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

    <appender name="wlfAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 如果是true,日志被追加到文件结尾,如果是false,清空现存文件.默认是true -->
        <append>true</append>
        <prudent>false</prudent>
        <File>logs/wlf/wlf.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/wlf/%d{yyyy-MM-dd}/wlf%i.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- 单个日志文件最大体积 -->
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!-- 对日志进行格式化 -->
            <pattern>%date %level [%thread] %logger{10}.%class{0}#%method[%file:%line] %n%msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>
    <!-- 
        这个配置表示把com.wlf包下的日志以wlfAppender的配置进行打印日志
        level: 代表日志级别,不配置默认为all
    -->
    <logger name="com.wlf" level="debug">
        <appender-ref ref="wlfAppender"/>
    </logger>

    <root level="all">
        <appender-ref ref="soutAppender"/>
        <appender-ref ref="infoAppender"/>
        <appender-ref ref="errorAppender"/>
    </root>
</configuration>

这个不怎么常用,只需要知道就行了,一般只使用第二个配置就行了,直接CV就行。至于和springboot中使用,可以整,但是没啥必要。具体的配置还是多看看介绍,多多学习。或者直接CV。😁😁

主要内容到这里就介绍完了,logback的配置十分的灵活,但想要灵活的使用,还是需要多多的了解。当然打印日志归打印日志,如果你写代码打日志时不按照标准输出,再牛逼的配置也救不了。打印日志也需要规范一点。开发使用debug,需要留存的日志信息用info,异常信息用error。

封面

2022新年快乐


关于Java程序的日志处理
https://wangijun.com/2022/01/05/java-07/
作者
无良芳
发布于
2022年1月5日
许可协议