当前位置: 代码网 > it编程>编程语言>Java > SpringBoot中日志输出规范的五种策略

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

2025年06月08日 Java 我要评论
一、统一日志格式配置策略1.1 基本原理统一的日志格式是团队协作的基础,可以提高日志的可读性和可分析性。springboot允许开发者自定义日志输出格式,包括时间戳、日志级别、线程信息、类名和消息内容

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

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.logback.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 基本原理

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

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.mycompany.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集成,实现全链路追踪:

<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日志输出规范的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com