当前位置: 代码网 > it编程>编程语言>Java > springboot中的日志实现方式

springboot中的日志实现方式

2025年12月26日 Java 我要评论
很多人写代码,只管业务逻辑,不知道系统在跑的时候:哪个接口慢?哪个服务 qps 高?哪个下游抖了?哪条链路撑不住了?哪个线程池快爆了?哪次 gc 卡顿导致 rt 抖动?这些其实都可以通过观测发现,所以

很多人写代码,只管业务逻辑,不知道系统在跑的时候:

  • 哪个接口慢?
  • 哪个服务 qps 高?
  • 哪个下游抖了?
  • 哪条链路撑不住了?
  • 哪个线程池快爆了?
  • 哪次 gc 卡顿导致 rt 抖动?

这些其实都可以通过观测发现,所以我想通过一系列的文章分享下“观测”的实现。

一:观测

1.1 “事件记录”

你可以理解为:

“发生了什么”

比如:订单创建、用户登录、异常、重试、降级等等。

  • 如果没有 traceid,日志是碎片;
  • 如果有 traceid,日志就是“故事”。

1.2 “系统运行状态的数字化”

你可以理解为:

“系统现在的健康状况是什么”

比如:

  • qps:有没有被压?
  • rt:变慢了吗?
  • p99:高峰压力如何?
  • jvm 堆:是否泄漏?
  • 线程池:是否被打满?

1.3 “服务之间的全链路剖面”

你可以理解为:

“系统调用链长什么样”

比如:一次下单 → 走了哪些服务、哪些接口、哪些耗时?

二:日志

2.1依赖

<dependency>
    <groupid>net.logstash.logback</groupid>
    <artifactid>logstash-logback-encoder</artifactid>
    <version>7.3</version>
</dependency>

2.2配置

在resources文件夹下,添加logback-spring.xml文件,配置如下,

<configuration>
    <appender name="json_file" class="ch.qos.logback.core.rolling.rollingfileappender">
        <file>具体文件路径</file>
        <rollingpolicy class="ch.qos.logback.core.rolling.timebasedrollingpolicy">
            <filenamepattern>具体文件路径.%d{yyyy-mm-dd}.log</filenamepattern>
            <maxhistory>7</maxhistory>
        </rollingpolicy>
        <encoder class="net.logstash.logback.encoder.logstashencoder"/>
    </appender>

    <appender name="console" class="ch.qos.logback.core.consoleappender">
        <encoder>
            <pattern>%d{hh:mm:ss.sss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console"/>打印日志在控制台(可以删除,减小开销)
        <appender-ref ref="json_file"/>输出日志到文件
    </root>
</configuration>

三:异常机制

3.1统一相应结构

@data
public class apiresponse<t> {
    private integer code;
    private string message;
    private t data;

    public static <t> apiresponse<t> success(t data) {
        apiresponse<t> resp = new apiresponse<>();
        resp.setcode(0);
        resp.setmessage("ok");
        resp.setdata(data);
        return resp;
    }

    public static apiresponse<?> fail(integer code, string message) {
        apiresponse<?> resp = new apiresponse<>();
        resp.setcode(code);
        resp.setmessage(message);
        return resp;
    }
}

3.2错误码枚举

@getter
public enum errorcode {
    system_error(10001, "系统异常,请稍后再试"),
    bad_request(10002, "请求参数错误"),
    not_found(10003, "资源不存在"),
    business_error(20001, "业务异常");

    private final int code;
    private final string msg;

    errorcode(int code, string msg) {
        this.code = code;
        this.msg = msg;
    }
}

3.3业务异常类

@getter
public class bizexception extends runtimeexception {

    private final int code;

    public bizexception(errorcode errorcode) {
        super(errorcode.getmsg());
        this.code = errorcode.getcode();
    }
}

3.4全局异常处理器(核心)

@slf4j
@restcontrolleradvice
public class globalexceptionhandler {

    /**
     * 处理业务异常
     */
    @exceptionhandler(bizexception.class)
    public apiresponse<?> handlebizexception(bizexception e) {
        log.warn("business exception: {}", e.getmessage(), e);
        return apiresponse.fail(e.getcode(), e.getmessage());
    }

    /**
     * 处理系统异常
     */
    @exceptionhandler(exception.class)
    public apiresponse<?> handleexception(exception e) {
        log.error("system exception:", e);
        return apiresponse.fail(
                errorcode.system_error.getcode(),
                errorcode.system_error.getmsg()
        );
    }
}

四:日志进阶

4.1加强日志

<configuration>

    <!-- json 文件输出,按日期滚动 -->
    <appender name="json_file" class="ch.qos.logback.core.rolling.rollingfileappender">
        <file>你的文件名</file>

        <rollingpolicy class="ch.qos.logback.core.rolling.timebasedrollingpolicy">
            <filenamepattern>你的文件名.%d{yyyy-mm-dd}.log</filenamepattern>
            <maxhistory>7</maxhistory>
        </rollingpolicy>

         <!-- 核心:使用 composite encoder,让我们能添加 mdc -->
        <encoder class="net.logstash.logback.encoder.loggingeventcompositejsonencoder">
            <providers>
                <!-- 时间戳 -->
                <timestamp />

                <!-- 日志等级 -->
                <loglevel />

                <!-- 线程名 -->
                <threadname />

                <!-- 日志位置 -->
                <callerdata />

                <!-- 日志内容 -->
                <message />

                <!-- 核心:输出 mdc,如 traceid -->
                <mdc />

                <!-- 记录 logger 名字 -->
                <loggername />
            </providers>
        </encoder>
    </appender>

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.consoleappender">
        <encoder>
            <pattern>%d{hh:mm:ss.sss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="json_file" />
    </root>
</configuration>

4.2traceid 工具类

//追踪id
public class traceutil {

    private static final string trace_id = "traceid";

    public static string inittrace() {
        string traceid = uuid.randomuuid().tostring().replace("-", "");
        mdc.put(trace_id, traceid);
        return traceid;
    }

    public static void settrace(string traceid) {
        mdc.put(trace_id, traceid);
    }

    public static void clear() {
        mdc.remove(trace_id);
    }

    public static string gettrace() {
        return mdc.get(trace_id);
    }
}

4.3filter

@component
public class traceidfilter extends onceperrequestfilter {

    @override
    protected void dofilterinternal(httpservletrequest request,
                                    httpservletresponse response,
                                    filterchain filterchain)
                                    throws servletexception, ioexception {

        // 如果 header 已经有 traceid,如网关或 nginx 传递
        string traceid = request.getheader("traceid");

        if (traceid == null || traceid.isempty()) {
            traceid = traceutil.inittrace();
        } else {
            traceutil.settrace(traceid);
        }

        try {
            filterchain.dofilter(request, response);
        } finally {
            traceutil.clear();
        }
    }
}

4.4线程池追踪

@configuration
public class threadpoolconfig {

    @bean("commonexecutor")
    public threadpooltaskexecutor commonexecutor() {
        threadpooltaskexecutor executor = new threadpooltaskexecutor();

        executor.setcorepoolsize(8);
        executor.setmaxpoolsize(16);
        executor.setqueuecapacity(200);
        executor.setkeepaliveseconds(60);
        executor.setthreadnameprefix("common-exec-");

        // 拒绝策略 
        executor.setrejectedexecutionhandler(new threadpoolexecutor.abortpolicy());

        // 任务装饰器(增强:日志、链路追踪、异常等)
        executor.settaskdecorator(runnable -> () -> {
            try {
                runnable.run();
            } catch (exception e) {
                system.err.println("execute error: " + e.getmessage());
                throw e;
            }
        });

        executor.initialize();
        return executor;
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

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

发表评论

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