很多人写代码,只管业务逻辑,不知道系统在跑的时候:
- 哪个接口慢?
- 哪个服务 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;
}
}
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论