# II. 配置客户端
# 4. 启用客户端
在minbox-logging-spring-context
依赖内提供了@EnableLoggingClient
注解来启用客户端,配置使用该注解后通过@Import
自动注册Logging Client
运行时所需要的Bean
。
@EnableLoggingClient使用示例如下所示:
@SpringBootApplication
@EnableLoggingClient
public class ApiBootLoggingApplication {
/**
* logger instance
*/
static Logger logger = LoggerFactory.getLogger(ApiBootLoggingApplication.class);
public static void main(String[] args) {
SpringApplication.run(ApiBootLoggingApplication.class, args);
logger.info("{}服务启动成功.", "ApiBoot Logging Client");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 5. 透传链路信息
每发送一个请求时就会产生一条链路信息,而链路单元(Span)之前的相互访问目前则以http
、rpc
等方式作为主要占比。
链路信息(Trace)的传递,Logging Client
内部提供了提取请求header
内的链路信息编号(TraceID)、上级单元编号(Parent SpanID),整条链路都通过这种方式来进行上下级单元关系、链路关系绑定。
# 5.1. RestTemplate透传链路信息
RestTemplate
是Spring Web
组件提供的请求封装对象,可用于发送指定方式的请求到目标地址,可携带header
信息进行传递身份认证信息、请求、响应等信息。
Logging Client
则是利用RestTemplate
的拦截器将链路(Trace)信息写入请求的header
进行传递到下一个单元(Span)。
小提示
Logging Client
已经提供了RestTemplate
拦截器实现类LoggingRestTemplateInterceptor
,在LoggingFactoryBean#afterPropertiesSet
方法内进行实例化并且已经设置了拦截器,在Logging Client
上报请求日志信息时,都是通过LoggingFactoryBean#restTemplate
来执行发送请求到Admin
,因此只需要实例化LoggingFactoryBean
即可。
# 5.2. OpenFeign透传链路信息
OpenFeign
是SpringCloud
为服务之间方法相互调用的实现方式,根据接口配置信息来发送请求并获取响应内容。
Logging Client
同样是利用OpenFeign
提供的拦截器将链路(Trace)信息写入服务相互调用的请求header
,进行传递到下一个服务。
小提示
Logging Client
内部提供了RequestInterceptor
接口实现类LoggingOpenFeignInterceptor
来完成链路信息透传,OpenFeign
会自动检索Spring IOC
容器内RequestInterceptor
接口的实现类实例,每次通过OpenFeign
发起请求时会调用RequestInterceptor
实现类的apply
方法来完成拦截业务处理。
# 6. 发现Admin并上报日志
Logging Client
默认本地不进行持久化存储请求日志
信息,而是将本地生成的请求日志
详细信息上报到Logging Admin
,由Admin
进行存储、分析等。
Logging Client
内部提供LoggingAdminDiscovery#lookup
接口方法来进行发现Admin地址。
# 6.1. 指定地址发现Admin
Logging Client
获取指定Admin
地址是通过LoggingAdminDiscovery
其中一个实现类LoggingAppointAdminDiscovery
来进行获取。
下面是ApiBoot
配置使用LoggingAppointAdminDiscovery实践示例,
详见源码,ApiBootLoggingAdminAppointAutoConfiguration:
/**
* ApiBoot Logging Admin Config Discovery
* Multiple Use "," Separation
*
* @return LoggingAdminDiscovery
*/
@Bean
@ConditionalOnMissingBean
public LoggingAppointAdminDiscovery loggingConfigAdminDiscovery() {
String[] adminAddressArray = apiBootLoggingProperties.getAdmin().getServerAddress().split(",");
LoggingAppointAdminDiscovery appointAdminDiscovery = new LoggingAppointAdminDiscovery(adminAddressArray);
return appointAdminDiscovery;
}
2
3
4
5
6
7
8
9
10
11
12
13
LoggingAppointAdminDiscovery
构造函数需提供Logging Admin
地址数组,格式为:ip(IP地址):port(端口号)
,并不需要添加任何http
、https
前缀。
# 6.1.1. 多Admin地址负载均衡配置
如果我们在创建LoggingAppointAdminDiscovery
对象时传递了多个Logging Admin
地址,比如:
@Bean
@ConditionalOnMissingBean
public LoggingAppointAdminDiscovery loggingConfigAdminDiscovery() {
// 初始化Logging Admin地址列表
String[] adminAddressArray = {"127.0.0.1:8080,127.0.0.1:9090"};
LoggingAppointAdminDiscovery appointAdminDiscovery = new LoggingAppointAdminDiscovery(adminAddressArray);
return appointAdminDiscovery;
}
2
3
4
5
6
7
8
如上所示,我启动了两个Logging Admin
来进行接收Logging Client
采集到的请求日志信息后执行存储,而Logging Client
具体使用什么LoadBlanace
(负载均衡)策略来进行选择上报的Logging Admin
节点?
Logging Client
提供了LoadBalanceStrategy
负载均衡策略接口,而内部提供了两种策略的实现,分别是:RandomWeightedStrategy
、SmoothWeightedRoundRobinStrategy
。
Logging Client默认采用SmoothWeightedRoundRobinStrategy(平滑轮询权重)负载均衡策略。
# 6.1.2. 随机权重负载策略
虽然LoggingAppointAdminDiscovery
在构造函数内默认实例化了平滑轮询负载策略
,我们当然可以通过LoggingAppointAdminDiscovery#setLoadBalanceStrategy
方法来进行设置具体的策略,随机权重策略
设置方式如下所示:
@Bean
@ConditionalOnMissingBean
public LoggingAppointAdminDiscovery loggingConfigAdminDiscovery() {
// 初始化Logging Admin地址列表
String[] adminAddressArray = {"127.0.0.1:8080,127.0.0.1:9090"};
LoggingAppointAdminDiscovery appointAdminDiscovery = new LoggingAppointAdminDiscovery(adminAddressArray);
// 实例化随机权重策略
RandomWeightedStrategy randomWeightedStrategy = new RandomWeightedStrategy();
// 设置负载均衡策略
appointAdminDiscovery.setLoadBalanceStrategy(randomWeightedStrategy);
return appointAdminDiscovery;
}
2
3
4
5
6
7
8
9
10
11
12
RandomWeightedStrategy(随机权重负载策略)是随机分配选择指定的Logging Admin
地址,在上面示例中,随机权重的结果可能为:
随机权重获取到的 Logging Admin 地址:
127.0.0.1:8080
127.0.0.1:8080
127.0.0.1:9090
127.0.0.1:8080
127.0.0.1:9090
127.0.0.1:9090
127.0.0.1:9090
2
3
4
5
6
7
8
# 6.1.3. 平滑轮询权重负载策略
SmoothWeightedRoundRobinStrategy(平滑轮询权重负载策略)是平滑分配指定的Logging Admin
地址,在上面示例中,平滑轮询权重的结果为:
平滑轮询权重获取到的 Logging Admin 地址:
127.0.0.1:8080
127.0.0.1:9090
127.0.0.1:8080
127.0.0.1:9090
127.0.0.1:8080
127.0.0.1:9090
2
3
4
5
6
7
# 6.2. 服务注册中心发现Admin
在SpringCloud MicroService
部署方式下使用时,可以将Logging Admin
作为一个单独的服务进行注册到Service Registry Center
(服务注册中心,如:Eureka
、Zookeeper
、Consul
、Nacos Discovery
等),这样在Logging Client
通过服务注册的发现接口即可完成Logging Admin
的发现,获取地址后进行上报请求日志
。
Logging Client
内部提供了集成服务注册中心
的服务发现实现LoggingRegistryCenterAdminDiscovery
,通过配置实例化该类并放入Spring IOC
即可完成自动从服务注册中心
内获取Logging Admin
信息。
ApiBoot
配置使用LoggingRegistryCenterAdminDiscovery实践示例,详见源码,ApiBootLoggingAdminDiscoveryAutoConfiguration。
/**
* ApiBoot Logging Admin Registry Center Discovery
* @param loadBalancerClient LoadBalance Client
* @return LoggingRegistryCenterAdminDiscovery
*/
@Bean
@ConditionalOnMissingBean
public LoggingRegistryCenterAdminDiscovery loggingRegistryCenterAdminDiscovery(LoadBalancerClient loadBalancerClient) {
LoggingRegistryCenterAdminDiscovery registryCenterAdminDiscovery =
new LoggingRegistryCenterAdminDiscovery(apiBootLoggingProperties.getDiscovery().getServiceId(), loadBalancerClient);
return registryCenterAdminDiscovery;
}
2
3
4
5
6
7
8
9
10
11
12
LoadBalancerClient是SpringCloud
负载均衡客户端对象,通过SpringCloud
依赖的自动配置并且放入Spring IOC
,注入该对象后即可负载均衡的发现一个可用的指定serviceID
的服务对象ServiceInstance
。
# 7. 延迟上报日志
Logging Client
默认采用了just
(直接上报)的方式来上报采集到的请求日志
,每产生一条请求日志都会实时上报到Logging Admin
,而有些时候需求往往变化比较大,比如:降低Logging Admin
压力,这时可能每次上报20条请求日志
到Logging Admin
。
针对这种业务情况,Logging Client
提供了定时上报方式。
# 7.1. 配置上报方式
上报方式
通过LoggingFactoryBean#setReportAway
方法来修改默认值,参数为org.minbox.framework.logging.core.ReportAway
枚举,修改如下所示:
// 设置上报方式为:timing
factoryBean.setReportAway(ReportAway.timing);
2
# 7.2. 设置单次上报的日志数量
单次上报请求日志数量默认值为:10
。
通过LoggingFactoryBean#setNumberOfRequestLog
方法来修改默认值,如下所示:
// 设置每次上报的请求日志数量
factoryBean.setNumberOfRequestLog(20);
2
# 7.3. 设置上报日志间隔时间
上报日志默认间隔时间为:5秒
。
通过LoggingFactoryBean#setReportIntervalSecond
方法来修改默认值,如下所示:
// 设备上报间隔时间,单位:秒
factoryBean.setReportIntervalSecond(5);
2
# 8. 自定义TraceID生成规则
Logging Client
默认使用UUID
生成的字符串作为TraceId
(链路编号),通过LoggingFactoryBean#setTraceGenerator
方法来修改默认的生成规则,自定义策略需要实现LoggingTraceGenerator
接口,如下所示:
/**
* 自定义链路编号(TraceID){@link LoggingTraceGenerator}
*
* @author 恒宇少年
*/
public class CustomerTraceIdGenerator implements LoggingTraceGenerator {
@Override
public String createTraceId() throws MinBoxLoggingException {
return UUID.randomUUID().toString().replace("-", "");
}
}
2
3
4
5
6
7
8
9
10
11
设置使用自定义的策略如下所示:
// 创建自定义策略对象
CustomerTraceIdGenerator customerTraceIdGenerator = new CustomerTraceIdGenerator();
// 设置使用自定义生成TraceID的策略
factoryBean.setTraceGenerator(customerTraceIdGenerator);
2
3
4
# 9. 自定义SpanID生成规则
Logging Client
默认使用UUID
生成的字符串作为SpanId
(单元编号),通过LoggingFactoryBean#setSpanGenerator
方法来修改默认的生成规则,自定义策略需要实现LoggingSpanGenerator
接口,如下所示:
/**
* 自定义单元编号(SpanID){@link LoggingSpanGenerator}
*
* @author 恒宇少年
*/
public class CustomerSpanIdGenerator implements LoggingSpanGenerator {
@Override
public String createSpanId() throws MinBoxLoggingException {
String currentTime = String.valueOf(System.currentTimeMillis());
return String.format("%s-%s", "span", currentTime);
}
}
2
3
4
5
6
7
8
9
10
11
12
设置使用自定义策略如下所示:
// 创建自定义策略对象
CustomerSpanIdGenerator customerSpanIdGenerator = new CustomerSpanIdGenerator();
// 设置使用自定义生成SpanID的策略
factoryBean.setSpanGenerator(customerSpanIdGenerator);
2
3
4
# 10. 排除部分路径不进行上报日志
Logging Client
内默认排除了/error
路径不进行上报日志,如果业务服务存在一些访问比较频繁的接口,而且接口并不涉及业务请求,那么建议将这些请求进行排除,比如:集成SpringBootAdmin
后会频繁访问/actuator/health
来检查服务的健康程度。
通过LoggingFactoryBean#setIgnorePaths
方法进行追加排除路径
,这里注意是追加而不是替换,所以/error
始终是在排除的列表内,配置排除路径如下所示:
// 需要排除的路径列表
String[] ignorePaths = new String[]{
"/actuator/health",
"/index",
"/test"
};
// 设置排除的路径列表
factoryBean.setIgnorePaths(ignorePaths);
2
3
4
5
6
7
8
# 11. 安全上报日志
分布式的日志采集与日志存储定然会存在安全性问题,那么在Logging Admin
服务端已经解决了这个问题,Logging Admin
通过集成Spring Security
配置用户名、密码来完成Basic Auth
认证。
在Logging Client
发起上报请求时,会提取Logging Admin
路径内的Basic Auth
认证信息,通过header
形式进行传递认证信息。
# 11.1. 指定Admin地址方式配置
如果采用的是LoggingAppointAdminDiscovery
方式配置Logging Admin
服务地址发现,那么在构造函数初始化Logging Admin
地址时,需要携带Basic Auth
的用户名、密码信息,如下所示:
@Bean
@ConditionalOnMissingBean
public LoggingAppointAdminDiscovery loggingConfigAdminDiscovery() {
// 初始化Logging Admin地址列表
String[] adminAddressArray = {"user:123@127.0.0.1:8080,user:123@127.0.0.1:9090"};
LoggingAppointAdminDiscovery appointAdminDiscovery = new LoggingAppointAdminDiscovery(adminAddressArray);
return appointAdminDiscovery;
}
2
3
4
5
6
7
8
在上面示例中可以看到Basic Auth
是通过username:password@IP:Port
格式来进行配置,其中user
为用户名,而123
则是该用户的密码。
# 11.2. 服务注册中心配置
如果采用LoggingRegistryCenterAdminDiscovery
方式配置Logging Admin
服务地址发现,配置如下所示:
/**
* ApiBoot Logging Admin Registry Center Discovery
* setting basic auth username if not empty {@link LoggingRegistryCenterAdminDiscovery#setUsername(String)}
* setting basic auth password if not empty {@link LoggingRegistryCenterAdminDiscovery#setPassword(String)}
*
* @param loadBalancerClient LoadBalance Client
* @return LoggingRegistryCenterAdminDiscovery
*/
@Bean
@ConditionalOnMissingBean
public LoggingRegistryCenterAdminDiscovery loggingRegistryCenterAdminDiscovery(LoadBalancerClient loadBalancerClient) {
LoggingRegistryCenterAdminDiscovery registryCenterAdminDiscovery =
new LoggingRegistryCenterAdminDiscovery(apiBootLoggingProperties.getDiscovery().getServiceId(), loadBalancerClient);
// 用户名
String basicAuthUserName = apiBootLoggingProperties.getDiscovery().getUsername();
if (ObjectUtils.isEmpty(basicAuthUserName)) {
registryCenterAdminDiscovery.setUsername(basicAuthUserName);
}
// 密码
String basicAuthPassword = apiBootLoggingProperties.getDiscovery().getPassword();
if (!ObjectUtils.isEmpty(basicAuthPassword)) {
registryCenterAdminDiscovery.setPassword(basicAuthPassword);
}
return registryCenterAdminDiscovery;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上面示例所示,根据LoggingRegistryCenterAdminDiscovery#setUsername
方法来设置用户名,根据LoggingRegistryCenterAdminDiscovery#setPassword
方法来设置密码。
# 12. 控制台显示上报日志
Logging Client
默认不会在控制台打印即将要上报的请求日志
信息,可以通过LoggingFactiory#setShowConsoleLog
方法进行设置,如下所示:
// 设置在控制台输出上报的日志
factoryBean.setShowConsoleLog(true);
2
# 13. 格式化控制台显示上报日志
Logging Client
在控制台打印上报的请求日志时,默认不进行格式化json
字符串,根据LoggingFactoryBean#setFormatConsoleLog
方法来进行设置,如下所示:
// 设置格式化输出上报的日志
factoryBean.setFormatConsoleLog(true);
2
# 14. 自定义日志上报通知
Logging Client
提供日志上报通知功能,只需要实现LoggingNotice
接口即可获取每次上报的请求日志详细对象
,进行日志的自定义处理,如下所示:
/**
* 自定义日志通知
* @author 恒宇少年
*/
@Component
public class CustomerLoggingNotice implements LoggingNotice {
/**
* 通知方法
* 处理自定义的业务逻辑
* @param minBoxLog
*/
@Override
public void notice(MinBoxLog minBoxLog) {
System.out.println(minBoxLog.getTraceId());
// 自定义业务处理...
}
/**
* 通知执行优先级
* {@link #getOrder()}方法返回值值越小优先级越高
*
* @return
*/
@Override
public int getOrder() {
return 1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 14.1. 内置的日志通知
Logging Client
内部提供了日志通知的具体实现,分别是:LoggingLocalNotice
,LoggingAdminNotice
。
日志通知实现类 | 功能作用 |
---|---|
LoggingLocalNotice | LoggingLocalNotice 日志通知用于在控制台显示、格式化 日志对象详细信息,优先级为Integer.MIN_VALUE ,源码详见org.minbox.framework.logging.client.notice.support.LoggingLocalNotice 。 |
LoggingAdminNotice | LoggingAdminNotice 日志通知用于上报日志 信息到Logging Admin ,优先级为Integer.MIN_VALUE + 1 ,源码详见:org.minbox.framework.logging.client.notice.support.LoggingAdminNotice 。 |
# 14.2. 自定义多个日志上报通知
Logging Client
内部通过ApplicationContext
从Spring IOC
内获取指定LoggingNotice
类型的实例列表,正因为这样也就支持了多日志通知
的方式。
注意事项
日志通知实现类的优先级值建议不要重复。
# 15. 全局日志
全局日志(GlobalLogging)是可以采集业务代码内的日志信息,将一个请求链路下所采集到的日志通过上报到Logging Admin
进行存储。
在一个请求中,全局日志可能会存在多个,而在请求日志结束时需要一并将采集到的全局日志列表进行上报,这时就需要一个临时保存的机制,在minbox-logging-client
提供了一个GlobalLogging
接口,该接口主要是用来定义全局日志的采集方法,按照日志的等级划分。
GlobalLogging
接口提供的方法:
方法 | 备注 |
---|---|
debug(String msg); | 采集debug等级的日志 |
debug(String format, Object... arguments); | 采集debug等级的日志,支持参数格式化占位符 |
info(String msg); | 采集info等级的日志 |
info(String format, Object... arguments); | 采集info等级的日志,支持参数格式化占位符 |
error(String msg); | 采集error等级的日志 |
error(String msg, Throwable throwable); | 采集error等级的日志,支持采集Exception堆栈信息 |
error(String format, Object... arguments); | 采集error等级的日志,支持参数格式化占位符 |
注意事项
日志等级是对不同的日志划分的方式,具体使用什么样子的等级来采集日志要根据自己的业务而定。
# 15.1 日志占位符
占位符的方式是仿照logback
的形式来提供的功能,我们在记录日志时往往会在日志内中加入一些逻辑性判断的值或者结果,这是占位符就非常好用了,占位符使用{}
来代替指定索引的参数,第一个{}
对应第一个参数,如下所示:
/**
* 全局日志接口
* {@link GlobalLogging}
*
* @see org.minbox.framework.logging.client.global.support.GlobalLoggingMemoryStorage
*/
@Autowired
private GlobalLogging globalLogging;
public void printLogs() {
// 格式化后输出内容:请求地址:/index,用户名:恒宇少年
globalLogging.debug("请求地址:{},用户名:{}", "/index", "恒宇少年");
}
2
3
4
5
6
7
8
9
10
11
12
13
注意事项:
占位符数量
:占位符虽然没有做数量限制,但是不宜过多。占位符参数类型
:占位符的接收参数为java.lang.Object
类型,所以我们可以将任意类型的值进行打印输出。
# 15.2 内存方式存储
GlobalLogging
是一个接口的方式进行定义,而当我们真正使用这个接口时需要实例化它的某一个具体的实现类才可以,为了方便使用,不依赖第三方存储介质,因此提供了一个名为GlobalLoggingMemoryStorage
的内存方式存储实现类。
GlobalLoggingMemoryStorage
从命名上我们可以了解到它内部使用内存方式进行临时存储一个请求内所有的GlobalLog(全局日志)
,而在多线程的场景下为了各个线程之间的数据安全,采用了ThreadLocal
来进行存储,保证每一个请求所创建的线程之间保存的全局日志相互不影响,详见:GlobalLoggingThreadLocal。
通过以下方式来使用GlobalLoggingMemoryStorage
:
@Bean
public GlobalLogging globalLogging(){
return new GlobalLoggingMemoryStorage();
}
2
3
4
小提示
这种方式可以将方法返回的对象自动加入到Spring IOC
容器内,使用时直接注入GlobalLogging
接口即可。
# 15.3 采集异常堆栈信息
在GlobalLogging
接口定义中,提供了可以采集异常堆栈详细信息的error
方法,我们只需要将Throwable
子类的异常实例作为参数传递给该方法,就会自动采集到该异常的详细Stack
堆栈信息,方便我们进行检查代码运行期间遇到的问题,让我们可以尽快的做出代码调整。
使用如下所示:
try {
int a = 3 / 0;
} catch (Exception e) {
// 采集error日志,将Exception对象实例e作为参数传递
globalLogging.error(e.getMessage(), e);
}
2
3
4
5
6
在上面代码中,由于被除数不允许为0
,所以这段代码在运行期间会出现java.lang.ArithmeticException
异常,通过GlobalLogging#error
方法可以将该异常的详细堆栈信息采集成为字符串并且可以上传到Logging Admin
。
堆栈信息的采集使用minbox-framework
核心依赖内的工具类,详见:StackTraceUtil
← I. 概念 III. 配置服务端 →