本指南旨在为您提供基本的见解和实践,以确保您能够更有效地监控和排除服务故障。
在应用程序开发中,日志记录经常被忽视,但它是构建强大且可观察系统的关键组成部分。正确的日志记录实践可以增强应用程序的可见性,加深对其内部工作原理的理解,并改善应用程序的整体健康状况。
在应用程序的入口点加入默认日志记录机制非常有益。这种自动日志记录可以捕获必要的交互,并可能包括入口点的参数。但是,必须谨慎,因为记录密码等敏感信息可能会带来隐私和安全风险。
应用程序执行的每项重要操作都必须生成日志条目,尤其是那些改变其状态的操作。这种详尽的日志记录方法是快速识别和解决问题的关键,可以透明地查看应用程序的运行状况和功能。如此认真的日志记录可确保更轻松地进行诊断和维护。
采用适当的日志级别对于管理和解释应用程序生成的大量数据至关重要。通过根据日志的严重性和相关性对其进行分类,您可以确保及时发现和解决关键问题,同时仍可访问不太紧急的信息,而不会让您的监控工作不堪重负。
以下是有效利用日志级别的指南:
等级 | 描述和示例 | 接受使用 | 不接受 |
---|---|---|---|
| 导致系统停止运行的致命事件。例如,丢失数据库连接 | 严重系统错误 | 非严重错误,例如用户登录尝试失败 |
| 存在问题,但系统可以继续执行并完成请求的操作 | 导致问题的潜在问题 | 常规状态变化 |
| 了解正常的应用程序功能,例如用户帐户创建或数据写入 | 状态更改 | 只读操作,无需更改 |
| 详细的诊断信息,例如进程开始/结束 | 记录进程步骤不会改变系统状态 | 常规状态变化或高频操作 |
| 最详细的级别,包括方法入口/出口 | 了解流程和过程的细节 | 记录敏感信息 |
在应用程序中记录操作时,包含直接涉及的实体的 ID 对于将日志信息链接到数据库数据至关重要。分层方法可帮助您通过将项目链接到其父组或类别来快速找到与应用程序特定部分相关的所有日志。
例如,当消息发送失败时,您不应只记录聊天 ID,还应记录聊天室及其所属公司的 ID。这样,您可以获得更多背景信息,并了解问题的更广泛影响。
Failed to send the message - chat=$roomId, chatRoomId=chatRoomId, company=$companyId
下面是使用分层方法时生产日志的示例:
在所有团队中标准化日志格式可以使您的日志更易于阅读和理解。以下是一些需要考虑的标准化前缀:
将变量名称和值与日志消息正文分开有几个优点:
Log message - valueName=value
以下是遵循讨论的最佳实践的结构良好的日志条目示例:
2023-10-05 14:32:01 [INFO] Successful login attempt - userId=24543, teamId=1321312 2023-10-05 14:33:17 [WARN] Failed login attempt - userId=536435, teamId=1321312
这些例子表明:
下面是使用建议的实践时生产日志的示例:
为了有效地将日志与特定用户操作关联起来,在日志中包含traceId
或也称为correlationId
至关重要。该 ID 应在该入口点触发的逻辑生成的所有日志中保持一致,从而清晰地显示事件序列。
虽然某些监控服务(例如 Datadog)提供了开箱即用的日志分组功能,但也可以手动实现。在使用 Spring 的 Kotlin 应用程序中,您可以使用 HandlerInterceptor 为 REST 请求实现跟踪 ID。
@Component class TraceIdInterceptor : HandlerInterceptor { companion object { private const val TRACE_ID = "traceId" } override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { val traceId = UUID.randomUUID().toString() MDC.put(TRACE_ID, traceId) return true } override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) { MDC.remove(TRACE_ID) } }
该拦截器为每个请求生成一个唯一的traceId
,在请求开始时将其添加到 MDC,并在请求完成后将其删除。
实现此类日志聚合将使您能够过滤类似以下示例的日志
在许多系统中,实体可能使用UUID
或Long
ID 作为其主要标识符,而某些系统可能将两种类型的 ID 用于不同的目的。了解每种类型对日志记录的影响对于做出明智的选择至关重要。
以下是需要考虑的事项的细分:
可读性: Long
ID 更易于阅读,而且明显更短,尤其是当它们不在Long
范围的高端时。
唯一值: UUID
ID 在整个系统中提供唯一性,使您可以使用 ID 搜索日志而不会遇到 ID 冲突的问题。这里的冲突意味着来自不相关数据库表的两个实体有可能具有相同的Long
ID。
系统限制:在使用长主键作为实体 ID 的系统中,添加随机UUID
ID 通常很简单,但在具有UUID
实体 ID 的分布式系统中,专门用于日志记录的Long
ID 可能会很有挑战性或成本很高。
现有日志:日志中使用的 ID 类型的一致性至关重要,至少对于每个实体而言都是如此。如果系统已经为某些实体生成日志,而您不打算更改所有实体,则最好坚持使用已用于标识实体的类型。在过渡期间可以考虑记录两个 ID,但永久使用多个 ID 会不必要地使日志变得混乱。
正确的日志记录实践对于有效的服务可观察性至关重要。通过结合全面的日志记录、适当的日志级别、跟踪 ID 和标准化的日志格式,您可以显著增强监控和排除应用程序故障的能力。这些做法可以提高日志的清晰度和一致性,让您更轻松地快速诊断和解决问题。
感谢您花时间阅读这篇文章!