Today

OpenTelemetry Filelog Receiver: руководство по приему лог-файлов

Перевод оригинальной статьи OpenTelemetry Filelog Receiver: A Guide to Ingesting Log Files.

Перевод сделан специально для телеграм-канала Мониторим ИТ. Подписывайтесь! Там еще больше полезных постов о мониторинге.

Даже в эпоху облачных приложений и распределенного трейсинга обычные файлы логов остаются одним из самых ценных источников достоверной информации в любой системе. От корпоративных приложений и пакетных заданий до NGINX, баз данных и локальной инфраструктуры — критически важная диагностическая информация по-прежнему записывается на диск.

Получатель filelog в OpenTelemetry Collector позволяет интегрировать эти логи в современный конвейер наблюдаемости. Он непрерывно отслеживает файлы, анализирует их содержимое и преобразует необработанный текст в структурированные записи журналов OpenTelemetry.

В этой статье показано, как использовать эти возможности на практике, от базового чтения файла до построения готового к эксплуатации конвейера, который корректно обрабатывает ротацию логов, восстанавливает работу после перезапуска и не теряет ни одной строки.

Прежде чем углубляться: если вы контролируете приложения, записывающие эти логи, наилучшим решением будет настроить их на вывод структурированных JSON-логов, размещённых в одной строке. Чтобы получатель filelog в этом случае стал простым и удобным уровнем приема данных.

Значительная часть этой статьи посвящена ситуациям, когда такой вариант невозможен. В подобных случаях получатель filelog предоставляет мощные инструменты для анализа, структурирования и обогащения любых получаемых логов, превращая их в полноценные данные наблюдаемости.

Начнём!

Как работает получатель filelog

Прежде чем перейти к деталям конфигурации, полезно представить, как получатель обрабатывает файл логов на протяжении всего своего жизненного цикла. Можно представить это как простой повторяющийся четырехэтапный цикл:

  1. Обнаружение (Discover): получатель через заданные интервалы времени сканирует файловую систему, используя настроенные шаблоны include and exclude, чтобы определить, какие файлы логов необходимо отслеживать.
  2. Чтение (Read): после обнаружения файла получатель открывает его и начинает отслеживать появление новых строк. Параметр start_at определяет, следует ли начинать чтение с beginning или отслеживать только новые данные, начиная с end.
  3. Разбор (Parse): каждая строка (или блок строк, если используется разбор многострочных записей) проходит через последовательность операторов Stanza(если они настроены). Эти операторы разбирают необработанный текст, извлекают ключевые атрибуты, назначают временные метки и уровни критичности, а затем формируют структурированные данные логов.
  4. Передача (Emit) в завершение структурированные записи логов передаются в конвейер OpenTelemetry Collector, где они могут быть отфильтрованы, дополнительно преобразованы или экспортированы в серверную часть.

Цикл Discover -> Read -> Parse -> Emit лежит в основе всего сбора данных.

Быстрый старт: отслеживание файла логов

Одним из наиболее распространённых сценариев является ситуация, когда приложение уже записывает логи в формате JSON в файл. Например, представим сервис, который записывает JSON-логи в файл /var/log/myapp/app.log:

json

{"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, который считывает такие логи и передаёт их в конвейер OpenTelemetry.

# otelcol.yaml
receivers:
  filelog:
    # 1. DISCOVER all .log files in /var/log/myapp/
    include: [/var/log/myapp/*.log]
    # 2. READ from the beginning of new files
    start_at: beginning
    # 3. PARSE using the json_parser operator
    operators:
      - type: json_parser
        # Tell the parser where to find the timestamp and how it's formatted
        timestamp:
          parse_from: attributes.time
          layout: "%Y-%m-%d %H:%M:%S"
        # Tell the parser which field contains the severity
        severity:
          parse_from: attributes.level

exporters:
  debug:
    verbosity: detailed

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

Ниже приведено объяснение приведённой выше конфигурации.

  • include: указывает получателю все файлы .log в каталоге /var/log/myapp/.
  • start_at: beginning: гарантирует, что при первом обнаружении файла получатель обработает его полностью. По умолчанию используется значение end, при котором будут считываться только новые строки, записанные после запуска Collector.
  • operators: в данном случае он всего один: json_parser. Его задача заключается в том, чтобы интерпретировать каждую строку лога как JSON, а затем перенести выбранные поля в основную метаинформацию записи лога.
  • timestamp и severity: внутри json_parser из JSON извлекаются поля time и level и преобразуются в поля верхнего уровня 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)

Дополнительные возможности обогащения данных описаны в официальной документации OpenTelemetry Filelog Receiver.

Фильтрация и управление файлами логов

Наиболее важным шагом в настройке настройке получателя filelog является указание того, какие файлы необходимо отслеживать. Это настраивается с помощью шаблонов include и exclude.

Сначала получатель использует шаблоны include для формирования списка всех потенциальных файлов, после чего применяет шаблоны exclude, исключая из этого списка нежелательные файлы.

Пример:

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

В данном случае получатель будет собирать все файлы .log в каталоге /var/log/apps/, включая все вложенные каталоги, но пропустит любой файл с именем 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 соответствует шаблону, он будет пропущен, если не изменялся в течение последних 24 часов.

Разбор неструктурированного текста с помощью регулярных выражений

Большинство инфраструктурных логов не записываются в аккуратном формате JSON. Гораздо чаще они представляют собой обычный текст, соответствующий некоторому шаблону, например логи доступа веб-сервера, логи запросов к базе данных или сообщения операционной системы. Такие логи легко читаются человеком, но их сложно анализировать автоматически, пока им не будет придана структура.

Для решения этой задачи Collector предоставляет оператор regex_parser. Используя регулярные выражения с именованными группами захвата, вы можете разбить исходную строку лога на осмысленные поля и преобразовать их в структурированные атрибуты.

Например, рассмотрим лог доступа NGINX в в формате Common Log Format.

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
        # Use named capture groups to extract data
        regex:
          '^(?P<client_ip>[^ ]+) - - \[(?P<timestamp>[^\]]+)\]
          "(?P<http_method>[A-Z]+) (?P<http_path>[^ "]+)[^"]*"
          (?P<status_code>\d{3}) (?P<response_size>\d+)#39;
        # Parse the extracted timestamp
        timestamp:
          parse_from: attributes.timestamp
          layout: "%d/%b/%Y:%H:%M:%S %z"
        # Map status codes to severities
        severity:
          parse_from: attributes.status_code
          mapping:
            info:
              - min: 200
                max: 399
            warn: 4xx
            error: 5xx

В основе данной конфигурации лежит регулярное выражение regex с именованными группами захвата. Каждая группа выделяет определённую часть строки, чтобы анализатор мог преобразовать её в атрибут. Группа client_ip извлекает IP-адрес клиента, timestamp — временную метку в квадратных скобках, http_method и http_path — HTTP-метод и путь запроса, status_code — трёхзначный код ответа, а response_size — размер ответа в байтах.

После создания этих атрибутов параметр timestamp преобразует строковое значение timestamp в корректное значение даты и времени, а блок severity преобразует коды состояния в соответствующие уровни критичности с помощью явного сопоставления (mapping): ответы 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. Следующим логичным шагом является приведение полученных атрибутов в соответствие с семантическими соглашениями HTTP с помощью процессоров attributes или transform.

Обработка нескольких форматов логов

Файлы логов редко имеют единый формат. Например, может потребоваться одновременно принимать логи NGINX, логи PostgreSQL и приложения, каждый из которых использует собственный формат.

Наиболее правильный подход — определить отдельный получатель filelog для каждого типа файлов. Каждый получатель использует собственные правила разбора и работает независимо, что делает конфигурацию более понятной и значительно упрощает её отладку.

Это лучший вариант, когда форматы логов полностью различаются и не имеют ничего общего.

# otelcol.yaml
receivers:
  # NGINX access logs
  filelog/nginx_access:
    include: [/var/log/nginx/access.log]
    operators:
      - type: regex_parser
        # ... NGINX access log parsing rules

  # NGINX error logs
  filelog/nginx_error:
    include: [/var/log/nginx/error.log]
    operators:
      - type: regex_parser
        # ... NGINX error log parsing rules

Однако иногда различия встречаются внутри одного и того же файла.

Например, большинство строк могут содержать только простые сообщения, тогда как некоторые дополнительно содержат поле trace_id.

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

Вместо создания одного большого регулярного выражения, охватывающего все возможные варианты, можно использовать условные операторы с параметром if.

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/app.log]
    operators:
      # Parse the basic structure of every line
      - type: regex_parser
        id: base_parser # a unique ID is required when multiple operators of the same type is being used
        regex: '^(?P<severity>\w+): (?P<message>.*)#39;

      # Only run this parser when "trace_id" appears
      - type: regex_parser
        id: trace_parser
        if: 'attributes["message"] matches "trace_id"'
        parse_from: attributes.message
        regex: '.*trace_id=(?P<trace_id>\w+).*'

Вот что происходит:

  • Первый анализатор выполняется для каждой строки лога и извлекает поля severity и message.
  • Второй анализатор выполняется только в том случае, если сообщение содержит 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 будет рассматривать каждую строку как отдельную запись лога. Это не соответствует ожидаемому поведению, поскольку сообщение об ошибке и каждый фрейм стек-трейса относятся к одной записи.

Если вы контролируете источник логов, лучшим решением будет настроить его таким образом, чтобы исключения и трассировки стека сериализовались в одно строковое поле JSON (например, exception.stacktrace), а не записывались в несколько строк.

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

Но если изменить формат логов невозможно, можно использовать приведённую ниже конфигурацию multiline, которая указывает получателю, как объединять несколько строк в одну запись LogRecord.

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

    multiline:
      # New entry starts when a line begins with "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. Включить feature gate 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 # required
    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). Настроив расширение storage, вы указываете получателю filelog сохранять свою позицию в каждом файле (смещение последней прочитанной записи) на диск и после перезапуска продолжать чтение именно с этого места.

Наиболее распространённым способом является использование расширения расширения file_storage.

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

receivers:
  filelog:
    include: [/var/log/myapp/*.log]
    start_at: beginning
    # Link the receiver to the storage extension
    storage: file_storage

# ... processors, exporters

service:
  # The extension must be enabled in the service section
  extensions: [file_storage]
  pipelines:
    logs:
      receivers: [filelog]
      # ...

После включения расширения storage получатель будет выполнять следующие действия.

  1. При запуске проверять каталог /var/otelcol/storage на наличие сохранённых смещений.
  2. Продолжать чтение каждого отслеживаемого файла с ранее сохранённого смещения, гарантируя отсутствие потери и дублирования данных.
  3. Периодически обновлять информацию о своём текущем положении в хранилище.

Использование контрольных точек обеспечивает устойчивость процесса сбора логов к перезапускам, обновлениям и даже аварийным завершениям работы. Это является одной из важнейших рекомендаций при построении надёжного конвейера приема логов.

Корректная обработка ошибок доставки логов

Использование контрольных точек совместно с расширением storage защищает данные при перезапуске Collector, однако существует ещё один распространённый сценарий сбоя: получатель успешно считывает пакет логов, но не может передать его следующему компоненту конвейера.

Это может произойти, если экспортёр не может установить соединение со своей конечной точкой или если процессор memory_limiter отклоняет данные. По умолчанию получатель просто отбросит этот пакет логов и перейдёт к следующему, что приведёт к незаметной потере данных.

Чтобы избежать этого, получатель имеет встроенный механизм повторной отправки неудачно переданных пакетов. Если включить параметр retry_on_failure, получатель приостанавливает работу, ожидает заданный интервал времени и повторяет попытку отправить тот же самый пакет логов. Этот процесс повторяется с использованием экспоненциального увеличения интервала ожидания (exponential backoff) до тех пор, пока пакет не будет успешно отправлен либо пока не будет достигнуто значение max_elapsed_time.

# otelcol.yaml
receivers:
  filelog:
    retry_on_failure:
      enabled: true
      # Wait 5 seconds after the first failure before the first retry.
      initial_interval: 5s
      # The longest the receiver will wait between retries is 30 seconds.
      max_interval: 30s
      # Give up trying to send a batch after 10 minutes.
      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

Чтобы эта возможность работала, необходимо также включить feature gate filelog.allowFileDeletion.

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

Наконец, убедитесь, что файлы могут быть удалены и что служба Collector обладает достаточными правами для их удаления. Если прав недостаточно, в логах появится запись "could not delete":

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 способен автоматически обрабатывать распространённые схемы ротации, например переименование app.log в app.log.1, без потери данных.

Вместо того чтобы полагаться исключительно на имя файла, получатель отслеживает каждый файл с помощью уникального отпечатка (fingerprint), вычисленного на основе первых нескольких килобайт его содержимого. Когда происходит ротация, получатель определяет, что исходный файл был переименован, завершает его чтение, после чего начинает работу с новым файлом app.log с самого начала.

Такое поведение не требует дополнительной настройки и работает сразу после установки, обеспечивая надёжный прием логов даже в средах с частой ротацией файлов.

Чтение сжатых файлов

Многие инструменты ротации логов сжимают старые файлы для экономии дискового пространства, создавая файлы вида access.log.1.gz. Получатель filelog способен работать с такими файлами, автоматически распаковывая их в процессе чтения.

Для этого используется параметр compression. Он сообщает получателю, что некоторые или все обнаруженные файлы могут быть сжаты и перед разбором должны быть распакованы.

Для параметра compression доступны два основных значения:

  • gzip: считать все найденные файлы сжатыми в формате gzip.
  • auto: автоматически определять наличие сжатия по расширению файла (в настоящее время поддерживается расширение .gz). Этот вариант является наиболее предпочтительным, если каталог содержит одновременно активные несжатые логи и старые сжатые файлы.

Например, если каталог содержит файлы app.log (активный) и app.log.1.gz (ротированный и сжатый), получатель можно настроить следующим образом.

# otelcol.yaml
receivers:
  filelog:
    include: [/var/log/myapp/*]
    start_at: beginning
    # Automatically detect and decompress .gz files
    compression: auto
    operators:
      - type: regex_parser
        # ... your parsing rules

При работе со сжатыми логами следует помнить о двух основных вещах.

Во-первых, получатель предполагает, что сжатые файлы могут увеличиваться только путём добавления новых данных в конец. Если файл полностью переписывается, например исходное содержимое заново архивируется вместе с новыми строками, получатель может обработать его некорректно.

Во-вторых, необходимо учитывать особенности формирования хэш-сумм файлов. По умолчанию получатель идентифицирует файлы по их сжатому содержимому. В большинстве случаев этого достаточно, однако при переименовании или перемещении файлов это может привести к неоднозначности. Для повышения надёжности идентификации можно включить feature gate filelog.decompressFingerprint. В этом случае отпечаток будет вычисляться по уже распакованному содержимому файла:

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

Одно важное замечание: если включить эту возможность в уже существующей системе, отпечатки файлов изменятся. Это означает, что ранее обработанные сжатые файлы могут быть повторно приняты получателем.

Настройка производительности для сред с высокой нагрузкой

Настройки получателя filelog в OTel Collector по умолчанию оптимизированы для общего использования, однако в производственных средах с сотнями файлов логов или очень высокой интенсивностью поступления данных, скорее всего, потребуется дополнительная настройка производительности.

По умолчанию получатель пытается одновременно читать все найденные файлы. В системе, создающей тысячи файлов, это может привести к чрезмерной загрузке процессора и быстрому достижению лимита открытых файловых дескрипторов.

Параметр max_concurrent_files ограничивает количество файлов, читаемых одновременно. По умолчанию его значение равно 1024, однако уменьшение этого значения позволяет предотвратить чрезмерную нагрузку на систему.

Другим важным параметром является poll_interval, который определяет, как часто получатель проверяет наличие новых файлов и новых строк логов. По умолчанию используется значение 200ms, благодаря чему новые логи появляются практически мгновенно, однако при этом возрастает нагрузка на процессор, поскольку файловая система сканируется чаще.

Для менее критичных логов или сред с ограниченными ресурсами увеличение этого значения до 1s или даже 5s может стать хорошим компромиссом, поскольку уменьшит нагрузку, связанную с периодическим опросом файловой системы, практически без заметного влияния на наблюдаемость в большинстве сценариев.

Наконец, защита от чрезмерно больших записей логов обеспечивается параметром max_log_size. Он определяет максимально допустимый размер записи лога. Если запись превышает этот размер, она будет усечена. По умолчанию используется значение 1MiB, которое подходит для большинства рабочих нагрузок.

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

Обеспечение порядка обработки файлов логов

В большинстве случаев порядок приема файлов логов не имеет значения. Однако некоторые системы формируют логи в виде последовательности файлов, для которых порядок обработки является критически важным.

По умолчанию получатель filelog читает все соответствующие шаблону файлы одновременно, поэтому их обработка может происходить не по порядку. Параметр 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
      # Extract the sequence number from the filename
      regex: batch-run-(?P<seq_num>\d+)\.log
      # Sort files by the sequence number as a number, not a string
      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 некоторые проблемы возникают особенно часто. Ниже приведены рекомендации по их быстрой диагностике и устранению.

Файлы логов не отслеживаются

Когда 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"}

В первую очередь необходимо проверить параметры include, excludeи exclude_older_than, чтобы убедиться, что заданные шаблоны действительно соответствуют ожидаемым файлам.

Затем следует убедиться, что процесс Collector обладает правами доступа как к самим файлам, так и к каталогам, в которых они расположены. Отсутствие прав доступа к каталогам является одной из наиболее распространённых причин, по которым файлы не обнаруживаются и не начинают отслеживаться.

Файлы отслеживаются, но строки логов не считываются

Если сообщения "Started watching file" присутствуют, но логи не поступают, наиболее вероятной причиной является параметр 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.

Если данная ошибка не появляется, но регулярное выражение по-прежнему не работает, следует проверить, не установлен ли параметр on_error в один из режимов _quiet. Эти значения подавляют сообщения об ошибках операторов, если уровень логирования Collector не установлен в значение DEBUG.

Наиболее распространёнными причинами несоответствия регулярного выражения являются невидимые пробелы или символы табуляции, отсутствие якорей (^ или $), неправильное экранирование символов либо небольшие различия между фактическим форматом лога и шаблоном регулярного выражения. Прежде чем искать более сложные причины, следует внимательно проверить именно эти моменты.

После перезапуска появляются дублирующиеся логи

Если после перезапуска Collector появляются дублирующиеся записи логов, это обычно означает, что получатель не запоминает место, на котором остановился. Для устранения этой проблемы необходимо включить расширение storage, чтобы получатель filelog мог сохранять контрольные точки своего положения в каждом файле.

Это позволит получателю продолжить чтение точно с того места, где оно было остановлено, предотвращая как потерю данных, так и их дублирование. Без использования расширения storage после каждого перезапуска получатель будет заново считывать все файлы целиком.

Заключительные мысли

Получатель filelog в OpenTelemetry является важным связующим звеном между традиционным ведением логов в файлы (зачастую содержащие неструктурированные данные) и современным миром структурированной наблюдаемости.

Освоив его основные принципы — обнаружение файлов, разбор данных с помощью операторов и использование контрольных точек — вы сможете построить надёжный конвейер приема логов для любого сервиса, записывающего логи в файл.

После преобразования необработанных текстовых логов в хорошо структурированные данные OpenTelemetry становятся доступны все возможности экосистемы наблюдаемости. Вы сможете обогащать, фильтровать и маршрутизировать их в любую систему, поддерживающую протокол OTLP.

Подписывайтесь на телеграм-канал Мониторим ИТ, там еще больше полезной информации о мониторинге!