logback日志自定义占位符

前言

在大型系统运维中,很大程度上是需要依赖日志的。在java大型web工程中,一般都会使用slf4j+logback这一个组合来实现日志的管理。

logback中很多现成的占位符可以可以直接使用,比如线程号【%t】、时间【%d】、日志等级【%p】,更多详细的占位符可以参考后文的占位符总结。

实际应用中,仅仅使用这些已经存在的占位符可能不够,比如我想给我的日志加一个traceId,通过这个traceId我可以快速的定位到我这个请求的所有日志,方便日志追踪。

针对日志中自定义占位符,logback提供了两种解决方案。下面分别来介绍一下

自定义traceId占位符

使用MDC

mdc的全称是Mapped Diagnostic Context,是Logback日志框架中的一个功能,它允许你在同一个线程的执行路径上设置一系列的上下文信息(即键值对),这些上下文信息可以自动地添加到日志输出中

logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false"><contextName>${APP_NAME}</contextName><property name="APP_NAME" value="MouseDemo"/><property name="LOG_PATH" value="./system_log/MouseDemo"/><property name="CONSOLE_LOG_PATTERN"value="%d | %X{traceId}| %highlight(%-5level) | %boldYellow(%thread) | %boldGreen(%logger) | %msg%n"/><!--输出到控制台--><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><root level="info"><appender-ref ref="console"/></root></configuration>

在logback中,自定义的占位符是通过%X{配置的键}来获取的

设置MDC

在web工程中,设置traceId的时机很重要,一般是放在拦截器中。

自定义一个拦截器

package com.tml.mouseDemo.config;import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import static com.tml.mouseDemo.constants.CommonConstants.TRACE_ID;@Slf4j
public class TraceInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String traceId = UUID.fastUUID().toString(true);log.info("TraceInterceptor preHandle,generate traceId is:{}", traceId);MDC.put(TRACE_ID, traceId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("TraceInterceptor afterCompletion,ready to clean traceId:{}", MDC.get(TRACE_ID));MDC.remove(TRACE_ID);}
}

注册拦截器

package com.tml.mouseDemo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.nio.charset.Charset;
import java.util.List;@Configuration
public class WebAppConfig implements WebMvcConfigurer {@Beanpublic HttpMessageConverter<String> responseBodyConverter() {StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8"));return stringHttpMessageConverter;}@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(responseBodyConverter());}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new TraceInterceptor()).addPathPatterns("/**");registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**");}
}

 

演示案例

经过上面两步的配置,日志上就会额外多一个traceId了

先定义一个service类

package com.tml.mouseDemo.service;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class TraceService {public void trace(String type) throws InterruptedException {log.info("trace start,type:{}", type);Thread.sleep(1000L);log.info("trace end");}
}

 

定义一个restful接口

package com.tml.mouseDemo.controller;import com.tml.mouseDemo.constants.CommonResponse;
import com.tml.mouseDemo.service.TraceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@RestController
@Slf4j
public class TraceController {@Autowiredprivate TraceService traceService;@PostMapping("/trace")public CommonResponse<String> trace(String type) throws InterruptedException {log.info("trace type:{}", type);traceService.trace(type);return CommonResponse.success("success");}}

这里的案例是单线程,看下运行结果

 日志中,确实是多了一个traceId,符合预期

不过MDC有其局限性,仅支持单线程的数据传递。因为其底层是基于ThreadLocal来实现的,如下图

 

 局限性

下面来看一下再多线程环境下,使用MDC会有什么样的效果

package com.tml.mouseDemo.controller;import com.tml.mouseDemo.constants.CommonResponse;
import com.tml.mouseDemo.service.TraceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@RestController
@Slf4j
public class TraceController {@Autowiredprivate ThreadPoolExecutor executor;@Autowiredprivate Executor ttlExecutor;@Autowiredprivate TraceService traceService;@PostMapping("/traceWithThread")public CommonResponse<String> traceWithThread(String type) throws InterruptedException {log.info("traceWithThread type:{}", type);traceService.trace(type);//模拟异步发短信new Thread(() -> {try {Thread.sleep(2000);String sendMsg = "hello world";log.info("sendMsg:{}", sendMsg);} catch (Exception e) {log.error("traceWithThread occur error", e);}}, "trace--001").start();return CommonResponse.success("success");}}

这里通过在主线程中new一个子线程来模拟多线程,运行一下看下结果

从图中可以看出,子线程的日志中是没有输出traceId的,这个就是MDC的局限性。看过前文阿里巴巴TransmittableThreadLocal使用指南的朋友们应该知道,这种new出来的线程不支持,那么使用线程池也肯定是不支持数据传递。

 

使用自定义Converter

相比MDC,使用自定义的Converter就会显得更加的灵活了,看下使用自定义的Converter的使用流程

定义traceId的存取

package com.tml.mouseDemo.config;import com.alibaba.ttl.TransmittableThreadLocal;public class TraceContext {private static final ThreadLocal<String> TRACE_CONTEXT = new TransmittableThreadLocal<>();public static String get() {return TRACE_CONTEXT.get();}public static void set(String age) {TRACE_CONTEXT.set(age);}public static void clean() {if (TRACE_CONTEXT.get() != null) {TRACE_CONTEXT.remove();}}
}

MDC使用的是ThreadLocalL来存储,我们这里自定义采用阿里巴巴的TransmittableThreadLocal,为什么要使用这个,详细的可以参考这篇博文阿里巴巴TransmittableThreadLocal使用指南 

定义Converter

package com.tml.mouseDemo.core.log;import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.tml.mouseDemo.config.TraceContext;import java.util.Optional;public class TraceIdConverter extends ClassicConverter {@Overridepublic String convert(ILoggingEvent iLoggingEvent) {return Optional.ofNullable(TraceContext.get()).orElse("");}
}

拦截器调整

package com.tml.mouseDemo.config;import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
public class TraceInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String traceId = UUID.fastUUID().toString(true);log.info("TraceInterceptor preHandle,generate traceId is:{}", traceId);TraceContext.set(traceId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("TraceInterceptor afterCompletion,ready to clean traceId:{}", TraceContext.get());TraceContext.clean();}
}

改成从TraceContext中获取traceId,拦截器的注册和上文的保持一致。 

 

注册Converter

 <conversionRule conversionWord="traceId" converterClass="com.tml.mouseDemo.core.log.TraceIdConverter"/>

logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false"><conversionRule conversionWord="traceId" converterClass="com.tml.mouseDemo.core.log.TraceIdConverter"/><contextName>${APP_NAME}</contextName><property name="APP_NAME" value="MouseDemo"/><property name="LOG_PATH" value="./system_log/MouseDemo"/><property name="CONSOLE_LOG_PATTERN"value="%d | %traceId| %highlight(%-5level) | %boldYellow(%thread) | %boldGreen(%logger) | %msg%n"/><!--输出到控制台--><appender name="console" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>${CONSOLE_LOG_PATTERN}</pattern><charset>UTF-8</charset></encoder></appender><root level="info"><appender-ref ref="console"/></root></configuration>

这里直接使用注册的Converter的traceId来作为占位符,而不是使用%X{键}来获取traceId

演示案例

直接new线程

代码案例和MDC中使用new创建线程一样,直接看下运行结果

子线程的日志中有traceId,并且是和父线程的traceId保持一致,达到预期

使用线程池

为了达到演示效果,我这里定义了两个线程池

package com.tml.mouseDemo.config;import com.alibaba.ttl.threadpool.TtlExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.*;@Slf4j
@Configuration
public class CommonConfig {Thread.UncaughtExceptionHandler exceptionHandler = (Thread t, Throwable e) -> {log.info("current thread occurs error!", e);};@Beanpublic ThreadPoolExecutor executor() {ThreadFactory threadFactory = new ThreadFactoryBuilder().setUncaughtExceptionHandler(exceptionHandler).setNameFormat("mouse-worker-%d").build();int processors = Runtime.getRuntime().availableProcessors();log.info("processors:{}", processors);ThreadPoolExecutor executor = new ThreadPoolExecutor(1,processors * 2,0L,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),threadFactory,new ThreadPoolExecutor.AbortPolicy());return executor;}/*** 使用阿里的 TransmittableThreadLocal 装饰线程池* @return*/@Beanpublic Executor ttlExecutor() {ThreadFactory threadFactory = new ThreadFactoryBuilder().setUncaughtExceptionHandler(exceptionHandler).setNameFormat("mouse-worker-ttl-%d").build();int processors = Runtime.getRuntime().availableProcessors();log.info("processors:{}", processors);ThreadPoolExecutor executor = new ThreadPoolExecutor(1,processors * 2,0L,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),threadFactory,new ThreadPoolExecutor.AbortPolicy());Executor ttlExecutor = TtlExecutors.getTtlExecutor(executor);return ttlExecutor;}}

这里线程池的核心线程数设置为1,是为了方便测试,和这篇文章的思路一致web项目国际化指南

 

测试代码

package com.tml.mouseDemo.controller;import com.tml.mouseDemo.constants.CommonResponse;
import com.tml.mouseDemo.service.TraceService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;@RestController
@Slf4j
public class TraceController {@Autowiredprivate ThreadPoolExecutor executor;@Autowiredprivate Executor ttlExecutor;@Autowiredprivate TraceService traceService;@PostMapping("/traceWithThreadPool")public CommonResponse<String> traceWithThreadPool(String type) throws InterruptedException {log.info("traceWithThreadPool type:{}", type);traceService.trace(type);//模拟异步发短信executor.execute(() -> {try {Thread.sleep(2000);String sendMsg = "hello world";log.info("sendMsg:{}", sendMsg);} catch (Exception e) {log.error("traceWithThreadPool occur error", e);}});//模拟异步发短信ttlExecutor.execute(() -> {try {Thread.sleep(2000);String sendMsg = "hello world";log.info("sendMsg:{}", sendMsg);} catch (Exception e) {log.error("traceWithThreadPool occur error", e);}});return CommonResponse.success("success");}
}

运行两次,看下效果

 

 可以看到,使用ttl装饰的线程池的日志是正常的,使用普通的线程池的日志的traceId错乱了,这个也是尤其需要注意的点。

总结

在logback中,可以使用多种方式来自定义占位符,通常一般的做法主要是上面两种。使用MDC的方式就是简单,不过他的缺陷也是显而易见的,就是他不支持多线程的数据传递。

所以,如果你的项目中想要自定义占位符,强烈建议使用自定义Convertor的方式。详细的代码已上传至github,欢迎前来围观我的github

常见的日志占位符总结

这里总结下logback中常见的占位符,欢迎补充

占位符名称占位符含义备注
%d日期和时间

%d{HH:mm:ss.SSS}

%d{yyyy-MM-dd HH:mm:ss}

可以自定义时间的格式

%t线程名称
%p日志的优先级
%c日志记录器名称

一般表示类名,可以%c{20},可以通过

这样来设置类名的最大长度

%m日志消息
%n换行符
%L日志行号
%X{key}代表 MDC中存储的 key 对应的信息不建议使用,不支持多线程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/5311.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt中自定义信号与槽

在学习信号和槽的时候&#xff0c;我们知道信号一般对应的就是用户的行为&#xff0c;槽指的是接受到信号后的响应&#xff0c;在类内有许多的内置信号和槽函数&#xff0c;能够去实现一些常见的行为&#xff0c;但实际业务开发中&#xff0c;尤其是接受到信号的响应会根据具体…

Yearning开源MySQL SQL审核平台

一款MYSQL SQL语句/查询审计工具&#xff0c;为DBA与开发人员使用. 本地部署&#xff0c;注重隐私&#xff0c;简单高效的MYSQL审计平台。 它可以通过流程审批&#xff0c;实现真实线上环境sql的审核和执行&#xff0c;还可以回滚执行&#xff0c;能够确保线上SQL更新的可靠性…

【Python项目】小区监控图像拼接系统

【Python项目】小区监控图像拼接系统 技术简介&#xff1a;采用Python技术、B/S框架、MYSQL数据库等实现。 系统简介&#xff1a;小区监控拼接系统&#xff0c;就是为了能够让业主或者安保人员能够在同一时间将不同地方的图像进行拼接。这样一来&#xff0c;可以很大程度的方便…

汇编与逆向(一)-汇编工具简介

RadASM是一款著名的WIN32汇编编辑器&#xff0c;支持MASM、TASM等多种汇编编译器&#xff0c;Windows界面&#xff0c;支持语法高亮&#xff0c;自带一个资源编辑器和一个调试器。 一、汇编IDE工具&#xff1a;RadASM RadASM有内置的语言包 下载地址&#xff1a;RadASM asse…

基于STM32的智能门锁安防系统(开源)

目录 项目演示 项目概述 硬件组成&#xff1a; 功能实现 1. 开锁模式 1.1 按键密码开锁 1.2 门禁卡开锁 1.3 指纹开锁 2. 功能备注 3. 硬件模块工作流程 3.1 步进电机控制 3.2 蜂鸣器提示 3.3 OLED显示 3.4 指纹与卡片管理 项目源代码分析 1. 主程序流程 (main…

AUTOSAR OS模块详解(三) Alarm

AUTOSAR OS模块详解(三) Alarm 本文主要介绍AUTOSAR OS的Alarm&#xff0c;并对基于英飞凌Aurix TC3XX系列芯片的Vector Microsar代码和配置进行部分讲解。 文章目录 AUTOSAR OS模块详解(三) Alarm1 简介2 功能介绍2.1 触发原理2.2 工作类型2.3 Alarm启动方式2.4 Alarm配置2.5…

YOLO目标检测1

一. 参考资料 《YOLO目标检测》 by 杨建华博士 二. 背景 2.1 目标检测发展简史 2014年&#xff0c;RCNN问世&#xff0c;R-CNN的思路是先使用一个搜索算法从图像中提取出若干感兴趣区域(region of interest&#xff0c;RoI)&#xff0c;然后使用一个卷积神经网络(convolutio…

【Qt 常用控件】显示类控件——QLabel

目录 1.QLabel 1.1 textFormat 文本类型 普通文本和富文本 Markdown格式 1.2 alignment 文本对齐方式 1.3 wordWrap 自动换行 1.4 indent 文本缩进 1.5 margin 边距 1.6 buddy&#xff0c;qlabel伙伴 1.7 pixmap图片 和 scaledContents自动填充 1.QLabel 功能&#x…

vif-方差膨胀因子计算

vif-方差膨胀因子 使用statsmodels中的variance_inflation_factor&#xff0c;数据集使用乳腺癌数据集 import pandas as pd import numpy as np from sklearn.datasets import load_breast_cancer from tqdm import notebook from statsmodels.stats.outliers_influence impor…

查看电脑或笔记本CPU的核心数方法及CPU详细信息

一、通过任务管理器查看 1.打开任务管理器 可以按下“Ctrl Shift Esc”组合键&#xff0c;或者按下“Ctrl Alt Delete”组合键后选择“任务管理器”来打开。 2.查看CPU信息 在任务管理器界面中&#xff0c;点击“性能”标签页&#xff0c;找到CPU使用记录区域&#xff0c…

数据恢复常见故障(四)关键信号的耦合电容撞件后导致SATA前端通信异常

数据恢复常见故障&#xff08;四&#xff09;关键信号耦合电容撞件后导致SATA前端通信异常 SATA固态硬盘SATA差分信号上有耦合电容&#xff0c;电容被撞件后&#xff0c;偏移&#xff0c;导致接触不良&#xff0c;引起SATA前端信号通信异常&#xff0c;故障现象表现为不认盘&a…

[HCTF 2018]WarmUp

题目&#xff1a;一上来给了个图片还是很懵的&#xff0c;于是尝试查看一下源代码&#xff1a;发现有提示&#xff1a;于是访问source.php得到了php代码&#xff1a;(这里将代码和代码分析放一块) <?phphighlight_file(__FILE__); class emmm{public static function chec…

MYSQL数据库基础-01.数据库的基本操作

数据库的语法是大小写不敏感的&#xff0c;可以使用大写&#xff0c;也可以使用小写。 每条语句要以&#xff1b;结尾&#xff0c;可以多行输入。 名称不能是关键字,若想用关键字命名,要用反引号 引起来。 目录 一.数据库的基本操作 1.创建数据库&#xff1a; 2.查看数据库…

Decode Global专注金融创新,构建便捷的交易平台

随着金融市场持续进阶&#xff0c;越来越多的年轻正在涌入金融交易的体系中&#xff0c;针对当下年轻人崇尚精简&#xff0c;优中取优的特点&#xff0c;Decode Global紧跟金融市场发展的步伐&#xff0c;不断创新体验&#xff0c;致力于打造一个精简快捷&#xff0c;安全便捷的…

系统思考—转型

我们大多数问题的来源是&#xff1a;人们的思考方式与大自然的运作方式之间的差异。——葛雷果利贝特森&#xff08;人类学家、生物学家及系统思考先驱&#xff09; 在企业转型的过程中&#xff0c;许多企业创始人常常面临一个困境——过去的成功经验和旧有的思维方式&#xf…

Flutter:carousel_slider 横向轮播图、垂直轮播公告栏实现

安装依赖 carousel_slider: ^5.0.01、垂直滚动公告栏 import package:carousel_slider/carousel_options.dart;// 垂直滚动公告栏Widget _buildNotice() {return <Widget>[<Widget>[TDImage(assetUrl: "assets/img/home11.png",width: 60.w,height: 60.w…

【二叉树的深搜】计算布尔二叉树的值 求根节点到叶节点数字之和

文章目录 2331. 计算布尔二叉树的值解题思路&#xff1a;后序遍历129. 求根节点到叶节点数字之和解题思路&#xff1a;深度优先搜索 前序遍历 2331. 计算布尔二叉树的值 2331. 计算布尔二叉树的值 给你一棵 完整二叉树 的根&#xff0c;这棵树有以下特征&#xff1a; 叶子节…

【高阶数据结构】布隆过滤器(BloomFilter)

1. 概念 1.1 背景引入 背景&#xff1a;在计算机软件中&#xff0c;一个常见的需求就是 在一个集合中查找一个元素是否存在 &#xff0c;比如&#xff1a;1. Word 等打字软件需要判断用户键入的单词是否在字典中存在 2. 浏览器等网络爬虫程序需要保存一个列表来记录已经遍历过…

偏序关系.

一、偏序&#xff08;半序&#xff09;关系 偏序关系 自反反对称传递性 二、全序&#xff08;线序、链&#xff09;关系 三、偏序集中的重要元素 1. 极大元与极小元 极大元找所在集合的一个或几个最高点&#xff1b; 极小元找所在集合的一个或几个最低点。 2. 最大元与最小…

国产编辑器EverEdit - 列编辑模式

1 列模式 1.1 应用背景 在编辑CSV格式&#xff0c;或者比较规整的配置文件时&#xff0c;可能会用到一列的内容都要进行修改的情况&#xff0c;在不支持列模式的编辑器中&#xff0c;可能需要用户逐行去编辑&#xff0c;比如有下面一段扯淡文本&#xff1a; ADD NRNFREQ:LOCA…