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

你可以通过Spring AOP实现这个功能。具体步骤如下: 1. 创建一个实体类,用于保存错误日志信息,包括用户信息、请求url、方法、参数、异常信息等。 2. 创建一个切面类,使用@Aspect注解标注,该切面类中定义一个@Before通知,用于捕获Controller中抛出的异常,并将异常信息和其他相关信息保存到数据库中。 3. 在切面类中通过@Pointcut注解指定切入点,即对哪些Controller中的方法进行切面处理。 4. 在切面类中使用@Autowired注解注入实体类对应的Repository,然后在@Before通知中调用Repository中的方法将错误信息保存到数据库中。 具体实现细节可以参考以下代码: ```java @Aspect @Component public class ErrorLogAspect { @Autowired private ErrorLogRepository errorLogRepository; @Pointcut("execution(* com.example.demo.controller..*.*(..))") public void logPointcut() {} @Before("logPointcut()") public void logError(JoinPoint joinPoint) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); ErrorLog errorLog = new ErrorLog(); errorLog.setUserId(request.getHeader("userId")); // 从请求头中获取用户信息 errorLog.setUrl(request.getRequestURI()); // 获取请求url errorLog.setMethod(joinPoint.getSignature().toShortString()); // 获取方法名 errorLog.setArgs(Arrays.toString(joinPoint.getArgs())); // 获取方法参数 errorLog.setExceptionMessage(getExceptionMessage(joinPoint)); // 获取异常信息 errorLogRepository.save(errorLog); // 保存错误日志到数据库 } private String getExceptionMessage(JoinPoint joinPoint) { Throwable ex = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(ExceptionHandler.class).value(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); ex.printStackTrace(pw); return sw.toString(); } } ``` 其中,ErrorLog是保存错误日志信息的实体类,ErrorLogRepository是对应的Repository。在logError方法中,通过JoinPoint获取方法名、方法参数等信息,通过RequestContextHolder获取请求信息,最后将错误日志保存到数据库中。getExceptionMessage方法用于获取异常信息。在Controller中,需要使用@ExceptionHandler注解来捕获异常,并将异常信息保存到数据库中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值