日志级别动态调整——小工具解决大问题

随着外卖业务的快速发展,业务复杂度不断增加,线上系统环境有任何细小波动,对整个外卖业务都可能产生巨大的影响,甚至形成灾难性的雪崩效应,造成巨大的经济损失。每一次客诉、系统抖动等都是对技术人员的重大考验,我们必须立即响应,快速解决问题。

如何提高排查问题的效率呢?最有效的方式是通过分析系统日志。如果系统日志全面,会为我们排查解决线上问题带来绝大的帮助,但是要想保证系统日志全面,就必须打印出所有的系统或业务日志。这样就会带来另一个问题,那就是日志量的暴涨,过多的日志除了能够帮助我们解决问题外,同时会直接造成系统性能下降,极端情况下,甚至导致系统宕机。在这种背景下,为了兼顾性能和快速响应线上问题,我们设计开发了日志级别动态调整组件。通过使用该组件,可以在需要解决线上问题时,实时调整线上日志输出级别,获取全面的Debug日志,帮助工程师提高定位问题的效率。

使用场景

场景一

业务依赖复杂。某一时刻,依赖的下游服务故障,导致请求大量超时,尤其是像外卖这种集中性特别明显的业务,平均每秒QPS在8000以上,1分钟的故障就会集中产生大量的错误日志,导致磁盘IO急剧提高,耗费大量CPU,进而导致整个服务瘫痪。如果该业务不能立即降级,怎么办?

从代码级别解决问题到发版上线,暂且不说流程长、操作麻烦,同时还存在引入其它故障的高风险。如果系统恰好使用Log4j版本,在极短时间内打印出了海量错误日志,会快速耗尽Buffer区内存,从而拖慢主线程,造成服务性能整体下降,甚至还没有来得及修复问题,海量日志已经拖垮服务,造成服务宕机,损失惨重。

场景二

大量的订单、结算等客诉问题反馈过来,一线工程师大量精力埋没于排查问题中,而排查定位问题的最终手段仍然是依赖线上日志。由于链路较长,任一日志的缺失,都给问题的排查带来极大的障碍,面对运营的催促,怎么办?

工程师为了以后排查问题的方便,在任一可能出现异常的地方,都会打印出关键日志,然后发版上线。但好不容易解决了本次问题,还没来得及收获喜悦,就又面临着一个新问题,那就是场景三。

场景三

由于线上业务系统默认日志打印级别是INFO级别,为了排查问题方便,调试型日志都以该级别打印出来。这样的话给系统带来了额外的负担,在高峰期大量调试日志时会拖慢系统性能,增大出故障的风险,怎么办?

一方面要快速响应业务,另一方面要兼顾系统性能,能不能两方面兼顾?我们的动态调整日志级别工具正是为了解决这种痛点。

能解决哪些问题

  1. 日志降级。 兼容Log4j、Log4j2和Logback主流日志框架,如果遇到场景一,可以通过我们的日志工具,快速调整日志输出级别,降低系统日志的输出,从而达到日志降级的效果,同时能够给RD争取充裕的排查问题时间。
  2. 规范日志级别滥用,帮助工程师快速定位解决线上问题。 使用日志级别动态调整组件,可以实时动态调整线上服务的日志打印级别,调试型日志可以使用低级别打印出,减轻线上服务的负载压力。遇到排查问题时,可以临时将日志级别调低,快速得到精准化的日志信息,排查解决问题。

日志级别动态调整组件定位为中间件,在设计之初重点考虑了以下几点: 1. 低侵入性

  • 接入服务仅需要引入JAR包和XML配置文件即可,不存在额外编码工作,业务耦合低、接入成本小。
  1. 安全可靠

    • 更改接入服务的日志输出级别,只能通过我们提供的管理系统,所有的操作记录有迹可查。
    • 引入权限认证,确保工程师只能操作自己负责的服务或系统,同时会把操作内容实时周知给系统的所有相关责任人,避免误伤。
  2. 可视化操作

    • 操作者可以通过我们提供的管理页面,定向修改一个或一批服务节点。
    • 提供可视化的操控开关,可以随时关闭或开启服务。

sys-inf

调用组件

本组件采用工厂模式实现,保障其高可扩展性。目前已实现日志级别动态调整和方法调用处理单元,下面主要介绍日志级别动态调整处理单元的实现。 dynamic-invoker

目前美团外卖业务系统基本统一采用的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 bindingStaticLoggerBinder.getSingleton();INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;reportActualBinding(staticLoggerBinderPathSet);fixSubstitutedLoggers();...
}

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

接下来,咱们分三部分,来说说ChangeLogLevelProcessUnit类:

  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. 获取Logger列表:从本地Map容器取出。

    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();
    }
    
  3. 修改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";
    }
    

上面介绍了如何拿到日志配置文件中的Logger,以及修改Logger的级别。

通信方式

我们根据Web项目和纯粹RPC项目,分别提供HTTP和Thrift两种通信协议。

场景一、Thrift服务

所有的请求信息都包含在JSON String的数据结构里面,其中包含有签名信息,请求时签名验证失败将直接抛出异常。

引入组件提供的dynamic-invoker.xml配置,将会在系统中自动注入开启一个专为日志级别调整的接口服务,该接口是一个单纯的Thrift服务,能够通过ZooKeeper实现服务注册与发现,并且有可视化的开启与关闭管理后台,简单明了,操作方便。

场景二、HTTP服务

对于一些Web项目,暴露一个RPC服务相当不安全。为此,我们提供了HTTP协议接口,接入流程完全一样,在真正修改日志输出级别时,会根据系统类型自主判断使用哪种协议,有独立实现的签名认证,安全可靠。

从2016年9月V1.0版本上线以来,陆续接入外卖配送的20多个核心应用,覆盖推送、接单、配送调度、斑马配送、活动等核心交易服务。

举例:

  • 问题描述:发配送服务化项目由于间接依赖,引入了Logback日志框架。在项目启动加载时,SLF4J动态绑定到Logback框架上,但是由于发配送项目使用的Log4j,并未配置Logback.xml文件,导致在打印日志时,SLF4J无法匹配到具体的日志配置,从而为项目自动创建了一个日志级别为Debug的ROOT节点,所有的日志以该级别打印输出,导致发配送服务化项目在中午11:30左右高峰期,短时间内打印过多的系统日志,引起Load飙高,重新修改发版上线已经来不及,如果不能立即解决,势必造成服务化宕机,损失非常严重。

  • 处理结果:使用我们这个日志工具,批量将服务化项目所有的日志输出级别调整为ERROR级别,大大减少了日志量的输出,给工程师留出充裕的的时间完美的解决了该问题,避免造成更大的系统故障。

  • 后记:更重要的是以该工具组件为切入点,帮助各业务系统逐渐规范系统日志使用,取得很好效果。

后续我们规划将其推广成为公司级别的工具,为越来越多的项目提供便利。

欢迎感兴趣的同学与我们进一步交流。

  1. Simple Logging Facade for Java (SLF4J)
  2. Log4j 2 Architecture - Apache Log4j 2
  3. Hash-based message authentication code - Wikipedia

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

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

相关文章

LeetCode 324. 摆动排序 II

文章目录1. 题目2. 解题1. 题目 给定一个无序的数组 nums&#xff0c;将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。 示例 1: 输入: nums [1, 5, 1, 1, 6, 4] 输出: 一个可能的答案是 [1, 4, 1, 5, 1, 6] 示例 2: 输入: nums [1, 3, 2, 2, 3,…

最全的Pycharm debug技巧

最全的Pycharm debug技巧&#xff1a; 工欲善其事&#xff0c;必先利其器。无论你的 IDE 是 IntelliJ IDEA、Pycharm、WebStorm、GoLang、还是PhpStorm &#xff0c;调试器都是标配。在遇到有问题的程序时&#xff0c;合理的利用调试器的跟踪和断点技巧&#xff0c;可以很快的…

OpenKG祝大家端午安康

—????????OpenKG祝大家端午安康????????—

两个月,刷了八千篇Arxiv,我发现……

文 | 白鹡鸰编 | 小轶从五月初到现在&#xff0c;大约刷了八千篇Arxiv之后&#xff0c;我发现我有毛病。当然&#xff0c;这是读论文上头时的牢骚&#xff0c;不是真心话&#xff0c;只是说&#xff0c;我在Arxiv上投入的精力的努力&#xff0c;与我预计的收获不成正比。故事的…

深度学习在美团的应用

近年来&#xff0c;深度学习在语音、图像、自然语言处理等领域取得非常突出的成果&#xff0c;成了最引人注目的技术热点之一。美团这两年在深度学习方面也进行了一些探索&#xff0c;其中在自然语言处理领域&#xff0c;我们将深度学习技术应用于文本分析、语义匹配、搜索引擎…

LeetCode 315. 计算右侧小于当前元素的个数(二叉查找树二分查找归并排序逆序数总结)

文章目录1. 题目2. 解题2.1 二叉查找树2.2 二分插入2.3 归并排序1. 题目 给定一个整数数组 nums&#xff0c;按要求返回一个新数组 counts。数组 counts 有该性质&#xff1a; counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。 示例: 输入: [5,2,6,1] 输出: [2,1,1…

领域应用 | 金融资管领域知识图谱的构建和应用

转载公众号 | DataFunTalk分享嘉宾&#xff1a;李渔 熵简科技 联合创始人编辑整理&#xff1a;唐汝佳出品平台&#xff1a;DataFunTalk导读&#xff1a;本次分享的主题是金融资管领域知识图谱的构建和应用&#xff0c;主要介绍如何运用大数据、AI这些技术手段&#xff0c;来帮助…

我分析了ACL21论文列表,发现对比学习已经...

文 | 花小花Posy小伙伴们&#xff0c;好久不见呀&#xff0c;小花又回来了&#xff01;最近关注对比学习&#xff0c;所以ACL21的论文列表出来后&#xff0c;小花就搜罗了一波&#xff0c;好奇NLPers们都用对比学习干了什么&#xff1f;都是怎么用的呀&#xff1f;效果怎样呀&a…

LeetCode 629. K个逆序对数组(DP)

文章目录1. 题目2. 动态规划3. 优化的DP1. 题目 给出两个整数 n 和 k&#xff0c;找出所有包含从 1 到 n 的数字&#xff0c;且恰好拥有 k 个逆序对的不同的数组的个数。 逆序对的定义如下&#xff1a;对于数组的第i个和第 j个元素&#xff0c;如果满i < j且 a[i] > a[…

快速的找出元素是否在list中 python

number [[1,2],[3,2]] num np.array(number) np.argwhere(num2) np.argwhere(num2) array([[0, 1], [1, 1]], dtypeint64) 注意&#xff1a;只能是维度相同的时候&#xff0c;才能用该方法。 om ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or nd…

论文浅尝 | 知识图谱的神经符号推理(上)

笔记整理 | 叶橄强&#xff0c;浙江大学在读硕士&#xff0c;研究方向为知识图谱的表示学习和预训练。知识图谱推理是支撑信息提取、信息检索和推荐等机器学习任务的基础组成部分&#xff0c;并且由于知识图可以看作知识的离散符号表示&#xff0c;自然可以利用符号技术做知识图…

ICML2021 | Self-Tuning: 如何减少对标记数据的需求?

文 | 王希梅&#xff0c;高敬涵&#xff0c;龙明盛&#xff0c;王建民源 | THUML本文介绍ICML2021的中稿论文&#xff1a;Self-Tuning for Data-Efficient Deep Learning&#xff0c;就“如何减少对标记数据的需求”这一重要问题给出了我们的思考。论文标题&#xff1a;Self-Tu…

美团点评Docker容器管理平台

本文是郑坤根据第14期美团点评技术沙龙“你不知道的美团云”演讲内容整理而成&#xff0c;已发表在《程序员》杂志2017年1月刊。 美团点评容器平台简介 本文介绍美团点评的Docker容器集群管理平台&#xff08;以下简称“容器平台”&#xff09;。该平台始于2015年&#xff0c;是…

Python 获取本机或者服务器的 IP 地址

获取计算机名称 hostname socket.gethostname() 获取本机 IP ip socket.gethostbyname(hostname) print(ip) 具体操作 import socket hostname socket.gethostname() ip socket.gethostbyname(hostname) print(ip)通常使用 socket.gethostname() 方法即可获取本机 IP …

LeetCode 754. 到达终点数字(数学推理)

1. 题目 在一根无限长的数轴上&#xff0c;你站在0的位置。终点在target的位置。 每次你可以选择向左或向右移动。第 n 次移动&#xff08;从 1 开始&#xff09;&#xff0c;走 n 步。 返回到达终点需要的最小移动次数。 示例 1: 输入: target 3 输出: 2 解释: 第一次移动…

论文浅尝 | 神经符号推理综述(下)

笔记整理 | 许泽众&#xff0c;浙江大学在读博士3、神经驱动的符号推理相比于之前的两种类型&#xff0c;神经驱动的符号推理的目的是挖掘规则&#xff0c;而神经网络在其中扮演的作用是解决纯符号推理的不确定性&#xff0c;并且能够有效的减少搜索空间。这种类型的方法的基本…

没有导师指导,该如何自己选题发CVPR?

| 背景底层计算机视觉技术&#xff0c;如图像增强、图像复原等&#xff0c;一直以来都是一个重要且热门的研究方向。传统的方法多基于稀疏编码、小波变换等技术&#xff0c;近年来&#xff0c;深度学习的兴起为该领域带来了新的发展机遇&#xff0c;同时大幅度提升了方法性能。…

Android硬件加速原理与实现简介

在手机客户端尤其是Android应用的开发过程中&#xff0c;我们经常会接触到“硬件加速”这个词。由于操作系统对底层软硬件封装非常完善&#xff0c;上层软件开发者往往对硬件加速的底层原理了解很少&#xff0c;也不清楚了解底层原理的意义&#xff0c;因此常会有一些误解&…

LeetCode 482. 密钥格式化

1. 题目 给定一个密钥字符串S&#xff0c;只包含字母&#xff0c;数字以及 ‘-’&#xff08;破折号&#xff09;。N 个 ‘-’ 将字符串分成了 N1 组。给定一个数字 K&#xff0c;重新格式化字符串&#xff0c;除了第一个分组以外&#xff0c;每个分组要包含 K 个字符&#xf…

暑期学校 | 东南大学2021年国际暑期学校项目:从感知理解到智能认知 (知识图谱及应用课程)...

国际暑期学校开课啦 项目介绍 从感知理解到智能认知——走近新一代人工智能From perceptual understanding to intelligent cognition-Approaching a new generation of AI本项目是由东南大学计算机科学与工程学院、软件学院、人工智能学院组织&#xff0c;通过开设人工智…