这几天稍微在看公司项目里的日志,使用的是logback来打印日志,log日志对于我而言,主要的作用便是在排查问题与监控系统。在我之前的项目里,线上的日志输出级别都是info级别,一般只有info和error级别的日志信息才能被打印出来,这样做减少了线上日志的打印,减小了系统开销。而我们在系统开发过程中,一般都是用log.debug来打印一些调试信息的。但是随着线上项目的运行,久而久之就会出现一点问题,之前我也不太懂线上问题排查与处理,同事教我修改项目日志级别,然后重启线上项目这样来修改线上项目的日志输出级别。一开始我没有感觉到有什么不妥,后知后觉发现这样做导致线上项目不稳定,也不符合公司的线上项目管理规定,于是开始寻找解决之法,是否可以动态的修改线上项目的日志输出级别。

我们项目使用的是spring,关于日志都是使用配置的方式。

web.xml里需要添加如下配置

<context-param>
    <!-- 用于加载logback.xml配置文件 -->
    <param-name>logbackConfigLocation</param-name>
    <param-value>classpath:logback.xml</param-value>
  </context-param>
  <listener>
    <!-- 用于 -->
    <listener-class>ch.qos.logback.ext.spring.web.LogbackConfigListener</listener-class>
  </listener>
  <listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
  </listener>

pom.xml里需要添加如下配置 

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.25</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
      <!--<scope>test</scope>-->
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.logback-extensions/logback-ext-spring -->
    <dependency>
      <groupId>org.logback-extensions</groupId>
      <artifactId>logback-ext-spring</artifactId>
      <version>0.1.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.slf4j/jcl-over-slf4j -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>1.7.25</version>
    </dependency>
  </dependencies>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="20 seconds">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="/app/logs" />
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="INFO"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/info/manage-remote-info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>16</MaxHistory>
            <!--日志文件切割大小-->
            <maxFileSize>20MB</maxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>
    <appender name="WARN"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/warn/manage-remote-warn.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>24</MaxHistory>
            <!--日志文件切割大小-->
            <maxFileSize>20MB</maxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>
    <appender name="ERROR"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/error/manage-remote-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>32</MaxHistory>
            <!--日志文件切割大小-->
            <maxFileSize>20MB</maxFileSize>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
    <!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder"  level="TRACE" />
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor"  level="DEBUG" />
    <logger name="org.hibernate.SQL" level="DEBUG" />
    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />

    <!--myibatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <!-- 控制台输出 -->
        <appender-ref ref="STDOUT" />
        <!--<!– 文件输出 –>-->
        <appender-ref ref="ERROR" />
        <appender-ref ref="INFO" />
        <appender-ref ref="WARN" />
        <appender-ref ref="DEBUG" />
        <appender-ref ref="TRACE" />
    </root>
</configuration>

一:logback.xml自动扫描重新加载配置

logback.xml中的configuration标签有<configuration scan="true" scanPeriod="60 seconds" debug="false">这样一段配置,其中的scan如果设置为true的话,项目在启动后就会默认每一分钟自动扫描配置文件,如果有改变则重新加载,而我们还可以设置自动扫描的时间间隔属性scanPeriod,可以设置成30秒或者5分钟。

在实际的问题排查过程中,我之前都是先修改一下logback.xml中的root标签中的level属性,然后替换线上环境服务器项目中classes路径下的logback.xml文件,然后再重启项目,这样项目就可以重新加载logback.xml配置文件。现在只要在logback.xml中添加一个属性,以后可以直接在服务器上修改logback.xml中root的level属性或者直接替换整个logback.xml文件,然后等一会,项目的日志输出级别就会改变了,不需要再重启项目。

二:通过接口动态修改项目的日志级别

我们也可以写个接口专门去修改项目的日志输出级别。

package log;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogController {
    private static Logger logger = LoggerFactory.getLogger(LogController.class);

    /**
     * 修改项目日志输出级别
     * @param allLevel 全局日志级别
     * @param singleLevel 某个类日志级别
     * @param singlePath 需要单独设置日志输出级别的类的全限定名(例:log.SecondController)
     * @return
     */
    @RequestMapping(value = "changeLevel")
    public String changeLevel(String allLevel,String singleLevel,String singlePath) {
        LoggerContext loggerContext= (LoggerContext) LoggerFactory.getILoggerFactory();
        if(!StringUtils.isEmpty(allLevel)){
            //设置全局日志级别
            ch.qos.logback.classic.Logger logger=loggerContext.getLogger("root");
            logger.setLevel(Level.toLevel(allLevel));
        }

        if (!StringUtils.isEmpty(singleLevel)) {
            //设置某个类日志级别-可以实现定向日志级别调整
            ch.qos.logback.classic.Logger vLogger = loggerContext.getLogger(singlePath);
            if (vLogger!=null){
                vLogger.setLevel(Level.toLevel(singleLevel));
            }
        }
        return "success";
    }

    @RequestMapping(value = "test")
    public String changeLogLevel() {
        logger.info("这是LogController的Info啊");
        logger.error("这是LogController的error啊");
        logger.debug("这是LogController的debug啊");
        logger.trace("这是LogController的trace啊");
        logger.warn("这是LogController的warn啊");
        return "success";
    }
}
package log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName SecondController
 * @Description
 * @Author shenshuiyuan
 * @Date 2018/11/23 17:22
 * @Version 1.0
 **/
@RestController
public class SecondController {

    private static Logger logger = LoggerFactory.getLogger(SecondController.class);

    @RequestMapping(value = "secondtest")
    public String changeLogLevel() {
        logger.info("这是SecondController的Info啊");
        logger.error("这是SecondController的error啊");
        logger.debug("这是SecondController的debug啊");
        logger.trace("这是SecondController的trace啊");
        logger.warn("这是SecondController的warn啊");
        return "success";
    }
}

可以通过调用上述的修改日志输出级别的接口来达到修改整个项目的日志输出级别,但是有时候我们的排查目标仅仅是特定的一个类,那么这个时候也可以单独的修改这一个类中的日志输出级别,因为一些并发量大的项目的日志会给服务器造成开销,影响线上服务。