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