日志是分布式系统中最重要的一部分。许多事情可能会出错,但一旦日志系统出现问题,我们将完全迷失方向。在这篇博客文章中,我们将了解日志级别以及如何在分布式系统中高效地记录日志。
日志级别
每当我们记录一条消息时,我们需要指定日志级别或日志严重性。这是表明消息重要性和谁应该关注的一个指示器。
日志级别已经伴随我们很长时间了。自从计算机系统早期开始,我们就需要提供日志文件并定义级别。通常,我们可以使用以下级别:
错误
警告
信息
调试
跟踪
有时会使用更长的层次结构,我们可能会有如下额外的类别:
紧急
警报
严重
通知
我们以两种方式使用日志级别:
我们为每条记录的消息定义级别。换句话说,在应用程序源代码中记录消息时,我们需要指定这条特定消息的级别。
我们指定哪些消息存储在日志存储中。换句话说,在添加新的日志存储(如日志文件或日志表)时,我们指定想要存储的最大日志级别。
无论我们能使用哪些级别(这取决于我们使用的库和监控系统),都应该遵循一些最佳实践。让我们逐一了解它们。
跟踪级别
这个级别的日志通常显示“一切”发生的内容。它们旨在解释一切以便于调试任何问题。这包括显示通过网络发送的原始数据包或内存字节的十六进制表示。
这个级别的日志由于我们要记录的原始数据而可能占用很多空间。例如,如果您的应用程序需要通过网络发送1GB的数据,它将把数据的内容存储在日志文件中,这将至少是1GB。
这个级别也可能带来安全风险,因为我们发出了的一切,包括密码或敏感信息。因此,这个级别不应该在生产系统中使用。
这个级别的消息通常对开发人员有用,因为它们有助于调试错误。它们很少对数据库管理员有帮助,几乎对普通用户没有兴趣。
调试级别
这个级别与跟踪级别类似,但专注于对程序员有帮助的精心挑选的消息。在这个级别上,我们很少存储一切内容的原始内容。我们更专注于非常详细的信息,显示可能帮助程序员的关键数据。例如,我们不会存储通过网络发送的确切密码,但我们会存储连接的元数据和使用的加密方案。
这个级别的日志仍然非常大,不应在生产中启用。它们可能也包含敏感数据,因此可能带来安全风险。这些消息对程序员有用,对其他人非常有用。
信息级别
这个级别的日志通常显示“发生了什么”。它们只是通知发生了某些事情,通常是从业务角度呈现。这是我们初始通常使用的级别,只有在我们生成太多日志时才会更改它。
这个级别的消息专注于业务上下文和业务信号。它们非常少展示用户实际发送的数据。更多的时候,它们关注的是发生的事实。
这个级别的消息对于希望监视系统行为的典型用户非常有帮助,包括数据库管理员和程序员。这个级别的消息对普通用户很少有帮助。
除非我们生成太多日志(这可能发生在我们系统每秒处理成千上万笔交易时),否则我们应默认使用这个级别。
警告级别
在这个级别,我们会记录与系统自动处理的可能错误情况相关的消息。例如,我们可能通知用户使用不安全的密码登录。系统仍然正确运行,从理论上讲这不是一个错误,但这种情况仍然需要一些关注。
这个级别的日志对数据库管理员和用户是有用的。例如,它们可能指示如果尽早处理就可以预防的问题,或者在更新系统到下一个版本后可能会损坏的事物。
我们总是应该将这些日志存储在日志文件中。
错误级别
这个级别显示了系统无法自动处理的错误情况。根据上下文,一种情况可能从用户的角度来看是“错误”,而从系统的角度来看则不是“错误”,反之亦然。例如,当用户在登录时提供错误的密码时,用户应该收到一个错误信息(因此数据库客户端应该记录一个错误),而系统应该只记录一个警告信息(因为这可能是暴力攻击)。
在这个级别的日志应该始终存储在日志文件中。根据错误严重性,我们可能希望围绕这些日志建立警报,并相应地通知用户。错误日志对用户和管理员都有帮助。
日志内容
在记录信息时,我们总是应该包括所有“需要”的内容,不要过多。我们需要在记录过多(消耗磁盘空间)和记录过少(所以我们以后无法调查问题)之间找到平衡。
考虑到分布式系统,我们应该记录以下内容:
时间戳:人性化消息的时间戳;例如,“2024.06.21 13:08:23.125422”。最好从年份开始时间戳,以便更容易排序。根据我们处理的系统,我们应该至少记录小时和分钟,但我们可以深入到纳秒。
应用名称:这是我们理解的应用名称。它不应该是通用的,如“数据库”,而应该是有业务意义的,如“票务数据库”。
服务器标识符:当我们扩展到多台服务器时,我们应该总是记录服务器标识符,以便了解问题发生在哪里。
进程标识符:这是由操作系统报告的进程标识符。当我们将应用程序通过进程间复制进行扩展时,这很有帮助。
线程标识符:这是记录消息的线程的标识符。我们可能会有这类标识符的多个(操作系统级标识符和运行时线程标识符),我们应该记录下所有的它们。线程的名称也很有帮助。线程标识符在分析内存转储时非常有用。
日志级别:显然,消息的级别。
相关标识符:我们正在执行的操作的唯一标识符。这可以是来自OpenTelemetry的跟踪ID和跨度ID或其他等效标识符。
活动:我们正在执行的工作流程的人类可读名称;例如,“购票”
逻辑时间:我们拥有的任何其他排序标识符,例如,Lamport的before时间或分布式系统中的向量时间
日志记录器ID:我们使用的日志记录器的标识符(尤其是如果我们在进程中有多个子系统)
根据我们的需要,我们可以记录更多的事情。把这当作一个良好的起点。
我们通常应该将日志存储到信息级别。如果我们遇到日志过早变得过大或者由于日志写入导致磁盘活动过多的问题,我们应该考虑限制日志文件,只存储到警告级别的消息。
如何有效使用日志与可观测性
配置日志时,我们应该遵循一些最佳实践,让我们来看看它们。
滚动旧文件
我们通常在日志文件中存储日志消息。它们可能会变得非常大(达到数十或数百吉字节),因此我们需要经常滚动文件。通常是每天或每小时。
滚动文件后,我们应该压缩旧文件并将其发送到集中位置。每小时滚动文件可以给我们带来更多的可靠性(在最坏的情况下,我们只会丢失一小时的日志),但也使得文件数量增加。
异步日志和集中化
日志是昂贵的。当您的系统每秒处理数千笔交易时,我们根本无法同步记录所有数据。因此,总是将日志卸载到后台线程中,并异步记录日志。
然而,异步记录日志可能会破坏日志的顺序。为了解决这个问题,总是包含逻辑排序ID(如Lamport的happened-before)以能够重建正确的顺序。请记住,时间戳仅在比较同一线程内的数据时才足够好(并且即使在这种情况下也可能不正确),因此在分布式系统中应避免使用。
最好将日志记录到本地日志文件中(以最小化写入时间),然后定期提取日志并将它们发送到某些外部存储。这通常是通过一个后台守护进程来实现的,该进程读取文件、压缩它们,并将它们发送到某个中央监控位置。
异常检测
一旦我们将日志文件集中存储,我们就可以寻找异常。例如,我们可以在日志中搜索“异常”、“错误”、“问题”或“致命”等词汇。然后我们可以将这些日志与其他信号(尤其是业务维度)或跟踪和数据分布相关联。
在人工智能的帮助下,我们可以使异常检测变得更好。我们可以分析模式并在数据中寻找可疑的趋势。这在云基础设施中通常是开箱即用的。
通过相关ID过滤
相关ID可以让我们更容易地调试场景。无论何时出现问题时,我们都可以向用户显示相关ID,并要求他们在回应用户时提及该ID。
一旦我们有了相关ID,我们只需根据相关ID过滤日志,按照逻辑时钟(如向量时钟)排序日志条目,然后按照时间戳排序,最后进行可视化。这样,我们就可以轻松地看到系统内的所有交互。
按需追踪
最好能够按需启用追踪,例如,基于cookie、查询字符串参数或某些众所周知的会话ID。这有助于我们在问题发生时进行现场调试。
有了这样的功能,我们可以动态地重新配置日志文件,仅存储某些交易的日志消息(最高到跟踪级别)。这样,我们不会使日志系统过载,但我们仍然可以获得错误的业务流程的关键日志。
在数据库中记录日志
数据库中的日志记录与常规分布式系统没有太大区别。我们需要配置以下内容:
使用什么日志收集器 – 这指定了我们记录消息的位置。这可以是输出流或文件等。
日志存储位置 – 如果记录到文件,我们需要指定目录、名称和文件格式(如CSV或JSON)。
如何处理日志文件 – 无论它们是每天、每小时甚至更频繁地轮换,还是在系统重新启动时截断文件。
消息格式 – 包含哪些信息;例如,事务标识符或某些序列编号,使用哪个时区等。
日志级别 – 要在日志文件中存储哪些消息
抽样 – 例如,仅记录较长的查询或较慢的交易
类别 – 记录什么;例如,是否记录连接尝试或仅记录交易。
正如之前提到的,我们需要在记录太多和记录太少之间找到平衡。例如,请查看PostgreSQL文档以了解可以配置哪些内容以及它们如何影响日志记录系统。
通常,您可以使用SQL查询来检查日志大小。例如,对于PostgreSQL,您可以这样检查当前日志文件的大小:
SELECT size FROM pg_stat_file(pg_current_logfile());
size 94504316
此查询显示所有日志文件的总大小:
SELECT sum(stat.size) FROM pg_ls_dir(current_setting('log_directory')) AS logs CROSS JOIN LATERAL pg_stat_file(current_setting('log_directory') || '/' || logs) AS stat;
sum 54875270066
您也可以通过命令行来验证。转到您的数据库安装目录(可能是/var/lib/postgresql/data/log或类似目录),然后您可以使用SHOW data_directory;命令进行检查,并运行以下命令:
du -cb
54893969026 . 54893969026 total
根据您如何托管数据库,您可能会默认将日志交付到外部系统。例如,您可以配置RDS中的PostgreSQL以将日志发送到Amazon CloudWatch,然后您可以在日志组/aws/rds/instance/instance_name/postgresql中查看日志。
您可以看到许多类型的日志,例如:
启动和关闭日志 – 这些描述您的数据库启动和关闭程序。
查询日志 – 这些显示执行的SQL查询。
查询持续时间日志 – 它们显示查询完成所需的时间。
错误日志 – 它们显示了诸如无效SQL查询、打字错误、约束违反等错误。
连接和断开日志 – 它们显示了用户如何连接到数据库。
写前日志(WAL) – 它们显示了检查点以及数据保存到驱动器的时间。
根据您的配置,您的日志文件可能会很容易增长。例如,如果您决定记录每个查询,并且每秒有数千个事务,那么日志文件将存储所有查询的历史,几小时后可能会非常庞大。
总结日志是分布式系统最重要的部分。任何事情都可能出错,但如果日志出错,那么我们就处于盲目状态。因此,了解如何记录、记录什么以及如何处理日志以高效调试问题至关重要。