写日志是一项具有挑战性的任务,在工作中我们常常面临一些困境,比如:
这些问题导致了许多矛盾的产生。然而,当问题出现时,我们需要依赖日志记录来建立一种“不在场证明”,找出哪一方有问题。
正是由于这种需求,我们在开发应用程序时需要遵循良好的实践,选择成熟的日志收集机制和管理方案,从而缓解这些矛盾。
实际上,对于大多数开发人员来说,在调试代码问题、解决不同环境的 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 |
某工程师在调查生产环境中某个创建资源的 API 性能问题时,发现该 API 接口中打印了INFO
级别的日志,导致业务峰期时出现海量日志,耗尽 Buffer
区内存,拖慢主线程,影响服务性能。
后续功能优化中工程师删除了写业务INFO
级别日志的操作以解决性能问题。
然而,由于某天修改了 API 服务调用链路上的某服务代码,导致 API 创建出的对象存在错误。但是在生产环境中缺少了该资源的日志,工程师无法准确排查问题。在这种情况下,工程师可能需要重新修改日志级别,将业务日志重新启用,并重新构建发布上线,
假设将生产环境的日志设置为 ERROR
级别。某一时刻,依赖的下游服务故障,导致请求大量超时。由于业务峰期 QPS
非常高,短时间内集中产生大量错误日志,导致磁盘 IO
急剧提高,消耗大量 CPU
,导致整个服务瘫痪。
某工程师在排查生产问题时,发现 INFO
级别的日志无法满足排查根本原因。他需要 DEBUG
级别的日志,但生产环境只配置为 INFO
级别。
日志级别的规范和动态调整有助于在开发、调试和生产环境中更有效地管理日志信息。
使用配置文件(如 logback.xml 或 log4j2.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>
<?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>
<?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>
<?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 允许在运行时修改日志级别。
通过 JConsole,VisualVM 或其他 JMX 工具,可以直接管理日志框架的运行时配置。
在 application.properties
或 application.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 等,以便在需要时能够通过监控界面进行动态调整。
综合利用这些方法,可以在不同的环境和阶段更好地管理日志级别,既保持足够的信息用于排查问题,又避免在生产环境中过度记录冗余信息。
参考链接:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。