关于 otel-generic-style
面向没有专属配套风格的语言,提供通用 OpenTelemetry 实践指南。内容涵盖 SDK 选择、追踪、指标、日志、资源属性及 OTLP/HTTP 备用方案。
主要功能
- 01使用官方语言 SDK 和 HTTP OTLP 导出器
- 02跨度与错误处理规范
- 03指标和资源属性指南
- 04OTLP/HTTP JSON 备用方案
- 05自动插桩建议
适用场景
- 01为 Go 或 Java 服务添加遥测
- 02为有明确边界的业务操作插桩
- 03转发带追踪上下文的日志
- 04在缺少 SDK 的运行时发送 OTLP
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ superloglabs/ skills --skill otel-generic-style安装后在 Claude Code 中输入/otel-generic-style确认已加载。
详细介绍
通用 OTel 风格
适用于没有专属 otel-*-style
配套指南的语言(Go、Java/Kotlin、Ruby、Rust、.NET/C#、PHP、Elixir,以及不使用 Next.js 或 Expo 的纯
Node/TypeScript 等)。请先阅读 otel-onboarding-style
了解跨语言规则;本指南是该指南的语言无关版本。
如果你正在使用的运行时有对应的语言专属指南(otel-python-style、otel-fastapi-style、otel-livekit-style、
otel-nextjs-style、otel-expo-style、otel-supabase-edge-style),请优先使用该指南——它会覆盖本指南。
使用语言官方的 OTel SDK
从 OpenTelemetry 项目获取该语言的 SDK 和导出器。 不要围绕它们自行设计辅助 API。示例:
- Go:
go.opentelemetry.io/otel、otel/sdk、otel/exporters/otlp/otlptrace/otlptracehttp - Java/Kotlin:
io.opentelemetry:opentelemetry-sdk+opentelemetry-exporter-otlp(或使用 Java agent 进行自动插桩) - Ruby:
opentelemetry-sdk、opentelemetry-exporter-otlp、opentelemetry-instrumentation-all - Rust:
opentelemetry、opentelemetry_sdk、opentelemetry-otlp - .NET/C#:
OpenTelemetry、OpenTelemetry.Exporter.OpenTelemetryProtocol、框架OpenTelemetry.Instrumentation.*包 - PHP:
open-telemetry/sdk、open-telemetry/exporter-otlp - Elixir:
:opentelemetry、:opentelemetry_api、:opentelemetry_exporter - 纯 Node/TS:
@opentelemetry/sdk-node+@opentelemetry/auto-instrumentations-node(Next.js 使用@vercel/otel——参见otel-nextjs-style)
使用 HTTP OTLP 导出器,不要使用 gRPC。gRPC 会引入原生绑定,导致打包器、精简容器和无服务器环境的冷启动出现问题。
在模块/包作用域内只获取一次 Tracer/Meter
无论该语言惯用的“模块顶部”写法是什么——Go 中的包级变量、Java/.NET 中的静态字段、Ruby/Node 中的文件顶部常量,
或 Elixir 中 @moduledoc 级别的属性。不要在每次调用时都新建 Tracer 或 Meter。
// Go
var (
tracer = otel.Tracer("mugline.api")
meter = otel.Meter("mugline.api")
ordersSubmitted, _ = meter.Int64Counter("orders.submitted")
)
// Java
private static final Tracer TRACER = GlobalOpenTelemetry.getTracer("mugline.api");
private static final Meter METER = GlobalOpenTelemetry.getMeter("mugline.api");
private static final LongCounter ORDERS_SUBMITTED =
METER.counterBuilder("orders.submitted").build();
有界工作
对于任何起止边界明确的函数,创建一个覆盖整个操作且会自动结束的 Span。使用该语言内置的构造——defer span.End()、try-with-resources、using、with、块语法等。对于有界工作,不要在一处启动 Span,却在其他位置手动结束。
ctx, span := tracer.Start(ctx, "order.submit")
defer span.End()
span.SetAttributes(
attribute.String("tenant.id", tenantID),
attribute.String("order.id", orderID),
)
using var activity = ActivitySource.StartActivity("order.submit");
activity?.SetTag("tenant.id", tenantId);
activity?.SetTag("order.id", orderId);
Span 名称应遵循惯例并保持低基数:domain.verb
(order.submit、payment.charge、voice.session、llm.generate_copy)。
错误路径
在当前活动的 Span 上记录异常并将 Span 状态设为错误,然后重新抛出异常,让调用方仍能感知失败。
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return err
}
rescue => e
span = OpenTelemetry::Trace.current_span
span.record_exception(e)
span.status = OpenTelemetry::Trace::Status.error(e.message)
raise
end
日志
如果声称日志会通过 OTLP 转发,请安装该语言的 OTel 日志桥接,这样现有日志调用就会自动携带 trace_id / span_id。
保留现有日志记录器(log/slog、Logback、Logger、Serilog、Logger.Logger 等)——只需在其下方添加 OTLP 处理器/处理管线。
不要用自定义日志记录器替换项目的结构化日志记录器。
初始化行为
在遥测初始化区域使用 otel-onboarding-style 中的源码级公开 Superlog 配置模式。公开的项目令牌是只写的,应与端点放在同一个配置块中,例如 PostHog 项目令牌或 Sentry DSN。
如果初始化可能运行多次(测试重新加载、框架重启、多个入口点),请为 Provider/导出器设置添加保护,避免重复安装处理器。
资源属性
为每个服务设置在 OTel 资源上:
service.nameservice.version(如果可用)deployment.environment.namevcs.repository.url.full— 仓库的规范 HTTPS URLvcs.ref.head.revision— 尽可能从构建环境获取的提交 SHA (GITHUB_SHA、VERCEL_GIT_COMMIT_SHA、RAILWAY_GIT_COMMIT_SHA、SOURCE_COMMIT、GIT_COMMIT、HEROKU_SLUG_COMMIT等)
不要在运行中的进程里通过 shell 调用 git。请严格使用 OTel
语义约定中的键,不要自行定义 git.repo / app.repo_url。
指标
用于累加总量的计数器:请求、事件、作业、错误、业务状态转换。用于表示分布的直方图:持续时间、延迟、负载大小、队列深度。
仅使用低基数属性——租户/组织/项目、操作/用例、提供方/模型、结果。不要在指标标签中放入原始用户 ID 或请求 ID。
对于 LLM 令牌/成本遥测,请遵循 otel-onboarding-style 中的 gen_ai.* / app.gen_ai.* 约定。不要添加应用端 llm.cost_usd 指标——Superlog 会集中计算成本。
当不存在 OTel SDK 时
有些运行时没有可用的 OpenTelemetry SDK——较旧的语言(Perl、 Bash/shell、Lua、Crystal、Nim、Zig)、小众运行时、受限的边缘 环境,或是其“SDK”已被弃用/仍处于预 Alpha 阶段,效果不如自己实现 的语言。在这种情况下,可直接发送 OTLP/HTTP JSON。 其线格式稳定、文档齐全且精简。
手动实现 OTLP 发送器的规则:
- 使用 OTLP/HTTP+JSON,而非供应商格式。 POST 到
https://intake.superlog.sh/v1/traces、/v1/metrics、/v1/logs,并设置Content-Type: application/json,在x-api-key请求头中传入公开项目令牌(x-api-key: sl_public_...)。摄取端只读取x-api-key或Authorization: Bearer <token>(字面前缀Bearer)—— 即使令牌有效,任何其他请求头名称都会导致每次请求都返回 401。请求体 结构遵循 OTLP proto 到 JSON 的 映射(resourceSpans/resourceMetrics/resourceLogs→scopeSpans→spans,其中traceId/spanId使用小写十六进制)。 - 让垫片保持精简并符合 OTel 风格。 暴露未来移植 SDK 时可直接使用的名称:
startSpan(name)、setAttributes、recordException、endSpan、counter.add、histogram.record。不要发明sendSuperlogEvent或recordCounter——这正是其他地方的风格 所禁止的。 - 正确生成 ID。 16 字节的 trace ID 和 8 字节的 span ID,均使用小写十六进制;使用运行时的 CSPRNG。时间戳为自 Unix 纪元以来的纳秒数(相当于
time.UnixNano()——注意某些语言的默认单位是毫秒或微秒)。 - 批量处理并刷新。 在内存中缓冲 spans/metrics/logs,每隔较短时间(几秒)以及进程退出时刷新。缓冲区溢出时丢弃数据,而不要阻塞关键路径。对于 CLI 或短生命周期脚本,在退出前执行一次最终同步刷新。
- 资源属性仍然适用。 在资源上设置一次
service.name、service.version、deployment.environment.name、vcs.repository.url.full、vcs.ref.head.revision,并在每个导出封装中复用。 - 使用与基于 SDK 的服务相同的公开配置。 将端点和公开项目令牌放在同一个配置块中,并在每个导出封装中设置资源属性。如果注册流程仍在进行中,请正常完成初始化,并注明在浏览器流程认领令牌之前,摄取端可能会拒绝生成的令牌。
- Trace 上下文传播使用 W3C
traceparent。 如果运行时是服务端,则从传入 HTTP 请求头中读取;在传出请求中发送traceparent: 00-<trace-id>-<span-id>-01。
对于 shell 脚本和一次性工具,即使只用一条 curl POST 向
/v1/traces 发送手动构造的 JSON 请求体也可以——这仍然是
OTLP,而且可以与用户流水线中的其他部分组合使用。
如果该语言确实有 SDK,但 SDK 无法在这个特定运行时中加载(打包器、边缘环境、受限的 FFI),则同样适用垫片方案。 Supabase Edge 技能就是一个实际示例。
自动插桩
如果该语言有打包好的自动插桩方案,请以此为基础:
- Java:OpenTelemetry Java agent(
-javaagent:opentelemetry-javaagent.jar) - .NET:
OpenTelemetry.Instrumentation.AspNetCore/Http/SqlClient等 - Ruby:
opentelemetry-instrumentation-all - Node:
@opentelemetry/auto-instrumentations-node - Go:特定框架的
otelhttp、otelgin、otelpgx、otelsql包装器(Go 没有全局 agent) - PHP:
open-telemetry/opentelemetry-auto-*扩展 - Elixir:
:opentelemetry_phoenix、:opentelemetry_ecto、:opentelemetry_cowboy
然后,围绕自动插桩无法观察到的业务操作添加自定义 spans、metrics 和 logs—— 当出现异常时,人类运维人员真正希望检查的那些操作。