开发者

SpringBoot中日志输出规范的五种策略

目录
  • 一、统一日志格式配置策略
    • 1.1 基本原理
    • 1.2 实现方式
      • 1.2.1 配置文件方式
      • 1.2.2 自定义Logback配置
      • 1.2.3 jsON格式日志配置
    • 1.3 最佳实践
    • 二、分级日志策略
      • 2.1 基本原理
        • 2.2 实现方式
          • 2.2.1 配置不同包的日志级别
          • 2.2.2 基于环境的日志级别配置
          • 2.2.3 编程式日志级别管理
        • 2.3 日志级别使用规范
          • 2.4 最佳实践
          • 三、日志切面实现策略
            • 3.1 基本原理
              • 3.2 实现方式
                • 3.2.1 基础日志切面
                • 3.2.2 API请求响应日志切面
                • 3.2.3 自定义注解实现有选择的日志记录
              • 3.3 最佳实践
              • 四、MDC上下文跟踪策略
                • 4.1 基本原理
                  • 4.2 实现方式
                    • 4.2.1 配置MDC过滤器
                    • 4.2.2 日志格式中包含MDC信息
                    • 4.2.3 分布式追踪集成
                    • 4.2.4 手动管理MDC上下文
                  • 4.3 最佳实践
                  • 五、异步日志策略
                    • 5.1 基本原理
                      • 5.2 实现方式
                        • 5.2.1 Logback异步配置
                        • 5.2.2 Log4j2异步配置
                        • 5.2.3 性能优化配置
                        • 5.2.4 自定义异步日志记录器
                      • 5.3 最佳实践
                      • 六、总结

                        一、统一日志格式配置策略

                        1.1 基本原理

                        统一的日志格式是团队协作的基础,可以提高日志的可读性和可分析性。

                        SpringBoot允许开发者自定义日志输出格式,包括时间戳、日志级别、线程信息、类名和消息内容等。

                        1.2 实现方式

                        1.2.1 配置文件方式

                        application.propertiesapplication.yml中定义日志格式:

                        # application.properties
                        # 控制台日志格式
                        logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
                        
                        # 文件日志格式
                        logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}
                        

                        YAML格式配置:

                        logging:
                          pattern:
                            console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"
                            file: "%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"
                        

                        1.2.2 自定义Logback配置

                        对于更复杂的配置,可以使用logback-spring.XML:

                        <?xml version="1.0" encoding="UTF-8"?>
                        <configuration>
                            <property name="CONSOLE_LOG_PATTERN" 
                                value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
                            <property name="FILE_LOG_PATTERN" 
                                value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
                            
                            <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
                                <encoder>
                                    <pattern>${CONSOLE_LOG_PATTERN}</pattern>
                                    <charset>UTF-8</charset>
                                </encoder>
                            </appender>
                            
                            <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                                <file>logs/application.log</file>
                                <encoder>
                                    <pattern>${FILE_LOG_PATTERN}</pattern>
                                    <charset>UTF-8</charset>
                                </encoder>
                                <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                                    <fileNamePattern>logs/archived/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                                    <maxFileSize>10MB</maxFileSize>
                                    <maxHistory>30</maxHistory>
                                    <totalSizeCap>3GB</totalSizeCap>
                                </rollingPolicy>
                            </appender>
                            
                            <root level="INFO">
                                <appender-ref ref="CONSOLE" />
                                <appender-ref ref="FILE" />
                            </root>
                        </configuration>
                        

                        1.2.3 JSON格式日志配置

                        对于需要集中式日志分析的系统,配置JSON格式日志更有利于日志处理:

                        <dependency>
                            <groupId>net.logstash.logback</groupId>
                            <artifactId>logstash-logback-encoder</artifactId>
                            <version>7.2</version>
                        </dependency>
                        
                        <appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                            <file>logs/application.json</file>
                            <encoder class="net.logstash.www.devze.comlogback.encoder.LogstashEncoder">
                                <includeMdcKeyName>requestId</includeMdcKeyName>
                                <includeMdcKeyName>userId</includeMdcKeyName>
                                <customFields>{"application":"my-service","environment":"${ENVIRONMENT:-development}"}</customFields>
                            </encoder>
                            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                                <fileNamePattern>logs/archived/application.%d{yyyy-MM-dd}.%i.json</fileNamePattern>
                                <maxFileSize>10MB</maxFileSize>
                                <maxHistory>30</maxHistory>
                                <totalSizeCap>3GB</totalSizeCap>
                            </rollingPolicy>
                        </appender>
                        

                        1.3 最佳实践

                        • 环境区分:为不同环境配置不同的日志格式(开发环境可读性高,生产环境机器可解析)
                        <springProfile name="dev">
                            <!-- 开发环境配置 -->
                            <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
                                <encoder>
                                    <pattern>%d{HH:mm:ss.SSS} %highlight(%-5level) %cyan(%logger{15}) - %msg%n</pattern>
                                </encoder>
                            </appender>
                        </springProfile>
                        <springProfile name="prod">
                            <!-- 生产环境配置 -->
                            <appender name="JSON_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
                                <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
                            </appender>
                        </springProfile>
                        
                        • 添加关键信息:确保日志中包含足够的上下文信息
                        %d{yyyy-MM-dd HH:mm:ss.SSS} [%X{requestId}] [%X{userId}] %-5level [%thread] %logger{36} - %msg%n
                        
                        • 注意敏感信息:避免记录密码、令牌等敏感信息,必要时进行脱敏处理

                        二、分级日志策略

                        2.1 基本原理

                        合理使用日志级别可以帮助区分不php同重要程度的信息,便于问题定位和系统监控。

                        SpringBoot支持标准的日志级别:TRACE、DEBUG、INFO、WARN、ERROR。

                        2.2 实现方式

                        2.2.1 配置不同包的日志级别

                        # 全局日志级别
                        logging.level.root=INFO
                        
                        # 特定包的日志级别
                        logging.level.org.springframework.web=DEBUG
                        logging.level.org.hibernate=ERROR
                        logging.level.com.mycompany.app=DEBUG
                        

                        2.2.2 基于环境的日志级别配置

                        # application.yml
                        spring:
                          profiles:
                            active: dev
                        
                        ---
                        spring:
                          config:
                            activate:
                              on-profile: dev
                        logging:
                          level:
                            root: INFO
                            com.mycompany.app: DEBUG
                            org.springframework: INFO
                        
                        ---
                        spring:
                          config:
                            activate:
                              on-profile: prod
                        logging:
                          level:
                            root: WARN
                            com.mycompany.app: INFO
                            org.springframework: WARN
                        

                        2.2.3 编程式日志级别管理

                        @RestController
                        @RequestMapping("/api/logs")
                        public class LoggingController {
                            
                            @Autowired
                            private LoggingSystem loggingSystem;
                            
                            @PutMapping("/level/{package}/{level}")
                            public void changeLogLevel(
                                    @PathVariable("package") String packageName,
                                    @PathVariable("level") String level) {
                                LogLevel logLevel = LogLevel.valueOf(level.toUpperCase());
                                loggingSystem.setLogLevel(packageName, logLevel);
                            }
                        }
                        

                        2.3 日志级别使用规范

                        建立清晰的日志级别使用规范对团队协作至关重要:

                        • ERROR:系统错误、应用崩溃、服务不可用等严重问题
                        try {
                            // 业务操作
                        } catch (Exception e) {
                            log.error("Failed to process payment for order: {}", orderId, e);
                            throw new PaymentProcessingException("Payment processing failed", e);
                        }
                        
                        • WARN:不影响当前功能但需要注意的问题
                        if (retryCount > maxRetries / 2) {
                            log.warn("High number of retries detected for operation: {}, current retry: {}/{}", 
                                operationType, retryCount, maxRetries);
                        }
                        
                        
                        • INFO:重要业务流程、系统状态变更等信息
                        log.info("Order {} has been successfully processed with {} items", 
                            order.getId(), order.getItems().size());
                        
                        • DEBUG:调试信息,详细的处理流程
                        log.debug("Processing product with ID: {}, name: {}, category: {}", 
                            product.getId(), product.getName(), product.getCategory());
                        
                        • TRACE:最详细的追踪信息,一般用于框架内部
                        log.trace("Method execution path: class={}, method={}, params={}", 
                            className, methodName, Arrays.toString(args));
                        

                        2.4 最佳实践

                        • 默认使用INFO级别:生产环境默认使用INFO级别,开发环境可使用DEBUG
                        • 合理划分包结构:按功能或模块划分包,便于精细控制日志级别
                        • 避免日志爆炸:谨慎使用DEBUG和TRACE级别,避免产生大量无用日志
                        • 条件日志:使用条件判断减少不必要的字符串拼接开销
                        // 推荐方式
                        if (log.isDebugEnabled()) {
                            log.debug("Complex calculation result: {}", calculateComplexResult());
                        }
                        
                        // 避免这样使用
                        log.debug("Complex calculation result: " + calculateComplexResult());
                        

                        三、日志切面实现策略

                        3.1 基本原理

                        使用AOP(面向切面编程)可以集中处理日志记录,避免在每个方法中手动编写重复的日志代码。尤其适合API调用日志、方法执行时间统计等场景。

                        3.2 实现方式

                        3.2.1 基础日志切面

                        @ASPect
                        @Component
                        @Slf4j
                        public class LoggingAspect {
                            
                            @Pointcut("execution(* com.mycpythonompany.app.service.*.*(..))")
                            public void serviceLayer() {}
                            
                            @Around("serviceLayer()")
                            public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
                                String className = joinPoint.getSignature().getDeclaringTypeName();
                                String methodName = joinPoint.getSignature().getName();
                                
                                log.info("Executing: {}.{}", className, methodName);
                                
                                long startTime = System.currentTimeMillis();
                                try {
                                    Object result = joinPoint.proceed();
                                    long executionTime = System.currentTimeMillis() - startTime;
                                    log.info("Executed: {}.{} in {} ms", className, methodName, executionTime);
                                    return result;
                                } catch (Exception e) {
                                    log.error("Exception in {}.{}: {}", className, methodName, e.getMessage(), e);
                                    throw e;
                                }
                            }
                        }
                        

                        3.2.2 API请求响应日志切面

                        @Aspect
                        @Component
                        @Slf4j
                        public class ApiLoggingAspect {
                            
                            @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
                                      "@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
                                      "@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
                                      "@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
                                      "@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
                            public void apiMethods() {}
                            
                            @Around("apiMethods()")
                            public Object logApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
                                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                                        .currentRequestAttributes()).getRequest();
                                
                                String requestURI = request.getRequestURI();
                                String httpMethod = request.getMethod();
                                String clientIP = request.getRemoteAddr();
                                
                                log.info("API Request - Method: {} URI: {} Client: {}", httpMethod, requestURI, clientIP);
                                
                                long startTime = System.currentTimeMillis();
                                try {
                                    Object result = joinPoint.proceed();
                                    long duration = System.currentTimeMillis() - startTime;
                                    
                                    log.info("API Response - Method: {} URI: {} Duration: {} ms Status: SUCCESS", 
                                             httpMethod, requestURI, duration);
                                    return result;
                                } catch (Exception e) {
                                    long duration = System.currentTimeMillis() - startTime;
                                    log.error("API Response - Method: {} URI: {} Duration: {} ms Status: ERROR Message: {}", 
                                             httpMethod, requestURI, duration, e.getMessage(), e);
                                    throw e;
                                }
                            }
                        }
                        

                        3.2.3 自定义注解实现有选择的日志记录

                        @Retention(RetentionPolicy.RUNTIME)
                        @Target({ElementType.METHOD})
                        public @interface LogExecutionTime {
                            String description() default "";
                        }
                        
                        @Aspect
                        @Component
                        @Slf4j
                        public class CustomLogAspect {
                            
                            @Around("@annotation(logExecutionTime)")
                            public Object logExecutionTime(ProceedingJoinPoint joinPoint, LogExecutionTime logExecutionTime) throws Throwable {
                                String description = logExecutionTime.description();
                                String methodName = joinPoint.getSignature().getName();
                                
                                log.info("Starting {} - {}", methodName, description);
                                long startTime = System.currentTimeMillis();
                                
                                try {
                                    Object result = joinPoint.proceed();
                                    long executionTime = System.currentTimeMillis() - startTime;
                                    log.info("Completed {} - {} in {} ms", methodName, description, executionTime);
                                    return result;
                                } catch (Exception e) {
                                    long executionTime = System.currentTimeMillis() - startTime;
                                    log.error("Failed {} - {} after {} ms: {}", methodName, description, 
                                             executionTime, e.getMessage(), e);
                                    throw e;
                                }
                            }
                        }
                        

                        使用示例:

                        @Service
                        public class OrderService {
                            
                            @LogExecutionTime(description = "Process order payment")
                            public PaymentResult processPayment(Order order) {
                                // 处理支付逻辑
                            }
                        }
                        

                        3.3 最佳实践

                        • 合理定义切点:避免过于宽泛的切点定义,防止产生过多日志
                        • 注意性能影响:记录详细参数和结果可能带来性能开销,需权衡取舍
                        • 异常处理:确保日志切面本身不会抛出异常,影响主业务流程
                        • 避免敏感信息:敏感数据进行脱敏处理后再记录
                        // 敏感信息脱敏示例
                        private String maskCardNumber(String cardNumber) {
                            if (cardNumber == null || cardNumber.length() < 8) {
                                return "***";
                            }
                            return "******" + cardNumber.substring(cardNumber.length() - 4);
                        }
                        

                        四、MDC上下文跟踪策略

                        4.1 基本原理

                        MDC (Mapped Diagnostic Context) 是一种用于存储请求级别上下文信息的工具,它可以在日志框架中保存和传递这些信息,特别适合分布式系统中的请求跟踪。

                        4.2 实现方式

                        4.2.1 配置MDC过滤器

                        @Component
                        @Order(Ordered.HIGHEST_PRECEDENCE)
                        public class MdcLoggingFilter extends OncePerRequestFilter {
                            
                            @Override
                            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                                           FilterChain filterChain) throws ServletException, IOException {
                                try {
                                    // 生成唯一请求ID
                                    String requestId = UUID.randomUUID().toString().replace("-", "");
                                    MDC.put("requestId", requestId);
                                    
                                    // 添加用户信息(如果有)
                                    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                                    if (authentication != null && authentication.isAuthenticated()) {
                                        MDC.put("userId", authentication.getName());
                                    }
                                    
                                    // 添加请求信息
                                    MDC.put("clientIP", request.getRemoteAddr());
                                    MDC.put("userAgent", request.getHeader("User-Agent"));
                                    MDC.put("httpMethod", request.getMethod());
                                    MDC.put("requestURI", request.getRequestURI());
                                    
                                    // 设置响应头,便于客户端跟踪
                                    response.setHeader("X-Request-ID", requestId);
                                    
                                    filterChain.doFilter(request, response);
                                } finally {
                                    // 清理MDC上下文,防止内存泄漏
                                    MDC.clear();
                                }
                            }
                        }
                        

                        4.2.2 日志格式中包含MDC信息

                        <property name="CONSOLE_LOG_PATTERN" 
                            value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{requestId}] [%X{userId}] %-5level [%thread] %logger{36} - %msg%n"/>
                        

                        4.2.3 分布式追踪集成

                        与Spring Cloud Sleuth和Zipkin集成,实现http://www.devze.com全链路追踪:

                        <dependency>
                            <groupId>org.springframework.cloud</groupId>
                            <artifactId>spring-cloud-starter-sleuth</artifactId>
                        </dependency>
                        <dependency>
                            <groupId>org.springframework.cloud</groupId>
                            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
                        </dependency>
                        
                        spring.application.name=my-service
                        spring.sleuth.sampler.probability=1.0
                        spring.zipkin.base-url=http://localhost:9411
                        

                        4.2.4 手动管理MDC上下文

                        @Service
                        public class BackgroundJobService {
                            
                            private static final Logger log = LoggerFactory.getLogger(BackgroundJobService.class);
                            
                            @Async
                            public CompletableFuture<Void> processJob(String jobId, Map<String, String> context) {
                                // 保存原有MDC上下文
                                Map<String, String> previousContext = MDC.getCopyOfContextMap();
                                
                                try {
                                    // 设置新的MDC上下文
                                    MDC.put("jobId", jobId);
                                    if (context != null) {
                                        context.forEach(MDC::put);
                                    }
                                    
                                    log.info("Starting background job processing");
                                    
                                    // 执行业务逻辑
                                    // ...
                                    
                                    log.info("Completed background job processing");
                                    return CompletableFuture.completedFuture(null);
                                } finally {
                                    // 恢复原有MDC上下文或清除
                                    if (previousContext != null) {
                                        MDC.setContextMap(previousContext);
                                    } else {
                                        MDC.clear();
                                    }
                                }
                            }
                        }
                        

                        4.3 最佳实践

                        • 唯一请求标识:为每个请求生成唯一ID,便于追踪完整请求链路
                        • 传递MDC上下文:在异步处理和线程池中正确传递MDC上下文
                        • 合理选择MDC信息:记录有价值的上下文信息,但避免过多信息造成日志膨胀
                        • 与分布式追踪结合:与Sleuth、Zipkin等工具结合,提供完整的分布式追踪能力
                        // 自定义线程池配置,传递MDC上下文
                        @Configuration
                        public class AsyncConfig implements AsyncConfigurer {
                            
                            @Override
                            public Executor getAsyncExecutor() {
                                ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
                                executor.setCorePoolSize(5);
                                executor.setMaxPoolSize(10);
                                executor.setQueueCapacity(25);
                                executor.setThreadNamePrefix("MyAsync-");
                                
                                // 包装原始Executor,传递MDC上下文
                                executor.setTaskDecorator(runnable -> {
                                    Map<String, String> contextMap = MDC.getCopyOfContextMap();
                                    return () -> {
                                        try {
                                            if (contextMap != null) {
                                                MDC.setContextMap(contextMap);
                                            }
                                            runnable.run();
                                        } finally {
                                            MDC.clear();
                                        }
                                    };
                                });
                                
                                executor.initialize();
                                return executor;
                            }
                        }
                        

                        五、异步日志策略

                        5.1 基本原理

                        在高性能系统中,同步记录日志可能成为性能瓶颈,特别是在I/O性能受限的环境下。

                        异步日志通过将日志操作从主线程中分离,可以显著提升系统性能。

                        5.2 实现方式

                        5.2.1 Logback异步配置

                        <configuration>
                            <!-- 定义日志内容和格式 -->
                            <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                                <!-- 配置详情... -->
                          编程  </appender>
                            
                            <!-- 异步appender -->
                            <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
                                <appender-ref ref="FILE" />
                                <queueSize>512</queueSize>
                                <discardingThreshold>0</discardingThreshold>
                                <includeCallerData>false</includeCallerData>
                                <neverblock>false</neverBlock>
                            </appender>
                            
                            <root level="INFO">
                                <appender-ref ref="ASYNC" />
                            </root>
                        </configuration>
                        

                        5.2.2 Log4j2异步配置

                        添加依赖:

                        <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-log4j2</artifactId>
                        </dependency>
                        <dependency>
                            <groupId>com.lmax</groupId>
                            <artifactId>disruptor</artifactId>
                            <version>3.4.4</version>
                        </dependency>
                        

                        配置Log4j2:

                        <Configuration status="WARN">
                            <Appenders>
                                <Console name="Console" target="SYSTEM_OUT">
                                    <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
                                </Console>
                                
                                <RollingFile name="RollingFile" fileName="logs/app.log"
                                            filePattern="logs/app-%d{MM-dd-yyyy}-%i.log.gz">
                                    <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
                                    <Policies>
                                        <TimeBasedTriggeringPolicy />
                                        <SizeBasedTriggeringPolicy size="10 MB"/>
                                    </Policies>
                                    <DefaultRolloverStrategy max="20"/>
                                </RollingFile>
                                
                                <!-- 异步Appender -->
                                <Async name="AsyncFile">
                                    <AppenderRef ref="RollingFile"/>
                                    <BufferSize>1024</BufferSize>
                                </Async>
                            </Appenders>
                            
                            <Loggers>
                                <Root level="info">
                                    <AppenderRef ref="Console"/>
                                    <AppenderRef ref="AsyncFile"/>
                                </Root>
                            </Loggers>
                        </Configuration>
                        

                        5.2.3 性能优化配置

                        针对Log4j2进行更高级的性能优化:

                        <Configuration status="WARN" packages="com.mycompany.logging">
                            <Properties>
                                <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
                            </Properties>
                            
                            <Appenders>
                                <!-- 使用MappedFile提高I/O性能 -->
                                <RollingRandomAccessFile name="RollingFile" 
                                                       fileName="logs/app.log"
                                                       filePattern="logs/app-%d{MM-dd-yyyy}-%i.log.gz">
                                    <PatternLayout pattern="${LOG_PATTERN}"/>
                                    <Policies>
                                        <TimeBasedTriggeringPolicy />
                                        <SizeBasedTriggeringPolicy size="25 MB"/>
                                    </Policies>
                                    <DefaultRolloverStrategy max="20"/>
                                </RollingRandoMACcessFile>
                                
                                <!-- 使用更高性能的Async配置 -->
                                <Async name="AsyncFile" bufferSize="2048">
                                    <AppenderRef ref="RollingFile"/>
                                    <DisruptorBlockingQueue />
                                </Async>
                            </Appenders>
                            
                            <Loggers>
                                <!-- 降低某些高频日志的级别 -->
                                <Logger name="org.hibernate.SQL" level="debug" additivity="false">
                                    <AppenderRef ref="AsyncFile" level="debug"/>
                                </Logger>
                                
                                <Root level="info">
                                    <AppenderRef ref="AsyncFile"/>
                                </Root>
                            </Loggers>
                        </Configuration>
                        

                        5.2.4 自定义异步日志记录器

                        对于特殊需求,可以实现自定义的异步日志记录器:

                        @Component
                        public class AsyncLogger {
                            
                            private static final Logger log = LoggerFactory.getLogger(AsyncLogger.class);
                            
                            private final ExecutorService logExecutor;
                            
                            public AsyncLogger() {
                                this.logExecutor = Executors.newSingleThreadExecutor(r -> {
                                    Thread thread = new Thread(r, "async-logger");
                                    thread.setDaemon(true);
                                    return thread;
                                });
                                
                                // 确保应用关闭时处理完所有日志
                                Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                                    logExecutor.shutdown();
                                    try {
                                        if (!logExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
                                            log.warn("AsyncLogger executor did not terminate in the expected time.");
                                        }
                                    } catch (InterruptedException e) {
                                        Thread.currentThread().interrupt();
                                    }
                                }));
                            }
                            
                            public void info(String format, Object... arguments) {
                                logExecutor.submit(() -> log.info(format, arguments));
                            }
                            
                            public void warn(String format, Object... arguments) {
                                logExecutor.submit(() -> log.warn(format, arguments));
                            }
                            
                            public void error(String format, Object... arguments) {
                                Throwable throwable = extractThrowable(arguments);
                                if (throwable != null) {
                                    logExecutor.submit(() -> log.error(format, arguments));
                                } else {
                                    logExecutor.submit(() -> log.error(format, arguments));
                                }
                            }
                            
                            private Throwable extractThrowable(Object[] arguments) {
                                if (arguments != null && arguments.length > 0) {
                                    Object lastArg = arguments[arguments.length - 1];
                                    if (lastArg instanceof Throwable) {
                                        return (Throwable) lastArg;
                                    }
                                }
                                return null;
                            }
                        }
                        

                        5.3 最佳实践

                        • 队列大小设置:根据系统吞吐量和内存情况设置合理的队列大小
                        • 丢弃策略配置:在高负载情况下,可以考虑丢弃低优先级的日志
                        <AsyncAppender name="ASYNC" queueSize="512" discardingThreshold="20">
                            <!-- 当队列剩余容量低于20%时,会丢弃TRACE, DEBUG和INFO级别的日志 -->
                        </AsyncAppender>
                        
                        • 异步日志的注意事项
                          • 异步日志可能导致异常堆栈信息不完整
                          • 系统崩溃时可能丢失最后一批日志
                          • 需要权衡性能和日志完整性
                        • 合理使用同步与异步
                          • 关键操作日志(如金融交易)使用同步记录确保可靠性
                          • 高频但不关键的日志(如访问日志)使用异步记录提高性能
                        // 同步记录关键业务日志
                        log.info("Transaction completed: id={}, amount={}, status={}", 
                                 transaction.getId(), transaction.getAmount(), transaction.getStatus());
                        
                        // 异步记录高频统计日志
                        asyncLogger.info("API usage stats: endpoint={}, count={}, avgResponseTime={}ms", 
                                        endpoint, requestCount, avgResponseTime);
                        

                        另外,性能要求较高的应用推荐使用log4j2的异步模式,性能远高于logback。

                        六、总结

                        这些策略不是相互排斥的,而是可以结合使用,共同构建完整的日志体系。

                        在实际应用中,应根据项目规模、团队情况和业务需求,选择合适的日志规范策略组合。

                        好的日志实践不仅能帮助开发者更快地定位和解决问题,还能为系统性能优化和安全审计提供重要依据。

                        以上就是SpringBoot中日志输出规范的五种策略的详细内容,更多关于SpringBoot日志输出规范的资料请关注编程客栈(www.devze.com)其它相关文章!

                        0

                        上一篇:

                        下一篇:

                        精彩评论

                        暂无评论...
                        验证码 换一张
                        取 消

                        最新开发

                        开发排行榜