本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford

Log4j2 异步日志核心通过 RingBuffer 实现,如果某一时刻产生大量日志并且写的速度不及时导致 RingBuffer 满了,业务代码中调用日志记录的地方就会阻塞。所以我们需要对 RingBuffer 进行监控。Log4j2 对于每一个 AsyncLogger 配置,都会创建一个独立的 RingBuffer,例如下面的 Log4j2 配置:

  1. <!--省略了除了 loggers 以外的其他配置-->
  2. <loggers>
  3. <!--default logger -->
  4. <Asyncroot level="info" includeLocation="true">
  5. <appender-ref ref="console"/>
  6. </Asyncroot>
  7. <AsyncLogger name="RocketmqClient" level="error" additivity="false" includeLocation="true">
  8. <appender-ref ref="console"/>
  9. </AsyncLogger>
  10. <AsyncLogger name="com.alibaba.druid.pool.DruidDataSourceStatLoggerImpl" level="error" additivity="false" includeLocation="true">
  11. <appender-ref ref="console"/>
  12. </AsyncLogger>
  13. <AsyncLogger name="org.mybatis" level="error" additivity="false" includeLocation="true">
  14. <appender-ref ref="console"/>
  15. </AsyncLogger>
  16. </loggers>

这个配置包含 4 个 AsyncLogger,对于每个 AsyncLogger 都会创建一个 RingBuffer。Log4j2 也考虑到了监控 AsyncLogger 这种情况,所以将 AsyncLogger 的监控暴露成为一个 MBean(JMX Managed Bean)。

相关源码如下:

Server.java

  1. private static void registerLoggerConfigs(final LoggerContext ctx, final MBeanServer mbs, final Executor executor)
  2. throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
  3. //获取 log4j2.xml 配置中的 loggers 标签下的所有配置值
  4. final Map<String, LoggerConfig> map = ctx.getConfiguration().getLoggers();
  5. //遍历每个 key,其实就是 logger 的 name
  6. for (final String name : map.keySet()) {
  7. final LoggerConfig cfg = map.get(name);
  8. final LoggerConfigAdmin mbean = new LoggerConfigAdmin(ctx, cfg);
  9. //对于每个 logger 注册一个 LoggerConfigAdmin
  10. register(mbs, mbean, mbean.getObjectName());
  11. //如果是异步日志配置,则注册一个 RingBufferAdmin
  12. if (cfg instanceof AsyncLoggerConfig) {
  13. final AsyncLoggerConfig async = (AsyncLoggerConfig) cfg;
  14. final RingBufferAdmin rbmbean = async.createRingBufferAdmin(ctx.getName());
  15. register(mbs, rbmbean, rbmbean.getObjectName());
  16. }
  17. }
  18. }

创建的 MBean 的类源码:RingBufferAdmin.java

  1. public class RingBufferAdmin implements RingBufferAdminMBean {
  2. private final RingBuffer<?> ringBuffer;
  3. private final ObjectName objectName;
  4. //... 省略其他我们不关心的代码
  5. public static final String DOMAIN = "org.apache.logging.log4j2";
  6. String PATTERN_ASYNC_LOGGER_CONFIG = DOMAIN + ":type=%s,component=Loggers,name=%s,subtype=RingBuffer";
  7. //创建 RingBufferAdmin,名称格式符合 Mbean 的名称格式
  8. public static RingBufferAdmin forAsyncLoggerConfig(final RingBuffer<?> ringBuffer,
  9. final String contextName, final String configName) {
  10. final String ctxName = Server.escape(contextName);
  11. //对于 RootLogger,这里 cfgName 为空字符串
  12. final String cfgName = Server.escape(configName);
  13. final String name = String.format(PATTERN_ASYNC_LOGGER_CONFIG, ctxName, cfgName);
  14. return new RingBufferAdmin(ringBuffer, name);
  15. }
  16. //获取 RingBuffer 的大小
  17. @Override
  18. public long getBufferSize() {
  19. return ringBuffer == null ? 0 : ringBuffer.getBufferSize();
  20. }
  21. //获取 RingBuffer 剩余的大小
  22. @Override
  23. public long getRemainingCapacity() {
  24. return ringBuffer == null ? 0 : ringBuffer.remainingCapacity();
  25. }
  26. public ObjectName getObjectName() {
  27. return objectName;
  28. }
  29. }

我们的微服务项目中使用了 spring boot,并且集成了 prometheus。我们可以通过将 Log4j2 RingBuffer 大小作为指标暴露到 prometheus 中,通过如下代码:

对应源码:Log4j2Configuration.java

  1. import io.micrometer.core.instrument.Gauge;
  2. import io.micrometer.prometheus.PrometheusMeterRegistry;
  3. import lombok.extern.log4j.Log4j2;
  4. import org.apache.commons.lang3.StringUtils;
  5. import org.apache.logging.log4j.LogManager;
  6. import org.apache.logging.log4j.core.LoggerContext;
  7. import org.apache.logging.log4j.core.jmx.RingBufferAdminMBean;
  8. import org.springframework.beans.factory.ObjectProvider;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.boot.actuate.autoconfigure.metrics.export.ConditionalOnEnabledMetricsExport;
  11. import org.springframework.context.annotation.Configuration;
  12. import org.springframework.context.event.ContextRefreshedEvent;
  13. import org.springframework.context.event.EventListener;
  14. import javax.annotation.PostConstruct;
  15. import javax.management.ObjectName;
  16. import java.lang.management.ManagementFactory;
  17. @Log4j2
  18. @Configuration(proxyBeanMethods = false)
  19. //需要在引入了 prometheus 并且 actuator 暴露了 prometheus 端口的情况下才加载
  20. @ConditionalOnEnabledMetricsExport("prometheus")
  21. public class Log4j2Configuration {
  22. @Autowired
  23. private ObjectProvider<PrometheusMeterRegistry> meterRegistry;
  24. //只初始化一次
  25. private volatile boolean isInitialized = false;
  26. //需要在 ApplicationContext 刷新之后进行注册
  27. //在加载 ApplicationContext 之前,日志配置就已经初始化好了
  28. //但是 prometheus 的相关 Bean 加载比较复杂,并且随着版本更迭改动比较多,所以就直接偷懒,在整个 ApplicationContext 刷新之后再注册
  29. // ApplicationContext 可能 refresh 多次,例如调用 /actuator/refresh,还有就是多 ApplicationContext 的场景
  30. // 这里为了简单,通过一个简单的 isInitialized 判断是否是第一次初始化,保证只初始化一次
  31. @EventListener(ContextRefreshedEvent.class)
  32. public synchronized void init() {
  33. if (!isInitialized) {
  34. //通过 LogManager 获取 LoggerContext,从而获取配置
  35. LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
  36. org.apache.logging.log4j.core.config.Configuration configuration = loggerContext.getConfiguration();
  37. //获取 LoggerContext 的名称,因为 Mbean 的名称包含这个
  38. String ctxName = loggerContext.getName();
  39. configuration.getLoggers().keySet().forEach(k -> {
  40. try {
  41. //针对 RootLogger,它的 cfgName 是空字符串,为了显示好看,我们在 prometheus 中将它命名为 root
  42. String cfgName = StringUtils.isBlank(k) ? "" : k;
  43. String gaugeName = StringUtils.isBlank(k) ? "root" : k;
  44. Gauge.builder(gaugeName + "_logger_ring_buffer_remaining_capacity", () ->
  45. {
  46. try {
  47. return (Number) ManagementFactory.getPlatformMBeanServer()
  48. .getAttribute(new ObjectName(
  49. //按照 Log4j2 源码中的命名方式组装名称
  50. String.format(RingBufferAdminMBean.PATTERN_ASYNC_LOGGER_CONFIG, ctxName, cfgName)
  51. //获取剩余大小,注意这个是严格区分大小写的
  52. ), "RemainingCapacity");
  53. } catch (Exception e) {
  54. log.error("get {} ring buffer remaining size error", k, e);
  55. }
  56. return -1;
  57. }).register(meterRegistry.getIfAvailable());
  58. } catch (Exception e) {
  59. log.error("Log4j2Configuration-init error: {}", e.getMessage(), e);
  60. }
  61. });
  62. isInitialized = true;
  63. }
  64. }
  65. }

增加这个代码之后,请求 /actuator/prometheus 之后,可以看到对应的返回:

  1. //省略其他的
  2. # HELP root_logger_ring_buffer_remaining_capacity
  3. # TYPE root_logger_ring_buffer_remaining_capacity gauge
  4. root_logger_ring_buffer_remaining_capacity 262144.0
  5. # HELP org_mybatis_logger_ring_buffer_remaining_capacity
  6. # TYPE org_mybatis_logger_ring_buffer_remaining_capacity gauge
  7. org_mybatis_logger_ring_buffer_remaining_capacity 262144.0
  8. # HELP com_alibaba_druid_pool_DruidDataSourceStatLoggerImpl_logger_ring_buffer_remaining_capacity
  9. # TYPE com_alibaba_druid_pool_DruidDataSourceStatLoggerImpl_logger_ring_buffer_remaining_capacity gauge
  10. com_alibaba_druid_pool_DruidDataSourceStatLoggerImpl_logger_ring_buffer_remaining_capacity 262144.0
  11. # HELP RocketmqClient_logger_ring_buffer_remaining_capacity
  12. # TYPE RocketmqClient_logger_ring_buffer_remaining_capacity gauge
  13. RocketmqClient_logger_ring_buffer_remaining_capacity 262144.0

这样,当这个值为 0 持续一段时间后(就代表 RingBuffer 满了,日志生成速度已经远大于消费写入 Appender 的速度了),我们就认为这个应用日志负载过高了。

其实可以通过 JMX 直接查看动态修改 Log4j2 的各种配置,Log4j2 中暴露了很多 JMX Bean,例如通过 JConsole 可以查看并修改:

但是,JMX 里面包含的信息太多,并且我们的服务器在世界各地,远程 JMX 很不稳定,所以我们还是通过 actuator 暴露 http 接口进行操作。

首先,要先配置 actuator 要通过 HTTP 暴露出日志 API,我们这里的配置是:

  1. management:
  2. endpoints:
  3. # 不通过 JMX 暴露任何 actuator 接口
  4. jmx:
  5. exposure:
  6. exclude: '*'
  7. # 通过 JMX 暴露所有 actuator 接口
  8. web:
  9. exposure:
  10. include: '*'

请求接口 GET /actuator/loggers,可以看到如下的返回,可以知道当前日志框架支持哪些级别的日志配置,以及每个 Logger 的级别配置。

  1. {
  2. "levels": [
  3. "OFF",
  4. "FATAL",
  5. "ERROR",
  6. "WARN",
  7. "INFO",
  8. "DEBUG",
  9. "TRACE"
  10. ],
  11. "loggers": {
  12. "ROOT": {
  13. "configuredLevel": "WARN",
  14. "effectiveLevel": "WARN"
  15. },
  16. "org.mybatis": {
  17. "configuredLevel": "ERROR",
  18. "effectiveLevel": "ERROR"
  19. }
  20. },
  21. "groups": {
  22. }
  23. }

如果我们想增加或者修改某一 Logger 的配置,可以通过 POST /actuator/loggers/自定义logger名称,Body 为:

  1. {
  2. "configuredLevel": "WARN"
  3. }

我们这一节详细分析了我们微服务框架中日志相关的各种配置,包括基础配置,链路追踪实现与配置以及如果没有链路追踪信息时候的解决办法,并且针对一些影响性能的核心配置做了详细说明。然后针对日志的 RingBuffer 监控做了个性化定制,并且说明了通过 actuator 查看并动态修改日志配置。下一节我们将会开始分析基于 spring-mvc 同步微服务使用的 web 容器 - Undertow。

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer

SpringCloud升级之路2020.0.x版-11.Log4j2 监控相关的更多相关文章

  1. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  2. SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 接下来,将进入我们升级之路的又一大模块,即网关模块.网关模块我们废弃了已经进入维护状态的 ...

  3. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  4. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  5. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

  6. SpringCloud升级之路2020.0.x版-31. FeignClient 实现断路器以及线程隔离限流的思路

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们实现了 FeignClient 粘合 resilience4j 的 Ret ...

  7. SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...

  8. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...

  9. SpringCloud升级之路2020.0.x版-2.微服务框架需要考虑的问题

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 上图中演示了一 ...

随机推荐

  1. 1、mysql基础入门(1)

    1.mysql基础入门: 1.1.数据库介绍:

  2. gitlab找回管理员密码

    1.登陆后台服务器,切换git用户 su - git 2.登录GitLab的Rails控制台 gitlab-rails console production 另一种 切换root账户 执行:  git ...

  3. bugku crypto wp上半部分汇总

    1.滴答~滴 摩斯码,在线解开. 2. 栅栏密码,在线解就出flag了. 3. Ook解密,由.?!Ook组成密文,在线网站解密 4.这不是摩斯密码 有点像jsfuck,发现又不是,因为不会出现大于号 ...

  4. Jmeter监控服务器CPU,Memory,Disk,Network性能指标

    本文主要说一下如何通过JMeter插件来监控服务器CPU.内存.磁盘.网络等相关资源. 一.下载 第一种方案: 首先进入网址https://jmeter-plugins.org/downloads/o ...

  5. 阿里云低延时直播 RTS 能力升级 让直播推流效果更佳

    行业背景 直播技术飞速发展让各个行业的用户体验呈现多样化和个性化,不同业务场景下创新实践满足大众对于音视频互动体验和参与的高标准要求.历经2020年初的巨变之后,以视频.游戏.电商.教育为主的互联网经 ...

  6. 关于kong | API Gateway

    目录 为什么需要 API 网关(more) kong的概念 为什么使用Kong Kong 的管理方式 高可扩展性的背后-插件机制 [前言]: Kong是一个云原生,高效,可扩展的分布式 API 网关. ...

  7. python 图中找目标并截图

    import numpy as npdef sjjt(xha,sjh,beitu,jl,xx,yy): #检查目标,并将目标指定范围内截图 pull_screenshot(xha,sjh,xx) #p ...

  8. 基于FPGA的图像镜像

    图像镜像,一种较为常见的图像处理操作,分为水平镜像.垂直镜像.对角镜像.水平镜像即处理后的图像与原图像关于垂直线对称,垂直镜像为处理后的图像与 原图像关于水平线对称,对角镜像则关于对角线对称. 关于低 ...

  9. W: GPG 错误:http://mirrors.aliyun.com xenial/mongodb-org/3.2 Release: 由于没有公钥,无法验证下列签名: NO_PUBKEY D68FA50FEA312927

    更新错误: 正在读取软件包列表... 完成 W: GPG 错误:http://mirrors.aliyun.com xenial/mongodb-org/3.2 Release: 由于没有公钥,无法验 ...

  10. 【LeetCode】496.下一个更大元素I

    496.下一个更大元素I 知识点:栈:HashMap: 题目描述 给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集. 请你找出 nums1 中每个元 ...