从文件中收集符合OpenTelemetry规范的Java日志

博客文章在发布后不会更新。这篇文章已经发布一年多了,其内容可能已过时,部分链接可能无效。在依赖任何信息之前,请务必核实。

如果您想将 Java 应用程序的日志摄取到兼容 OpenTelemetry 的日志后端,最简单且推荐的方法是使用 OpenTelemetry 协议 (OTLP) 导出器。然而,由于组织或可靠性需求,某些场景要求将日志输出到文件或 stdout。

集中日志的一种常用方法是使用非结构化日志,通过正则表达式进行解析,然后添加上下文属性。

然而,正则表达式解析存在问题。当处理所有日志字段、异常中的换行符以及意外的日志格式更改时,它们会迅速变得复杂且脆弱。使用此方法不可避免地会出现解析错误。

Java 日志的本地解决方案

OpenTelemetry Java Instrumentation 代理和 SDK 现在提供了一种简单的解决方案,可将 SLF4J/Logback 或 Log4j2 等框架的日志转换为符合 OTel 标准的 OTLP/JSON 日志输出到 stdout,并包含所有资源和日志属性。

这是一个真正的即插即用解决方案

  • 无需更改代码或依赖项,只需进行一些典型的生产部署配置调整。
  • 日志收集器中没有复杂的字段映射。只需使用 OTLP/JSON 连接器 来摄取有效负载。
  • 日志、跟踪和指标之间的自动关联。

这篇博文将逐步演示如何设置此解决方案。

  • 在第一部分,我们将展示如何配置 Java 应用程序以 OTLP/JSON 格式输出日志。
  • 在第二部分,我们将展示如何配置 OpenTelemetry Collector 来摄取日志。
  • 最后,我们将展示一个特定于 Kubernetes 的设置来处理容器日志。
OTLP/JSON Architecture

配置 Java 应用程序输出 OTLP/JSON 日志

无需代码更改。继续使用您喜欢的日志记录库,包括模板化日志、映射诊断上下文和结构化日志记录。

Logger logger = org.slf4j.LoggerFactory.getLogger(MyClass.class);
...
MDC.put("customerId", customerId);

logger.info("Order {} successfully placed", orderId);

logger.atInfo().
   .addKeyValue("orderId", orderId)
   .addKeyValue("outcome", "success")
   .log("placeOrder");

使用 OTel JSON 格式(也称为 OTLP/JSON)将 OTel Java 代理捕获的日志导出到 stdout。用于 LogbackLog4j 的配置参数是可选的,但推荐使用。

# Tested with opentelemetry-javaagent v2.10.0
#
# Details on -Dotel.logback-appender.* params on documentation page:
# https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-appender-1.0/javaagent

java -javaagent:/path/to/opentelemetry-javaagent.jar \
     -Dotel.logs.exporter=experimental-otlp/stdout \
     -Dotel.instrumentation.logback-appender.experimental-log-attributes=true \
     -Dotel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes=true \
     -Dotel.instrumentation.logback-appender.experimental.capture-mdc-attributes=* \
     -jar /path/to/my-app.jar

-Dotel.logs.exporter=experimental-otlp/stdout JVM 参数和环境变量 OTEL_LOGS_EXPORTER="experimental-otlp/stdout" 可以互换使用。

验证 OTLP/JSON 日志是否输出到 stdout。日志格式为 OTLP/JSON,每行一个 JSON 对象。日志记录嵌套在 resourceLogs 数组中。示例:

{"resourceLogs":[{"resource" ...}]}
{
  "resourceLogs": [
    {
      "resource": {
        "attributes": [
          {
            "key": "deployment.environment.name",
            "value": {
              "stringValue": "staging"
            }
          },
          {
            "key": "service.instance.id",
            "value": {
              "stringValue": "6ad88e10-238c-4fb7-bf97-38df19053366"
            }
          },
          {
            "key": "service.name",
            "value": {
              "stringValue": "checkout"
            }
          },
          {
            "key": "service.namespace",
            "value": {
              "stringValue": "shop"
            }
          },
          {
            "key": "service.version",
            "value": {
              "stringValue": "1.1"
            }
          }
        ]
      },
      "scopeLogs": [
        {
          "scope": {
            "name": "com.mycompany.checkout.CheckoutServiceServer$CheckoutServiceImpl",
            "attributes": []
          },
          "logRecords": [
            {
              "timeUnixNano": "1730435085776869000",
              "observedTimeUnixNano": "1730435085776944000",
              "severityNumber": 9,
              "severityText": "INFO",
              "body": {
                "stringValue": "Order order-12035 successfully placed"
              },
              "attributes": [
                {
                  "key": "customerId",
                  "value": {
                    "stringValue": "customer-49"
                  }
                },
                {
                  "key": "thread.id",
                  "value": {
                    "intValue": "44"
                  }
                },
                {
                  "key": "thread.name",
                  "value": {
                    "stringValue": "grpc-default-executor-1"
                  }
                }
              ],
              "flags": 1,
              "traceId": "42de1f0dd124e27619a9f3c10bccac1c",
              "spanId": "270984d03e94bb8b"
            }
          ]
        }
      ],
      "schemaUrl": "https://opentelemetry.org.cn/schemas/1.24.0"
    }
  ]
}

配置 Collector 以摄取 OTLP/JSON 日志

使用 OTelBin 查看 OTel Collector 管道

# tested with otelcol-contrib v0.112.0

receivers:
  filelog/otlp-json-logs:
    # start_at: beginning # for testing purpose, use "start_at: beginning"
    include: [/path/to/my-app.otlpjson.log]
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:
  resourcedetection:
    detectors: ['env', 'system']
    override: false

connectors:
  otlpjson:

service:
  pipelines:
    logs/raw_otlpjson:
      receivers: [filelog/otlp-json-logs]
      # (i) no need for processors before the otlpjson connector
      # Declare processors in the shared "logs" pipeline below
      processors: []
      exporters: [otlpjson]
    logs:
      receivers: [otlp, otlpjson]
      processors: [resourcedetection, batch]
      # remove "debug" for production deployments
      exporters: [otlphttp, debug]

exporters:
  debug:
    verbosity: detailed
  # Exporter to the OTLP backend like `otlphttp`
  otlphttp:

通过检查 OTel Collector Debug 导出器的输出来验证 OTel Collector 收集的日志

2024-11-01T10:03:31.074+0530	info	Logs	{"kind": "exporter", "data_type": "logs", "name": "debug", "resource logs": 1, "log records": 1}
2024-11-01T10:03:31.074+0530	info	ResourceLog #0
Resource SchemaURL: https://opentelemetry.org.cn/schemas/1.24.0
Resource attributes:
     -> deployment.environment.name: Str(staging)
     -> service.instance.id: Str(6ad88e10-238c-4fb7-bf97-38df19053366)
     -> service.name: Str(checkout)
     -> service.namespace: Str(shop)
     -> service.version: Str(1.1)
ScopeLogs #0
ScopeLogs SchemaURL:
InstrumentationScope com.mycompany.checkout.CheckoutServiceServer$CheckoutServiceImpl
LogRecord #0
ObservedTimestamp: 2024-11-01 04:24:45.776944 +0000 UTC
Timestamp: 2024-11-01 04:24:45.776869 +0000 UTC
SeverityText: INFO
SeverityNumber: Info(9)
Body: Str(Order order-12035 successfully placed)
Attributes:
     -> customerId: Str(cust-12345)
     -> thread.id: Int(44)
     -> thread.name: Str(grpc-default-executor-1)
Trace ID: 42de1f0dd124e27619a9f3c10bccac1c
Span ID: 270984d03e94bb8b
Flags: 1
     {"kind": "exporter", "data_type": "logs", "name": "debug"}

在 OpenTelemetry 后端验证日志。

在管道端到端工作后,确保生产就绪

  • 从 OTel Collector 配置的 logs 管道中移除 debug 导出器。
  • 禁用日志框架中的文件和控制台导出器(例如 logback.xml),但继续使用日志配置来过滤日志。OTel Java 代理会将 JSON 日志输出到 stdout。
<!-- tested with logback-classic v1.5.11 -->
<configuration>
  <logger name="com.example" level="debug"/>
  <root level="info">
    <!-- No appender as the OTel Agent emits otlpjson logs through stdout -->
    <!--
      IMPORTANT enable a console appender to troubleshoot cases where
      logs are missing in the OTel backend
    -->
  </root>
</configuration>

在 Kubernetes 中配置 OpenTelemetry Collector 来处理容器日志

为了支持 Kubernetes 和容器的特定功能,可以在管道中添加内置的 container 解析步骤,而无需进行特定的映射配置。

<<namespace>><<pod_name>><<container_name>> 替换为所需的值,或使用更广泛的 glob 模式,例如 *

receivers:
  filelog/otlp-json-logs:
    # start_at: beginning # for testing purpose, use "start_at: beginning"
    include: [/var/log/pods/<<namespace>>_<<pod_name>>_*/<<container_name>>/]
    include_file_path: true
    operators:
      - type: container
        add_metadata_from_filepath: true

  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:
  resourcedetection:
    detectors: ['env', 'system']
    override: false

connectors:
  otlpjson:

service:
  pipelines:
    logs/raw_otlpjson:
      receivers: [filelog/otlp-json-logs]
      # (i) no need for processors before the otlpjson connector
      # Declare processors in the shared "logs" pipeline below
      processors: []
      exporters: [otlpjson]
    logs:
      receivers: [otlp, otlpjson]
      processors: [resourcedetection, batch]
      # remove "debug" for production deployments
      exporters: [otlphttp, debug]

exporters:
  debug:
    verbosity: detailed
  # Exporter to the OTLP backend like `otlphttp`
  otlphttp:

结论

这篇博文展示了如何使用 OpenTelemetry 收集基于文件的 Java 日志。该解决方案易于设置,并为将 SLF4J/Logback 或 Log4j2 等框架的日志转换为符合 OTel 标准的 OTLP/JSON 日志输出到 stdout(包含所有资源和日志属性)提供了即插即用解决方案。此 JSON 格式确实冗长,但通常对性能影响很小,并且通过提供可与跟踪和指标关联的高度上下文化日志,提供了坚实的平衡。

如果任何步骤不清楚或您遇到问题,请查看此 部署在 Kubernetes 上的示例应用程序,该应用程序会不断更新并针对最新版本进行测试。

有任何反馈或问题吗?请在 GitHubSlack (#otel-collector) 上联系我们。

最后修改于 2025 年 6 月 11 日: 警报清理 (#7090) (c392c714)