SpringBoot2.x整合logback日志框架(2)—layout和MDC机制

JAVA && Spring && SpringBoot2.x — 学习目录

如何根据日志文件快速定位到应用的行为。

1. layout

Layout组件:([累奥特] 布局),将日志事件进行格式化,返回一个字符串

1.1 常用的转换词

转换词 备注
C{length},class{length} 输出发出日志请求调用者的完整类名。注:类名最右边部分不会省略,即使长度超过了设定了length长度。
contextName 日志记录器上下文的名字
d{pattern},date{pattern},d{pattern,timezone},date{pattern,timezone} 日志事件的日期,日期转换词需要一个格式化字符串作为参数,格式化字符串语义与java.text.SimpleDateFormat相同。
F/file 日志请求的java源文件名,一般生成文件信息比较慢,因此一般不使用这个转换词
caller{depth} 输出日志调用者位置信息
L/line 日志请求的行号,由于生成行号信息比较慢,一般不使用这个转换词
m/msg/message 日志事件相关的应用提供信息
M/method 输出与日志事件相关的应用提供的信息
n 输出与平台独立的行分割符,等价于"\n"或者"\r\n"
p/le/level 输出日志级别
r/relative(相对的) 输出日志事件的相对时间
t/thread[斯乱德] 输出产生日志事件的线程名
X{key:-defaultVal} 输出与产生日志事件线程相关的MDC(mapped diagnosis context)。如果mdc转换词后面花括号中有key,那么value就会输出,如果值为空,那么输出默认值;若没有默认值,则输出空字符串。
ex{depth}[带婆斯] 若发生异常时,指定输出异常的行数。
replace(p){r,t} 用't'替换p中正则表达式r的内容,例如:%replace('%m'){'\s',''}会去替换事件消息中包含的所有空格

详细请见:https://blog.csdn.net/lingbomanbu_lyl/article/details/89852037

1.2 自定义layout

  1. 继承自LayoutBase接口即可:
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;

/**
 * @ClassName MySampleLayout
 * @Description 日志布局器
 * @Date 2019/7/1
 * @Version 1.0
 **/
public class MySampleLayout extends LayoutBase {
    @Override
    public String doLayout(ILoggingEvent event) {
        StringBuffer sb=new StringBuffer();
        sb.append(event.getTimeStamp()-event.getLoggerContextVO().getBirthTime());
        sb.append(" [").append(event.getThreadName()).append("] ");
        sb.append(event.getLoggerName()).append("-");
        sb.append(event.getFormattedMessage()).append(CoreConstants.LINE_SEPARATOR);
        return sb.toString();
    }
}
  1. 配置文件

    
    
    
    
    
        
            
            
            
            
        
    

  1. 输出格式:
1230 [restartedMain] com.MmWebApplication-The following profiles are active: test

若包含自定义参数的layout组件

  1. java实现类
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;
import org.apache.commons.lang3.StringUtils;

/**
 * @ClassName CustomLayout
 * @Description 自定义Layout
 * @Date 2019/7/1
 * @Version 1.0
 **/
public class CustomLayout extends LayoutBase {

    private String prefix = null;
    boolean printThreadName = true;

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public void setPrintThreadName(boolean printThreadName) {
        this.printThreadName = printThreadName;
    }

    @Override
    public String doLayout(ILoggingEvent event) {

        StringBuffer sbuf = new StringBuffer(128);
        if (StringUtils.isNotBlank(prefix)) {
            sbuf.append(prefix).append(" ");
        }
        sbuf.append(event.getTimeStamp() - event.getLoggerContextVO().getBirthTime());
        sbuf.append(" ");
        sbuf.append(event.getLevel());
        if (printThreadName) {
            sbuf.append(" [");
            sbuf.append(event.getThreadName());
            sbuf.append("] ");
        } else {
            sbuf.append(" ");
        }
        sbuf.append(event.getLoggerName());
        sbuf.append(" - ");
        sbuf.append(event.getFormattedMessage());
        sbuf.append(CoreConstants.LINE_SEPARATOR);
        return sbuf.toString();
    }
}
  1. 配置文件

    
    
    
    
    
        
            
                MyPrefix
                false
            
            
            
            
        
    

  1. 输出格式
MyPrefix 1405 INFO com.MmWebApplication - The following profiles are active: test

2. MDC

logback内置的日志字段还是比较少的,如果我们需要打印有关业务的更多内容,包括自定义的一些数据,需要借助logback的MDC机制,(映射诊断上下文),即将一些运行时的上下文数据通过logback打印出来;此时我们需要借助org.sl4j.MDC类。

MDC类的原理:其内部持有一个InheritableThreadLocal [因海瑞特包 遗传]实例,用于保存context数据,MDC提供了put/get/clear等几个接口,用来操作ThreadLocal中的数据;ThreadLocal中的K-V,可以在logback.xml中声明,最终会打印在日志中。

MDC.put("userId",1000);

那么在logback.xml中,即可在layout中通过"%X{userId}"来打印此信息。

注意:因为使用的是ThreadLocal,在需要线程退出之前,需要清除(clear)MDC里面的数据;在线程池中使用MDC时,在子线程退出之前调用MDC.clear()方法。

2.1 MDC实践

public class LogMDCUtil {
    //接口名
    private final static String SVR_ID = "SVR_ID";

    private final static Logger logger = LoggerFactory.getLogger(LogMDCUtil.class);
/**
     * 为日志设置服务名
     *
     * @param serviceId
     */
    public static void setServiceNameToLog(String serviceId) {
        try {
            
            MDC.put(SVR_ID, serviceId);
        } catch (Exception e) {
            logger.error("【Logback设置MDC】-设置服务名失败", e);
        }
    }

    /**
     * 删除ThreadLocal中关于ServiceName的数据,防止内存溢出
     */
    public static void removeServiceNameToLog() {
        //线程执行完毕,需要删除ServiceName
        try {
            MDC.remove(SVR_ID);
        } catch (Exception e) {
            logger.error("【Logback设置MDC】-删除服务名失败", e);
        }
    }
}

配置文件:

 
        
            %d %p [%t %X{SVR_ID}] (%C{0}:%line\): %m%n
            
            UTF-8
        
    

2.2 MDC实战—为请求生成唯一编号

如何为每一请求生成唯一编号,并在日志中打印出来呢?

  1. 借助Redis自增命令,获取唯一编号
@Service
public class GainIdService {

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

    @Resource
    private StringRedisTemplate template;

    //前缀 不同系统的前缀可以不相同
    public String gainId(String prefix) {
        Long seq = template.opsForValue().increment("system:idseq:" + pre.getPrefix());
        DecimalFormat df = new DecimalFormat("0000");
        String str = df.format(seq);
        //substring(int beginIndex),[beginIndex,endIndex]的子串,即获取后四位编号
        str = str.substring(str.length() - 4);
        SimpleDateFormat sf=new SimpleDateFormat("yyyyMMddHHmmss");
        String timeStr = sf.format(new Date());
        return pre.getPrefix() + timeStr  + str;
    }
}
  1. 在SpringBoot拦截器中放入MDC中

springBoot—拦截器详解

@Component
public class LogInterceptor implements HandlerInterceptor {

    private final static String REQ_ID = "REQ_ID";

    @Autowired
    GainIdService gainIdService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String traceId = gainIdService.gainId(IdPrefixEnum.REQ_ID);
        MDC.put(REQ_ID, traceId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        MDC.remove(REQ_ID);
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}
  1. 将拦截器配置到系统中
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Autowired
    private LogInterceptor logInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor);
    }
}

参考文档

(译)(五)Logback中的Layout

你可能感兴趣的:(SpringBoot2.x整合logback日志框架(2)—layout和MDC机制)