动态修改日志级别,太有用了!

2023-11-20

首发于公众号:BiggerBoy

背景

我们在系统中一般都会打印一些日志,并且在开发、测试、生产各个环境中的日志级别可能不一样。在开发过程中为了方便调试打印了很多debug日志,但是生产环境为了性能,为了节约存储资源,我们会将日志级别设置为info或error较高的级别,只保留一些关键的必要的日志。

当线上出现问题需要排查时,最有效的方式是分析系统日志。此时因为线上环境日志级别较高,对排查问题有一定的阻碍,为了快速响应线上问题,我们需要更全面的日志帮助排查问题,传统的做法是修改日志级别重启项目。

目标

为了兼顾性能和快速响应线上问题,实现不重启项目的前提下动态修改日志级别。通过使用该功能,可以在需要解决线上问题时,实时调整线上日志输出级别,获取全面的Debug日志,帮助工程师提高定位问题的效率。

技术方案

本文列举了几种实现方案,已经验证可用,供大家参考。

方案一、LoggingSystem

在Spring Boot项目中可以通过LoggingSystem来获取或修改日志配置。

1.1 获取日志Logger配置
通过LoggingSystem API getLoggerConfigurations获取所有Logger配置

List loggerConfigs = loggingSystem.getLoggerConfigurations();
图片

1.2 修改日志级别
通过调用LoggingSystem API setLogLevel设置包或具体Logger的日志级别,修改成功,立即生效。

@Autowired
private LoggingSystem loggingSystem;

@RequestMapping(value = "/changeLogLevel", method = RequestMethod.POST)
public void changeLogLevel(String loggerName, String newLevel) {
    log.info("更新日志级别:{}", newLevel);

    LogLevel level = LogLevel.valueOf(newLevel.toUpperCase());

    loggingSystem.setLogLevel(loggerName, level);
    log.info("更新日志级别:{} 更新完毕", newLevel);
}

方案二、日志框架提供的API

参考美团技术文章:https://tech.meituan.com/2017/02/17/change-log-level.html

想必现在的业务系统基本都是采用SLF4J日志框架吧,在应用初始化时,SLF4J会绑定具体的日志框架,如Log4j、Logback或Log4j2等。具体源码如下(slf4j-api-1.7.7):

private final static void bind() {
  try {
    // 查找classpath下所有的StaticLoggerBinder类。
    Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); 
    reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
    // 每一个slf4j桥接包中都有一个org.slf4j.impl.StaticLoggerBinder类,该类实现了LoggerFactoryBinder接口。
    // the next line does the binding
    StaticLoggerBinder.getSingleton();
    INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
    reportActualBinding(staticLoggerBinderPathSet);
    fixSubstitutedLoggers();
    ...
}

findPossibleStaticLoggerBinderPathSet方法用来查找当前classpath下所有的org.slf4j.impl.StaticLoggerBinder类。每一个slf4j桥接包中都有一个StaticLoggerBinder类,该类实现了LoggerFactoryBinder接口。具体绑定到哪一个日志框架则取决于类加载顺序。

动态调整日志级别具体实现步骤如下:

2.1 初始化
确定所使用的日志框架,获取配置文件中所有的Logger内存实例,并将它们的引用缓存到Map容器中。

String type = StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr();
if (LogConstant.LOG4J_LOGGER_FACTORY.equals(type)) {
    logFrameworkType = LogFrameworkType.LOG4J;
    Enumeration enumeration = org.apache.log4j.LogManager.getCurrentLoggers();
    while (enumeration.hasMoreElements()) {
        org.apache.log4j.Logger logger = (org.apache.log4j.Logger) enumeration.nextElement();
        if (logger.getLevel() != null) {
            loggerMap.put(logger.getName(), logger);
        }
    }
    org.apache.log4j.Logger rootLogger = org.apache.log4j.LogManager.getRootLogger();
    loggerMap.put(rootLogger.getName(), rootLogger);
} else if (LogConstant.LOGBACK_LOGGER_FACTORY.equals(type)) {
    logFrameworkType = LogFrameworkType.LOGBACK;
    ch.qos.logback.classic.LoggerContext loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory();
    for (ch.qos.logback.classic.Logger logger : loggerContext.getLoggerList()) {
        if (logger.getLevel() != null) {
            loggerMap.put(logger.getName(), logger);
        }
    }
    ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    loggerMap.put(rootLogger.getName(), rootLogger);
} else if (LogConstant.LOG4J2_LOGGER_FACTORY.equals(type)) {
    logFrameworkType = LogFrameworkType.LOG4J2;
    org.apache.logging.log4j.core.LoggerContext loggerContext = (org.apache.logging.log4j.core.LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
    Map<String, org.apache.logging.log4j.core.config.LoggerConfig> map = loggerContext.getConfiguration().getLoggers();
    for (org.apache.logging.log4j.core.config.LoggerConfig loggerConfig : map.values()) {
        String key = loggerConfig.getName();
        if (StringUtils.isBlank(key)) {
            key = "root";
        }
        loggerMap.put(key, loggerConfig);
    }
} else {
    logFrameworkType = LogFrameworkType.UNKNOWN;
    LOG.error("Log框架无法识别: type={}", type);
}

2.2 获取Logger列表
从本地Map容器取出,封装成包含loggerName、logLevel的对象。

private String getLoggerList() {
    JSONObject result = new JSONObject();
    result.put("logFramework", logFrameworkType);
    JSONArray loggerList = new JSONArray();
    for (ConcurrentMap.Entry<String, Object> entry : loggerMap.entrySet()) {
        JSONObject loggerJSON = new JSONObject();
        loggerJSON.put("loggerName", entry.getKey());
        if (logFrameworkType == LogFrameworkType.LOG4J) {
            org.apache.log4j.Logger targetLogger = (org.apache.log4j.Logger) entry.getValue();
            loggerJSON.put("logLevel", targetLogger.getLevel().toString());
        } else if (logFrameworkType == LogFrameworkType.LOGBACK) {
            ch.qos.logback.classic.Logger targetLogger = (ch.qos.logback.classic.Logger) entry.getValue();
            loggerJSON.put("logLevel", targetLogger.getLevel().toString());
        } else if (logFrameworkType == LogFrameworkType.LOG4J2) {
            org.apache.logging.log4j.core.config.LoggerConfig targetLogger = (org.apache.logging.log4j.core.config.LoggerConfig) entry.getValue();
            loggerJSON.put("logLevel", targetLogger.getLevel().toString());
        } else {
            loggerJSON.put("logLevel", "Logger的类型未知,无法处理!");
        }
        loggerList.add(loggerJSON);
    }
    result.put("loggerList", loggerList);
    LOG.info("getLoggerList: result={}", result.toString());
    return result.toString();
}

结果:

{
    "loggerList": [
        {
            "logLevel": "OFF",
            "loggerName": "org.springframework.ldap"
        },
        {
            "logLevel": "INFO",
            "loggerName": "ROOT"
        },
        {
            "logLevel": "OFF",
            "loggerName": "com.sun.jersey.api.client"
        },
        {
            "logLevel": "OFF",
            "loggerName": "com.netflix.discovery"
        }
    ],
    "logFramework": "LOGBACK"
}

2.3 修改日志级别
通过调用具体的日志框架提供的API setLevel修改Logger日志级别,修改成功,立即生效。

private String setLogLevel(JSONArray data) {
    LOG.info("setLogLevel: data={}", data);
    List<LoggerBean> loggerList = parseJsonData(data);
    if (CollectionUtils.isEmpty(loggerList)) {
        return "";
    }
    for (LoggerBean loggerbean : loggerList) {
        Object logger = loggerMap.get(loggerbean.getName());
        if (logger == null) {
            throw new RuntimeException("需要修改日志级别的Logger不存在");
        }
        if (logFrameworkType == LogFrameworkType.LOG4J) {
            org.apache.log4j.Logger targetLogger = (org.apache.log4j.Logger) logger;
            org.apache.log4j.Level targetLevel = org.apache.log4j.Level.toLevel(loggerbean.getLevel());
            targetLogger.setLevel(targetLevel);
        } else if (logFrameworkType == LogFrameworkType.LOGBACK) {
            ch.qos.logback.classic.Logger targetLogger = (ch.qos.logback.classic.Logger) logger;
            ch.qos.logback.classic.Level targetLevel = ch.qos.logback.classic.Level.toLevel(loggerbean.getLevel());
            targetLogger.setLevel(targetLevel);
        } else if (logFrameworkType == LogFrameworkType.LOG4J2) {
            org.apache.logging.log4j.core.config.LoggerConfig loggerConfig = (org.apache.logging.log4j.core.config.LoggerConfig) logger;
            org.apache.logging.log4j.Level targetLevel = org.apache.logging.log4j.Level.toLevel(loggerbean.getLevel());
            loggerConfig.setLevel(targetLevel);
            org.apache.logging.log4j.core.LoggerContext ctx = (org.apache.logging.log4j.core.LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
            ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig.
        } else {
            throw new RuntimeException("Logger的类型未知,无法处理!");
        }
    }
    return "success";
}

方案三、spring-boot-starter-actuator

3.1 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3.2 开启日志端点配置

# 由于Spring Boot 2.x默认只暴露 /health 以及 /info 端点,
# 而日志控制需要用到 /loggers 端点,故而需要设置将其暴露。当然把loggers替换成*也是可以的;开启所有!
management:
  endpoints:
    web:
      exposure:
        include: 'loggers'

可以通过访问URL/actuator/loggers/后加包名或者类名来查询指定包或者类的当前日志级别。

curl http://127.0.0.1:8007/manage/actuator/loggers/com.trrt.ep
 {"configuredLevel":"DEBUG","effectiveLevel":"DEBUG"}

3.3 查看所有Logger
http://127.0.0.1:8007/manage/actuator/loggers
在这里插入图片描述

3.4 修改日志级别
可以通过访问URL/actuator/loggers/后加包名或者类名来修改指定包或者类的当前日志级别。

curl -X POST "http://127.0.0.1:8007/manage/actuator/loggers/com.trrt.ep" -H "Content-Type: application/json;charset=UTF-8" --data '{"configuredLevel":"debug"}'

最后,如果你觉得这篇文章有用,辛苦转发给你的小伙伴吧~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态修改日志级别,太有用了! 的相关文章

  • 数据预处理-独热编码(One-Hot)

    1 部分特征如人的性别有男女 国家有中国 美国 法国等 并不是连续的 而是离散的 无序的 通常我们需要对其进行特征数字化 2 假如某个样本 某个人 他的特征是这样的 男 中国 乒乓球 我们可以用 0 0 4 来表示 但是这样的特征处理并不能
  • BeautifulSoup基本用法总结

    BeautifulSoup是Python的一个库 最主要的功能就是从网页爬取我们需要的数据 BeautifulSoup将html解析为对象进行处理 全部页面转变为字典或者数组 相对于正则表达式的方式 可以大大简化处理过程 0x01 安装 建
  • Leetcode 106. 从中序与后序遍历序列构造二叉树

    文章目录 题目 代码 9 18 首刷自解 题目 Leetcode 106 从中序与后序遍历序列构造二叉树 代码 9 18 首刷自解 class Solution public unordered map
  • UE4文字显示乱码“字字字字字字字字”的解决办法

    键盘win R 搜索fonts 2 滑到最底下右键复制 宋体常规简体字 3 复制到ue4项目的字体文件夹中 如下 注意在外部文件处复制 4 回到项目界面 此时右下角会有个弹窗提示是否确认导入 点击导入 然后会弹一个 字体样式导入选项 弹框
  • openGauss学习笔记-63 openGauss 数据库管理-资源池化架构

    文章目录 openGauss学习笔记 63 openGauss 数据库管理 资源池化架构 openGauss学习笔记 63 openGauss 数据库管理 资源池化架构 本文档主要介绍资源池化架构下的一些最佳实践和使用注意事项 用于支撑对相
  • go Cobra命令行工具入门

    简介 Github https github com spf13 cobra Star 26 5K Cobra是一个用Go语言实现的命令行工具 并且现在正在被很多项目使用 例如 Kubernetes Hugo和Github CLI等 通过使
  • Failed to set locale, defaulting to C.UTF-8解决方法

    CentOS 8Linux系统提示 Failed to set locale defaulting to C UTF 8 这是由于没有配置正确的语言环境导致的 Linux百科 使用root账户登录你的CentOS操作系统 然后执行两条命令
  • 现阶段计算机网络技术专业人才培养的发展对策

    确立具有高职特色的人才培养目标 在市场经济的条件下 人才培养首先要适应市场需求 以市场行业的需求为导 向制定人才培养目标 学校人才培养是否能满足社会需求 可以通过学生在对口行 业及相关领域的就业情况来衡量 高职教育培养高技能应用型人才 与研
  • Objective-C中的封装、继承、多态、分类

    封装 封装最好理解了 封装是面向对象的特征之一 是对象和类概念的主要特性 封装 也就是把客观事物封装成抽象的类 并且类可以把自己的数据和方法只让可信的类或者对象操作 对不可信的进行信息隐藏 继承 面向对象编程 OOP 语言的一个主要功能就是
  • 测试工具73款

    我们将本文的软件测试工具分为4类 1 Web应用测试工具 2 网站安全测试工具 3 跨浏览器测试工具 4 移动应用测试工具 注 工具排名没有任何意义 1 Web应用测试工具 我们列出了一些在Web应用程序上执行性能 负载和压力测试的关键工具
  • 开源项目MiniWord .NET Word 操作由Word模板和数据简单、快速生成文件

    MiniWord NET Word 介绍 MiniWord NET Word模板引擎 藉由Word模板和数据简单 快速生成文件 image Getting Started 安装 nuget link https www nuget org
  • ubuntu18.04命令安装ros2

    ROS2官方文档 本教程为apt get命令安装方式 官网教程有点问题 借鉴一下大佬的安装方式 文章目录 1 安装ROS2 1 1 安装秘钥相关指令 1 2 授权秘钥 1 3 添加ROS2软件源 1 4 安装 2 设置环境 可选但是推荐 2
  • vue路由器学习(个人学习笔记四)

    目录 友情提醒 第一章 路由简介 1 1 什么是路由 1 2 安装路由插件 第二章 自定义路由器 2 1 创建路由器文件index js文件 2 2 index js文件中配置路由信息 第三章 使用路由器 3 1 在main js文件中将路
  • 基于Item的协同过滤算法实践(最简单的在线电影推荐系统)

    上一篇文章 基于用户的协同过滤算法实践 中 基于用户的相似度生成推荐列表 本文将基于Item的相似度阐述 1 相似度 基于物品的协同过滤算法 简称ItemCF 给用户推荐那些和他们之前喜欢的物品相似的物品 不过ItemCF不是利用物品的内容
  • 用户权限数据转换为用户组列表(3/3) - Excel PY公式

    最近Excel圈里的大事情就是微软把PY塞进了Excel单元格 可以作为公式使用 轻松用PY做数据分析 系好安全带 老司机带你玩一把 实例需求 如下是AD用户的列表 每个用户拥有该应用程序的只读或读写权限 现在需要创建新的AD用户组 并根据
  • Source Insight4.0的安装以及配置

    安装source insight4 工具的动机 1 公司需求 2 source insight4 0工具是集开发快速以及界面美观和方便等多种优点于一个软件的编辑器 1 需要准备资料 source insight4 0的安装包 以及安装的过程
  • iOS内存详解

    堆栈 iOS内存条中有一部分是只读的 有一部分是可读可写的 我们操作的是可读可写部分 那么在这块内存当中 我们怎么划分堆和栈呢 我们可以限定死堆栈的内存空间 但是这样显然是不好的 那么可以使用相对弹性的空间 一个从上往下扩展 一个从下往上扩
  • Arthas阿里 阿尔萨斯诊断工具的学习

    以下所有内容基于Arthas的3 6 9版本 一 Arthas 基础 背景 线上诊断问题比较难复现 DEBUG等 都很痛苦 功能好处 通过JVM开放出来接口 代理功能 对JVM访问 获取JVM内存 线程 类 方法 变量等各种操作函数 并控制

随机推荐

  • Mysql数据库连接池的简单实现(基于C++11), 基础学完, 包教包会.

    项目技术点 C语言进行MYSQL数据库编程 无锁单例 基于STL队列加C 11新特性保证线程安全实现的生产者消费者模型 C 11多线程编程 线程间同步与互斥 基于CAS的原子整形 lambda表达式 shared ptr智能指针管理Conn
  • Pipenv:作为 Python 开发人员为什么应该使用它

    Pipenv 是一个旨在将所有打包世界中最好的东西带到 Python 世界的工具 它将 Pipfile pip 和 virtualenv 整合到一个命令中 它会自动为您的项目创建和管理虚拟环境 并在您安装 卸载包时从您的 Pipfile 添
  • 大清相国 -陈廷敬

    以前都没听说过这个人 读了读 原来是康熙时的重臣 一开始 太完美了 为民做主 不爱权 不爱财 后来 明白不能让皇帝太难堪 不能让天下百姓知道居然这么多贪官 委屈求全 最后一举将高士奇等一起干掉 狠字当头 最后 装傻退出朝廷 在家养老 一句话
  • Kubernetes笔记(六):了解控制器 —— Deployment

    Pod 容器组 是 Kubernetes 中最小的调度单元 可以通过 yaml 定义文件直接创建一个 Pod 但 Pod 本身并不具备自我恢复 self healing 功能 如果一个 Pod 所在的节点出现故障 或者调度程序自身出现问题
  • C++---区间DP---加分二叉树(每日一道算法2023.4.28)

    题目 设一个 n 个节点的二叉树 tree 的中序遍历为 1 2 3 n 其中数字 1 2 3 n 为节点编号 每个节点都有一个分数 均为正整数 记第 i 个节点的分数为 di tree 及它的每个子树都有一个加分 任一棵子树 subtre
  • hello,os

    于渊的书确实不错 先看第一章 出了个hello os 用虚拟机搞的 引导区
  • C# 如何将SPL文件转换成EMF文件

    本文主要讲述如何将SPL文件转换成EMF文件 目录 一 什么是SPL文件和EMF文件 一 SPL文件 二 EMF文件 二 文件解析 一 SPL格式 二 打开SPL文件 三 解析SPL文件 三 编程思路 一 记录EMF文件的位置和大小 二 找
  • JS对象的深复制

    JS实现对象的深复制 function cloneObject source target var list number string boolean undefined null function if target undefined
  • 区块链的安全性与去中心化特点:深入探讨区块链技术的安全性和去中心化特点

    摘要 本文将深入探讨区块链技术的两个核心特点 安全性和去中心化 区块链作为一种分布式账本技术 通过其独特的安全性和去中心化特点 在许多领域引起了广泛关注 我们将分析区块链的安全性原理和其与去中心化的关系 以及区块链技术在保护数据安全和提供信
  • 【Web常规漏洞】SSRF服务端请求伪造漏洞

    文章目录 参考 概念 产生原因 可能存在漏洞的代码 漏洞分类 潜在危害 漏洞利用 漏洞防御 漏洞绕过 概念 SSRF Server Side Request Forgery 服务器端请求伪造 是一种利用漏洞伪造服务器端发起请求 一般情况下
  • windows服务器被当矿机的问题处理实战-conhosts.exe

    windows服务器被当矿机的问题处理实战 conhosts exe 服务器最近比较卡 调开任务管理器查看 CPU占用偏高 发现进程 conhosts exe 占用CPU 75 通过pid查询 该进程通过syn sent向陌生IP 163
  • STM32G30C8T6hal库串口非固定长度

    1 由于从标准库转到hal库 还是特别不适应 串口测试遇到了一下问题 记录一下 2 hal库串口的配置不再赘述 hal库串口接收完毕可调用回调函数 接收的字节为固定长度才会回调 感觉非常麻烦 而且要重新开启接收中断 特别不适用于项目 想按照
  • 最新网络工程毕设选题题目推荐

    文章目录 0 简介 1 如何选题 2 最新网络工程选题 2 1 Java web SSM 系统 2 2 大数据方向 2 3 人工智能方向 2 4 其他方向 4 最后 0 简介 学长搜集分享最新的网络工程专业毕设毕设选题 难度适中 适合作为毕
  • python爬虫,多线程与生产者消费者模式

    使用队列完成生产者消费者模式 使用类创建多线程提高爬虫速度 https sc chinaz com tupian index html https sc chinaz com tupian index 2 html https sc chi
  • elasticsearch 安装教程

    一 jdk安装 es要求jdk版本在1 8以上 所以先安装jdk1 8 安装步骤 1 安装完Centos6 5的Base Server版会默认安装OpenJDK 首先需要删除OpenJDK 命令 rpm qa grep java 显示如下
  • 头条员工自爆:拿遍BAT和TMD的offer,面试过于NB!

    最近看到一位今日头条员工在脉脉发帖称 最近两次找工作 BAT TMD的offer几乎拿了个遍 但一般一家只能待两年 原因是面试的时候表现过于NB 导致下家对自己期望值过高 实际工作中面临的阻力很大的时候就会退缩 自己的能力项可能是面试 每次
  • Android 网络管理

    系统中对网络的判断和选在是在Connectivityervice这个服务中来处理的 在系统启动的时候会启动这个系统服务 系统启动完毕后 ConnectivityService在系统启动的时候就启动了 在android内部 用framewor
  • 如何学好C语言的数据结构与算法?

    C语言的数据结构与算法 难就难在链表 学会了链表 可能后面就一点都不难了 书籍推荐 数据结构与算法分析 C语言描述版 要深入学习的话可以选择这本书 因为针对链表的讲解是比较详细的 所以可以很快理解链表 跟着书上一点点实现基本操作 增删改查
  • Vue中的过滤器

    过滤器 定义 对要显示的数据进行特定格式化后再显示 适用于一些简单逻辑的处理 语法 1 注册过滤器 Vue filter name callback 全局 或 new Vue filters 局部 2 使用过滤器 xxx 过滤器名 或 v
  • 动态修改日志级别,太有用了!

    首发于公众号 BiggerBoy 背景 我们在系统中一般都会打印一些日志 并且在开发 测试 生产各个环境中的日志级别可能不一样 在开发过程中为了方便调试打印了很多debug日志 但是生产环境为了性能 为了节约存储资源 我们会将日志级别设置为