MST

星途 面试题库

面试题:Java日志格式化自定义模式及性能优化

假设你在一个高并发的Java应用中,需要自定义日志格式化模式来记录详细的请求信息,包括请求时间、请求ID、处理线程等。请阐述如何实现自定义日志格式化模式,并分析在高并发场景下可能遇到的性能问题及优化方案。
13.6万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

实现自定义日志格式化模式

  1. 使用SLF4J和Logback
    • pom.xml中添加SLF4J和Logback依赖:
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback - classic</artifactId>
        <version>1.2.11</version>
    </dependency>
    
    • src/main/resources下创建logback.xml文件,定义自定义格式化模式。例如:
    <configuration>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy - MM - dd HH:mm:ss.SSS} [%thread] %X{requestId} - %msg%n</pattern>
            </encoder>
        </appender>
        <root level="info">
            <appender - ref ref="STDOUT"/>
        </root>
    </configuration>
    
    这里%d{yyyy - MM - dd HH:mm:ss.SSS}表示请求时间,%thread表示处理线程,%X{requestId}表示请求ID(需在代码中设置MDC,即Mapped Diagnostic Context中的requestId)。
    • 在Java代码中设置MDC:
    import org.slf4j.MDC;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class RequestHandler {
        private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
    
        public void handleRequest(String requestId) {
            MDC.put("requestId", requestId);
            try {
                // 处理请求逻辑
                logger.info("Handling request...");
            } finally {
                MDC.remove("requestId");
            }
        }
    }
    
  2. 使用Log4j2
    • pom.xml中添加Log4j2依赖:
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j - api</artifactId>
        <version>2.14.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j - core</artifactId>
        <version>2.14.1</version>
    </dependency>
    
    • src/main/resources下创建log4j2.xml文件,定义自定义格式化模式:
    <Configuration status="WARN">
        <Appenders>
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{yyyy - MM - dd HH:mm:ss.SSS} [%t] %X{requestId} - %msg%n"/>
            </Console>
        </Appenders>
        <Loggers>
            <Root level="info">
                <AppenderRef ref="Console"/>
            </Root>
        </Loggers>
    </Configuration>
    
    • 在Java代码中设置Context Map(类似于SLF4J的MDC):
    import org.apache.logging.log4j.ThreadContext;
    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.LogManager;
    
    public class RequestHandler {
        private static final Logger logger = LogManager.getLogger(RequestHandler.class);
    
        public void handleRequest(String requestId) {
            ThreadContext.put("requestId", requestId);
            try {
                // 处理请求逻辑
                logger.info("Handling request...");
            } finally {
                ThreadContext.remove("requestId");
            }
        }
    }
    

高并发场景下可能遇到的性能问题及优化方案

  1. 性能问题
    • I/O瓶颈:高并发下频繁的日志写入操作会导致I/O性能瓶颈,尤其是写入文件时。大量的日志写入磁盘会导致磁盘I/O负载过高,影响应用整体性能。
    • MDC操作开销:在高并发环境中,频繁地设置和清除MDC(或Log4j2的ThreadContext)数据可能会带来一定的性能开销,因为这涉及到线程本地存储的操作。
    • 锁竞争:如果日志框架内部使用锁来保证线程安全的日志写入,高并发时可能会出现锁竞争问题,降低系统并发性能。
  2. 优化方案
    • 异步日志写入
      • Logback:可以使用AsyncAppender。在logback.xml中配置如下:
      <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
          <appender - ref ref="STDOUT"/>
      </appender>
      <root level="info">
          <appender - ref ref="ASYNC"/>
      </root>
      
      • Log4j2:使用AsyncLoggerAsyncAppender。在log4j2.xml中配置:
      <AsyncLogger name="com.example" level="info" includeLocation="true">
          <AppenderRef ref="Console"/>
      </AsyncLogger>
      
    • 减少MDC操作频率:尽量在请求处理的入口和出口统一设置和清除MDC数据,避免在请求处理过程中频繁操作。可以使用过滤器(如Servlet Filter)在Web应用中统一处理。
    • 优化日志输出级别:根据实际需求,在高并发场景下适当提高日志输出级别,如将debug级别的日志关闭,只保留info及以上级别的日志,减少不必要的日志记录。
    • 使用无锁数据结构:某些日志框架支持使用无锁的数据结构来避免锁竞争,例如Log4j2的Disruptor模式。在log4j2.xml中配置:
    <Appenders>
        <Async name="Async" bufferSize="1024" blocking="false" discardThreshold="0">
            <Disruptor waitStrategy="Sleeping"/>
            <AppenderRef ref="Console"/>
        </Async>
    </Appenders>
    
    这里Disruptor使用Sleeping等待策略,可减少线程竞争。