OpenTelemetry 日志
简介
在所有遥测信号中,日志可能是历史最悠久的。大多数编程语言都有内置的日志记录功能或广为人知的、广泛使用的日志库。
对于指标和追踪,OpenTelemetry 采取了从零开始的设计方法,定义了一个新的 API,并提供了该 API 在多种语言中的完整实现。
我们在日志方面的处理方式略有不同。为了让 OpenTelemetry 在日志领域取得成功,我们需要支持现有的日志和日志库的历史遗留,同时在可能的情况下提供改进和与其余可观测性世界的更好集成。
这本质上是 OpenTelemetry 日志支持背后的理念。我们拥抱现有的日志解决方案,并确保 OpenTelemetry 与现有的日志库、日志收集和处理解决方案能够良好协作。
非 OpenTelemetry 解决方案的局限性
不幸的是,现有的日志解决方案目前与其余可观测性信号的集成度很低。日志在追踪和监控工具中通常支持有限,通常以链接的形式出现,这些链接使用可用且通常不完整的相关信息(例如时间戳和来源属性)。这种关联可能很脆弱,因为属性通常通过不同的方式添加到日志、追踪和指标中(例如,使用不同的收集代理)。没有标准化的方法可以包含日志的来源和出处(例如应用程序以及应用程序运行的位置/基础设施)的信息,这些信息与追踪和指标统一,并能够以精确且稳健的方式全面关联所有遥测数据。
同样,日志也没有标准化的方法来传播和记录请求执行的上下文。在分布式系统中,这通常导致从系统不同组件收集的日志集不完整。
这是今天典型的非 OpenTelemetry 可观测性收集管道的样子

通常存在不同的库和不同的收集代理,使用不同的协议和数据模型,遥测数据最终进入不了解如何很好地协同工作的独立后端。
OpenTelemetry 解决方案
分布式追踪引入了追踪上下文传播的概念。
从根本上说,没有什么能阻止日志采用相同的上下文传播概念。如果记录的日志包含追踪上下文标识符(例如追踪 ID 和 Span ID 或用户定义的 Baggage),将极大地丰富日志与追踪之间的关联,以及分布式系统中不同组件发出的日志之间的关联。这将使日志在分布式系统中具有显著更高的价值。
这是可观测性工具最有希望的演进方向之一。标准化日志与追踪和指标的关联,增加对日志分布式上下文传播的支持,统一日志、追踪和指标的来源归属,将提高日志、追踪和指标信息的个体和组合价值,无论是对于遗留系统还是现代系统。这就是 OpenTelemetry 的日志、追踪和指标收集的愿景。

我们以符合 OpenTelemetry 数据模型的方式发出日志、追踪和指标,通过 OpenTelemetry Collector 发送数据,在那里数据可以以统一的方式进行丰富和处理。例如,Collector 可以向来自 Kubernetes Pod 的所有遥测数据添加描述该 Pod 的几个属性,并且可以使用 k8sattributesprocessor 自动完成此操作,而无需应用程序进行任何特殊操作。最重要的是,这种丰富对于所有 3 种信号是完全统一的。Collector 保证日志、追踪和指标拥有完全相同的属性名称和值来描述它们所来自的 Kubernetes Pod。这使得后端能够精确且明确地关联信号。
对于追踪和指标,OpenTelemetry 定义了一个应用程序开发人员必须使用的新 API 来发出追踪和指标。
对于日志,我们没有采取同样的路径。我们意识到在日志领域存在更大、更多样化的历史遗留。存在许多不同语言的现有日志库,每种库都有自己的 API。许多编程语言为使用特定日志库建立了标准。例如,在 Java 世界中,有几个非常流行且广泛使用的日志库,如 Log4j 或 Logback。
还有无数现有的预构建应用程序或系统以特定格式发出日志。这些应用程序的操作员对日志的发出方式几乎没有控制权或控制权有限。OpenTelemetry 需要支持这些日志。
鉴于上述日志领域的状态,我们采取了以下方法:
OpenTelemetry 定义了一个 日志数据模型。该数据模型的目的是对 LogRecord 是什么、需要记录、传输、存储和由日志系统解释的数据有一个共同的理解。
新设计的日志系统应根据 OpenTelemetry 的日志数据模型发出日志。更多内容 稍后 讨论。
现有的日志格式可以 无歧义地映射 到 OpenTelemetry 日志数据模型。OpenTelemetry Collector 可以读取此类日志并将其转换为 OpenTelemetry 日志数据模型。
OpenTelemetry 定义了一个日志 API,用于 发出 LogRecord。它为库作者提供,用于构建 日志附加器,这些附加器使用该 API 在现有日志库和 OpenTelemetry 日志数据模型之间进行桥接。现有的日志库通常提供比 OpenTelemetry 定义的更丰富的功能集。OpenTelemetry 的目标不是提供一个功能丰富的日志库。然而,如果您更愿意将代码与此耦合而不是使用桥接的日志库,也可以直接使用日志 API。
OpenTelemetry 定义了一个 SDK 来实现 API,这使得可以配置 LogRecord 的 处理 和 导出。
这种方法允许 OpenTelemetry 读取现有的系统和应用程序日志,为新构建的应用程序提供发出丰富、结构化的 OpenTelemetry 兼容日志的方式,并确保所有日志最终都以统一的日志数据模型表示,后端可以在此基础上进行操作。
稍后在本文档中,我们将更详细地讨论 各种日志源如何被处理,但首先我们需要更详细地描述一个重要概念:日志关联。
日志关联
日志可以在几个维度上与其余可观测性数据进行关联
按执行时间。日志、追踪和指标可以记录执行发生的时间点或时间范围。这是最基本的关联形式。
按执行上下文,也称为追踪上下文。将执行上下文(追踪 ID 和 Span ID 以及用户定义的上下文)记录在 Span 中是一种标准做法。OpenTelemetry 通过在 LogRecord 中包含 TraceId 和 SpanId 将此做法扩展到日志(如果可能)。这允许直接关联对应于同一执行上下文的日志和追踪。它还允许关联参与特定请求执行的分布式系统中不同组件发出的日志。
按遥测来源,也称为资源上下文。OpenTelemetry 追踪和指标包含有关其来源的 Resource 信息。我们将此做法扩展到日志,方法是在 LogRecord 中包含 Resource。
这 3 种关联可以成为强大的导航、过滤、查询和分析功能的基石。OpenTelemetry 旨在以一种能够实现这种关联的方式记录和收集日志。
遗留和现代日志源
区分几种遗留和现代日志源很重要。首先,这直接影响我们如何获取这些日志以及如何收集它们。其次,我们对这些日志的生成方式以及是否可以修改包含在日志中的信息有不同程度的控制。
下面我们列出几类日志,并描述如何为每个类别获得更好的可观测性解决方案体验。
系统日志
这些是由操作系统生成的日志,我们无法对其进行控制。我们无法更改格式或影响包含哪些信息。系统格式的例子是 Syslog 和 Windows 事件日志。
系统日志在主机级别(可能是物理、虚拟或容器化的)写入,并具有预定义的格式和内容(请注意,应用程序也可以将记录写入标准的系统日志:这种情况将在下面的“第三方应用程序日志”部分中涵盖)。
日志中记录的系统操作可能是请求执行的结果。然而,系统日志要么不包含任何关于追踪上下文的数据,要么如果包含,则非常特殊化,因此难以识别、解析和使用。这使得对系统日志进行追踪上下文关联几乎不可能。然而,我们可以并且应该用资源上下文——收集期间可用的有关主机的信息——来自动丰富系统日志。这可以包括主机名、IP 地址、容器或 Pod 名称等。这些信息应该添加到收集到的日志数据的 Resource 字段中。
OpenTelemetry Collector 可以读取系统日志(链接待定),并使用 resourcedetection processor 自动用资源信息丰富它们。
基础设施日志
这些是由各种基础设施组件生成的日志,例如 Kubernetes 事件。与系统日志一样,基础设施日志缺乏追踪上下文,可以通过资源上下文进行丰富——有关节点、Pod、容器等的信息。
OpenTelemetry Collector 或其他代理可用于查询大多数常见基础设施控制器中的日志。
第三方应用程序日志
应用程序通常将日志写入标准输出、文件或其他专用介质(例如,应用程序的 Windows 事件日志)。这些日志可以有多种不同的格式,在以下几种变体之间分布:
自由文本格式,没有易于自动化且可靠地从中解析结构化数据的方法。
更好的指定且有时可自定义的格式,可以解析以提取结构化数据(例如 Apache 日志或 RFC5424 Syslog)。
正式结构化格式(例如,具有良好定义模式的 JSON 文件或 Windows 事件日志)。
收集系统需要能够发现最常用的应用程序,并具有可以将这些日志转换为结构化格式的解析器。与系统和基础设施日志一样,应用程序日志通常缺乏请求上下文,但可以通过资源上下文进行丰富,包括描述主机和基础设施的属性,以及应用程序级别的属性(例如应用程序名称、版本、数据库名称 - 如果是 DBMS 等)。
OpenTelemetry 建议使用 Collector 的 filelog receiver 来收集应用程序日志。或者,另一个日志收集代理,如 FluentBit,可以收集日志,然后发送到 OpenTelemetry Collector,在那里日志可以进一步处理和丰富。
遗留自研应用程序日志
这些是内部创建的应用程序。负责设置日志收集基础设施的人员有时能够修改这些应用程序,以更改日志的写入方式以及日志中包含的信息。例如,应用程序的日志格式化程序可以重新配置为输出 JSON 而不是纯文本,从而帮助提高日志收集的可靠性。
通过其开发人员手动进行对这些应用程序的更重要的修改,例如向每个日志语句添加追踪上下文,但由于所需工作量,这种情况可能会极其罕见。
与手动工作相对比,我们有机会以一种不那么费力的方式“升级”应用程序日志,通过提供完全或半自动仪器化解决方案,修改应用程序使用的日志追踪库,以自动输出追踪上下文,如追踪 ID 或 Span ID,伴随每个日志语句。如果使用符合标准的请求传播,例如通过 W3C TraceContext,可以自动从传入的请求中提取追踪上下文。此外,应用程序发出的请求可能被注入相同的追踪上下文数据,从而通过应用程序实现上下文传播,并为从所有以这种方式仪器化的应用程序收集的日志提供完整的追踪上下文。
一些日志库被设计成可以相对容易地以这种方式扩展。无需实际修改库,我们可以为这些库实现“日志附加器”或“日志桥”组件,并在这些组件中实现额外的 LogRecord 丰富。
通常有两种方法可以从这些应用程序收集日志:
通过文件或标准输出日志
第一种方法,假设日志写入文件或标准输出,需要能够读取文件日志,跟踪它们,在日志轮转时正确工作,还可以选择性地解析日志以将其转换为更结构化的格式。解析需要支持不同类型的解析器,这些解析器也可以配置为解析自定义格式,并能够添加自定义解析器。解析器需要支持的常见格式示例包括:CSV、Common Log Format、Labeled Tab-separated Values (LTSV)、Key/Value Pair 格式、JSON 等。为了支持此方法,OpenTelemetry 建议使用 OpenTelemetry Collector 收集日志。

或者,如果 Collector 没有必要的文件读取和解析功能,另一个日志收集代理(如 FluentBit)可以收集日志,然后将日志发送给 OpenTelemetry Collector。

使用中间媒介的好处在于,应用程序如何生成日志以及它们写入的位置无需或只需极少的更改。缺点是需要通常非平凡的文件日志读取和解析功能。如果输出格式未明确定义,解析也可能不可靠。有关记录和解析追踪上下文的详细信息,请参阅 非 OTLP 日志格式中的追踪上下文。
直接发送到 Collector
第二种方法是修改应用程序,使其通过网络协议输出日志,例如通过 OTLP。实现这一点的最方便的方法是为常用日志库提供插件或扩展。这些插件实现通过此类网络协议发送,这通常需要对应用程序代码进行少量、局部的更改以更改日志目标。

应用程序日志也将通过资源上下文得到丰富,类似于对第三方应用程序的做法,因此可能在所有上下文维度上具有完整的关联信息。
这种方法的缺点是丢失了将日志保存在本地文件的简单性(例如,能够轻松地在本地检查日志文件),并且需要完全接受 OpenTelemetry 的日志方法。此方法也仅在需要将日志传递到的目标能够通过 OpenTelemetry 可以发送的网络协议接收日志时才有效。
此方法的优点是它以定义良好、正式、高度结构化的格式发出日志,消除了与文件日志相关的所有复杂性,例如解析器、日志跟踪和轮转。它还使得可以直接将日志发送到日志后端,而无需使用日志收集代理。
为了支持上述两种方法,OpenTelemetry 提供了一个 API 和 SDK,可以与现有日志库一起使用,以自动将追踪上下文注入发出的日志,并提供一种简便的方式通过 OTLP 发送日志。开发人员无需修改每个日志语句,日志附加器使用 API 将现有日志库的日志桥接到 OpenTelemetry 数据模型,然后 SDK 控制日志如何被处理和导出。应用程序开发人员只需在应用程序启动时配置 Appender 和 SDK。
新的自研应用程序日志
这些是全新的开发项目。OpenTelemetry 提供了关于如何从这些应用程序发出日志(以及追踪和指标)的建议和最佳实践。对于适用的语言和框架,自动仪器化或简单的日志库配置以使用 OpenTelemetry 日志附加器仍然是发出上下文丰富日志的最简单方法。如前所述,我们为一些流行的日志库语言提供了扩展,以支持手动仪器化案例。这些扩展将支持将追踪上下文包含在日志中,并允许使用 OTLP 协议将日志发送到后端或 Collector,从而绕过将日志表示为文本文件的需要。发出的日志会自动补充应用程序特定的资源上下文(例如,进程 ID、编程语言、日志库名称和版本等)。这些日志将具有所有上下文维度的完整关联。
以下是典型新应用程序使用 OpenTelemetry API、SDK 和现有日志库的方式

OpenTelemetry Collector
为了启用符合本规范的日志收集,我们使用 OpenTelemetry Collector。
存在以下功能来启用日志收集:
支持基于 日志数据模型 的日志数据类型和日志管道。这包括可以操作日志数据的处理器,例如 attributesprocessor。
能够从文本文件中读取日志,跟踪文件,理解常见的日志轮转方案,监视目录中日志文件的创建,能够检查文件位置并从检查点恢复读取。此功能通过使用 Collector 的 filelog receiver 或使用外部运行的代理(如 FluentBit)来实现。
能够解析常见文本格式的日志,并允许最终用户自定义解析格式并根据需要添加自定义解析器。Collector 的 parsers 或外部代理中的解析用于此目的。
能够通过常见的网络协议接收日志,如 Syslog,并根据本规范中定义的语义约定进行解释。FluentBit 或类似代理用于此目的。随着时间的推移,其中一些功能可能会直接迁移到 Collector。
能够通过常见的网络协议发送日志,如 Syslog,或供应商特定的日志格式。Collector 包含直接实现此功能的导出器。
自动仪器化现有日志
我们可以为大多数流行的日志库提供自动仪器化。自动仪器化的日志语句将执行以下操作:
读取传入的追踪上下文(这是自动仪器化库执行的更广泛仪器的一部分)。
配置日志库以使用请求上下文中的追踪 ID 和 Span ID 字段作为日志上下文,并自动将它们包含在所有记录的语句中。
这对于某些语言(例如 Java)是可行的,我们可以重用 现有的开源库 来实现这一点。
进一步的可选修改是自动仪器化日志记录器,以便它们通过 OTLP 直接将日志发送到后端,而不是写入文件或标准输出,或者除了写入文件或标准输出之外。