2021-09-18

日志技术

日志的概念

日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志。

具有处理历史数据、诊断 问题的追踪以及理解系统的活动等重要作用。

在计算机中,日志文件是记录在操作系统或其他软件运行中发生的事件或在通信软件的不同用户之间的 消息的文件。

记录是保持日志的行为。

在最简单的情况下,消息被写入单个日志文件。

许多操作系统,软件框架和程序包括日志系统。广泛使用的日志记录标准是在因特网工程任务组 (IETF)RFC5424中定义的syslog。 syslog标准使专用的标准化子系统能够生成,过滤,记录和分析日 志消息。

  • 调试日志

    软件开发中,我们经常需要去调试程序,做一些信息,状态的输出便于我们查询程序的运行状况。为了让我们能够更加灵活和方便的控制这些调试的信息,所有我们需要专业的日志技术。java中寻找bug会需要重现。调试也就是debug 可以在程序运行中暂停程序运行,可以查看程序在运行中的情况。日志主要是为了更方便的去重现问题。
    
  • 系统日志

    系统日志是记录系统中硬件、软件和系统问题的信息,同时还可以监视系统中发生的事件。用户可以通过它来检查错误发生的原因,或者寻找受到攻击时攻击者留下的痕迹。系统日志包括系统日志、应用程序日志和安全日志。
    
    系统日志策略可以在故障刚刚发生时就向你发送警告信息,系统日志帮助你在最短的时间内发现问题。
    系统日志是一种非常关键的组件,因为系统日志可以让你充分了解自己的环境。这种系统日志信息对于决定故障的根本原因或者缩小系统攻击范围来说是非常关键的,因为系统日志可以让你了解故障或者袭击发生之前的所有事件。为虚拟化环境制定一套良好的系统日志策略也是至关重要的,因为系统日志需要和许多不同的外部组件进行关联。良好的系统日志可以防止你从错误的角度分析问题,避免浪费宝贵的排错时间。另外一种原因是借助于系统日志,管理员很有可能会发现一些之前从未意识到的问题,在几乎所有刚刚部署系统日志的环境当中。
    

java日志框架

需要解决的问题:

  • 控制日志输出的内容和格式
  • 控制日志输出的位置
  • 日志优化:异步日志,日志文件的归档和压缩
  • 日志系统的维护
  • 面向接口开发 – 日志的门面

日志门面

JCL(jakarta commons logging)、slf4j( Simple Logging Facade for Java)

日志实现

JUL(java util logging)、logback、log4j、log4j2

logback

Logback是由log4j创始人设计的另一个开源日志组件,性能比log4j要好。

官方网站:https://logback.qos.ch/index.html

Logback主要分为三个模块:

  • logback-core:其它两个模块的基础模块

  • logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API

  • logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

后续的日志代码都是通过SLF4J日志门面搭建日志系统,所以在代码是没有区别,

主要是通过修改配置文件和pom.xml依赖

logback入门

导入依赖

 				<!--slf4j 日志门面-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.26</version>
        </dependency>
        <!--logback 日志实现-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

java代码

public class LogbackTest {

    public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);


    @Test
    public void testQuick()throws Exception{

      // 日志输出
      LOGGER.error("error");
      LOGGER.warn("wring");
      LOGGER.info("info");
      LOGGER.debug("debug");// 默认级别
      LOGGER.trace("trace");

    }
}

logback配置

logback会依次读取以下类型配置文件:

  • logback.groovy
  • logback-test.xml
  • logback.xml
  • 如果均不存在会采用默认配置

logback组件之间的关系

  1. Logger:日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。

  2. Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。

  3. Layout:负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封装在encoder中。

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

    <!--
        配置集中管理属性
        我们可以直接改属性的 value 值
        格式:${name}
    -->
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property>
    <!--
    日志输出格式:
        %-5level
        %d{yyyy-MM-dd HH:mm:ss.SSS}日期
        %c类的完整名称
        %M为method
        %L为行号
        %thread线程名称
        %m或者%msg为信息
        %n换行
      -->
    <!--定义日志文件保存路径属性-->
    <property name="log_dir" value="/logs"></property>


    <!--控制台日志输出的 appender-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--日志文件输出的 appender-->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!--日志文件保存路径-->
        <file>${log_dir}/logback.log</file>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--html 格式日志文件输出 appender-->
    <appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
        <!--日志文件保存路径-->
        <file>${log_dir}/logback.html</file>
        <!--html 消息格式配置-->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern>
            </layout>
        </encoder>
    </appender>


    <!--日志拆分和归档压缩的 appender 对象-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志文件保存路径-->
        <file>${log_dir}/roll_logback.log</file>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--指定拆分规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--按照时间和压缩格式声明拆分的文件名-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--按照文件大小拆分-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
        <!--日志级别过滤器-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--日志过滤规则 大于等于error级别的日志才会输出到文件-->
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--异步日志-->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <!--指定某个具体的 appender-->
        <appender-ref ref="rollFile"/>
    </appender>


    <!--root logger 配置-->
    <root level="ALL">
        <appender-ref ref="console"/>
        <appender-ref ref="async"/>
    </root>

    <!--自定义 looger 对象
        additivity="false" 自定义 logger 对象是否继承 rootLogger
     -->
    <logger name="com.itheima" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>

lock4j.properties转换为logback.xml : http://logback.qos.ch/translator/

logback-access使用

logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使用logback-access模块来替换tomcat的访问日志。

  1. 将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下

  2. 修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:

    <Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
    
  3. logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <!-- always a good activate OnConsoleStatusListener -->
      <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/> 
      
      <property name="LOG_DIR" value="${catalina.base}/logs"/>
     
      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/access.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
        </rollingPolicy>
     
        <encoder>
          <pattern>combined</pattern>
        </encoder>
      </appender>
      
      <appender-ref ref="FILE"/>
    </configuration>
    
    <encoder>
      <pattern>%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"</pattern>
    </encoder>
    

    等价于

    <encoder>
      <pattern>combined</pattern>
    </encoder>
    

    更多配置查看官网 http://logback.qos.ch/access.html#configuration

log4j2

Apache Log4j 2是对Log4j的升级版,参考了logback的一些优秀的设计,并且修复了一些问题,因此带

来了一些重大的提升,主要有:

  • 异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。

  • 性能提升, log4j2相较于log4j 和logback都具有很明显的性能提升,后面会有官方测试的数据。

  • 自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用。

  • 无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。

log4j2 入门

目前市面上最主流的日志门面就是SLF4J,虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现,Slf4j + Log4j2应该是未来的大势所趋。

导入依赖

				<!--log4j2日志门面-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.1</version>
        </dependency>
        <!--log4j2 日志实现-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.1</version>
        </dependency>

java代码

public class Log4j2Test {

    // 定义日志记录器对象
    public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);

    // 快速入门
    @Test
    public void testQuick()throws Exception{
        // 日志消息输出
        LOGGER.fatal("fatal");
        LOGGER.error("error");
        LOGGER.warn("warn");
        LOGGER.info("inf");
        LOGGER.debug("debug");
        LOGGER.trace("trace");


    }
}

使用slf4j作为日志的门面,使用log4j2作为日志的实现

        <!--使用slf4j 作为日志门面-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.26</version>
        </dependency>


        <!--使用 log4j2 的适配器进行绑定-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>


        <!--log4j2日志门面-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.1</version>
        </dependency>
        <!--log4j2 日志实现-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.1</version>
        </dependency>

java代码

public class Slf4jTest {

    public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);

    // 快速入门
    @Test
    public void test01()throws Exception{
        // 日志输出
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info");
        LOGGER.debug("debug");
        LOGGER.trace("trace");

    }
}

log4j2配置

log4j2默认加载classpath下的 log4j2.xml 文件中的配置。

<?xml version="1.0" encoding="UTF-8"?>
<!--
    status="warn" 日志框架本身的输出日志级别
    monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒,可以实现热更新
-->
<Configuration status="debug" monitorInterval="5">

    <!--
        集中配置属性进行管理
        使用时通过:${name}
    -->
    <properties>
        <property name="LOG_HOME">/logs</property>
    </properties>

    <!--日志处理-->
    <Appenders>
        <!--控制台输出 appender-->
        <Console name="Console" target="SYSTEM_ERR">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
        </Console>

        <!--日志文件输出 appender-->
        <File name="file" fileName="${LOG_HOME}/myfile.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </File>

        <!--<Async name="Async">-->
            <!--<AppenderRef ref="file"/>-->
        <!--</Async>-->

        <!--使用随机读写刘的日志文件输出 appender,性能提高-->
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
        </RandomAccessFile>

        <!--按照一定规则拆分的日志文件的 appender-->
        <RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log"
                     filePattern="/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
            <!--日志级别过滤器-->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
            <!--日志消息格式-->
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
            <Policies>
                <!--在系统启动时,出发拆分规则,生产一个新的日志文件-->
                <OnStartupTriggeringPolicy />
                <!--按照文件大小拆分,10MB -->
                <SizeBasedTriggeringPolicy size="10 MB" />
                <!--按照时间节点拆分,规则根据filePattern定义的-->
                <TimeBasedTriggeringPolicy />
            </Policies>
            <!--在同一个目录下,文件的个数限定为 30 个,超过进行覆盖-->
            <DefaultRolloverStrategy max="30" />
        </RollingFile>

    </Appenders>

    <!--logger 定义-->
    <Loggers>


        <!--自定义异步 logger 对象
            includeLocation="false" 关闭日志记录的行号信息
            additivity="false" 不在继承 rootlogger 对象
        -->
        <AsyncLogger name="com.itheima" level="trace" includeLocation="false" additivity="false">
            <AppenderRef ref="Console"/>
        </AsyncLogger>


        <!--使用 rootLogger 配置 日志级别 level="trace"-->
        <Root level="trace">
            <!--指定日志使用的处理器-->
            <AppenderRef ref="Console" />

            <!--使用异步 appender-->
            <AppenderRef ref="Async" />
        </Root>
    </Loggers>
</Configuration>

log4j2异步日志

异步日志

log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益,我们来看看如何使用log4j2的异步日志。

  • 同步

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L8wQaDFu-1631934393570)(日志技术.assets/截屏2021-09-18 上午12.07.18.png)]

  • 异步

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wVd81SY-1631934393604)(日志技术.assets/截屏2021-09-18 上午12.07.35.png)]

Log4j2提供了两种实现日志的方式,一个是通过AsyncAppender,一个是通过AsyncLogger,分别对应

前面我们说的Appender组件和Logger组件。

注意:配置异步日志需要添加依赖

        <!--异步日志依赖-->
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.4</version>
        </dependency>
  1. AsyncAppender方式

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="warn">
        <properties>
            <property name="LOG_HOME">D:/logs</property>
        </properties>
        <Appenders>
            <File name="file" fileName="${LOG_HOME}/myfile.log">
                <PatternLayout>
                    <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
                </PatternLayout>
            </File>
            <Async name="Async">
                <AppenderRef ref="file"/>
            </Async>
        </Appenders>
        <Loggers>
            <Root level="error">
                <AppenderRef ref="Async"/>
            </Root>
        </Loggers>
    </Configuration>
    
  2. AsyncLogger方式

    AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的更快。你可以有两种选择:全局异步和混合异步。

    • 全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要添加log4j2.component.properties 配置;
    Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerCon textSelector
    
    
    • 混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。

      <?xml version="1.0" encoding="UTF-8"?>
      <Configuration status="WARN">
          <properties>
              <property name="LOG_HOME">D:/logs</property>
          </properties>
          <Appenders>
              <File name="file" fileName="${LOG_HOME}/myfile.log">
                  <PatternLayout>
                      <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
                  </PatternLayout>
              </File>
              <Async name="Async">
                  <AppenderRef ref="file"/>
              </Async>
          </Appenders>
          <Loggers>
              <AsyncLogger name="com.itheima" level="trace" includeLocation="false" additivity="false">
                  <AppenderRef ref="file"/>
              </AsyncLogger>
              <Root level="info" includeLocation="true">
                  <AppenderRef ref="file"/>
              </Root>
          </Loggers>
      </Configuration>
      

      如上配置: com.itheima 日志是异步的,root日志是同步的。

使用异步日志需要注意的问题

  1. 如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。性能会和AsyncAppender一致,降至最低。

  2. 设置includeLocation=false ,打印位置信息会急剧降低异步日志的性能,比同步日志还要慢。

log4j性能

Log4j2最牛的地方在于异步输出日志时的性能表现。

Log4j2在多线程的环境下吞吐量与Log4j和Logback的比较如下图。

下图比较中Log4j2有三种模式:1)全局使用异步模式;2)部分Logger采用异步模式;3)异步Appender。可以看出在前两种模式下,Log4j2的性能较之Log4j和Logback有很大的优势。

无垃圾记录

垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停。

许多日志库(包括以前版本的Log4j)在稳态日志记录期间分配临时对象,如日志事件对象,字符串,字符数组,字节数组等。这会对垃圾收集器造成压力并增加GC暂停发生的频率。

从版本2.6开始,默认情况下Log4j以“无垃圾”模式运行,其中重用对象和缓冲区,并且尽可能不分配临时对象。还有一个“低垃圾”模式,它不是完全无垃圾,但不使用ThreadLocal字段。

Log4j 2.6中的无垃圾日志记录部分通过重用ThreadLocal字段中的对象来实现,部分通过在将文本转换为字节时重用缓冲区来实现。

**使用Log4j 2.5:内存分配速率****809 MB /**秒,141个无效集合。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PLnoB5IP-1631934393608)(日志技术.assets/截屏2021-09-18 上午12.28.05.png)]

Log4j 2.6****没有分配临时对象:0(零)垃圾回收。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1FEcQYzW-1631934393613)(日志技术.assets/截屏2021-09-18 上午12.28.17.png)]

有两个单独的系统属性可用于手动控制Log4j用于避免创建临时对象的机制:

log4j2.enableThreadlocals - 如果“true”(非Web应用程序的默认值)对象存储在threadLocal字段中并重新使用,否则将为每个日志事件创建新对象。

log4j2.enableDirectEncoders - 如果将“true”(默认)日志事件转换为文本,则将此文本转换为字节而不创建临时对象。注意: 由于共享缓冲区上的同步,在此模式下多线程应用程序的同步日志记录性能可能更差。如果您的应用程序是多线程的并且日志记录性能很重要,请考虑使用异步记录器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值