详解Spring Boot下使用logback 记录多个文件日志

背景

这两天遇到一个比较有意思的日志问题.

近期对我之前的python代码进行java的重构, 一方面是因为java使用的医疗库非常健全稳定, 可以商用. 另一方面是因为java速度快, 这个库的实现的效率也高, 性能是Python版本的好几倍.

但是作为这个项目的唯一作者, 我的癖好也成为这个项目的风格. 这个项目会给很多部署工程师使用. 当然项目的可用性和性能作为第一考虑的因素, 但是作为一个懒人, 对使用软件时候的复杂部署过程和混乱调试信息深恶痛绝. 所以我在项目中使用了高度可配置/易用和多文件日志.

说道多文件日志, 它的优点是每个日志只容纳自身的逻辑, 所以对于一般的入门开发者或者是初级运维工程师查看起来非常方便.

初步尝试

因为spring boot的配置一般来讲是application.properties, 但是同时开发者可以使用yml格式的配置, 二者相比, yml文件更为简洁. 熟读python之禅的我当然是简洁胜于冗余选择了yml.

发现spring-boot可以通过application.yml配置日志. 高兴的配置一番之后发现没法配置多个logger, 弃用! 改用logback-spring.xml(为什么不用logback.xml? 因为-spring这种文件可以获取到spring配置中的变量.下面再说)

第一次实现

我有好几个服务需要打日志. 一般来讲我的日志风格是 *.log 保存 INFO以上级别日志. *.err.log保存ERROR以上级别日志. 我如果每个文件日志都使用一个Appender的话, 配置文件太长了. 而且很难看, 不是我的风格.

Google了一下, 发现了这种方案:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  # 下面这一行的意思是使用application.yml中的global.log-dir变量
  <springProperty scope="context" name="LOG_DIR" source="global.log-dir" defaultValue="./log/"/>
  <!-- 追加器开始 -->
  # 这个是一个可以定义变量的Appender
  <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
    # 使用 LoggerNameBasedDiscriminator 这个类根据当前Logger获取变量
    <discriminator class="com.utils.loggers.LoggerNameBasedDiscriminator">
      <defaultValue>general</defaultValue>
    </discriminator>
    <sift>
      # 根据变量loggerName名字生成根据日期滚动的Appender
      <appender name="FILE-${loggerName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>
          ${LOG_DIR}/${loggerName}.log
        </file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>
            ${LOG_DIR}/${loggerName}.%d{yyyy-MM-dd}.log.gz
          </fileNamePattern>
          <maxHistory>15</maxHistory>
        </rollingPolicy>

        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
          <level>INFO</level>
        </filter>

        <encoder>
          <pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n
          </pattern>
        </encoder>
      </appender>

      <appender name="FILE-ERROR-${loggerName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/${loggerName}.err.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>
            ${LOG_DIR}/${loggerName}.%d{yyyy-MM-dd}.err.log.gz
          </fileNamePattern>
          <maxHistory>15</maxHistory>
        </rollingPolicy>

        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
          <level>ERROR</level>
        </filter>

        <encoder>
          <pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n
          </pattern>
        </encoder>
      </appender>
    </sift>
  </appender>
  <!-- 追加器结束 -->

  <!-- 日志开始 -->
  <logger name="com.some.service" level="INFO" additivity="false">
    <appender-ref ref="SIFT"/>
  </logger>
  <!-- 日志结束 -->

</configuration>

下的是对应的 LoggerNameBasedDiscriminator 类

package com.utils.loggers;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.sift.AbstractDiscriminator;

public class LoggerNameBasedDiscriminator extends AbstractDiscriminator<ILoggingEvent> {
  private static final String KEY = "loggerName";
  private String defaultValue;

  public String getDefaultValue() {
    return defaultValue;
  }

  public void setDefaultValue(String defaultValue) {
    this.defaultValue = defaultValue;
  }
  # 这就是之所以xml里面可以引用loggerName变量的原因
  public String getKey() {
    return KEY;
  }

  public void setKey() {
    throw new UnsupportedOperationException("Key not settable. Using " + KEY);
  }

  public String getDiscriminatingValue(ILoggingEvent e) {
    String loggerName = e.getLoggerName();

    if (loggerName == null)
      return defaultValue;
    else {
      String[] split = loggerName.split("\\.");
      return split[split.length - 1];
    }
  }
}

最开始我的日志里面没有报错信息, 正常的生成INFO日志. 但是后来发现事情好像不是想象的那样

问题出现

后来我把程序改成多线程. 发现所有涉及到多线程的服务日志里面都没信息了. Google半天, 发现几个令我震惊的真相:

  • 真相1: 所有滚动Appender都不支持异步追加 (其实也不是, 但是那种方式需要写死日志文件名, 不推荐, 不讲)
  • 真相2: SiftingAppender 内部最多嵌套一个Appender. 所以理论上我的ERROR的日志里面应该永远不会有内容.

问题解决

对于之前的两个问题, 分而治之.

不支持异步

再次谷歌(到这里读者基本上发现了我搬砖的本质), 发现有个Appender名字叫AsyncAppender, 这玩意是一个其他Appender的Wrapper. 说白了, 就是你打日志的命令是异步的, 放到队列里面, 而它真正的打日志的动作是一个单独的同步线程. 这就牛逼了, 使用这玩意收集我所有日志, 然后再转发给SiftingAppender 进行分发即可.

SiftingAppender 内部最多嵌套一个Appender

这个好办, 把INFO的Appender和ERROR的Appender拆开放到两个SiftingAppender里面就行了, 不过这样的话, 前面提到的的AsyncAppender 也要写两个.

最后, logback-spring.xml文件如下

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <springProperty scope="context" name="LOG_DIR" source="global.log-dir" defaultValue="./log/"/>

  <!-- 追加器开始 -->
  <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator class="com.utils.loggers.LoggerNameBasedDiscriminator">
    </discriminator>
    <sift>
      <appender name="FILE-${loggerName}"
           class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/${loggerName}.log</file>
        <rollingPolicy
            class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>
            ${LOG_DIR}/${loggerName}.%d{yyyy-MM-dd}.log
          </fileNamePattern>
          <maxHistory>15</maxHistory>
        </rollingPolicy>

        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
          <level>INFO</level>
        </filter>

        <encoder>
          <pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n
          </pattern>
        </encoder>
      </appender>
    </sift>
  </appender>
  <appender name="SIFT-ERR" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator class="com.infervision.utils.loggers.LoggerNameBasedDiscriminator">
    </discriminator>
    <sift>
      <appender name="FILE-ERROR-${loggerName}" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_DIR}/${loggerName}.err.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <fileNamePattern>
            ${LOG_DIR}/${loggerName}.%d{yyyy-MM-dd}.err.log
          </fileNamePattern>
          <maxHistory>15</maxHistory>
          <totalSizeCap>50MB</totalSizeCap>
        </rollingPolicy>

        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
          <level>ERROR</level>
        </filter>

        <encoder>
          <pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n
          </pattern>
        </encoder>
      </appender>
    </sift>
  </appender>

  <!-- 异步输出 -->
  <appender name ="ASYNC" class= "ch.qos.logback.classic.AsyncAppender">
    <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
    <discardingThreshold >0</discardingThreshold>
    <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
    <queueSize>512</queueSize>
    <!-- 添加附加的appender,最多只能添加一个 -->
    <appender-ref ref ="SIFT"/>
  </appender>

  <!-- 异步输出 -->
  <appender name ="ASYNC-ERR" class= "ch.qos.logback.classic.AsyncAppender">
    <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
    <discardingThreshold >0</discardingThreshold>
    <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
    <queueSize>512</queueSize>
    <!-- 添加附加的appender,最多只能添加一个 -->
    <appender-ref ref ="SIFT-ERR"/>
  </appender>
  <!-- 追加器结束 -->

  <!-- 日志开始 -->

  <logger name="com.some.service" level="INFO" additivity="false">
    <appender-ref ref="ASYNC"/>
  </logger>
  <!-- 日志结束 -->

</configuration>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Boot 使用 logback、logstash、ELK 记录日志文件的方法

    Spring Boot 下,尝试使用 log4j 记录日志到 logstash,在src/main/resources 目录下添加 log4j.properties 文件进行自定义输出日志文件,未能成功.在 application.yml 中 配置 logging path 打印日志成功了,但是未能调试成功日志分文件记录.网上查阅资料,说是 Spring Boot 默认使用 logback 记录日志.log4j 多次尝试后无果,遂改为使用 logback 记录,最终测试成功. 1. 关于 Spr

  • springboot整合mybatis将sql打印到日志的实例详解

    在前台请求数据的时候,sql语句一直都是打印到控制台的,有一个想法就是想让它打印到日志里,该如何做呢? 见下面的mybatis配置文件: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-

  • Springboot整合log4j2日志全解总结

    在项目推进中,如果说第一件事是搭Spring框架的话,那么第二件事情就是在Sring基础上搭建日志框架,我想很多人都知道日志对于一个项目的重要性,尤其是线上Web项目,因为日志可能是我们了解应用如何执行的唯一方式. 在18年大环境下,更多的企业使用Springboot和Springcloud来搭建他们的企业微服务项目 ,此篇文章是博主在实践中用Springboot整合log4j2日志的总结. 常用日志框架 java.util.logging:是JDK在1.4版本中引入的Java原生日志框架 Lo

  • Spring Boot中slf4j日志依赖关系示例详解

    前言 SpringBoot底层使用的是slf4j+logback来进行日志记录 把其他common-logging.log4j.java.util.logging转换为slf4j 下面这篇文章主要给大家介绍了关于Spring Boot slf4j日志依赖关系的相关内容,下面话不多说了,来一起看看详细的介绍吧 底层依赖关系 关系如何转化 底层通过偷梁换柱的方法,用jcl.jul.log4j中间转换包进行转化 如果要引入其他框架,必须将其中默认日志依赖剔除 SpringBoot从maven依赖中剔除

  • Spring Boot 把配置文件和日志文件放到jar外部

    如果不想使用默认的application.properties,而想将属性文件放到jar包外面,可以使用如下两种方法: 只能设置全路径.因为Java -jar运行jar包时,无法指定classpath(无论通过参数还是环境变量,设置的classpath都会被覆盖). 方法1:命令行传参指定spring.config.location java -jar -Dspring.config.location=D:\zTest\config\config1.properties springbootre

  • Spring boot按日切分spring boot的nohup.out日志文件的方法

    过大的日志文件维护起来存在诸多问题,所以最好是能够按日或按大小切分日志文件,便于查找需要的信息. 网上有各种各样的日志切分方法,有的有用,有的没用,有的还麻烦. 最后搬运最简单易用的切分方法,如下: 安装cronolog 以如下命令启动应用: nohup app.jar | /usr/local/sbin/cronolog /console-%Y-%m-%d.out >> /dev/null 2>&1 & 只是搬运而已,免得挨个方法试过去了 总结 以上所述是小编给大家介绍

  • 如何修改覆盖spring boot默认日志策略logback详解

    背景 Spring Boot在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging,Log4J, Log4J2和Logback.每种Logger都可以通过配置使用控制台或者文件输出日志内容. 默认日志Logback SLF4J--Simple Logging Facade For Java,它是一个针对于各类Java日志框架的统一Facade抽象.Java日志框架众多--常用的有java.util.logging, log

  • Spring Boot系列教程之日志配置

    前言 日志,通常不会在需求阶段作为一个功能单独提出来,也不会在产品方案中看到它的细节.但是,这丝毫不影响它在任何一个系统中的重要的地位. 为了保证服务的高可用,发现问题一定要即使,解决问题一定要迅速,所以生产环境一旦出现问题,预警系统就会通过邮件.短信甚至电话的方式实施多维轰炸模式,确保相关负责人不错过每一个可能的bug. 预警系统判断疑似bug大部分源于日志.比如某个微服务接口由于各种原因导致频繁调用出错,此时调用端会捕获这样的异常并打印ERROR级别的日志,当该错误日志达到一定次数出现的时候

  • SpringBoot中使用AOP打印接口日志的方法

    前言 AOP 是 Aspect Oriented Program (面向切面)的编程的缩写.他是和面向对象编程相对的一个概念.在面向对象的编程中,我们倾向于采用封装.继承.多态等概念,将一个个的功能在对象中来实现.但是,我们在实际情况中也发现,会有另外一种需求就是一类功能在很多对象的很多方法中都有需要.例如有一些对数据库访问的方法有事务管理的需求,有很多方法中要求打印日志.按照面向对象的方式,那么这些相同的功能要在很多地方来实现或者在很多地方来调用.这就非常繁琐并且和这些和业务不相关的需求耦合太

  • Spring Boot整合logback一个简单的日志集成架构

    一.业务需求 在项目开发和运维过程中需要通过日志来分析问题,解决问题以保证项目的正常运行.通过SpringBoot自带的日志管理相对比较简单,已无法满足日常的运维需求,需要对日志文件进行分时分类管理,刚好通过学习接触到了logback日志系统.因此便决定将其加入到项目框架之中. 二.logback简介 至于简介,可自行网上查阅相关文档文献,这里不做详细描述,毕竟不是本文主要目的.只需理解它很好的实现了slf4j,是log4j的再发展即可. 三.具体实施方案(仅供参考) 1.引入依赖包 其实不需要

随机推荐