# 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");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 5. 透传链路信息

每发送一个请求时就会产生一条链路信息,而链路单元(Span)之前的相互访问目前则以httprpc等方式作为主要占比。

链路信息(Trace)的传递,Logging Client内部提供了提取请求header内的链路信息编号(TraceID)、上级单元编号(Parent SpanID),整条链路都通过这种方式来进行上下级单元关系、链路关系绑定。

# 5.1. RestTemplate透传链路信息

RestTemplateSpring Web组件提供的请求封装对象,可用于发送指定方式的请求到目标地址,可携带header信息进行传递身份认证信息、请求、响应等信息。

Logging Client则是利用RestTemplate的拦截器将链路(Trace)信息写入请求的header进行传递到下一个单元(Span)。

小提示

Logging Client已经提供了RestTemplate拦截器实现类LoggingRestTemplateInterceptor,在LoggingFactoryBean#afterPropertiesSet方法内进行实例化并且已经设置了拦截器,在Logging Client上报请求日志信息时,都是通过LoggingFactoryBean#restTemplate来执行发送请求到Admin,因此只需要实例化LoggingFactoryBean即可。

# 5.2. OpenFeign透传链路信息

OpenFeignSpringCloud为服务之间方法相互调用的实现方式,根据接口配置信息来发送请求并获取响应内容。

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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

LoggingAppointAdminDiscovery构造函数需提供Logging Admin地址数组,格式为:ip(IP地址):port(端口号),并不需要添加任何httphttps前缀。

# 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;
}
1
2
3
4
5
6
7
8

如上所示,我启动了两个Logging Admin来进行接收Logging Client采集到的请求日志信息后执行存储,而Logging Client具体使用什么LoadBlanace(负载均衡)策略来进行选择上报的Logging Admin节点?

Logging Client提供了LoadBalanceStrategy负载均衡策略接口,而内部提供了两种策略的实现,分别是:RandomWeightedStrategySmoothWeightedRoundRobinStrategy

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;
}
1
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
1
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
1
2
3
4
5
6
7

# 6.2. 服务注册中心发现Admin

SpringCloud MicroService部署方式下使用时,可以将Logging Admin作为一个单独的服务进行注册到Service Registry Center(服务注册中心,如:EurekaZookeeperConsulNacos 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;
}
1
2
3
4
5
6
7
8
9
10
11
12

LoadBalancerClientSpringCloud负载均衡客户端对象,通过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);
1
2

# 7.2. 设置单次上报的日志数量

单次上报请求日志数量默认值为:10

通过LoggingFactoryBean#setNumberOfRequestLog方法来修改默认值,如下所示:

// 设置每次上报的请求日志数量
factoryBean.setNumberOfRequestLog(20);
1
2

# 7.3. 设置上报日志间隔时间

上报日志默认间隔时间为:5秒

通过LoggingFactoryBean#setReportIntervalSecond方法来修改默认值,如下所示:

// 设备上报间隔时间,单位:秒
factoryBean.setReportIntervalSecond(5);
1
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("-", "");
    }
}
1
2
3
4
5
6
7
8
9
10
11

设置使用自定义的策略如下所示:

// 创建自定义策略对象
CustomerTraceIdGenerator customerTraceIdGenerator = new CustomerTraceIdGenerator();
// 设置使用自定义生成TraceID的策略
factoryBean.setTraceGenerator(customerTraceIdGenerator);
1
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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

设置使用自定义策略如下所示:

// 创建自定义策略对象
CustomerSpanIdGenerator customerSpanIdGenerator = new CustomerSpanIdGenerator();
// 设置使用自定义生成SpanID的策略
factoryBean.setSpanGenerator(customerSpanIdGenerator);
1
2
3
4

# 10. 排除部分路径不进行上报日志

Logging Client内默认排除了/error路径不进行上报日志,如果业务服务存在一些访问比较频繁的接口,而且接口并不涉及业务请求,那么建议将这些请求进行排除,比如:集成SpringBootAdmin后会频繁访问/actuator/health来检查服务的健康程度。

通过LoggingFactoryBean#setIgnorePaths方法进行追加排除路径,这里注意是追加而不是替换,所以/error始终是在排除的列表内,配置排除路径如下所示:

// 需要排除的路径列表
String[] ignorePaths = new String[]{
  "/actuator/health",
  "/index",
  "/test"
};
// 设置排除的路径列表
factoryBean.setIgnorePaths(ignorePaths);
1
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;
}
1
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;
}
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

上面示例所示,根据LoggingRegistryCenterAdminDiscovery#setUsername方法来设置用户名,根据LoggingRegistryCenterAdminDiscovery#setPassword方法来设置密码。

# 12. 控制台显示上报日志

Logging Client默认不会在控制台打印即将要上报的请求日志信息,可以通过LoggingFactiory#setShowConsoleLog方法进行设置,如下所示:

// 设置在控制台输出上报的日志
factoryBean.setShowConsoleLog(true);
1
2

# 13. 格式化控制台显示上报日志

Logging Client在控制台打印上报的请求日志时,默认不进行格式化json字符串,根据LoggingFactoryBean#setFormatConsoleLog方法来进行设置,如下所示:

// 设置格式化输出上报的日志
factoryBean.setFormatConsoleLog(true);
1
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;
    }
}
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内部提供了日志通知的具体实现,分别是:LoggingLocalNoticeLoggingAdminNotice

日志通知实现类 功能作用
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内部通过ApplicationContextSpring 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", "恒宇少年");
}
1
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();
}
1
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);
}
1
2
3
4
5
6

在上面代码中,由于被除数不允许为0,所以这段代码在运行期间会出现java.lang.ArithmeticException异常,通过GlobalLogging#error方法可以将该异常的详细堆栈信息采集成为字符串并且可以上传到Logging Admin

堆栈信息的采集使用minbox-framework核心依赖内的工具类,详见:StackTraceUtil

最后更新时间: 12/23/2019, 2:25:51 PM