OpenTelemetry Filelog Receiver:日志文件摄入指南

译文 2025-10-29 19:33:26

即便在云原生应用与分布式追踪盛行的时代,普通的日志文件依旧是任何系统中最丰富的信息来源之一。从遗留业务应用、批处理任务,到NGINX、数据库以及本地基础设施,关键的诊断信息仍会被写入磁盘。

OpenTelemetry Collector 的 filelog receiver(文件日志接收器)为我们提供了一种将这些日志接入现代可观测性流水线的方法。它会持续追踪文件、解析文件内容,并将原始文本转换为结构化的 OpenTelemetry LogRecords(日志记录)。

本指南将向你展示如何充分利用这一工具,从读取文件的基础操作,到构建可处理日志轮转、支持重启恢复且不会丢失任何一行日志的生产级流水线。你将学习如何对日志文件条目进行结构化处理、丰富内容并实现标准化,使其成为一流的可观测性数据。

现在,让我们开始吧!

Filelog Receiver 的工作原理

OpenTelemetry 中 filelog receiver 工作原理示意图

在深入了解配置细节之前,先弄清楚该接收器在整个日志文件生命周期内的处理方式会很有帮助。你可以将其看作一个简单的、循环执行的四步流程:

  1. 发现(Discover):接收器会按照你设置的 include(包含)和 exclude(排除)模式,定期扫描文件系统,确定需要关注的日志文件。
  2. 读取(Read):一旦识别到某个文件,接收器会打开该文件,并在有新行写入时持续追踪。start_at 配置项决定了接收器是从文件开头(beginning) 开始读取,还是仅从文件末尾(end) 开始追踪新写入的内容。
  3. 解析(Parse):每一行日志(如果配置了多行解析,则为每一块日志)都会经过一系列 Stanza 运算符(若已配置)的处理。这些运算符会解析原始文本、提取关键属性、分配时间戳和严重级别,并最终将日志数据转换为结构化格式。
  4. 发送(Emit):最后,结构化的日志记录会被传入 Collector 的流水线,在那里它们可以被过滤、进一步转换,或者导出到后端系统。

这个“发现→读取→解析→发送”的循环是该接收器所有功能的基础。

快速入门:追踪日志文件

最常见的使用场景之一是应用程序已将日志以JSON格式写入文件。例如,假设你有一个服务将JSON格式的日志写入 /var/log/myapp/app.log 文件,日志内容如下:

{"time":"2025-09-28 20:15:12","level":"INFO","message":"User logged in successfully","user_id":"u-123","source_ip":"192.168.1.100"}
{"time":"2025-09-28 20:15:45","level":"WARN","message":"Password nearing expiration","user_id":"u-123"}

以下是一个最小化的 filelog receiver 配置示例,可用于读取此类日志并将其摄入 OpenTelemetry 流水线:

# otelcol.yaml
receivers:
  filelog:
    # 1. 发现 /var/log/myapp/ 目录下所有 .log 文件
    include: [/var/log/myapp/*.log]
    # 2. 从新文件的开头开始读取
    start_at: beginning
    # 3. 使用 json_parser 运算符进行解析
    operators:
      - type: json_parser
        # 告知解析器时间戳的位置及其格式
        timestamp:
          parse_from: attributes.time
          layout: "%Y-%m-%d %H:%M:%S"
        # 告知解析器严重级别所在的字段
        severity:
          parse_from: attributes.level

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    logs:
      receivers: [filelog]
      exporters: [debug]

对上述配置的详细解析如下:

  • include:指定接收器关注 /var/log/myapp/ 目录下所有后缀为 .log 的文件。
  • start_at: beginning:确保接收器在首次发现文件时处理整个文件的内容。默认情况下(设置为 end),接收器仅会捕获 Collector 启动后新写入文件的内容。
  • operators:在本示例中,仅配置了一个运算符——json_parser(JSON解析器)。它的作用是将每一行日志按JSON格式进行解析,并将选定的字段提升为日志记录的核心元数据。
  • timestampseverity:在 json_parser 内部,我们从JSON数据中提取 timelevel 字段,并将它们分别提升为每个日志记录的 OpenTelemetry 顶级字段 Timestamp(时间戳)和 Severity*(严重级别相关字段)。

通过 debug 导出器,你可以看到经过解析后的结构化输出。与原始JSON不同,现在每个字段都能在日志记录中得到恰当的呈现:

LogRecord #0
ObservedTimestamp: 2025-09-28 20:48:36.728437503 +0000 UTC
Timestamp: 2025-09-28 20:15:12 +0000 UTC
SeverityText: INFO
SeverityNumber: Info(9)
Body: Str({"time":"2025-09-28 20:15:12","level":"INFO","message":"User logged in successfully","user_id":"u-123","source_ip":"192.168.1.100"})
Attributes:
-> user_id: Str(u-123)
-> source_ip: Str(192.168.1.100)
-> log.file.name: Str(myapp.log)
-> time: Str(2025-09-28 20:15:12)
-> level: Str(INFO)
-> message: Str(User logged in successfully)
Trace ID:
Span ID:
Flags: 0

原始的JSON日志现已转换为 OpenTelemetry 的统一日志数据格式,为跨系统的可观测性奠定了一致的基础。

log.file.name 属性是接收器默认自动添加的,你还可以启用 include_file_path 配置项来同时捕获文件的完整路径:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/myapp/*.log]
    include_file_path: true

这样一来,你就可以轻松地根据日志的精确源路径对日志进行过滤或查询:

Attributes:
-> log.file.path: Str(/var/log/myapp/app.log)
-> log.file.name: Str(app.log)

日志文件的过滤与管理

配置 filelog receiver 最基础的步骤是告知它需要监控哪些文件。这一功能通过 includeexclude 通配符模式来控制。

接收器首先会根据 include 模式生成所有潜在需要监控的文件列表,然后应用 exclude 模式从该列表中移除不需要的文件。

以下是一个示例:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/apps/**/*.log]
    exclude:
      - /var/log/apps/**/debug.log
      - /var/log/apps/**/*.tmp

在这个场景中,接收器会收集 /var/log/apps/ 目录及其所有子目录下的所有 .log 文件,但会跳过名为 debug.log 的文件以及所有后缀为 .tmp 的文件。

按修改时间排除文件

如果你读取的日志目录中包含大量已存在的日志文件,可以通过 exclude_older_than 配置项指示接收器忽略在指定时间窗口内未被修改过的文件:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/myapp/*.log]
    exclude_older_than: 24h
    start_at: beginning

在这个示例中,即使 app-2025-07-15.log 文件匹配 include 模式,但如果它在过去24小时内没有被更新过,也会被接收器跳过。

使用正则表达式解析非结构化文本

大多数基础设施日志并非以规整的JSON格式呈现。更多情况下,它们是遵循松散模式的纯文本字符串,例如Web服务器访问日志、数据库查询日志或操作系统消息日志。这些日志便于人类阅读,但在进行结构化处理之前,机器难以对其进行分析。

Collector 通过 regex_parser(正则表达式解析器)运算符解决了这一问题。利用带有命名捕获组的正则表达式,你可以将原始日志行拆分为有意义的字段,并将它们提升为结构化属性。

例如,考虑以下采用通用日志格式(Common Log Format)的 NGINX 访问日志:

127.0.0.1 - - [28/Sep/2025:20:30:00 +0000] "GET /api/v1/users HTTP/1.1" 200 512
127.0.0.1 - - [28/Sep/2025:20:30:05 +0000] "POST /api/v1/login HTTP/1.1" 401 128

你可以按如下方式配置 regex_parser,将这些日志解析为结构化属性:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/nginx/access.log]
    start_at: beginning
    operators:
      - type: regex_parser
        # 使用命名捕获组提取数据
        regex:
          '^(?P<client_ip>[^ ]+) - - \[(?P<timestamp>[^\]]+)\]
          "(?P<http_method>[A-Z]+) (?P<http_path>[^ "]+)[^"]*"
          (?P<status_code>\d{3}) (?P<response_size>\d+)$'
        # 解析提取到的时间戳
        timestamp:
          parse_from: attributes.timestamp
          layout: "%d/%b/%Y:%H:%M:%S %z"
        # 将状态码映射为严重级别
        severity:
          parse_from: attributes.status_code
          mapping:
            info:
              - min: 200
                max: 399
            warn: 4xx
            error: 5xx

该配置的核心是带有命名捕获组的正则表达式。每个捕获组都会对日志行的一部分进行标记,以便解析器将其转换为属性:

  • client_ip:提取远程地址;
  • timestamp:捕获方括号内的时间字符串;
  • http_methodhttp_path:提取请求相关的部分;
  • status_code:提取三位数字的响应状态码;
  • response_size:记录响应字节数。

一旦这些属性生成,timestamp 字段会将提取到的时间戳字符串解析为标准的日期时间值,而 severity 模块则通过明确的映射规则将状态码转换为有意义的严重级别:2xx 和 3xx 响应对应“INFO”(信息)级别,4xx 响应对应“WARN”(警告)级别,5xx 响应对应“ERROR”(错误)级别。

使用此配置摄入访问日志后,你将看到结构化的日志记录,其中所有重要信息都已提取为属性:

LogRecord #0
ObservedTimestamp: 2025-09-28 21:17:42.31729069 +0000 UTC
Timestamp: 2025-09-28 20:30:00 +0000 UTC
SeverityText: 200
SeverityNumber: Info(9)
Body: Str(127.0.0.1 - - [28/Sep/2025:20:30:00 +0000] "GET /api/v1/users HTTP/1.1" 200 512)
Attributes:
-> status_code: Str(200)
-> response_size: Str(512)
-> log.file.name: Str(myapp.log)
-> client_ip: Str(127.0.0.1)
-> timestamp: Str(28/Sep/2025:20:30:00 +0000)
-> http_method: Str(GET)
-> http_path: Str(/api/v1/users)
Trace ID:
Span ID:
Flags: 0

只需一个正则表达式和几个解析步骤,一条扁平的 NGINX 访问日志就被转换为了结构化的 OpenTelemetry 数据。接下来,一个很自然的操作是通过属性处理器将捕获到的属性与 OpenTelemetry 语义约定对齐。

处理多种日志格式

日志文件的格式往往不止一种。例如,你可能需要摄入 NGINX 日志、数据库日志和应用程序日志,而每种日志都有其独特的格式。

处理这种情况最简洁的方法是为每种文件类型定义一个独立的 filelog receiver。每个接收器都有自己的解析规则,并且独立运行,这样可以使你的配置结构清晰,便于调试。

当不同日志格式之间差异极大、毫无共通之处时,这种方法是最佳选择。

# otelcol.yaml
receivers:
  # NGINX 访问日志接收器
  filelog/nginx_access:
    include: [/var/log/nginx/access.log]
    operators:
      - type: regex_parser
        # ... NGINX 访问日志解析规则

  # NGINX 错误日志接收器
  filelog/nginx_error:
    include: [/var/log/nginx/error.log]
    operators:
      - type: regex_parser
        # ... NGINX 错误日志解析规则

不过,有时同一文件内部也会存在格式差异。例如,大多数日志行可能是简单的消息,但有些行可能会额外包含如 trace_id(追踪ID)这样的字段:

INFO: Application started successfully.
DEBUG: Processing request for trace_id=12345

与其编写一个复杂的正则表达式来覆盖所有情况,不如使用带有 if 条件的运算符:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/app.log]
    operators:
      # 解析所有日志行的基本结构
      - type: regex_parser
        id: base_parser  # 当存在多个同类型运算符时,必须指定唯一ID
        regex: '^(?P<severity>\w+): (?P<message>.*)$'

      # 仅当日志消息中包含 "trace_id" 时,才运行此解析器
      - type: regex_parser
        id: trace_parser
        if: 'attributes["message"] matches "trace_id"'
        parse_from: attributes.message
        regex: '.*trace_id=(?P<trace_id>\w+).*'

上述配置的工作流程如下:

  1. 第一个解析器会对每一行日志进行处理,提取出 severity(严重级别)和 message(消息内容)。
  2. 第二个解析器仅在日志消息中包含 trace_id 时才会运行,从而为日志补充这一额外字段。

通过结合这两种方法——为不相关的日志格式配置多个接收器,为轻微差异的日志格式使用条件解析——你几乎可以处理系统生成的任何类型的日志,而无需创建难以阅读或稳定性差的配置。

处理堆栈跟踪与多行日志

并非所有日志条目都能整齐地容纳在一行中。堆栈跟踪就是一个典型例子:

2025-09-28 21:05:42 [ERROR] Unhandled exception: Cannot read property 'foo' of undefined
TypeError: Cannot read property 'foo' of undefined
    at Object.<anonymous> (/usr/src/app/index.js:15:18)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Module._load (node:internal/modules/cjs/loader:958:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

如果将这些日志直接发送到 Collector,filelog receiver 会将每一行都视为一条独立的日志记录。这显然不是我们想要的结果,因为错误消息和每一行堆栈信息都应该是一个整体。

解决此问题的方法是使用 multiline(多行)配置,该配置会告知接收器如何将多行内容组合成一条日志条目:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/myapp/*.log]
    start_at: beginning

    multiline:
      # 当日志行以 "YYYY-MM-DD HH:MM:SS" 格式开头时,视为新日志条目的开始
      line_start_pattern: ^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}

    operators:
      - type: regex_parser
        regex: (?P<timestamp>\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\[(?P<severity>[A-Za-z]+)\]\s+(?P<message>.+)

        timestamp:
          parse_from: attributes.timestamp
          layout: "%Y-%m-%d %H:%M:%S"

        severity:
          parse_from: attributes.severity

在这里,line_start_pattern(行起始模式)起到了锚点的作用。只有当某一行以 YYYY-MM-DD HH:MM:SS 格式的日期开头时,才会被视为新日志条目的开始;不符合该模式的行都会被追加到前一条日志中。

这样处理后,整个堆栈跟踪信息——从错误消息到每一行 at ... 格式的堆栈帧——都会被捕获为一条结构化的日志记录。这保留了完整的上下文信息,极大地方便了错误分析和故障排查。

LogRecord #0
ObservedTimestamp: 2025-10-07 12:04:26.963143642 +0000 UTC
Timestamp: 2025-09-28 21:05:42 +0000 UTC
SeverityText: ERROR
SeverityNumber: Error(17)
Body: Str(2025-09-28 21:05:42 [ERROR] Unhandled exception: Cannot read property 'foo' of undefined
TypeError: Cannot read property 'foo' of undefined
    at Object.<anonymous> (/usr/src/app/index.js:15:18)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Module._load (node:internal/modules/cjs/loader:958:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47)
Attributes:
-> log.file.name: Str(/var/log/myapp/app.log)
-> message: Str(Unhandled exception: Cannot read property 'foo' of undefined)
-> timestamp: Str(2025-09-28 21:05:42)
-> severity: Str(ERROR)
Trace ID:
Span ID:
Flags: 0

从文件头部解析元数据

有些日志文件不仅包含日志条目,还会在开头部分包含关于整个文件的重要元数据。如果缺少这些上下文信息,单独的日志行可能难以理解。

这种模式在批处理任务和导出过程中很常见。例如,夜间计费任务可能会为每次执行生成一个新的日志文件。该文件的开头可能会包含如下内容:

# Job-ID: job-d8e8fca2
# Job-Type: nightly-billing-run
# Executed-By: scheduler-prod-1
# Records-To-Process: 1500
2025-10-08T08:20:00Z INFO: Starting billing run.
2025-10-08T08:21:15Z INFO: Processed account #1.
2025-10-08T08:21:16Z WARN: Account #2 has a negative balance.
. . .

前几行日志明确指出了生成后续日志的任务信息。如果忽略这些内容,你将丢失这一关键上下文。header(头部)功能通过从文件顶部解析元数据,并将其标记到后续每一条日志记录中,解决了这一问题。

它定义了一个小型的专用流水线,仅对文件开头的部分行进行处理。你需要指定一个正则表达式来匹配哪些行属于头部信息。然后,metadata_operators(元数据运算符)会将这些行解析为属性,并自动添加到后续的每一条日志条目中。

要使用此功能,你需要完成以下三个步骤:

  1. 启用 filelog.allowHeaderMetadataParsing 特性门控:
# docker-compose.yml
services:
  otelcol:
    command:
      [
        "--config=/etc/otelcol-contrib/config.yaml",
        "--feature-gates=filelog.allowHeaderMetadataParsing",
      ]
  1. 设置 start_at: beginning,因为必须从文件顶部读取头部信息。

  2. 同时配置 header 规则和主 operators(运算符)流水线。

以下是解析示例日志文件头部信息的配置:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/jobs/*.log]
    start_at: beginning  # 必需配置
    header:
      pattern: ^#
      metadata_operators:
        - type: key_value_parser
          delimiter: ": "
          pair_delimiter: "# "

上述配置的工作原理如下:

  • pattern: ^# 表示所有以 # 开头的行都属于头部信息。这些头部行随后会经过 metadata_operators 流水线的处理。
  • key_value_parser(键值解析器)运算符会以 : 作为分隔符,将每一行头部信息拆分为键和值;同时,# 被用作新键值对的起始标记。

这样处理后,该文件中后续的每一条日志条目都会包含以下属性:

Attributes:
-> Job-ID: Str(job-d8e8fca2)
-> Job-Type: Str(nightly-billing-run)
-> Executed-By: Str(scheduler-prod-1)
-> Records-To-Process: Str(1500)

如你所见,Job-ID 和其他头部字段现在已附加到日志记录中,提供了原本可能会丢失的宝贵上下文信息。

在此基础上,你可以进一步处理这些信息,例如将头部字段提升为资源属性,并与 OpenTelemetry 语义约定对齐。

如何避免日志丢失或重复

当 Collector 重启时,如果状态未得到保存,日志摄入很容易出现问题——可能会重新摄入旧数据,也可能会遗漏新日志。如果使用 start_at: beginning,接收器会重新读取所有日志文件,导致大量重复日志;如果使用 start_at: end,则可能会遗漏 Collector 停机期间写入的日志条目。

解决此问题的方法是使用 checkpointing(检查点)功能。通过配置存储扩展,你可以指示 filelog receiver 将其在每个文件中的读取位置(最后读取的偏移量)保存到磁盘,以便在重启后能精确地从上次停止的位置继续读取。

一种常用的方法是使用 file_storage(文件存储)扩展来实现此目的:

# otelcol.yaml
extensions:
  file_storage:
    directory: /var/otelcol/storage

receivers:
  filelog:
    include: [/var/log/myapp/*.log]
    start_at: beginning
    # 将接收器与存储扩展关联
    storage: file_storage

# ... 处理器、导出器配置

service:
  # 必须在服务部分启用该扩展
  extensions: [file_storage]
  pipelines:
    logs:
      receivers: [filelog]
      # ...

启用存储扩展后,接收器会执行以下操作:

  1. 启动时,检查 /var/otelcol/storage 目录,获取已保存的偏移量信息。
  2. 对于之前跟踪过的文件,从保存的偏移量位置继续读取,确保不会丢失数据或产生重复数据。
  3. 定期更新存储中的最新读取进度。

检查点功能确保了日志收集在重启、升级甚至崩溃等情况下都具有韧性,是实现可靠日志摄入的关键最佳实践。

优雅处理日志发送失败

通过存储扩展实现检查点功能可以在 Collector 重启时保护日志数据,但另一种常见的故障场景是:接收器成功读取了一批日志,但无法将其传递到下一个处理阶段。

这种情况可能发生在导出器无法连接到其端点,或者内存限制器拒绝接收数据时。在默认情况下,接收器会丢弃这一批日志并继续处理下一批,从而导致无声的数据丢失。

为防止这种情况发生,接收器内置了重试机制来重新发送失败的日志批次。启用 retry_on_failure(失败重试)后,接收器会暂停一段时间,等待配置的间隔后,尝试重新发送同一批日志。此过程会以指数退避的方式重复,直到日志批次成功发送或达到 max_elapsed_time(最大持续时间):

# otelcol.yaml
receivers:
  filelog:
    retry_on_failure:
      enabled: true
      # 首次失败后,等待5秒再进行第一次重试
      initial_interval: 5s
      # 重试之间的最长等待时间为30秒
      max_interval: 30s
      # 尝试发送一批日志的最长时间为10分钟,超时后停止重试
      max_elapsed_time: 10m

通过将检查点功能与稳健的重试策略相结合,你可以构建一个高韧性的日志文件摄入流水线,该流水线能够应对 Collector 重启以及下游系统临时中断或限流等情况。

处理完日志后删除文件

在某些工作流程中,需要在处理完日志文件后将其删除,以节省磁盘空间并避免重复处理。你可以通过启用 delete_after_read(读取后删除)功能来实现这一点,但该功能要求同时设置 start_at: beginning

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/archives/*.gz]
    start_at: beginning
    delete_after_read: true

要使此功能生效,你还必须启用 filelog.allowFileDeletion 特性门控:

# docker-compose.yml
services:
  otelcol:
    command:
      [
        "--config=/etc/otelcol-contrib/config.yaml",
        "--feature-gates=filelog.allowFileDeletion",
      ]

最后,请确保配置的文件是可删除的,并且 Collector 服务具有删除这些文件的足够权限。如果权限不足,你会看到一条“无法删除”的日志记录:

2025-10-08T06:42:03.973Z        error   reader/reader.go:278    could not delete        {"resource": {"service.instance.id": "7c0daf0e-e625-4da8-9577-072606dce057", "service.name": "otelcol-contrib", "service.version": "0.136.0"}, "otelcol.component.id": "filelog", "otelcol.component.kind": "receiver", "otelcol.signal": "logs", "component": "fileconsumer", "path": "/var/log/myapp/app.log", "filename": "/var/log/myapp/app.log"}

请注意,启用此设置时需谨慎,因为它会从磁盘上永久删除文件。

无缝处理日志轮转

日志文件不会无限增长,最终会进行轮转(至少理论上应该如此)。filelog receiver 具备处理常见日志轮转模式的能力,例如能自动将 app.log 轮转为 app.log.1,且不会丢失任何数据。

接收器并非仅依赖文件名来跟踪文件,而是通过从文件前几千字节内容中提取的唯一指纹来识别每个文件。当发生日志轮转时,接收器会识别出原始文件已被重命名,它会先完成对原始文件的读取,然后从新的 app.log 文件开头开始重新读取。

此功能无需额外配置,开箱即用,即使在日志频繁轮转的环境中,也能确保日志摄入的可靠性。

读取压缩文件

许多日志轮转工具会对旧日志进行压缩以节省磁盘空间,生成如 access.log.1.gz 这样的压缩文件。filelog receiver 能够通过即时解压缩的方式无缝处理这些压缩文件。

要实现这一功能,你需要使用 compression(压缩)配置项。该配置项会告知接收器,它所发现的部分或全部文件可能经过压缩,需要在解析前进行解压缩。

compression 配置项主要有两个可选值:

  • gzip:将所有匹配的文件都视为 gzip 压缩文件。
  • auto:根据文件扩展名(目前仅支持 .gz)自动检测文件是否压缩。当目录中同时包含活跃的未压缩日志和旧的压缩日志时,这是最佳选择。

例如,如果你的目录中同时存在 app.log(活跃日志,未压缩)和 app.log.1.gz(已轮转的压缩日志),可以按以下方式配置接收器:

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/myapp/*]
    start_at: beginning
    # 自动检测并解压缩 .gz 文件
    compression: auto
    operators:
      - type: regex_parser
        # ... 你的解析规则

在处理压缩日志时,有两个重要事项需要注意:

首先,接收器假设压缩文件只能通过追加新数据的方式增大。如果一个文件被完全重写(例如,将原始内容与新行重新压缩在一起),接收器可能无法正确处理。

其次,涉及文件指纹识别的问题。默认情况下,接收器根据文件的压缩字节来识别文件。在大多数情况下,这种方式工作正常,但如果文件被重命名或移动,可能会导致识别混乱。为了提高识别的可靠性,你可以启用 filelog.decompressFingerprint 特性门控。启用此功能后,指纹将根据解压缩后的文件内容计算得出:

# docker-compose.yml
services:
  otelcol:
    command:
      [
        "--config=/etc/otelcol-contrib/config.yaml",
        "--feature-gates=filelog.decompressFingerprint",
      ]

需要注意的一点是:如果在现有配置中启用此功能,文件指纹将会发生变化,这意味着已经读取过的压缩文件可能会被重新摄入。

高容量环境下的性能调优

OTel Collector 的 filelog receiver 默认设置针对通用场景进行了优化,但在包含数百个日志文件或吞吐量极高的生产环境中,你可能需要对其性能进行调优。

在默认配置下,接收器会尝试同时从所有匹配的文件中读取数据。在产生数千个文件的系统上,这可能会占用大量 CPU 资源,并迅速达到文件句柄限制。

max_concurrent_files(最大并发文件数)配置项会对同时读取的文件数量设置上限。默认值为 1024,但降低此值可以避免系统资源被过度占用。

另一个关键配置项是 poll_interval(轮询间隔),它控制接收器检查新文件和新日志行的频率。默认轮询间隔为 200 毫秒,这意味着日志几乎能立即被捕获,但由于需要更频繁地扫描文件系统,CPU 使用率也会相应升高。

对于非关键日志或资源受限的环境,将轮询间隔提高到 1 秒甚至 5 秒是一个不错的权衡方案——这将减少轮询带来的开销,同时对大多数场景下的可观测性影响微乎其微。

最后,max_log_size(最大日志大小)配置项用于防范异常大的日志条目。它定义了允许的最大日志条目大小,超过此大小的日志条目将被截断。默认值为 1MiB(1 兆字节),这对于大多数工作负载来说是一个合理的默认设置。

# otelcol.yaml
receivers:
  filelog/k8s_pods:
    include: [/var/log/pods/*/*/*.log]
    max_concurrent_files: 200
    poll_interval: 1s
    max_log_size: 2MiB

强制日志文件处理顺序

大多数情况下,日志文件的摄入顺序并不重要。但有些系统会生成一系列按顺序排列的日志文件,这些文件的处理顺序至关重要。

在默认情况下,filelog receiver 会并发读取所有匹配的文件,这可能导致文件处理顺序混乱。ordering_criteria(排序标准)配置项通过在读取文件时强制执行严格的顺序,解决了这一问题。

例如,假设有一组遵循以下命名规则的日志文件:

batch-run-001.log
batch-run-002.log
batch-run-003.log

你可以按以下方式配置接收器:

# otelcol.yaml
receivers:
  filelog/batch_logs:
    include: [/var/log/batch-runs/batch-run-*.log]
    start_at: beginning
    ordering_criteria:
      top_n: 1
      # 从文件名中提取序列号
      regex: batch-run-(?P<seq_num>\d+)\.log
      # 按序列号(数值类型,非字符串类型)对文件进行升序排序
      sort_by:
        - regex_key: seq_num
          sort_type: numeric
          ascending: true

通过此配置,接收器会发现所有匹配 batch-run-*.log 模式的文件,从每个文件名中提取序列号,并根据该序列号对文件进行数值升序排序。

top_n 属性用于确定应用排序标准后将跟踪的文件数量。设置 top_n: 1 时,仅会跟踪并摄入排序后的第一个文件(即 batch-run-001.log)。

Filelog Receiver 提示与最佳实践

在排查 filelog receiver 问题时,有些问题会反复出现。以下是快速诊断和解决这些问题的方法:

日志文件未被监控

当 Collector 开始监控某个文件以获取日志条目时,你会在其输出中看到如下日志:

2025-10-09T08:47:05.574Z        info    fileconsumer/file.go:261        Started watching file   {...}

如果你没有看到此消息,或者看到以下日志,则表明接收器尚未识别到任何文件:

2025-10-09T09:25:20.280Z        warn    fileconsumer/file.go:49 finding files   {..., "error": "no files match the configured criteria"}

首先,请仔细检查你的 includeexcludeexclude_older_than 设置,确保文件模式能正确匹配你期望监控的文件。

其次,验证 Collector 进程是否具有访问这些文件及其父目录的权限。目录级权限缺失是导致文件无法被发现或监控的最常见原因之一。

文件已被监控,但未读取到日志行

如果你能看到“Started watching file”(开始监控文件)的消息,但未收集到任何日志,最常见的原因是 start_at 设置。默认情况下,start_at 被设置为 end,这意味着接收器仅会从 Collector 启动后追加到文件的新行开始读取。

当你使用一个已存在且不再有新内容写入的文件进行测试时,就会出现没有日志被收集的情况。要从文件开头读取全部内容,请将 start_at 设置为 beginning

# otelcol.yaml
receivers:
  filelog:
    start_at: beginning

这一设置确保接收器在首次发现文件时,会处理该文件的所有现有内容。

正则表达式无法匹配日志行

如果日志未能正确解析,问题通常出在正则表达式上。当出现这种情况时,Collector 通常会记录如下错误日志:

2025-10-09T09:32:14.949Z        error   helper/transformer.go:154       Failed to process entry {"resource": {"service.instance.id": "f8ec2efd-16e9-44ad-9ed2-9f406e46719f", "service.name": "otelcol-contrib", "service.version": "0.136.0"}, "otelcol.component.id": "filelog", "otelcol.component.kind": "receiver", "otelcol.signal": "logs", "operator_id": "regex_parser", "operator_type": "regex_parser", "error": "regex pattern does not match", "action": "send", "entry.timestamp": "0001-01-01T00:00:00.000Z", "log.file.name": "batch-run-001.log"}

在调整 Collector 配置之前,建议先使用 Regex101 等工具在外部测试正则表达式。请确保选择 Golang 语法,以保证其行为与 Collector 的正则表达式引擎一致。

Regex101

如果你没有看到此错误,但正则表达式仍然无法正常工作,请检查 on_error 参数是否设置为某个 _quiet(静默)模式。这些模式会抑制运算符错误,除非将 Collector 的日志级别设置为 DEBUG(调试)级别。

导致正则表达式匹配失败的常见原因包括:不可见的空格或制表符、缺少锚点(^$)、转义错误,或者日志格式与正则表达式模式之间存在细微差异。在进一步排查之前,请仔细检查这些细节。

重启后日志重复

如果你发现在 Collector 重启后出现重复日志,通常意味着接收器没有记住上次读取的位置。要解决此问题,需启用存储扩展,以便 filelog receiver 能够在每个文件中设置检查点,记录其读取位置。

这样一来,接收器就能从上次停止的位置精确地继续读取,从而防止数据丢失和重复。如果不启用此功能,接收器在每次重启后都会从文件开头重新读取全部内容。

总结

OpenTelemetry 中的 filelog receiver 是连接传统基于文件的日志(通常为非结构化数据)与现代结构化可观测性体系的重要桥梁。

通过掌握其核心概念——文件发现、使用运算符解析日志以及检查点功能,你可以为任何将日志写入文件的服务构建可靠的日志摄入流水线。

一旦将原始文本日志转换为结构良好的 OpenTelemetry 数据,整个可观测性生态系统就会为你敞开大门。你可以对这些日志进行丰富、过滤,并将其路由到任何支持 OTLP(OpenTelemetry Protocol,OpenTelemetry 协议)的后端系统。

原文:https://www.dash0.com/guides/opentelemetry-filelog-receiver

快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云
OpenSource
开源版
Flashcat
Flashcat