SpringBoot-日志收集与设置调用链requestid到日志中

log4j可以配置日志打印的格式、输出的位置,现在通过扩展,收集指定级别的日志,比如做日志监控什么的。
log4j2.xml配置

<configuration type="off" packages="com.test.config.LogAppender">
    <Appenders>
        <Console name="ConsoleAppender" target="SYSTEM_OUT">
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%X{requestId}] [%t] [%-5level] %c:%M:%L - %m%n{%throwable}"/>
        </Console>
        <LogAppender name="logAppender">
            <PatternLayout
                    pattern="[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%X{requestId}] [%t] [%-5level] %c:%M:%L - %m%n{%throwable}"/>
        </LogAppender>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="ConsoleAppender" />
            <AppenderRef ref="logAppender" />
        </Root>
    </Loggers>
</configuration>

主要是配置控制台日志输出的格式,设置requestid到日志中(这个在后面会讲),还有配置自定义日志输出处理类,继承AbstractAppender
1 自定义日志处理

@Plugin(
		name = "logAppender",
		category = "Core",
		elementType = "appender",
		printObject = true
)
public class LogAppender extends AbstractAppender {

	protected LogAppender(String name, Filter filter, Layout<? extends Serializable> layout,boolean ignoreExceptions) {
		super(name, filter, layout,ignoreExceptions);
	}

	@Override
	public void append(LogEvent logEvent) {
		if ("ERROR".equalsIgnoreCase(logEvent.getLevel().toString())) {
			System.out.println(JSON.toJSONString(logEvent));
		}
	}

	// 下面这个方法可以接收配置文件中的参数信息
	@PluginFactory
	public static LogAppender createAppender(@PluginAttribute("name") String name,
											 @PluginElement("Filter") final Filter filter,
											 @PluginElement("Layout") Layout<? extends Serializable> layout,
											 @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
		if (name == null) {
			LOGGER.error("No name provided for MyCustomAppenderImpl");
			return null;
		}
		if (layout == null) {
			layout = PatternLayout.createDefaultLayout();
		}
		return new LogAppender(name, filter, layout, ignoreExceptions);
	}
}

主要是append方法,如果异常日志为error,则打印。

2 设置请求id到日志中,适用场景为,一个请求进入系统后生成一个requestid,并且打印到日志指定的位置,不需要代码入侵。
定义RequestModel,将当前请求信息存入ThreadLocal

@Data
public class RequestModel {

	private static final ThreadLocal<RequestModel> REQUEST_MODEL = new ThreadLocal<>();

	private String requestId;

	public static RequestModel getRequestModel() {
		return REQUEST_MODEL.get();
	}

	public static void setRequestModel(RequestModel requestModel) {
		REQUEST_MODEL.set(requestModel);
	}
}

拦截器设置请求requestId,该id为uuid。将requestid设置到MDC中,这样在日志中将会自动打印

@Configuration
public class InterceptorTest extends WebMvcConfigurerAdapter {

	private static Logger logger = LoggerFactory.getLogger(WebMvcConfigurerAdapter.class);
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		HandlerInterceptor handlerInterceptor=new HandlerInterceptor() {
			@Override
			public boolean preHandle(HttpServletRequest request,
									 HttpServletResponse response, Object handler) throws Exception {
				//生产请求id
				String requestId=UUID.randomUUID().toString().replaceAll("-","");
				MDC.put("requestId",requestId );
				RequestModel requestModel=new RequestModel();
				requestModel.setRequestId(requestId);
				RequestModel.setRequestModel(requestModel);
				logger.info("生成的requestId:{}",requestId);
				return true;
			}

			@Override
			public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

			}

			@Override
			public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
				//销毁请求id
				MDC.clear();
			}
		};
		registry.addInterceptor(handlerInterceptor).addPathPatterns("/**");
	}
}

如果用到了线程池异步执行任务,需要特殊实现

public class MDCThreadPoolExecutors extends ThreadPoolTaskExecutor {

	private boolean useFixedContext = false;
	private Map<String, String> fixedContext;

	public MDCThreadPoolExecutors() {
		super();
	}


	private Map<String, String> getContextForTask() {
		return useFixedContext ? fixedContext : MDC.getCopyOfContextMap();
	}

	@Override
	public void execute(Runnable command) {
		super.execute(wrapExecute(command, getContextForTask()));
	}

	@Override
	public <T> Future<T> submit(Callable<T> task) {
		return super.submit(wrapSubmit(task, getContextForTask()));
	}

	private <T> Callable<T> wrapSubmit(Callable<T> task, final Map<String, String> context) {
		return () -> {
			Map<String, String> previous = MDC.getCopyOfContextMap();
			if (context == null) {
				MDC.clear();
			} else {
				MDC.setContextMap(context);
			}
			try {
				return task.call();
			} finally {
				if (previous == null) {
					MDC.clear();
				} else {
					MDC.setContextMap(previous);
				}
			}
		};
	}

	private Runnable wrapExecute(final Runnable runnable, final Map<String, String> context) {
		return () -> {
			Map<String, String> previous = MDC.getCopyOfContextMap();
			if (context == null) {
				MDC.clear();
			} else {
				MDC.setContextMap(context);
			}
			try {
				runnable.run();
			} finally {
				if (previous == null) {
					MDC.clear();
				} else {
					MDC.setContextMap(previous);
				}
			}
		};
	}
}

测试

    @Qualifier(value = "mdcThreadPoolExecutors")
    @Autowired
    private MDCThreadPoolExecutors mdcThreadPoolExecutors;

    @GetMapping("/{name}")
    public Object hello(@PathVariable("name")String name){
        mdcThreadPoolExecutors.execute(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    logger.info("-----------------测试{}----------------",i);
                }
            }
        });
        if(Objects.equals("1",name)){
            logger.info("------------------------测试---------------------------");
        }else{
            logger.error("-----测试异常------",new HttpException("-----测试异常------"));
        }
        logger.info("requestId:{}", RequestModel.getRequestModel().getRequestId());
        return "hello "+name;
    }

在这里插入图片描述
请求requestid生成且打印到了日志中,线程池也是一样。
参考:
https://blog.csdn.net/qq_32447301/article/details/81207283
https://blog.csdn.net/z69183787/article/details/51776323
https://blog.csdn.net/zzq900503/article/details/87629782

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值