日志框架 log4j2 全解析

sdaq 2020-02-20

概述

logging翻译为日志记录

那问题是什么是日志?

日志实际上是日记的一种,用于记录某个时间点发生了什么事情,比如大学老师的教学日志,工作日志等

为什么要记录日志?

在实际生活中记录日志主要为了日后复查,

比如某个大学老师每天记录自己讲的什么内容,后面有学生某科成绩优异获奖了,校长想要奖励对应的老师,但由于每个老师教的班级都很多,并不一定记得是谁教的,这时候就可以查看教学日志来获取需要的信息了

再比如,工厂的生产日志,如果某个产品除了因为某个零件出现了故障,通过生成日志,可以找到与这个产品同批次的其他产品,进行返工,或是通过日志找到该零件的供应商,进行沟通解决!

程序中的日志

我们的程序开发完成后会被不同系统环境的用户下载使用,期间可能就会出现问题,直接把错误信息展示给用户看是没有任何意义的,用户看不懂也不会解决,那这时候就可以将用户执行的所有操作,以及代码运行的过程,记录到日志中,程序员通过分析日志内容,可以快速的定位问题

综上: 日志就是用来记录发生的事件的

日志并不会立即产生作用,而是当程序出现了问题时在去分析日志文件提取有用信息

java下的日志框架

日志框架 log4j2 全解析

门面

门面是Facade(外观模式)的实现,也称为门面模式,

是对内部多个子系统的封装,并对外提供一套统一的使用接口,从而屏蔽各个子系统在使用上的不同,大大降低了系统的使用难度,同时提高了系统的可维护性和扩展性;

实际上真正干活的还是是内部的子系统;就像给这些子系统加了一层装饰,Facede也得名于此;

图示:

日志框架 log4j2 全解析

因其性能优越性,实际开发中log4j是使用最多一个日志框架,也是我们需要掌握的目标;

官方性能对比:

日志框架 log4j2 全解析

log4j

日志级别:

日志级别其实指的就是日志信息应用场景,我们的程序会在不同的环境中运行,某些日志只有用在某些特殊场景中,例如:在开发阶段,我们为了检查错误,会输出一些调试信息,但是这些信息在生产环境下是不需要的,当然.我们可以在发布前删除这些调试代码,但这就显得非常low了,通过对日志信息区别对待,我们可以很方便的控制哪些日志在哪些场景下正常输出;
日志级别也在后续的问题定位中发挥着重要作用,当程序出现了问题,我们要根据日志来定位问题,这时便可以通过日志级别来快速过滤掉不需要的记录;

log4j日志级别:

log4j定义了8个级别,优先级从高到低依次为:

OFF>FATAL> ERROR> WARN> INFO> DEBUG> TRACE> ALL

  • ALL 最低等级的 用于打开所有日志记录
  • TRACE 很低的日志级别 一般不会使用
  • DEBUG 该级别信息对调试应用程序是非常有帮助的 主要用于开发过程中打印 一些运行信息
  • INFO 突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息 可用于生产环境中输出程序运行的一些重要信息,但是不能滥用 避免打印过多的日志
  • WARN 表明会出现潜在错误的情形 有些信息不是错误信息 但是也要给程序员的一些提示
  • ERROR 指出虽然发生错误事件 但仍然不影响系统的继续运行。打印错误和异常信息 如果不想输出太多日志 太多数情况下可以使用这个级别
  • FATAL 指出严重的错误事件,将会导致应用程序的退出。
  • OFF 最高等级的,用于关闭所有日志记录

依赖包:

<dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.5</version>
    </dependency>

配置文件:

2.x往后的版本不在支持properties作为配置文件,因其无法表达较为复杂的语法结构

log4j2会在classpath下查找配置文件,如果找不到则使用基础配置(输出到控制台),log4j支持 xml,json,jsn三种格式的配置文件,并可为测试环境和生产环境编写不同的配置文件;若有多个配置文件log4j将按照以下顺序读取:

  1. classpath下的名为log4j2-test.json 或者log4j2-test.jsn的文件.
  2. classpath下的名为log4j2-test.xml的文件.
  3. classpath下名为log4j2.json 或者log4j2.jsn的文件.
  4. classpath下名为log4j2.xml的文件.

示例log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!--   status="WARN" 用于设置log4j框架本身的日志级别-->
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="all">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

测试代码:

@Test
    public void test1(){
        Logger myLogger = LogManager.getLogger("myLogger");
        myLogger.debug("debug msg");
    }

Appenders节点:

该节点配置日志信息的输出目的地,可以是控制台,文件,邮件或数据库,控制台和文件是表常用的两个目的地;

上述案例既将日志信息输出到控制台,其子节点PatternLayout用于设置日志输出的字符传格式;

PatternLayout可用的格式化字符:

%d{HH:mm:ss.SSS}    表示输出到毫秒的时间
%t              输出当前线程名称
%-5level    输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补空格
%logger     输出logger名称,因为Root Logger没有名称
%msg            日志文本
%n              换行
%F              输出所在的类文件名,如Client.java
%L              输出行号
%M              输出所在方法名
%C                          产生log事件的java完全限定类名
%l              输出语句所在的行数, 包括类名、方法名、文件名、行数

输出到文件:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/>
        </Console>
        <File name="fileAppender" fileName="logs/app.log" append="false"><!--默认以当前项目为相对路径-->
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/>
        </File>
        
    </Appenders>
    <Loggers>
        <Root level="all">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="fileAppender"/><!--一个logger可配置对个appender输出到不同位置-->
        </Root>
    </Loggers>
</Configuration>
<!--File属性:
name用于给appender指定名字,以便logger引用 
fileName默认以当前项目为相对路径   
append参数表示是否将是追加到文件末尾 默认为true  为false即直接覆盖原文件-->

上面的配置会将所有日志输出到同一个文件,随着时间的推移该文件会越来越大,甚至无法打开,但是实际有用的日志都是近期的产生的,太久远的日志大多数是无用的,这就用到了滚动日志,其可以帮助我们实现日志文件的切割,以及无用日志的删除操作;

滚动日志配置:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <!--自定义属性信息-->
    <properties>
        <property name="LOG_HOME">logs</property>
        <property name="FILE_NAME">applog</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <!--滚动日志配置
            filePattern用于设置滚动文件的命名规则                                        
                        若以.zip为结尾则会自动归档日志文件 也支持其他的格式.gz, .zip, .bz2, 等-->
        <RollingRandomAccessFile name="RollingAppender"
                                 fileName="${LOG_HOME}/${FILE_NAME}.log"
                                 filePattern="${LOG_HOME}/$${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i.log">
            <PatternLayout
                    pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            <Policies>
                <!--滚动时间间隔 该参数需结合filePattern中的时间格式 此时表示为1分钟更换一个新文件
                    若时间格式为%d{yyyy-MM-dd HH}则表示每小时更换一个新文件-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <!--单个日志文件最大容量                -->
                <SizeBasedTriggeringPolicy size="1 MB"/>
            </Policies>
            <!--最大保留的日志文件个数  默认为7个          -->
            <DefaultRolloverStrategy max="20"/>
        </RollingRandomAccessFile>
    </Appenders>
    <Loggers>
        <Root level="all">
            <AppenderRef ref="RollingAppender"/>
        </Root>
    </Loggers>
</Configuration>

上述配置的整体含义:统一使用rootLogger,所有级别日志都会被记录到文件中,文件位于项目目录下的logs中,当单个文件超过1MB或是时间超过1分钟后则更换日志文件,最多保存20个日志文件;

补充:RollingFile也是做滚动日志的,但是据官方说效率没有RollingRandomAccessFile高;

Loggers节点:

需求:某个类或模块中产生的需要同时输出到控制台和文件,其他模块则仅输出到控制台,当遇到类似需求时,就需要定义不同的logger了,然后在程序中根据名称获取所需的logger

如果在配置中找不到名称匹配的logger时使用rootLogger;

logger配置:

注意:appender使用的还是上面的例子中的

<Loggers>
    <Root level="all">
        <AppenderRef ref="Console" />
    </Root>
    <Logger name="fileAndConsole" level="all" additivity="false">
        <AppenderRef ref="Console" />
        <AppenderRef ref="RollingAppender" />
    </Logger>
</Loggers>
<!-- additivity表示是否将日志传递给root继续输出 默认为true-->

测试代码:

@Test
public void test1(){
    Logger myLogger = LogManager.getLogger("myLogger");//rootlogger
    Logger myLogger2 = LogManager.getLogger("fileAndConsole");//fileAndConsole

    for (int i = 0;i < 50;i++){
        myLogger.debug("debug msg1");
    }
    for (int i = 0;i < 50;i++){
        myLogger2.debug("debug msg2");
    }
}

msg1将只出现在控制台,而msg2同时出现在控制台和日志文件;

Filter节点:

Filter用于对日志进行过滤,一些情况下我们可能需要对日志进行更加个性化的限制,

例如:

输出日志消息包含某个字符串的

按照时间不同输出到不同文件

日志的生命周期 :

一个日志事件(LogEvent)产生后到最终输出到目的地会经过以下环节:

全局过滤器 -> logger过滤器 -> logger -> appender过滤器 -> appender->输出

无论哪个环节的过滤器,每个过滤器在匹配或是不匹配时都要明确该日志事件的处理方式,包含三种:

  • ACCEPT接受(继续传递该LogEvent)
  • DENY拒绝(直接丢弃该LogEvent)
  • NEUTRAL中立(不清楚该怎么办继续往后传递LogEvent)

全局过滤器:评估结果为接受时,其他全局过滤器将不会再对该事件进行评估,且不再交给Logger过滤器评估

Logger过滤器:评估为拒绝时不再交给Appdener过滤器

Appdener过滤器:最终决定改日志是否输出

通常需要根据实际需求来配置过滤器:

下例配置列出了三种过滤器的示例(无实意义):

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="TRACE" monitorInterval="5" packages="com.kanq.extend.cat.log4j2">
    <Filters>
        <!-- 全局级别Filter -->
        <BurstFilter level="INFO" rate="16" maxBurst="100"/>
    </Filters>
    <Appenders>
        <RollingFile name="RollingFile" fileName="logs/app.log"
                     filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
            <!-- Appender级别的Filter -->
            <BurstFilter level="INFO" rate="16" maxBurst="100"/>
            <PatternLayout>
                <pattern>%d %p %c{1.} [%t] %m%n</pattern>
            </PatternLayout>
            <TimeBasedTriggeringPolicy />
        </RollingFile>
    </Appenders>
    <Loggers>
        <!-- Logger级别的Filter -->
        <Root level="error">
            <BurstFilter level="INFO" rate="16" maxBurst="100"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

过滤器案例:

通过时间过滤器实现将白天和夜晚的是指写入不同位置:

<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <File name="DayAppender" fileName="logs/appDay.log" append="true">
            <TimeFilter start="06:00:00" end="24:00:00" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/>
        </File>
        <File name="NightAppender" fileName="logs/appNight.log" append="true">
            <TimeFilter start="24:00:00" end="06:00:00" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger - %msg%n"/>
        </File>
    </Appenders>
    <Loggers>
        <Root level="all">
            <AppenderRef ref="Console" />
            <AppenderRef ref="DayAppender" />
            <AppenderRef ref="NightAppender" />
        </Root>
    </Loggers>
</Configuration>

当然官网还有其他的过滤器,如正则过滤等,大家根据需求选择即可;地址:官方手册

web项目中的使用

web环境下需要额外的依赖包:

<!-- web容器中需要添加log4j-web -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.5</version>
</dependency>

controller中的使用:

@Controller
@RequestMapping("/customer")
public class CustomerController {
//为controller添加属性 用于获取一个日志记录器(Logger)
private Logger logger = LogManager.getLogger(this.getClass().getPackage().getName());
@RequestMapping("/list")
    public String getCustomerList(Model model, SearchInfo searchInfo){
        logger.info("request this /list interface");
        //...
    }
}

若配置文件名称不是默认的跨域通过以下代码来加载:

File file = new File("/Users/jerry/LOGfj/src/main/resources/log4j3.xml");
BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
ConfigurationSource source = new ConfigurationSource(in);
Configurator.initialize(null, source);

相关推荐