OTLP 适配器
OTLP(OpenTelemetry 协议)适配器以标准的 OpenTelemetry 格式发送日志。这适用于任何兼容 OTLP 的后端,包括:
- Grafana Cloud (Loki)
- Datadog
- Honeycomb
- Jaeger
- Splunk
- New Relic
- 自建 OpenTelemetry 收集器
- HyperDX
添加 OTLP 排水适配器
安装
OTLP 适配器与 evlog 一起打包提供:
import { createOTLPDrain } from 'evlog/otlp'
快速开始
1. 设置您的 OTLP 端点
OTLP_ENDPOINT=http://localhost:4318
2. 将排水连接到您的框架
// server/plugins/evlog-drain.ts
import { createOTLPDrain } from 'evlog/otlp'
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createOTLPDrain())
})
// lib/evlog.ts
import { createEvlog } from 'evlog/next'
import { createOTLPDrain } from 'evlog/otlp'
export const { withEvlog, useLogger, log, createError } = createEvlog({
service: 'my-app',
drain: createOTLPDrain(),
})
import { createOTLPDrain } from 'evlog/otlp'
app.use(evlog({ drain: createOTLPDrain() }))
import { createOTLPDrain } from 'evlog/otlp'
app.use(evlog({ drain: createOTLPDrain() }))
import { createOTLPDrain } from 'evlog/otlp'
await app.register(evlog, { drain: createOTLPDrain() })
import { createOTLPDrain } from 'evlog/otlp'
app.use(evlog({ drain: createOTLPDrain() }))
import { createOTLPDrain } from 'evlog/otlp'
EvlogModule.forRoot({ drain: createOTLPDrain() })
import { createOTLPDrain } from 'evlog/otlp'
initLogger({ drain: createOTLPDrain() })
配置
适配器按优先级从多个来源读取配置(优先级从高到低):
- 传递给
createOTLPDrain()的覆盖项 - 运行时配置
runtimeConfig.otlp(仅限 Nuxt/Nitro) - 环境变量
环境变量
| 变量 | 说明 |
|---|---|
OTLP_ENDPOINT | OTLP HTTP 端点(例如,http://localhost:4318)。标准的 OTEL_EXPORTER_OTLP_ENDPOINT 也可用。 |
OTLP_HEADERS | 以 key=value 键值对形式提供的请求头,使用逗号分隔。标准的 OTEL_EXPORTER_OTLP_HEADERS 也可用。 |
OTEL_SERVICE_NAME | 服务名称覆盖项 |
运行时配置(仅限 Nuxt)
export default defineNuxtConfig({
runtimeConfig: {
otlp: {
endpoint: '', // 通过 OTLP_ENDPOINT(或 OTEL_EXPORTER_OTLP_ENDPOINT)设置
},
},
})
覆盖选项
const drain = createOTLPDrain({
endpoint: 'http://localhost:4318',
serviceName: 'my-api',
headers: {
'Authorization': 'Bearer xxx',
},
resourceAttributes: {
'deployment.environment': 'staging',
},
})
完整配置参考
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
endpoint | string | - | OTLP HTTP 端点(必填) |
serviceName | string | 来自事件 | 覆盖 service.name 资源属性 |
headers | object | - | 用于身份验证的自定义 HTTP 请求头 |
resourceAttributes | object | - | 其他 OTLP 资源属性 |
recordShape | 'json' | 'compact' | 'json' | 记录承载事件的方式(详情) |
timeout | number | 5000 | 请求超时时间(毫秒) |
部署
OTLP 是一种协议,而不是产品。相同的适配器既可以与您自行运行的收集器通信,也可以与托管网关通信。只有端点和请求头会发生变化。
自托管
运行一个 OpenTelemetry Collector,并让 evlog 指向它。无需其他配置:
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
exporters:
debug:
verbosity: detailed
service:
pipelines:
logs:
receivers: [otlp]
exporters: [debug]
docker run --rm -p 4318:4318 \
-v $(pwd)/otel-collector.yaml:/etc/otelcol/config.yaml \
otel/opentelemetry-collector:latest
OTLP_ENDPOINT=http://localhost:4318
从这里开始,收集器会将数据分发到您想要的任何位置:Loki、ClickHouse、Elasticsearch、托管后端,或同时分发到多个位置。这种间接层正是选择 OTLP 而不是直接适配器的原因。
托管网关
相同的适配器,加上需要凭据的端点:
OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic%20base64-encoded-credentials
OTLP_ENDPOINT=https://http-intake.logs.datadoghq.com
OTLP_HEADERS=DD-API-KEY=your-api-key
OTLP_ENDPOINT=https://api.honeycomb.io
OTLP_HEADERS=x-honeycomb-team=your-api-key
%20 表示空格。适配器会自动解码这种格式。OTLP 日志格式
evlog 将广泛事件映射到 OTLP 日志格式:
| evlog 字段 | OTLP 字段 |
|---|---|
level | severityNumber / severityText |
timestamp | timeUnixNano |
service | 资源属性 service.name |
environment | 资源属性 deployment.environment |
version | 资源属性 service.version |
region | 资源属性 cloud.region |
traceId | traceId |
spanId | spanId |
| 所有其他字段 | 日志属性 |
广泛事件中的每个字段都会作为 OTLP 记录字段、资源属性或日志属性发送。null 和 undefined 会被省略,而不会传输。json 仅在顶层删除它们,compact 则会在每一层删除它们,因此嵌套的 { user: { id: null } } 不会生成 user.id 属性。
记录形状
recordShape 控制记录承载事件的方式。默认值为 json。
{
"body": { "stringValue": "{\"timestamp\":\"…\",\"method\":\"POST\",\"user\":{\"id\":\"usr_123\"}}" },
"attributes": [
{ "key": "method", "value": { "stringValue": "POST" } },
{ "key": "user", "value": { "stringValue": "{\"id\":\"usr_123\",\"plan\":\"premium\"}" } }
]
}
{
"body": { "stringValue": "POST /api/checkout (500)" },
"attributes": [
{ "key": "method", "value": { "stringValue": "POST" } },
{ "key": "user.id", "value": { "stringValue": "usr_123" } },
{ "key": "user.plan", "value": { "stringValue": "premium" } }
]
}
当你的后端按摄取量收费或对属性建立分面时,值得切换到 compact:
- 正文是一行摘要:
POST /api/checkout (500),如果无法生成则回退到服务名称,而不是在属性旁边重复整个事件。将消息聚类成模板的后端只有在正文稳定时才能做到这一点。 - 嵌套字段会变成点号分隔的属性,因此每个叶字段都是自己的分面:server/api/checkout.post.ts
const drain = createOTLPDrain({ recordShape: 'compact' }) log.set({ user: { id: 'usr_123', plan: 'premium' } }) // → user.id, user.plan
只有普通对象会被遍历。数组会被序列化为单个 JSON 字符串。对数组建立索引,例如 ai.tools.0.name,会将列表变成一组无界的不同属性键,而大多数后端都会对此收费,并且没有任何后端可以将其制成图表。其他非普通对象也是如此,例如 Date。空对象会保留为单个 {} 属性,而不是消失。
compact 将成为默认值。如果你现在正在设置项目,请尽早切换。之后再迁移意味着需要重写基于 json 形状构建的查询。严重性映射
| evlog 级别 | OTLP 严重性编号 | OTLP 严重性文本 |
|---|---|---|
debug | 5 | DEBUG |
info | 9 | INFO |
warn | 13 | WARN |
error | 17 | ERROR |
故障排除
缺少端点错误
[evlog/otlp] 缺少端点。请设置 OTLP_ENDPOINT 或 OTEL_EXPORTER_OTLP_ENDPOINT
请确保设置了端点环境变量并且服务器已重新启动。
401 未授权
您的身份验证标头可能缺失或无效。请检查:
OTEL_EXPORTER_OTLP_HEADERS格式是否正确- 凭据是否有效且未过期
- 端点 URL 是否正确
404 未找到
适配器发送到 /v1/logs。请确保您的端点:
- 支持 OTLP HTTP(而非 gRPC)
- 是基础 URL,不包含
/v1/logs后缀
日志未显示
- 检查服务器控制台是否有
[evlog/otlp]错误消息 - 首先使用本地收集器测试以验证格式
- 检查后端的摄取延迟(某些后端有 1-2 分钟的延迟)。
直接 API 用法
对于高级用例:
import { sendToOTLP, sendBatchToOTLP, toOTLPLogRecord } from 'evlog/otlp'
// 发送单个事件
await sendToOTLP(event, {
endpoint: 'http://localhost:4318',
})
// 发送多个事件
await sendBatchToOTLP(events, {
endpoint: 'http://localhost:4318',
})
// 将事件转换为 OTLP 格式(用于检查)
const otlpRecord = toOTLPLogRecord(event)
下一步
- Axiom 适配器 - 将日志发送到 Axiom
- PostHog 适配器 - 将日志发送到 PostHog
- 自定义适配器 - 构建你自己的适配器
- 最佳实践 - 安全和生产环境提示。