日志传习录 | 日志级别

举报
不惑 发表于 2024/11/05 09:14:18 2024/11/05
【摘要】 - 开发人员在编写代码时常常陷入纠结,不确定在何处打印日志才是最有意义的; - SRE人员在调查生产问题时可能因为缺乏必要的日志信息而束手无策; - 运维人员在面对处理海量日志时往往需要耗费大量的精力进行维护; - 项目管理者面对大量的无实际业务价值的日志,往往不愿投入过多人力和财力进行管理。

前言

写日志是一项具有挑战性的任务,在工作中我们常常面临一些困境,比如:

  • 开发人员在编写代码时常常陷入纠结,不确定在何处打印日志才是最有意义的;
  • SRE人员在调查生产问题时可能因为缺乏必要的日志信息而束手无策;
  • 运维人员在面对处理海量日志时往往需要耗费大量的精力进行维护;
  • 项目管理者面对大量的无实际业务价值的日志,往往不愿投入过多人力和财力进行管理。

这些问题导致了许多矛盾的产生。然而,当问题出现时,我们需要依赖日志记录来建立一种**“不在场证明”,**找出哪一方有问题。

正是由于这种需求,我们在开发应用程序时需要遵循良好的实践,选择成熟的日志收集机制和管理方案,从而缓解这些矛盾。
image.png

矛盾的起因

  • 首先,我们探讨为何需要记录日志以及日志的作用。

实际上,对于大多数开发人员来说,在调试代码问题、解决不同环境的 Bug 时,日志的价值是显而易见的。作为调试的得力助手和生产环境中不可或缺的救星。

通过查询日志,我们能够确定代码的执行过程API请求的正确性核心业务数据的准确性以及是否存在错误的堆栈信息等等操作,这些条件也构成了开发和运维人员判断代码和生产问题的首要手段。在一个复杂庞大的系统中,如果没有记录任何日志,那么在排查生产环境中的 Bug 时将变得极为困难。

  • 若每一行代码都记录上下文,是否就能解决所有问题呢?

理论上确实是可行的,但目前仍存在一些无法解决的问题。

首先是日志存储量的问题,典型的中大型系统日志可能达到TB级,而超大型系统的日志规模甚至可能达到PB级。

这对于存储而言是一个巨大的挑战。其次是搜索性能的下降,常见的日志存储方案如Elasticsearch数据库在面对海量日志时,可能导致维护的映射关系急剧增长,即使划分不同的索引、分布式管理不同的ElasticSearch集群,也难以做到搜索性能不随数据量增加而下降。最后,海量日志的生成可能在峰值时拖慢系统性能,增加出现故障的风险。

因此,日志既不能记录过多导致存储和管理困难,也不能因记录过少而导致运维人员无法排查问题。尽管听起来似乎自相矛盾,但这正是关于日志重要所在!在日志记录中,我们需要在**“太多”“太少”**之间找到平衡点,以确保既能有效排查问题同时又能够高效管理和存储日志。

日志级别

在决定记录日志之前,通常需要考虑选择适当的日志级别。在讨论如何确定日志级别之前,我们先来了解一下日志级别的作用。

  • 确定日志信息的优先级: 通过设定不同的日志级别,我们可以对日志信息进行优先级排序,从而有效减少信息噪音和警报疲劳。不同的日志级别对应不同的信息重要性,开发人员可以根据当前需求选择适当的级别,确保在解决问题或分析系统行为时能够集中关注最重要的信息。
  • 在查询日志时进行过滤: 添加日志级别的过滤可以在查询日志时更加精准地获取所需的信息。例如,在调试阶段可能需要详细的调试信息,而在生产环境中可能只关心警告和错误级别的日志。通过合理使用日志级别,可以提高日志的可读性和查询效率,同时降低处理冗余信息的成本。

因此,选择适当的日志级别是一项关键决策,它能够在不同场景中平衡信息的详细程度,帮助开发人员和运维人员更好地理解和管理系统的行为。

常见的日志级别有以下几类,并且从高到低的顺序是:致命(FATAL)、错误(ERROR)、警告(WARN)、信息(INFO)、调试(DEBUG)、痕迹(TRACE)和全部(ALL)

致命 错误 警告 信息 调试 痕迹 全部
致命 X
错误 X X
警告 X X X
信息 X X X X
调试 X X X X X
痕迹 X X X X X X
全部 X X X X X X X
  • FATAL: 严重错误级别,表示系统无法继续运行。
  • ERROR: 错误级别,用于记录错误信息。
  • WARN: 警告级别,表示潜在的问题,但不影响程序的运行。
  • INFO: 信息级别,用于记录程序的正常运行信息。
  • DEBUG: 调试级别,用于详细记录调试信息。
  • TRACE: 追踪级别,提供比DEBUG更详细的信息。
  • ALL: 最低级别,用于启用所有日志记录。

常见场景

场景

某工程师在调查生产环境中某个创建资源的 API 性能问题时,发现该 API 接口中打印了INFO 级别的日志,导致业务峰期时出现海量日志,耗尽 Buffer 区内存,拖慢主线程,影响服务性能。

后续功能优化中工程师删除了写业务INFO级别日志的操作以解决性能问题。

然而,由于某天修改了 API 服务调用链路上的某服务代码,导致 API 创建出的对象存在错误。但是在生产环境中缺少了该资源的日志,工程师无法准确排查问题。在这种情况下,工程师可能需要重新修改日志级别,将业务日志重新启用,并重新构建发布上线,

场景

假设将生产环境的日志设置为 ERROR 级别。某一时刻,依赖的下游服务故障,导致请求大量超时。由于业务峰期 QPS 非常高,短时间内集中产生大量错误日志,导致磁盘 IO ****急剧提高,消耗大量 CPU,导致整个服务瘫痪。

场景

某工程师在排查生产问题时,发现 INFO 级别的日志无法满足排查根本原因。他需要 DEBUG 级别的日志,但生产环境只配置为 INFO 级别。

日志级别规范与动态调整

日志级别的规范和动态调整有助于在开发、调试和生产环境中更有效地管理日志信息。

日志级别规范

  • TRACE: 在开发期间可以使用,但确保不要将它们提交到版本控制系统中,以避免不必要的日志信息混入生产环境。
  • DEBUG: 在进入生产阶段之前,对调试语句进行审查和缩减,只保留最关键、最有意义的调试信息。
  • INFO: 记录用户驱动的事件或系统的特定操作。这可以包括定期计划的任务、用户登录等。保持信息简洁明了,避免过多的冗余信息。
  • WARN: 记录可能成为错误的事件。例如,耗时较长的操作、接近容量的内存缓存等。允许设置自动警报,以及在故障排除期间更好地了解系统在故障之前的行为。
  • ERROR: 记录每个错误条件,包括 API 调用返回的错误或内部错误条件。
  • FATAL: 只用于表示整个服务已经无法工作的情况。通常,FATAL 级别记录表示程序的结束。

动态调整日志级别

配置文件动态调整

使用配置文件(如 logback.xml 或 log4j2.xml)来配置日志级别。这样,可以在不重新启动应用程序的情况下调整日志级别。

  • logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>

    <!-- 使用springProperty引用配置文件中的属性 -->
    <!-- <property resource="application.properties" /> -->

    <!-- 定义一个变量,用于动态设置日志级别 -->
    <property name="logLevel" value="INFO" />

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--
            日志输出格式:
                %d表示日期时间,
                %thread表示线程名,
                %-5level:级别从左显示5个字符宽度
                %logger{50} 表示logger名字最长50个字符,否则按照句点分割。
                %msg:日志消息,
                %n是换行符
        -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 指定日志级别为变量引用的值 -->
    <root level="${logLevel}">
        <appender-ref ref="console" />
    </root>

    <!-- 添加一个TurboFilter,用于动态更改日志级别 -->
    <turboFilter class="com.example.demo.config.LoggerNameChangeFilter" />

</configuration>
  • log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
    <!-- 定义日志输出的方式,这里使用控制台输出 -->
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <!-- 定义日志输出的格式 -->
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>

    <!-- 配置日志级别 -->
    <Loggers>
        <!-- 根日志级别设置为info -->
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>

        <!-- 设设置特定包(com.example.myapp)的日志级别为debug,additivity="false"表示不向父Logger传递日志。 -->
        <Logger name="com.example.myapp" level="debug" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
    </Loggers>
</Configuration>

  • logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 引入Spring Boot基础日志配置 -->
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <!-- 自定义Logback配置开始 -->

    <!-- 示例:添加一个自定义的控制台输出appender -->
    <appender name="customConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 示例:将自定义的控制台appender添加到根logger -->
    <root level="INFO">
        <appender-ref ref="customConsoleAppender"/>
    </root>

    <!-- 自定义Logback配置结束 -->

</configuration>

logback-spring.xml 可以通过配置,定时地检测配置修改,但是这会带来额外的资源消耗。

<configuration scan="true" scanPeriod="30 seconds" > 
 <!-- ... 具体配置 -->
</configuration>
  • 使用外部配置中心,例如 Spring Cloud Config,可以通过远程配置动态更改日志级别。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- 引入Spring Boot基础日志配置 -->
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <!-- 使用Spring表达式动态配置根logger的日志级别 -->
    <root level="${LOG_LEVEL:-INFO}">
        <appender-ref ref="CONSOLE"/>
    </root>

</configuration>

在这里,${LOG_LEVEL:-INFO} 使用了Spring表达式,它会从Spring Cloud Config中获取名为LOG_LEVEL的配置,如果未配置则默认为INFO级别。

JMX(Java Management Extensions)

使用 JMX 允许在运行时修改日志级别。

通过 JConsole,VisualVM 或其他 JMX 工具,可以直接管理日志框架的运行时配置。

application.propertiesapplication.yml 中启用 JMX:

spring.jmx.enabled=true

远程管理工具

使用远程管理工具,例如 Spring Boot Actuator,可以通过 HTTP 端点或其他远程管理手段动态调整日志级别。

Spring Boot Actuator 为Spring Boot应用程序提供了丰富的监控和管理功能。通过使用HTTP Endpoint(端点)或JMX(Java Management Extensions)来监视和管理应用程序,从而更好地理解其运行状况并进行调整。

确保在pom.xml中添加了Spring Boot Actuator的依赖

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

配置application.yml文件

# 设置日志级别为INFO
logging:
  level:
    root: INFO

# 允许Actuator修改日志配置
management:
  endpoints:
    web:
      exposure:
        include: loggers

最后,使用curl命令查看和修改日志级别:

  • 查看日志配置:
curl -X GET http://localhost:8080/actuator/loggers

你会看到根日志记录器的级别是INFO。

  • 修改日志级别:
curl -X POST http://localhost:8080/actuator/loggers/com.example -H 'Content-Type: application/json' -d '{"configuredLevel": "DEBUG"}'

这会将com.example包下的日志级别设置为DEBUG。

条件日志

在关键代码路径中使用条件日志,根据配置的条件来决定是否记录日志。这样可以更灵活地控制日志输出。

在 application.properties 或 application.yml 中添加一个属性,表示是否启用条件日志:

myapp.logging.enabled=true

创建一个类来进行条件日志,使用 @ConditionalOnProperty 注解来根据配置的条件来判断是否创建这个类的 Bean。这个类可以用于条件日志记录。

@Configuration
@ConditionalOnProperty(name = "myapp.logging.enabled", havingValue = "true")
public class ConditionalLoggerConfig

集成监控系统

将日志级别调整集成到监控系统中,例如 Prometheus、Grafana 等,以便在需要时能够通过监控界面进行动态调整。

总结

综合利用这些方法,可以在不同的环境和阶段更好地管理日志级别,既保持足够的信息用于排查问题,又避免在生产环境中过度记录冗余信息。

参考链接:

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。