首页 > 其他分享 >动态修改日志级别,太有用了!

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

时间:2023-08-25 10:37:21浏览次数:38  
标签:logging log4j 有用 apache org 日志 级别 Logger

背景

我们在系统中一般都会打印一些日志,并且在开发、测试、生产各个环境中的日志级别可能不一样。在开发过程中为了方便调试打印了很多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"}'

最后,如果你觉得这篇文章有用,动动你的小手点个赞吧

标签:logging,log4j,有用,apache,org,日志,级别,Logger
From: https://www.cnblogs.com/ibigboy/p/17656193.html

相关文章

  • 日志异步工作器的实现
    日志异步工作器的实现/*实现异步工作器*/#ifndef__M_LOOPER_H__#define__M_LOOPER_H__#include<mutex>#include<thread>#include<condition_variable>//条件变量#include"buffer.hpp"#include<functional>#include<memory>namespacen......
  • docker 清除日志文件
    查看日志大小:在linux系统中创建docker_logs.sh文件,输入sh docker_logs.sh命令查看日志文件大小/var/lib/docker/containers为docker默认日志输出路径echo"========dockercontainerslogsfilesize========"logs=$(find/var/lib/docker/containers/-name*-json.log)......
  • 大学生创新训练项目开发日志 (8-14 ~ 8-24)
    技术路线利用Xposed模块在应用加载app包时将获取到的软件资源打上包名标记后上传到内网服务器。实现流程初次尝试实现IXposedHookLoadPackage的handleLoadPackage方法。publicvoidhandleLoadPackage(finalLoadPackageParamlpparam)throwsThrowable{XposedB......
  • filebeat 配置采集nginx 日志
    filebeat配置nginx日志采集filebeat采集需求1.需要将以往30天的日志输出到es,并且以时间按天展示2.将不同的时间字段解析出来,输出到esnginx配置json日志log_formatlog_json'{"remoteAddr":"$clientRealIp",''"date_timeLocal":"$time_local",......
  • allure报告中firefox信息不展示(多浏览器或多线程执行时只显示一个浏览器的日志)
    安装的allure-pytest的版本要是2.11.1之前的(之后的版本有test_result.historyId,但位置及方法不一样,我目前没研究怎么修改)在依赖包Lib--site-packages--allure_pytest--listener.py文件里修改test_result.historyId=md5(item.nodeid)变成test_result.historyId=md5(item.nod......
  • mysql使用sql开启日志
    --查看日志是否开启和日志文件夹showvariableslike'%general%';SETGLOBALgeneral_log='On';setgloballog_syslog=on;--慢sql日志setglobalslow_query_log=on;setglobalsql_log_off=on;--设置日志生成道的文件夹SETGLOBALgeneral_log_file='/lo......
  • 【Oracle RAC Database】Oracle Grid Infrastructure 启动流程与日志
    OS启动OHASD(OracleHighAvailabilityServices)init.ohasd.run被启动,该进程负责启动ohasd.bin守护进程[root@node01~]#ps-ef|grepohasd|grep-vgreproot5151018:59?00:00:00/bin/sh/etc/init.d/init.ohasdrun>/dev/null2>&1</dev/......
  • IIS日志分析
    https://learn.microsoft.com/zh-cn/archive/blogs/exchange_chs/log-parser-studioIIS日志——统计IP访问次数的一种方法使用LogParser对IIS服务器被Hit访问的IP进行次数统计,方便结合防火墙IP***列表对IIS网站进行日志审计报表的编写配置IIS网站的日志下载进行日志分析的两个工具L......
  • 企业网络日志安全与 EventLog Analyzer
    企业的网络日志安全是一项至关重要的任务。随着信息技术的迅猛发展,网络攻击和数据泄露的威胁也与日俱增。为了应对这些威胁,企业需要强大的工具来监控、分析和保护其网络日志。而ManageEngine的EventLogAnalyzer正是这样一款卓越的解决方案。网络日志安全一、为什么网络日志安全如......
  • Ceph存储日志收集、过滤和分析
    一、方案简述存储服务组件众多,且容器化多服务实例部署后,日志分散,需要聚合分析,使用filebeat来收集节点系统日志、Ceph守护进程实例日志和容器日志,推送至ELK集群集中过滤、转换和分析,提高故障排查效率。二、方案架构图三、测试环境部署1、部署单节点ES容器化部署脚本:......