关于日志(slf4j的使用心得)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012107143/article/details/80258820

没有调试过线上bug的人学不会打log

1. error的缺陷

以下2者不能共存:
public void error(String format, Object... arguments);
public void error(String msg, Throwable t);

2. MDC的使用

本节内容摘取自:Slf4j MDC 使用和 基于 Logback 的实现分析(感谢原作者)
有了日志之后,我们就可以追踪各种线上问题。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。
MDC ( Mapped Diagnostic Contexts ),顾名思义,其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。
在日志模板中,使用 %X{ }来占位,替换到对应的 MDC 中 key 的值。

看一个MDC使用的简单示例:

public class LogTest {
    private static final Logger logger = LoggerFactory.getLogger(LogTest.class);
    public static void main(String[] args) {
        MDC.put("THREAD_ID", String.valueOf(Thread.currentThread().getId()));
        logger.info("纯字符串信息的info级别日志");
    }
}

logback的输出模板配置:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="log.base" value="${catalina.base}/logs" />
    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="UTF-8">
            <pattern>[%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5p) %logger.%M\(%F:%L\)] %X{THREAD_ID} %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="console" />
    </root>
</configuration>

于是,就有了输出:

[2015-04-30 15:34:35 INFO  io.github.ketao1989.log4j.LogTest.main(LogTest.java:29)] 1 纯字符串信息的info级别日志

3. 日志文件的分类

STDOUT:控制台输出日志
RollingFile:完整的日志文件
ErrorFile:保存系统报错
MainFile:保存系统一些关键日志,便于搜索

4. 常用配置

工具类:

package com.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

/**
 * 日志工具类
 * 
 * @author frcoder
 */
public class LogUtil {
    /**
     * 记录用户行为
     */
    public static Logger userLog = LoggerFactory.getLogger("@USER");

    public static void newUserLog(Object userId, String somethings) {
        MDC.put("USER", userId.toString());
        MDC.put("DO", somethings);
        userLog.debug("...");
    }

    public static void newUserLog(Object userId, Integer role, String somethings) {
        MDC.put("USER", UserRole.toRoleString(role) + ":" + userId.toString());
        MDC.put("DO", somethings);
        userLog.debug("...");
    }

    public static void userQuit() {
        MDC.clear();
    }
}

配置:

Configuration:
  # Internal Log4j events level
  status: warn
  # Automatic Reconfiguration, unit: second
  monitorInterval: 300
  dest: err
  name: YAMLConfig
  properties:
    property:
      -
        name: projectName
        value: me
      -
        name: logHome
        value: /tmp/logs
  thresholdFilter:
    level: debug
  appenders:
    ## Console appender
    Console:
      name: STDOUT
      PatternLayout:
        Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"
    ## RollingFile appender
    RollingRandomAccessFile:
      -
        name: RollingFile
        filename: "${logHome}/${projectName}.log"
        filePattern: "${logHome}/${projectName}.%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"
        Policies:
          SizeBasedTriggeringPolicy:
            size: 20MB
        DefaultRollOverStrategy:
          max: 5
          Delete:
            basePath: "/tmp/logs"
            maxDepth: 1
            IfFileName:
              glob: "epg*.log.*"
            IfLastModified:
              age: 5d
      -
        name: ErrorFile
        filename: "${logHome}/${projectName}-error.log"
        filePattern: "${logHome}/${projectName}-error.%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"
        Policies:
          SizeBasedTriggeringPolicy:
            size: 20MB
        DefaultRollOverStrategy:
          max: 5
      -
        name: MainFile
        filename: "${logHome}/${projectName}-main.log"
        filePattern: "${logHome}/${projectName}-main.%d{yyyy-MM-dd}-%i.log.gz"
        thresholdFilter:
          level: debug
        PatternLayout:
          Pattern: "%d %-5p %c [%L] [%t] [%X{USER}:%X{DO}] - %m%n"
        Policies:
          SizeBasedTriggeringPolicy:
            size: 20MB
        DefaultRollOverStrategy:
          max: 5

  Loggers:
    logger:
      -
        name: com.example
        level: debug
        additivity: false
        AppenderRef:
          - ref: MainFile
          - ref: STDOUT
      -
        name: "@USER"
        level: debug
        additivity: false
        AppenderRef:
          - ref: MainFile
          - ref: STDOUT
      -
        name: org.hibernate.SQL
        level: warn
    Root:
      level: info
      AppenderRef:
        - ref: STDOUT
          level: error
        - ref: RollingFile
          level: info
        - ref: ErrorFile
          level: error

注意:上面代码中的additivity属性(false:只在本logger中输出,不要传递给上级logger;true:不仅在本logger中输出,也会传递给上级,如果本logger和上级logger都指向同一个日志文件,则日志可能会在该文件中打印2次。)

5. 日志与行为

一般在行为完成之后才打日志,看到日志就表示该行为已完成。

6. Response模板类

package com.example;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

/**
 * Response模板类
 * 
 * @author frcoder
 */
@ApiModel(value = "Response", description = "接口响应对象")
public class Response<T> {

    @ApiModelProperty(value = "编码")
    @JsonProperty("code")
    private int code;

    @ApiModelProperty(value = "消息")
    @JsonProperty("message")
    private String message;

    @ApiModelProperty(value = "数据")
    @JsonProperty("data")
    private T data;

    @JsonIgnore
    private Exception exception;


    public static <T> Response<T> ok() {
        return ok(null, "success");
    }

    public static <T> Response<T> ok(T data) {
        return ok(data, "success");
    }

    public static <T> Response<T> ok(T data, String message) {
        return ok(0, data, message);
    }

    public static <T> Response<T> ok(Integer code, T data, String message) {
        return new Response(code, message, data);
    }

    public static <T> Response<T> fail(String message) {
        return fail(99, message);
    }

    public static <T> Response<T> fail(Integer code, String message) {
        return new Response(code, message);
    }

    public static <T> Response<T> failParam(String message) {
        return fail(400, message);
    }

    public static <T> Response<T> error(String message, Exception e) {
        return error(99, message, e);
    }

    public static <T> Response<T> error(Integer code, String message, Exception e) {
        return new Response(code, message).exception(e);
    }


    public Response() {
    }

    public Response(int code) {
        this.code = code;
    }

    public Response(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public Response(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Response code(int code) {
        this.code = code;
        return this;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Response message(String message) {
        this.message = message;
        return this;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Response data(T data) {
        this.data = data;
        return this;
    }

    public Exception getException() {
        return exception;
    }

    public void setException(Exception exception) {
        this.exception = exception;
    }

    public Response exception(Exception exception) {
        this.exception = exception;
        return this;
    }
}

7. 注解与切面在日志中的应用

1. 在gradle中引入jar包

compile "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"

2. 编写注解类

package com.example;

import java.lang.annotation.*;

/**
 * @Log注解类
 * 
 * @author frcoder
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    String value() default "";
}
package com.example;

import com.example.Response;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import static com.example.LogUtil.userLog;

/**
 * LogAop日志切面类
 * 
 * @author frcoder
 */
@Aspect
@Component
public class LogAop {

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

    /**
     * api.impl包下的函数,如果参数列表是以userId, role开头的会被记录用户日志
     */
    @Pointcut("(execution(public * com..api.impl..*.*(..)) || @annotation(Log)) && args(userId, role, ..))")
    public void userLog(Long userId, Integer role) {
    }

    @Before(value = "userLog(userId, role)")
    public void doBefore(JoinPoint joinPoint, Long userId, Integer role) {
        try {
            String methodName = joinPoint.getSignature().getName();
            LogUtil.newUserLog(userId, role, methodName);
        } catch (Exception e) {
            logger.debug("new UserLog is wrong", e);
        }
    }

    @AfterReturning(value = "userLog(userId, role)", returning = "ret")
    public void doAfterReturning(Object ret, Long userId, Integer role) {
        try {
            Response response = (Response) ret;
            userLog.info("[{}]: {}", response.getCode(), response.getMessage());
            if (response.getData() != null) {
                userLog.debug(response.getData().toString());
            }
            if (response.getException() != null) {
                userLog.error(response.getException().toString(), response.getException());
            }
        } catch (Exception e) {
            logger.debug("print UserLog is wrong", e);
        } finally {
            LogUtil.userQuit();
        }
    }

}

猜你喜欢

转载自blog.csdn.net/u012107143/article/details/80258820