SpringBoot log4j2日志追踪

log4j2(MDC)+Skywalking全局链路日志实现 1.问题 目前:当线上出现问题需上kibana上去查询es上收集的log4j2日志。 缺陷: 在多个服务相互调用下,若出现Error级别日志时,无法快速准确定位到由哪个服务引起,只能根据日志时间模糊去定位。 log4j2没有配置传入参数,无法准确的复现错误。 改进: 优化: 引入skywalking的traceId,能够显示出完整的链路调用。 收集 请求ip、请求参数、请求路径、返回状态码(属于业务状态码,并不是http状态码,例如Result类中的code),返回数据等信息。定义log规范,在es上 阅读详情

写在前面

本文只讨论非分布式体系的日志追踪,如果项目使用了微服务、RPC等分布式框架的话,日志追踪有很成熟的框架,Google的Dapper,阿里的EagleEye,Twitter基于Google的Dapper论文开发的Zipkin

通过本文将了解到什么是MDC、MDC应用中存在的问题、如何解决存在的问题

MDC介绍

简介:

MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 、logback及log4j2 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据

API说明:

  • clear() => 移除所有MDC
  • get (String key) => 获取当前线程MDC中指定key的值
  • getContext() => 获取当前线程MDC的MDC
  • put(String key, Object o) => 往当前线程的MDC中存入指定的键值对
  • remove(String key) => 删除当前线程MDC中指定的键值对

优点:

  • 代码简洁,日志风格统一,不需要在log打印中手动拼写traceId,即LOGGER.info("traceId:{} ", traceId)

这里使用log4j2打印日志

SpringBoot中使用log4j2可以参考SpringBoot log4j2日志之旅

pom.xml

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <!-- log4j2 start -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!-- log4j2 end -->

log4j2.xml

这里的关键是

<!-- 添加日志追踪traceId -->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%p] [%t] %c(%F:%L) - %m%n" />

%X{traceId}对应的就是MDC.put(Constants.TRACE_ID, traceId);
 

<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorinterval="10" status="debug">
  <Properties>
    <Property name="infoFile">./logs/info.log</Property>
    <Property name="infoFilePattern">./logs/info.log.%d{yyyy-MM-dd}_%i</Property>
    <Property name="errorFile">./logs/error.log</Property>
    <Property name="errorFilePattern">./logs/error.log.%d{yyyy-MM-dd}_%i</Property>
    <Property name="layoutPattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] [%t] %c(%F:%L) - %m%n</Property>
  </Properties>

  <Appenders>

    <Console name="stdout">
<!--      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] [%t] %c(%F:%L) - %m%n"/>-->
      <!-- 添加日志追踪traceId -->
      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{traceId}] [%p] [%t] %c(%F:%L) - %m%n" />
    </Console>

    <RollingFile fileName="${infoFile}" filePattern="${infoFilePattern}" name="infoAppender">

      <!--显示INFO级别日志-->
      <ThresholdFilter level="INFO" onMatch="ACCEPT"  onMismatch="DENY"/>
      <PatternLayout pattern="${layoutPattern}"/>

      <!--每个文件最多只能2G-->
      <Policies>
        <SizeBasedTriggeringPolicy size="2 GB" />
        <TimeBasedTriggeringPolicy modulate="true"/>
      </Policies>

      <!--删除超过15天的日志-->
      <DefaultRolloverStrategy>
        <Delete basePath="./logs" maxDepth="2">
          <IfLastModified age="30d" />
        </Delete>
      </DefaultRolloverStrategy>
    </RollingFile>


    <RollingFile fileName="${errorFile}" filePattern="${errorFilePattern}" name="errorAppender">

      <ThresholdFilter level="ERROR" onMatch="ACCEPT"  onMismatch="DENY"/>
      <PatternLayout pattern="${layoutPattern}"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="2 GB" />
        <TimeBasedTriggeringPolicy modulate="true"/>
      </Policies>
    </RollingFile>
  </Appenders>

  <Loggers>

    <!--additivity="false"表示在该logger中输出的日志不会再延伸到父层logger。这里如果改为true,则会延伸到Root Logger,遵循Root Logger的配置也输出一次。-->
    <Logger name="org.springframework.boot.autoconfigure" level="DEBUG" additivity="true">
      <AppenderRef ref="stdout"/>
    </Logger>
    <!-- druid配置 start -->
    <logger name="druid.sql.Statement" level="debug" additivity="false">
      <appender-ref ref="stdout"/>
    </logger>
    <logger name="druid.sql.ResultSet" level="debug" additivity="false">
      <appender-ref ref="stdout"/>
    </logger>
    <!-- druid配置 end -->

    <Root level="INFO">
      <AppenderRef ref="stdout"/>
      <AppenderRef ref="infoAppender"/>
      <AppenderRef ref="errorAppender"/>
    </Root>
  </Loggers>

</Configuration>

接口的日志追踪,traceId可以在拦截器中添加,也可以在过滤器中添加

{

    @Bean
    public RequestContextListener requestContextListener() {
        return new RequestContextListener();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 方式一:拦截器中添加traceId
        // registry.addInterceptor(new LogInterceptor())
        // .addPathPatterns("/**")
        // .excludePathPatterns("/static/**", "/templates/**");
    }

    @Bean
    public FilterRegistrationBean<LogTraceFilter> logTraceFilter() {
        // 方式二:过滤器中添加traceId
        final FilterRegistrationBean<LogTraceFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        final LogTraceFilter logTraceFilter = new LogTraceFilter();
        filterRegistrationBean.setFilter(logTraceFilter);
        filterRegistrationBean.setName("logTraceFilter");
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return filterRegistrationBean;
    }

}

方式一:拦截器添加traceId

package com.foo.bar.interceptor;

import com.foo.bar.constant.Constants;
import com.foo.bar.utils.TraceIdUtil;
import org.slf4j.MDC;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * 日志追踪拦截器
 * @author foo
 */
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //如果有上层调用就用上层的ID
        String traceId = request.getHeader(Constants.TRACE_ID);
        if (traceId == null) {
            traceId = TraceIdUtil.getTraceId();
        }

        MDC.put(Constants.TRACE_ID, traceId);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
        // Do nothing because of no business
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        //调用结束后删除
        MDC.remove(Constants.TRACE_ID);
    }
}

方式二:过滤器添加traceId

package com.foo.bar.filter;

import com.foo.bar.constant.Constants;
import com.foo.bar.utils.TraceIdUtil;
import org.slf4j.MDC;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author: foo
 * @create: 2020-09-04 11:02
 */
public class LogTraceFilter extends OncePerRequestFilter {


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        preHandle(request);
        filterChain.doFilter(request, response);
        afterCompletion();
    }

    /**
     * 过滤前处理
     * @param request 请求
     * @return
     */
    public void preHandle(HttpServletRequest request) {
        //如果有上层调用就用上层的ID
        String traceId = request.getHeader(Constants.TRACE_ID);
        if (traceId == null) {
            traceId = TraceIdUtil.getTraceId();
        }

        MDC.put(Constants.TRACE_ID, traceId);
    }

    /**
     * 过滤后处理
     */
    public void afterCompletion() {
        //调用结束后删除
        MDC.remove(Constants.TRACE_ID);
    }
}

constant.java 

package com.foo.bar.constant;

/**
 * 常量类
 * @author foo
 */
public class Constants {

    private Constants(){
        throw new UnsupportedOperationException();
    }

    /**
     * 日志追踪ID
     */
    public static final String TRACE_ID = "traceId";

}

TraceIdUtil.java 

package com.foo.bar.utils;

import java.util.UUID;

/**
 * traceId生成工具类
 * @author foo
 */
public class TraceIdUtil {

    private TraceIdUtil() {
        throw new UnsupportedOperationException("Utility class");
    }

    /**
     * 获取traceId
     * @return
     */
    public static String getTraceId() {
        return UUID.randomUUID().toString().replace("-", "").toUpperCase();
    }
}

 

MDC 存在的问题

  • 子线程中打印日志丢失traceId
  • kafka等中间件打印日志丢失traceId

子线程中打印日志丢失traceId

开启异步注解@EnableAsync

package com.foo.bar;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * @author foo
 */
@EnableAsync
@SpringBootApplication
public class FooBarApplication {

    public static void main(String[] args) {
        SpringApplication.run(FooBarApplication.class, args);
    }

}

配置异步线程池

package com.foo.bar.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置
 *
 * @author foo
 */
@Slf4j
@Configuration
public class ExecutorConfig {

    @Bean
    @Primary
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(10);
        //配置最大线程数
        executor.setMaxPoolSize(200);
        //配置队列大小
        executor.setQueueCapacity(99999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-");

        // 设置拒绝策略:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }

}

测试Controller

package com.foo.bar.controller;

import com.foo.bar.service.HelloService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: foo
 * @create: 2020-09-04 14:15
 */
@RestController
@Slf4j
@RequiredArgsConstructor
public class HelloController {

    private final HelloService helloService;

    @GetMapping("/hello")
    public String hello(){
        log.info("Hello World! hello无异步追踪!");
        helloService.hello();
        return "Hello World!";
    }

    @GetMapping("/hello-async")
    public String helloAsync(){
        log.info("Hello World! hello-async异步追踪!");
        helloService.helloAsync();
        return "Hello World!";
    }
}

测试Service

package com.foo.bar.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @author: foo
 * @create: 2020-09-04 14:21
 */
@Service
@Slf4j
public class HelloService {

    public void hello(){
        log.info("进入hello业务方法");
    }

    @Async
    public void helloAsync(){
        log.info("进入helloAsync业务方法");
    }
}

测试结果:异步线程丢失了traceId

解决子线程日志打印丢失traceId问题

子线程在打印日志的过程中traceId将丢失,解决方式为重写线程池

  • 线程池封装类:ThreadPoolExecutorMdcWrapper.java
package com.foo.bar.config;

import com.foo.bar.utils.ThreadMdcUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * spring线程池封装类
 * @author foo
 */
@Slf4j
public class ThreadPoolTaskExecutorWrapper extends ThreadPoolTaskExecutor {

    private void showThreadPoolInfo(String prefix){
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if(null==threadPoolExecutor){
            return;
        }

        log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));

    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), startTimeout);

    }

    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}
  •  线程traceId封装工具类:ThreadMdcUtil.java
package com.foo.bar.utils;

import com.foo.bar.constant.Constants;
import org.slf4j.MDC;

import java.util.Map;
import java.util.concurrent.Callable;


/**
 * 线程MDC工具类
 * @author foo
 */
public class ThreadMdcUtil {

    public static void setTraceIdIfAbsent() {
        if (MDC.get(Constants.TRACE_ID) == null) {
            MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
        }
    }

    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }

    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

说明【以封装Runnable为例】:

  • 判断当前线程对应MDC的Map是否存在,存在则设置
  • 设置MDC中的traceId值,不存在则新生成,针对不是子线程的情况,如果是子线程,MDC中traceId不为null
  • 执行run方法

代码等同于以下写法,会更直观

 

public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return new Runnable() {
            @Override
            public void run() {
                if (context == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(context);
                }
                setTraceIdIfAbsent();
                try {
                    runnable.run();
                } finally {
                    MDC.clear();
                }
            }
        };
    }

重新返回的是包装后的Runnable,在该任务执行之前【runnable.run()】先将主线程的Map设置到当前线程中【 即MDC.setContextMap(context)】,这样子线程和主线程MDC对应的Map就是一样的了

修改ExecutorConfig.java的asyncServiceExecutor方法,将ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor()改为ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorWrapper(),如下所示:

package com.foo.bar.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置
 *
 * @author foo
 */
@Slf4j
@Configuration
public class ExecutorConfig {

    @Bean
    @Primary
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutorWrapper();
        //配置核心线程数
        executor.setCorePoolSize(10);
        //配置最大线程数
        executor.setMaxPoolSize(200);
        //配置队列大小
        executor.setQueueCapacity(99999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-");

        // 设置拒绝策略:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }

}

重新测试异步线程日志追踪,异步子线程中的traceId与父线程程一致了,OK。

  • kafka等中间件打印日志丢失traceId

解决方式可以通过AOP切入onMessage这个方法

package com.foo.bar.aspect;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import com.foo.bar.constant.Constants;
import com.foo.bar.utils.TraceIdUtil;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * MDC日志kafka切面
 *
 * @author foo
 * @date 2020/9/7 14:36
 */
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class MdcKafkaAspect {

    /**
     * 定义切入点
     * 通过@Pointcut注解声明频繁使用的切点表达式
     */
    @Pointcut("execution(* onMessage(..)) && target(org.springframework.kafka.listener.MessageListener)")
    public void messageListener() {
        // This func is intentionally empty. Nothing special is needed here.
    }

    @Around(value = "messageListener()")
    public void wrapOnMessage(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId());
            joinPoint.proceed();
        } finally {
            MDC.clear();
        }
    }
}

参考:

Log4j日志写入Kafka(实战) 1.引入依赖-pom 这里为了避免包冲突,过滤掉了log4j12 <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.10</artifactId> <version>0.8.2.2</version&g... 阅读详情

相关推荐

Spring Boot项目中Log4j2日志系统配置与优化指南

日志系统是软件开发中不可或缺的基础组件,用于记录程序运行状态和异常信息。Log4j2作为Apache旗下的高性能日志框架,采用异步日志和Disruptor环形队列技术,显著提升了日志写入效率,特别适合高并发场景。相比传统的Logback实现,Log4j2支持多种配置格式和热更新,提供丰富的Appender和Filter功能,能够满足企业级应用的需求。在Spring Boot项目中集成Log4j2需要调整依赖配置并编写对应的XML或JSON配置文件。通过合理配置异步日志、多环境隔离和监控告警等功能,可以构建高

weixin_29895109的博客 171

springboot学习(八十) springboot中使用Log4j2记录分布式链路日志

可以为每一个请求分配一个traceId,记录日志时,记录此traceId,从网关开始,依次将traceId记录到请求头中,并借用log4j2的MDC功能实现traceId的打印。其中TRACE_ID_HEADER和LOG_TRACE_ID为上下游约定好的key。 2、注册traceId过滤器 将LogTraceIdAutoConfiguration全限定名 写入,如果是老版的springboot,写入如果需调用其他服务,需要将获取到的traceId,写入请求头的TRACE_ID_HEADER中,例如使用o

文若书生的专栏 3199

【SpringBoot笔记32】SpringBoot使用MDC + Log4j2添加日志链路追踪标识tracecId,配置控制台彩色日志输出

我们可以统一的在过滤器中,给每一次HTTP请求生成一个唯一的标识traceId,这里我将采用UUID作为请求标识traceId,然后再过滤器中通过调用【MDC】类中的put()和remove()方法,添加和删除traceId到log4j2日志输出中,注意:MDC是slf4j日志门面框架中提供的一个类。

✅ Java 开发工程师 1881

log4j MDC用户操作日志追踪配置

一、MDC介绍 MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。某些应用程序采用多线程的方式来处理多个用户的请求。在一个用户的使用过程中,可能有多个不同的线程来进行处理。典型的例子是 Web 应用服务器。当用户访问某个页面时,应用服务器可能会创建一个新的线程来处理该请求,也可能从线程池中复用已有的线程。在一个用户的会话存续期间,可能有多个线程处理过该用户的请求。这使得比较难以区分不同...

大叶子不小的博客 1746

SpringBoot中的日志组件Log4j2实战

本文将从零开始带领读者实现一个 Spring Boot 的应用项目中集成Log4j2作为日志组件的功能。Log4j2是一个非常流行的Java日志框架,其优秀的特性、强大的性能以及灵活的配置能力使得它在实际应用中的地位得到了极高的提升。随着微服务架构和云原生时代的到来,越来越多的人开始采用Spring Boot+Spring Cloud这种“约定大于配置”的开发模式。而当我们需要对应用系统进行日志处理时,选择Log4j2可能成为一个不错的选择。

AI天才研究院 1611

【项目实战-MATLAB】:基于机器学习的虹膜识别系统设计

虹膜识别主要包括虹膜定位、虹膜裁剪、极坐标变换、归一化、HOG特征提取、虹膜识别六个主要步骤。本系统采用MATLAB作为开发工具实现 主要算法:霍夫变换预处理 HOG特征提取 SVM进行识别

qq_45047246的博客 6537

从Apache Log4j漏洞 到 SpringBoot 日志

由log4j2漏洞引发的衍生及思考

qq_38150250的博客 1100

LEAP模型—低排放分析平台

LEAP 正在迅速成为进行综合资源规划、温室气体 (GHG) 缓解评估和低排放发展战略 (LEDS) 的国家的事实标准,尤其是在发展中国家,许多国家也选择使用 LEAP 作为其承诺的一部分向联合国气候变化框架公约(UNFCCC)报告。至少有 32 个国家使用 LEAP 来创建能源和排放情景,这些情景是其国家自主气候变化贡献计划 (INDC) 的基础:具有历史意义的巴黎气候协议的基础旨在表明各国开始经济脱碳的意图和投资于气候适应力。

weixin_54697010的博客 3280

java异步链路日志跟踪_SpringBoot日志的链路追踪

导入Maven的资源org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-loggingorg.springframework.bootspring-boot-starter-log4j2com.lmaxdisruptor3.3.11org.projectlomboklo...

weixin_28749363的博客 694

IPSec Over GRE和GRE Over IPSec技术

IPSec Over GRE 和 GRE Over IPSec 一. 技术介绍 IPsec     主要作用是对数据进行加密,因为他能提供所有有时候被单独用作实现加密的一种方法!IPsec建立的是一个逻辑隧道,并不是真正意义上的隧道!并且不能提供路由功能,因为IPsec不支持非ip流量,也不支持广播(组播)! GRE     (通用路由封装)能很好的提供一个真正意义上的点对点的隧道,GRE是一种三层VPN封装技术。GRE可以对某些网络层协议(如IPX、Apple Talk、IP等)的报文进行封装,使封装后

qq_56228347的博客 9868

brpc组件bvar源码解析(一)简介、使用和类的关系

bvar是brpc中的一个重要组件,brpc内部有很多builtin监控都是bvar实现的。 brpc文档介绍bvar“几乎不会给程序增加性能开销,也快于竞争频繁的原子操作”。它的原理是:多线程累加一个计数器时,每个线程只累加私有的变量而不参与全局竞争,因此写时没有锁的竞争;在读取时累加所有线程的私有变量。虽然读比之前慢多了,但写就快多了,因此它使用写多读少的场景,例如统计、监控场景。 1.使用 // bvar::Adder<T>用于累加,下面定义了一个统计read error总数的Adder。

华秋实的专栏 1851

springboot+log4j日志配置

日志是每个项目中必不可少的配置,主要用来运行情况和定位问题 以下为自己亲自测试的,如果大家有更好的方式,欢迎在下方留言! 步骤: 1.引入jar包 2.配置日志打印详细 1.引入jar包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId&g..

hauchun的博客 8027

信捷PLC XD5系列运动控制(10轴)编程应用篇

项目PLC采用XD5-60T10-E,36DI 24DO 晶体管输出,支持10路脉冲输出功能。TMR指令定时器时基可以自己设定,这点比其他品牌PLC方便。ZRN指令我们需要上升沿指令触发。 5、变址寻址 该说不说这个变址寻址是真的强,比西门子指针、三菱的变址寄存器Z 、V都要更加灵活。 7、特殊寄存器:时钟 8、如何建立PLC通讯...............

RXXW_Dor的博客 1万+

Log4j2中配置logId唯一标识来标记同一个请求

在项目的开发过程中,对于log打印时,一次request到response的请求,以及到响应到客户端的数据的请求,我们希望都能够串起来,因此,需要有一个唯一id来标识,这里我们通过log4j2中的logId来实现。 Log4j2中包含一个类:ThreadContext(实际上是一个本地线程) 可以自己生成一个唯一id:比如:uuid logId = uuid; 然后在过滤器或者拦截器之前设置:Th...

唐僧洗发用飘柔 3848

Dify 第 2 篇 Dify 插件离线安装

Dify是一个支持插件扩展的AI平台,其插件市场提供5种类型插件(模型、工具、Agent策略、扩展、插件包)。由于默认pip安装速度慢,可通过dify-plugin-repackaging工具将插件转换为离线包(使用阿里云镜像加速)。该工具提供两种方式:1)自备Python环境执行脚本;2)使用Docker环境(推荐)。脚本支持从Dify市场或GitHub下载插件,并重新打包为含依赖的离线包,便于通过"本地插件"方式快速安装。

甘蓝的专栏 1261

【表面缺陷检测】基于yolov5的钢轨表面缺陷检测(附代码和数据集,Windows系统)

【表面缺陷检测】基于yolov5的钢轨表面缺陷检测(附代码和数据集,Windows系统)

学无止境、积少成多、厚积薄发 3983

SpringBoot使用log4j2将日志记录到文件及自定义数据库

在Spring Boot应用中使用Log4j2实现日志记录是一个提升应用可管理性和可追踪性的好方法。Log4j2不仅支持日志输出到文件,还支持输出到数据库,这为开发人员提供了灵活的日志管理方案。本文旨在介绍如何在Spring Boot项目中配置Log4j2,以将日志记录到文件和自定义数据库中。

kqj1189584的博客 472

使用Matlab与V-REP联合仿真

在Matlab中,我们可以使用V-REP提供的远程API(Application Programming Interface)来连接到V-REP仿真环境。为了连接Matlab与V-REP,我们需要在Matlab中添加V-REP的远程API函数库。你可以根据具体的需求编写Matlab脚本来控制V-REP场景中的机器人,并进行系统建模和仿真。一旦成功连接到V-REP,我们可以使用Matlab来控制V-REP场景中的机器人。在本文中,我们将介绍如何使用Matlab与V-REP进行联合仿真,并提供相应的源代码。

HackMasterX的博客 1853

SpringBoot logback或log4j2日志打印增加trace_uuid追踪接口请求输出日志

为方便项目日志查询,可在日志配置文件中加入自定义标识,在日志头添加uuid,以达到为同一次接口请求标识添加唯一标识的目的。下面介绍一种简单的方式:通过查看slf4j源码注释,org.slf4j包中MDC类为log4j和logback提供了MDC【MDC ( Mapped Diagnostic Contexts ),它是一个线程安全的存放诊断日志的容器。)】功能,

m0_59061013的博客 635

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

【SpringBoot】整合 Log4j2 替代 Logback

本文将完整拆解 SpringBoot 整合 Log4j2 的全流程:从 Logback 与 Log4j2 的深度性能对比,到彻底排除 Logback 依赖、Log4j2 核心配置、异步日志优化,再到生产级运维最佳实践,一篇搞定 Log4j2 所有核心知识点。

qq_44530108的博客 690

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【SpringBoot】Spring Boot 与日志

一、日志框架 小张:开发了一个大型分布式系统; System.out.println("");将关键数据打印在控制台;去掉?写在一个文件? ​ 框架来记录系统的一些运行时信息;日志框架 ; zhanglogging.jar; ​ 高大上的几个功能?异步模式?自动归档?xxxx? zhanglogging-good.jar? ​ 将以前框架卸下来?换上新的框架,重新修改之前相关...

天天新知 487

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Sentry SpringBoot Log4j2 or Logback

一、系统环境 组件 版本 Ubuntu 20.04 SpringBoot 2.5.8 二、Sentry联动 1、创建一个新项目 2、获取DNS链接 3、按照官网教程配置Springboot 三、代码示例 1、Controller package com.example.demosentry.controller; import io.sentry.Sentry; import io.swagger.annotations.Api; import io.swagger.ann

sleetdream的博客 921

SpringCloud 集成 SkyWalking 链路追踪并收集日志

完成以上配置后,重启Spring Boot应用,SkyWalking将开始收集服务调用链路数据以及相关的应用日志。这些信息可以在SkyWalking UI中查看、分析和排查问题。SkyWalking支持自动收集应用程序的日志,并将它们与对应的请求链路关联起来。对于Log4j2或Logback,需要在配置文件中引入SkyWalking的日志适配器。

qq_33240556的博客 1827

SpringBoot 多线程中日志中增链路追踪trace id

trace id

紫蝶侠的博客 3432

Log4j2配置实战:如何为SpringBoot项目定制高性能日志方案(附模板下载)

本文详细介绍了如何为SpringBoot项目配置高性能的Log4j2日志方案,包括异步日志、分级存储和动态过滤等企业级实践。通过实战案例和配置模板,帮助开发者提升日志系统性能,适用于高并发场景下的日志管理需求。

weixin_28366353的博客 152

在SpringBoot-WebFlux中整合log4j2

  如果说在搭建项目时候第一事是创建项目,那么第二件事就一定是加入日志。日志在项目中有非常重要的作用,小到记录信息,大到排查问题,都要用到。废话不多说,我们直接开始! 2、在resources文件夹下创建log4j2.xml文件,内容如下: 3、在application.yml中加入 参考文章:SpringBoot系列(12):SpringBoot集成log4j2日志配置

陆漫漫的博客 876
上一篇: 一次springboot aop 不生效的奇葩分析之旅
下一篇: 记一次nginx 504排查
路飞说AI
博客等级 码龄10年 214粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值