ApiBoot 零侵入、链路式请求日志分析框架
ApiBoot
提供单应用、微服务应用下的请求日志分析框架ApiBoot Logging
,特性列表如下所示:
链路
:为每一个请求生成一个链路的单条或者多条请求日志信息,精准定位程序出现问题的位置。耗时
:可以分析出每一个请求的耗时,改善服务的性能瓶颈异常信息
:请求遇到异常,提供请求日志的异常堆栈信息记录请求参数
:可以获取RequestBody
、PathParam
两种方式的参数响应内容
:可以获取本次请求响应的内容信息。服务信息
:可获取提供服务的ID、IP、Port等信息。异步通知
:请求日志数据采集后通过Event/Listener
方式异步通知给RPC
、MQ
、REST
、Local
。数据分析
:阈值警告、异常通知等(短信、邮件等方式,2.1.1.RELEASE版本未实现.)
ApiBoot Logging
内部采用拦截器
、Filter
组合完成一系列的操作。
- 如果一个请求的
header
信息内包含traceId(链路ID)
则加入该链路,如果不存在则生成新的链路信息- 如果一个请求的
header
信息内包含spanId(跨度ID)
,则使用该spanId
作为parent spanId
,对两个请求进行上下级关联。
架构设计图
1. 添加依赖组件
在pom.xml
配置文件内添加依赖,如下所示:
<!--ApiBoot Logging-->
<dependency>
<groupId>org.minbox.framework</groupId>
<artifactId>api-boot-starter-logging</artifactId>
</dependency>
内部封装的组件
ApiBoot
从2.1.3.RELEASE
版本开始陆续会将api-boot-plugins
模块下的组件从api-boot
项目分离到minbox-projects
开源组织内作为独立的项目进行升级维护,ApiBoot Logging
内部通过封装minbox-projects/minbox-logging
组件的minbox-logging-client
实现。
minbox-logging
源码地址:https://gitee.com/minbox-projects/minbox-logging
注意:如果未添加
ApiBoot
版本依赖,请访问版本依赖查看添加方式。
2. 修改采集日志路径前缀
ApiBoot Logging
默认采集/**
下的所有请求路径,通过修改application.yml
配置来变更采集日志路径的前缀:
api:
boot:
logging:
# 修改日志路径前缀,可配置多个
logging-path-prefix:
- /user/**
- /order/**
在上面配置中,ApiBoot Logging
只会采集/user/**
、/order/**
下所有路径的请求,并将日志上报到ApiBoot Logging Admin
。
3. 排除不采集日志的路径
ApiBoot Logging
可以指定某些路径的请求日志不进行采集、上报,比如:/actuator/health
,通过修改application.yml
配置来排除采集日志的路径列表,如下所示:
api:
boot:
logging:
# 排除采集请求日志的路径列表
ignore-paths:
- /actuator/health
4. 修改日志等级
如果想让ApiBoot Logging
在控制台输出日志信息,需要修改application.yml
配置文件内的日志等级,如下所示:
logging:
level:
org.minbox.framework.api.boot.plugin.logging: debug
5. 美化控制台打印日志信息
为了方便查看控制台打印的请求日志信息,ApiBoot Logging
支持美化日志json
信息,配置文件如下所示:
api:
boot:
logging:
format-console-log-json: true
效果如下所示:
2019-07-24 12:56:11.231 DEBUG 3833 --- [nio-8080-exec-2] f.a.b.p.l.n.ApiBootLoggingNoticeListener : Request Uri:/index, Logging:
{
"endTime":1563944171115,
"httpStatus":200,
"requestBody":"{\n\t\"name\":\"测试\",\n\t\"email\":\"jnyuqy@gmail.com\"\n}",
"requestHeaders":{
"accept":"*/*",
"accept-encoding":"gzip, deflate",
"cache-control":"no-cache",
"connection":"keep-alive",
"content-length":"52",
"content-type":"application/json",
"cookie":"JSESSIONID=8E206A652D76A5DF1775FC8988549DF4",
"host":"localhost:8080",
"postman-token":"cc60ceb7-32de-46f1-a7a4-86d694edd073",
"user-agent":"PostmanRuntime/7.15.2"
},
"requestIp":"0:0:0:0:0:0:0:1",
"requestMethod":"POST",
"requestUri":"/index",
"responseBody":"测试",
"responseHeaders":{},
"serviceId":"api-boot-sample-logging",
"serviceIp":"192.168.10.156",
"servicePort":"8080",
"spanId":"81c0016e-9bf7-4e86-bcad-98574f0df14c",
"startTime":1563944171049,
"timeConsuming":66,
"traceId":"409b6f2c-f70a-4648-9673-915534bc5aa9"
}
6. 日志上报方式
ApiBoot Logging
内部提供了两种上报方式,分别是just
、timing
。
6.1 just方式
just
顾名思义是直接方式上报,也是ApiBoot Logging
内默认的方式,当一个请求日志产生之后会实时上报到Logging Admin
,利弊分析:
- 优点
- 数据分析延迟低
- 提高数据有效性,防止丢失
- 弊端
- 增加
Logging Admin
访问压力
- 增加
6.2 timing方式
timing
方式是定时上报,ApiBoot Logging
通过配置参数api.boot.logging.report-away=timing
启用,默认间隔5秒执行一次上报,每次上报10条请求日志。
6.2.1 修改上报间隔时间
ApiBoot Logging
默认间隔5秒
发起一次上报请求到Logging Admin
,修改配置如下所示:
api:
boot:
logging:
# 修改每间隔10秒执行一次上报日志
report-interval-second: 10
6.2.2 修改每次上报条数
ApiBoot Logging
默认每次发送缓存内的10条
日志到Logging Admin
,修改配置如下所示:
api:
boot:
logging:
# 修改每次上报2条请求日志
number-of-request-log: 2
7. 日志上报到指定Admin
ApiBoot Logging
支持上报请求日志到指定Admin
服务节点,配置如下所示:
api:
boot:
logging:
admin:
# Logging Admin 服务地址
server-address: 127.0.0.1:9090
注意:
Logging Admin
服务地址不需要配置http://
路径前缀。
8. 日志上报到服务注册中心Admin
ApiBoot Logging
支持从服务注册中心(Eureka Server、Nacos Discovery、Consul...)
中获取指定serviceID
的服务列表,并且自动通过LoadBalance
方式上报请求日志。
api:
boot:
logging:
discovery:
# Logging Admin ServiceID
service-id: sample-logging-admin
Logging Admin ServiceID
对应Logging Admin
服务的spring.application.name
属性配置。
9. 日志缓存
ApiBoot Logging
内部默认通过memory(内存方式)
进行缓存请求日志,目前版本无法修改缓存方式
。
10. 支持SpringSecurity安全上报日志
安全性
是日志上报的基础能力之一,ApiBoot Logging
内部集成Spring Security
的Basic
安全认证方式来完成上报鉴权。
10.1 指定Admin的安全配置
ApiBoot Logging
指定Admin
服务节点的方式跟Eureka Client ServerUrl
类似,在路径上添加Logging Admin
配置的Spring Security
用户名密码,配置如下所示:
api:
boot:
logging:
admin:
# 用户名:user,密码:123456
server-address: user:123456@127.0.0.1:9090
10.2 服务注册中心Admin的安全配置
服务注册中心
方式配置Logging Admin
的Spring Security
用户名、密码方式如下所示:
api:
boot:
logging:
discovery:
# Logging Admin ServiceID
service-id: sample-logging-admin
# 用户名
username: user
# 密码
password: 123456
11. 日志通知
ApiBoot Logging
提供了日志的通知功能,利用该功能可以对每一条请求日志进行输出、存储、分析等,通过实现LoggingNotice
接口使用通知功能,示例如下所示:
@Component
public class LocalNoticeSample implements LoggingNotice {
/**
* order 值越小执行越靠前
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
/**
* 请求日志通知执行方法
* MinBoxLog为一次请求日志对象基本信息
*
* @param minBoxLog ApiBoot Log
*/
@Override
public void notice(MinBoxLog minBoxLog) {
System.out.println(minBoxLog);
}
}
ApiBoot Logging
提供的LoggingNotice
支持多个实现类配置,执行顺序根据getOrder()
方法的返回值来定义,getOrder()
方法返回值越小越靠前执行。
MinBoxLog
对象定义如下所示:
/**
* ApiBoot Log Object
*
* @author:恒宇少年 - 于起宇
*/
@Data
public class MinBoxLog implements Serializable {
/**
* trace id
*/
private String traceId;
/**
* span id
*/
private String spanId;
/**
* parent span id
*/
private String parentSpanId;
/**
* request uri
*/
private String requestUri;
/**
* request method
*/
private String requestMethod;
/**
* http status code
*/
private int httpStatus;
/**
* request ip
*/
private String requestIp;
/**
* service ip address
*/
private String serviceIp;
/**
* service port
*/
private String servicePort;
/**
* start time
*/
private Long startTime;
/**
* end time
*/
private Long endTime;
/**
* this request time consuming
*/
private long timeConsuming;
/**
* service id
*/
private String serviceId;
/**
* request headers
*/
private Map<String, String> requestHeaders;
/**
* request param
*/
private String requestParam;
/**
* request body
*/
private String requestBody;
/**
* response headers
*/
private Map<String, String> responseHeaders;
/**
* response body
*/
private String responseBody;
/**
* exception stack
*/
private String exceptionStack;
/**
* Global method log list
*/
private List<GlobalLog> globalLogs;
}
12. 无缝支持Openfeign
ApiBoot Logging
支持Spring Cloud Openfeign
的方式请求,在SpringCloud
微服务应用中如果你发起一个Http
请求,而该请求在服务端通过openfeign
访问其他服务,这时ApiBoot Logging
会通过openfeign
的Interceptor
携带TraceId
、SpanId
到下一个服务,完成请求日志的链路信息透传。
13. GlobalLogging
GlobalLogging
是全局日志,可以用来采集业务代码中的不同等级的日志,支持采集异常堆栈
、日志产生类
、日志产生源码行
、日志产生方法
等信息。
ApiBoot Logging
提供了一个配置参数,用于修改GlobalLogging
的临时存储方式,默认为内存方式(memory)
,如下所示:
api:
boot:
logging:
global-logging-storage-away: memory
该配置目前只提供了这一种存储方式,所以我们直接使用默认值即可,可以不用在
application.yml
文件内添加。
13.1 Debug等级日志
在GlobalLogging
提供了两种debug
方法,方法定义源码如下所示:
/**
* Collect Debug Level Logs
*
* @param msg log content
*/
void debug(String msg);
/**
* Collect Debug Level Logs
*
* @param format Unformatted log content
* @param arguments List of parameters corresponding to log content
*/
void debug(String format, Object... arguments);
使用示例如下所示:
@Autowired
private GlobalLogging logging;
// debug(String msg)
logging.debug("这是一个debug等级的日志");
// debug(String format,Object... arguments)
logging.debug("这是一个debug等级的日志,执行时间:{}" , System.currentTimeMillis());
// debug(String format, Object... arguments)
logging.debug("多参数演示,第一个参数值:{},第二个参数值:{},第三个参数值:{}",16.7,"b",10);
由于
arguments
参数采用的可变长度参数传递,所以这里允许传递多个不同类型的填充{}
占位符的内容。
13.2 Info等级日志
在GlobalLogging
接口中同样为info
级别的日志采集提供了两个方法,方法定义源码如下所示:
/**
* Collect Info Level Logs
*
* @param msg log content
*/
void info(String msg);
/**
* Collect Info Level Logs
*
* @param format Unformatted log content
* @param arguments List of parameters corresponding to log content
*/
void info(String format, Object... arguments);
使用示例如下所示:
@Autowired
private GlobalLogging logging;
// info(String msg)
logging.info("这是一个info等级的日志");
// info(String format, Object... arguments)
logging.info("这是一个info等级的日志,记录时间:{}" , System.currentTimeMillis());
// info(String format, Object... arguments)
logging.info("多参数演示,第一个参数值:{},第二个参数值:{},第三个参数值:{}","a","b",10);
由于
arguments
参数采用的可变长度参数传递,所以这里允许传递多个不同类型的填充{}
占位符的内容。
13.3 Error等级日志
在GlobalLogging
接口中为error
等级的日志则是提供了3
个方法,比info/debug
多出了一个采集异常堆栈信息的方法,源码如下所示:
/**
* Collect Error Level Logs
*
* @param msg log content
*/
void error(String msg);
/**
* Collect Error Level Logs
*
* @param msg log content
* @param throwable Exception object instance
*/
void error(String msg, Throwable throwable);
/**
* Collect Error Level Logs
*
* @param format Unformatted log content
* @param arguments List of parameters corresponding to log content
*/
void error(String format, Object... arguments);
其中error(String msg)
以及error(String format, Object... arguments)
方法使用方式与debug/info
一致。
13.4 异常堆栈信息
/**
* Collect Error Level Logs
*
* @param msg log content
* @param throwable Exception object instance
*/
void error(String msg, Throwable throwable);
采集异常堆栈信息的error
方法,需要我们把异常实例作为参数传递,在内部通过printStackTrace()
方法进行转换堆栈信息。
使用示例如下所示:
try {
int a = 5 / 0;
} catch (Exception e) {
logging.error("出现了异常.", e);
}