从文件中收集符合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 的设置来处理容器日志。

配置 Java 应用程序输出 OTLP/JSON 日志
博客文章中的说明很容易过时。如果遇到问题,请查看此 部署在 Kubernetes 上的示例应用程序,该应用程序会不断更新并针对最新版本进行测试。
无需代码更改。继续使用您喜欢的日志记录库,包括模板化日志、映射诊断上下文和结构化日志记录。
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。用于 Logback 和 Log4j 的配置参数是可选的,但推荐使用。
# 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 日志导出器是实验性的,可能会发生变化。请查看 规范 PR 以获取最新更新。
验证 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 日志

# 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 上的示例应用程序,该应用程序会不断更新并针对最新版本进行测试。