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

news2024/12/24 2:42:52

首发于公众号: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"}'

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/916874.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设备管理系统是什么?的修设备管理系统有什么功能?

随着计算机技术的迅速发展和移动互联网的商业化和社会化应用&#xff0c;设备的种类和数量急剧增加。如何利用先进的网络技术和快速更新的计算机设备来有效地收集和处理设备信息&#xff0c;建立以信息化为核心的管理体系&#xff0c;减轻管理和业务人员的数据处理负担&#xf…

影像组学文章复现:ITHscore:通过多尺度放射学特征全面定量NSCLC肿瘤内异质性 ----小细胞肺癌肿瘤异质性评分

目的:通过计算机断层扫描(CT)图像量化非小细胞肺癌(NSCLC)的肿瘤内异质性(ITH)。 方法:整合局部辐射特征和全局像素分布模式,开发定量ITH测量-ITHscore。在六个患者队列(n = 1399)中检查ITHscore与肿瘤表型,基因型和患者预后的关联,以验证其在表征ITH方面的有效性…

【QT】绘制旋转等待

很高兴在雪易的CSDN遇见你 ,给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 程序中经常会遇到耗时的操作,需要提供等待的窗口,防止用户多次点击造成卡顿等问题。本文分享旋转等待技术,希望对各位小伙伴有所帮助!结果如下:

长胜证券:越南首富,又火了!旗下汽车股市值盘中超越比亚迪!

当地时刻8月22日&#xff0c;美股三大股指涨跌纷歧&#xff0c;其中&#xff0c;道指跌0.51%&#xff0c;标普500指数跌0.28%&#xff0c;纳斯达克指数涨0.06%。 异动股方面&#xff0c;8月22日周二&#xff0c;越南电动轿车出产商VinFast Auto ADR盘中上涨超越167%&#xff0c…

uniapp日期选择组件优化

<uni-forms-item label="出生年月" name="birthDate"><view style="display: flex;flex-direction: row;align-items: center;height: 100%;"><view class="" v-

九龙湖街道社工站 开展家长交流互助社群亲子情景剧体验活动

为丰富辖区内小朋友们的暑期生活&#xff0c;促进亲子交流&#xff0c;8月20日&#xff0c;在红谷滩区民政局的领导下&#xff0c;九龙湖街道社工站链接赣红孵红石榴志愿服务队&#xff0c;以“家长交流互助社群”为依托&#xff0c;组织辖区内家庭到小哆哆沉浸式儿童剧场开展情…

Git企业开发控制理论和实操-从入门到深入(一)|为什么需要Git|Git的安装

前言 那么这里博主先安利一些干货满满的专栏了&#xff01; 首先是博主的高质量博客的汇总&#xff0c;这个专栏里面的博客&#xff0c;都是博主最最用心写的一部分&#xff0c;干货满满&#xff0c;希望对大家有帮助。 高质量博客汇总https://blog.csdn.net/yu_cblog/cate…

【笔记】MySQL行转列函数

GROUP_CONCAT()函数 创建表person_info&#xff0c;并插入数据 CREATE TABLE person_info (id bigint(20) NOT NULL AUTO_INCREMENT,name varchar(100) DEFAULT NULL,family varchar(100) DEFAULT NULL,PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT8 DEFAULT CHARSETutf8;…

使用opencv-python在图片上显示中文

测试图像如下&#xff1a; 核心代码如下&#xff1a; import cv2 import numpy as np from PIL import Image, ImageDraw, ImageFontdef cv2ImgAddText(img, text, left, top, textColor(0, 255, 0), textSize20):if (isinstance(img, np.ndarray)): #判断是否OpenCV图片类型…

如何获取微软商店应用的appx程序包,并实现离线安装

我们以“NVIDIA Control Panel”为例 &#xff08;1&#xff09;先在网页版Microsoft应用商店(https://www.microsoft.com/zh-cn/store/apps/?rtc1)这里找到NVIDIA Control Panel&#xff0c;将网页链接地址复制下&#xff1b; &#xff08;2&#xff09;到 https://store.r…

恒运资本:沪指震荡跌0.55%坚守3100点,券商等板块走低,数据要素概念再活跃

23日早盘&#xff0c;两市股指低开低走&#xff0c;沪指盘中再次失守3100点&#xff0c;深成指、创业板指跌幅均超1%&#xff1b;北向资金连续流出态势&#xff0c;半日净卖出超70亿元。 截至午间收盘&#xff0c;沪指跌0.55%报3103.1点&#xff0c;深成指跌1.08%&#xff0c;创…

解决 go mod tidy 加载模块超时

如果go mod tidy 加载模块超时 解决方法 修改GOPROXY: 查看go环境相关信息&#xff1a; go envgo env -w GOPROXYhttps://goproxy.cn

fdm-cli,一个致力于管理项目初始化模板的工具脚手架

希望各位可以了解fdm-cli&#xff0c;并在合适的时候尝试使用一下。 阅读本文你将获得 一个&#xff08;好用的&#xff09;项目模板初始化工具这个&#xff08;好用的&#xff09;工具的使用方法给作者点一个 star 的机会 一、不愿再复制粘贴 小N每次在写项目的时候&#x…

第三届“赣政杯”总决赛 | 赛宁筑牢党政机关安全屏障

为提高江西省党政机关网络安全意识&#xff0c;普及网络安全知识&#xff0c;提升网络安全防护能力&#xff0c;筑牢网络安全屏障&#xff0c;由江西省委网信办、江西省发展改革委主办&#xff0c;江西省大数据中心、国家计算机网络与信息安全管理中心江西分中心承办&#xff0…

Java“牵手”虾皮商品列表数据,关键词搜索虾皮(Shopee)商品数据接口,虾皮API申请指南

虾皮&#xff08;SHOPEE&#xff09;商城是一个网上批发购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取虾皮商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问虾皮商城的网页来获取商品详情…

【Unity3D赛车游戏】【二】如何制作一个真实模拟的汽车

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

程序员35岁的破局之道

微信公众号访问地址&#xff1a;程序员35岁的破局之道 近期热推文章&#xff1a; 1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表; 2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据; 3、基于Redis的Geo实现附近商铺搜索(含源码) 4、基…

用Cmake build OpenCV后,在VS中查看OpenCV源码的方法(环境VS2022+openCV4.8.0) Part II

用Cmake build OpenCV后&#xff0c;在VS中查看OpenCV源码的方法 Part II 如何下载和安装openCV和Cmake可以看这篇文章。 用Cmake build OpenCV后&#xff0c;在VS中查看OpenCV源码的方法&#xff08;环境VS2022openCV4.8.0&#xff09; Part I_松下J27的博客-CSDN博客 下面我…

网络安全(黑客)入门

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全…

1782. 统计点对的数目

给你一个无向图&#xff0c;无向图由整数 n &#xff0c;表示图中节点的数目&#xff0c;和 edges 组成&#xff0c;其中 edges[i] [ui, vi] 表示 ui 和 vi 之间有一条无向边。同时给你一个代表查询的整数数组 queries 。 第 j 个查询的答案是满足如下条件的点对 (a, b) 的数…