java日志框架介绍、demo演示、加载过程、标签详解

日志整理

分享思路

  • 日志前言
  • 日志框架的简单介绍
  • 日志级别
  • 日志demo整合
  • springboot的日志加载过程
  • slf4j的加载过程
  • Log4j2的标签详解

日志前言

  1. 什么是日志 ?
    日志用来记录用户操作、系统运行状态等,是一个系统的重要组成部分。
  2. 日志的作用 ?
      好的日志可以帮助我们:了解线上系统的运行状态;快速准确定位线上问题;发现系统瓶颈;预警系统潜在风险;挖掘产品最大价值……
      不好的日志导致:对系统的运行状态一知半解;系统出现问题无法定位,或需要花费巨大的时间和精力;无法发现系统瓶颈;不知优化从何做起;无法对错误进行监控和报警;对挖掘用户行为和提升产品价值毫无帮助……
  3. 日志分类
    日志从功能来说,可分为诊断日志统计日志审计日志
  • 诊断日志
    3.1.1 请求的入口出口
    3.1.2 服务调用和返回
    3.1.3 资源消耗操作
    3.1.4 容错行为
    3.1.5 程序异常
    3.1.6 后台操作
    3.1.7 启动关闭、配置加载
  • 统计日志
    3.2.1用户统计访问
    3.2.2计费日志
  • 审计日志
    3.3.1按照指定格式输出日志
    3.3.2按照需求输出日志

    注意:日志我们需要记录我们正好需要的日志信息,尽量避免繁杂无用的日志信息出现

日志框架的简单介绍

目前在在市场上目前的日志框架有:JCL(Jakarta Commons Logging)、JUL(Java util Logging)、Jboss-LoggingSLF4JLog4jLogbackLog4j2

日志门面框架JCLSLF4J
日志实现框架JULLog4JLogbackLog4j2

Remember a person and an institution:
Ceki Gulcu -->SLF4J、Log4J、Logback
Apache -->JCL、Log4J2

  • JCL:采用适配器模式,是Java自身的一些包用了JUL,提供一套API来实现不同Logger之间的切换。JCL致命的缺点就是算法复杂,出现问题难以排除。
  • SLF4J:简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。解决了JCL的缺点。
  • JUL:是JDK 中自带的log功能,JDK1.4 才开始加入(2002年),但使用不够广泛。早期存在性能问题,到JDK1.5上才有了不错的进步,与Logback/Log4j2相比还是有所不如的。
  • Log4J:2001年由Ceki Gulcu发布,在2014年12月发行了最后一版。springboot从1.4版本之后不再支持。短板在于它的性能问题。
  • Logback:与Log4j相比,Logback重新了内核,使它的性能提升了很多,大约是Log4j的10倍,同时占用更小的内存,并且完整的实现了SLF4J API是你可以很方便的切换日志框架。
  • Log4j2:Log4j的升级版本,比Log4j 1.x有重大改进,修复了Logback中一些问题。但是不兼容Log1.x版本。Log4J2是现在最优秀的Java日志框架是Log4j2,没有之一。根据官方的测试表明,在多线程环境下,Log4j2的异步日志表现更加优秀。在异步日志中,Log4j2使用独立的线程去执行I/O操作,可以极大地提升应用程序的性能。

Logback和Log4J2性能对比

当线程数量较少时:Logback和Log4j2的性能相差无几
当线程数量较大时,很明显Log4j2的性能要10倍于Logback。Log4j2在独立应用程序中基本是无垃圾的,在web项目中是低垃圾的,减少了GC的内存消耗。
在这里插入图片描述

springboot集成slf4j抽象日志框架

  • 导入springboot启动依赖
    启动依赖
  • 写测试类进行原生slf4j抽象日志框架的日志打印
    main方法测试
    日志打印结果:
    console打印结果

springboot集成log4j2日志框架

  • 导入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<!-- 排除掉SpringBoot的默认log配置 -->
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-logging</artifactId>
		</exclusion>
	</exclusions>
</dependency>
<!--log4j2启动类-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency> 
  • xml的配置
    注意:springboot的加载顺序是会先去默认查找classes目录下是否存在log4j2.properties、log4j2.xml。如果存在,springboot便会直接使用该文件中的配置进行日志打印;如果不存在,会变回去springboot工程的配置文件application.properties中根据相关配置去匹配对象的日志配置文件。
 private String[] getCurrentlySupportedConfigLocations() {
        List<String> supportedConfigLocations = new ArrayList();
        supportedConfigLocations.add("log4j2.properties");
        if (this.isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) {
            Collections.addAll(supportedConfigLocations, new String[]{"log4j2.yaml", "log4j2.yml"});
        }

        if (this.isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
            Collections.addAll(supportedConfigLocations, new String[]{"log4j2.json", "log4j2.jsn"});
        }

        supportedConfigLocations.add("log4j2.xml");
        return StringUtils.toStringArray(supportedConfigLocations);
    }
  • 配置log4j2.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="WARN" monitorInterval="30">
   <Properties>
       <property name="LOG_INFO_STYLE" value="[time:] %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" />
       <Property name="logFilePath">D:/logs</Property>
       <Property name="logFileName">loggerTest</Property>
   </Properties>
   <appenders>
       <console name="Console" target="SYSTEM_OUT">
           <PatternLayout pattern="${LOG_INFO_STYLE}"/>
       </console>
       <RollingFile name="RollingFileInfo"
                    fileName="${logFilePath}/${logFileName}.log"
                    filePattern="${logFilePath}/${logFileName}_%d{yyyy-MM-dd}/${logFileName}.log">
           <PatternLayout pattern="${LOG_INFO_STYLE}"/>
           <Policies>
               <TimeBasedTriggeringPolicy interval="1"/>
               <SizeBasedTriggeringPolicy size="10 MB"/>
           </Policies>
           <DefaultRolloverStrategy max="20"/>
       </RollingFile>
   </appenders>
   <loggers>
       <logger name="org.springframework" level="INFO"></logger>
       <logger name="org.mybatis" level="INFO"></logger>
       <root level="ALL">
           <appender-ref ref="Console"/>
           <appender-ref ref="RollingFileInfo"/>
       </root>
   </loggers>
</configuration>
  • 我们这里写一个测试类来进行日志框架的打印输出
package com.fdd.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class log4j2Demo {
    public static void main(String[] args) {

        Logger logger= LoggerFactory.getLogger(log4j2Demo.class);

        //日志级别由低到高  trace<debug<info<warn<error
        logger.trace("这个是trace级别的日志信息....");
        logger.debug("这个是debug级别的日志信息....");
        logger.info("这个是info级别的日志信息....");
        logger.warn("这个是warn级别的日志信息....");
        logger.error("这个是error级别的日志信息....");
    }
}
  • 输出结果如下:
D:\develop\Java\jdk-1.8\bin\java 
[time:] 2020-01-06 14:40:06.555 TRACE com.fdd.test.log4j2Demo 13 main - 这个是trace级别的日志信息....
[time:] 2020-01-06 14:40:06.602 DEBUG com.fdd.test.log4j2Demo 14 main - 这个是debug级别的日志信息....
[time:] 2020-01-06 14:40:06.603 INFO  com.fdd.test.log4j2Demo 15 main - 这个是info级别的日志信息....
[time:] 2020-01-06 14:40:06.603 WARN  com.fdd.test.log4j2Demo 16 main - 这个是warn级别的日志信息....
[time:] 2020-01-06 14:40:06.603 ERROR com.fdd.test.log4j2Demo 17 main - 这个是error级别的日志信息....

springboot集成logback日志框架

  • 导入依赖
    由于springboot是默认采用上slf4j+logback进行日志打印的,所以我们只需要采用springboot的日志依赖即可。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

  spring-boot-starter-web包下引入了spring-boot-starter-logging依赖,集成了logback日志框架。

  • xml配置
    注意:springboot的加载顺序是会先去默认查找classes目录下是否存在logback-test.groovy、logback-test.xml、logback.groovy、logback.xml。如果存在,springboot便会直接使用该文件中的配置进行日志打印;如果不存在,会变回去springboot工程的配置文件application.properties中根据相关配置去匹配对象的日志配置文件。
  • 配置logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration >
    <property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS}  [%thread]  %-5level%logger{50} - %msg%n"/>
    <property name="TEST_FILE_PATH" value="D:/logback"/>
    <property name="PRO_FILE_PATH" value="/opt/test/log"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${PATTERN}</pattern>
        </encoder>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${PATTERN}</pattern>
        </layout>
    </appender>
    <appender name="TEST-FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${TEST_FILE_PATH}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <MaxHistory>100</MaxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${PATTERN}</pattern>
        </layout>
    </appender>
    <logger name="com.fdd" level="info"/>
    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="TEST-FILE"/>
    </root>
</configuration>
  • 日志打印结果如下:
2020-01-06 15:32:52.553  [main]  INFO com.fdd.springlogger001.Demo - 自定义 info ........................
2020-01-06 15:32:52.557  [main]  WARN com.fdd.springlogger001.Demo - 自定义 warn ........................
2020-01-06 15:32:52.558  [main]  ERRORcom.fdd.springlogger001.Demo - 自定义 error ........................

springboot日志配置的加载过程

springboot加载日志配置文件

  • LoggingApplicationListener类:
      在run();方法启动的时候,日志监听类LoggingApplicationListener便会对工程中的配置文件进行监听加载的操作,这里我们主要看启动事件的方法onApplicationStartingEvent();
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
        this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
        this.loggingSystem.beforeInitialize();
}

在这里我们主要关注前置初始化的方法beforeInitialize();以logback框架的实现来看:

public void beforeInitialize() {
        LoggerContext loggerContext = this.getLoggerContext();
        if (!this.isAlreadyInitialized(loggerContext)) {
            super.beforeInitialize();
            loggerContext.getTurboFilterList().add(FILTER);
  	 }
}

此初始化方法可以获得一个Context的日志容器。在此类中存在加载本地配置文件的方法getStandardConfigLocations();读取springboot的默认文件配置名称。

protected String[] getStandardConfigLocations() {
        return new String[]{"logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml"};
}

在获取日志容器的过程中,ch.qos.logback.classic包下存在一个ContextSelector类,内部含有getLoggerContextautoConfig()方法,为默认自动配置信息。

public LoggerContext getLoggerContext() {
        String contextName = null;
        Context ctx = null;
        LoggerContext lc = (LoggerContext)threadLocal.get();
        if (lc != null) {
            return lc;
        } else {
            try {
                ctx = JNDIUtil.getInitialContext();
                contextName = JNDIUtil.lookup(ctx, "java:comp/env/logback/context-name");
            } catch (NamingException var8) {
                ;
            }

            if (contextName == null) {
                return this.defaultContext;
            } else {
                LoggerContext loggerContext = (LoggerContext)this.synchronizedContextMap.get(contextName);
                if (loggerContext == null) {
                    loggerContext = new LoggerContext();
                    loggerContext.setName(contextName);
                    this.synchronizedContextMap.put(contextName, loggerContext);
                    URL url = this.findConfigFileURL(ctx, loggerContext);
                    if (url != null) {
                        this.configureLoggerContextByURL(loggerContext, url);
                    } else {
                        try {
                            (new ContextInitializer(loggerContext)).autoConfig();
                        } catch (JoranException var7) {
                            ;
                        }
                    }

                    if (!StatusUtil.contextHasStatusListener(loggerContext)) {
                        StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
                    }
                }

                return loggerContext;
            }
        }
}

slf4j的加载过程

  • org.slf4j.LoggerFactory包下的getLogger()方法为我们打印日志时候的入口;
 public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName()));
                Util.report("See http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
            }
        }
        return logger;
 }
  • 点击getLogger()方法
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
}
  • 进入到getLoggerFactory()方法中,我们可以看到:
public ILoggerFactory getLoggerFactory() {
        if (!this.initialized) {
            return this.defaultLoggerContext;
        } else if (this.contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("contextSelector cannot be null. See also http://logback.qos.ch/codes.html#null_CS");
        } else {
            return this.contextSelectorBinder.getContextSelector().getLoggerContext();
        }
    }

此时,在日志工厂方法中我门看到了获取日志容器的方法getLoggerContext();

public LoggerContext getLoggerContext() {
        String contextName = null;
        Context ctx = null;
        LoggerContext lc = (LoggerContext)threadLocal.get();
        if (lc != null) {
            return lc;
        } else {
            try {
                ctx = JNDIUtil.getInitialContext();
                contextName = JNDIUtil.lookup(ctx, "java:comp/env/logback/context-name");
            } catch (NamingException var8) {
                ;
            }
            if (contextName == null) {
                return this.defaultContext;
            } else {
                LoggerContext loggerContext = (LoggerContext)this.synchronizedContextMap.get(contextName);
                if (loggerContext == null) {
                    loggerContext = new LoggerContext();
                    loggerContext.setName(contextName);
                    this.synchronizedContextMap.put(contextName, loggerContext);
                    URL url = this.findConfigFileURL(ctx, loggerContext);
                    if (url != null) {
                        this.configureLoggerContextByURL(loggerContext, url);
                    } else {
                        try {
                            (new ContextInitializer(loggerContext)).autoConfig();
                        } catch (JoranException var7) {
                            ;
                        }
                    }
                    if (!StatusUtil.contextHasStatusListener(loggerContext)) {
                        StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
                    }
                }
                return loggerContext;
            }
        }
    }

此方法中含有autoConfig()自动配置的方法。这里就是我们日志加载过程中slf4j的默认自动配置信息。

标签详解

  这里我们以log4j2的标签作为代表进行介绍。在目前市场上的日志实现框架中它们的配置文件标签大都相差无几。我们学会log4j2的标签之后,其他日志框架的标签也是很好去学习的。

  • log4j.xml的实例代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--status="WARN" :用于设置log4j2自身内部日志的信息输出级别,默认是OFF-->
<!--monitorInterval="30"  :间隔秒数,自动检测配置文件的变更和重新配置本身-->
<configuration status="WARN" monitorInterval="30">
    <Properties>
        <!--自定义一些常量,之后使用${变量名}引用-->
        <Property name="logFilePath">log</Property>
        <Property name="logFileName">test.log</Property>
    </Properties>
    <!--appenders:定义输出内容,输出格式,输出方式,日志保存策略等,常用其下三种标签[console,File,RollingFile]-->
    <appenders>
        <!--console :控制台输出的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!--PatternLayout :输出日志的格式,LOG4J2定义了输出代码,详见第二部分-->
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
        </console>
        <!--File :同步输出日志到本地文件-->
        <!--append="false" :根据其下日志策略,每次清空文件重新输入日志,可用于测试-->
        <File name="log" fileName="${logFilePath}/${logFileName}" append="false">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
        </File>
        <!--SMTP :邮件发送日志-->
        <SMTP name="Mail" subject="****SaaS系统正式版异常信息" to="message@message.info" from="message@lengjing.info" smtpUsername="message@message.info" smtpPassword="LENG****1234" smtpHost="mail.lengjing.info" smtpDebug="false" smtpPort="25" bufferSize="10">
            <PatternLayout pattern="[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
        </SMTP>
        <!-- ${sys:user.home} :项目路径 -->
        <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log"
                     filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
            <!--ThresholdFilter :日志输出过滤-->
            <!--level="info" :日志级别,onMatch="ACCEPT" :级别在info之上则接受,onMismatch="DENY" :级别在info之下则拒绝-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <!-- Policies :日志滚动策略-->
            <Policies>
                <!-- TimeBasedTriggeringPolicy :时间滚动策略,默认0点小时产生新的文件,interval="6" : 自定义文件滚动时间间隔,每隔6小时产生新文件, modulate="true" : 产生文件是否以0点偏移时间,即6点,12点,18点,0点-->
                <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
                <!-- SizeBasedTriggeringPolicy :文件大小滚动策略-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>

        <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log"
                     filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
        <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
                     filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <!--Logger节点用来单独指定日志的形式,name为包路径,比如要为org.springframework包下所有日志指定为INFO级别等。 -->
        <logger name="org.springframework" level="INFO"></logger>
        <logger name="org.mybatis" level="INFO"></logger>
        <!-- Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出 -->
        <root level="all">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
        <!--AsyncLogger :异步日志,LOG4J有三种日志模式,全异步日志,混合模式,同步日志,性能从高到底,线程越多效率越高,也可以避免日志卡死线程情况发生-->
        <!--additivity="false" : additivity设置事件是否在root logger输出,为了避免重复输出,可以在Logger 标签下设置additivity为”false”-->
        <AsyncLogger name="AsyncLogger" level="trace" includeLocation="true" additivity="false">
            <appender-ref ref="RollingFileError"/>
        </AsyncLogger>
    </loggers>
</configuration>

(未完待续…)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值