otel-nextjs-style

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OTel Next.js Style

OTel Next.js 风格

For Next.js apps, prefer the framework entrypoint.
ts
// instrumentation.ts
import { registerOTel } from "@vercel/otel";

export function register() {
  registerOTel({
    serviceName: "mugline-web",
  });
}
Do not replace this with a custom
NodeSDK
bootstrap unless the repo is not a normal Next/Vercel app or already has a custom provider that must be extended.
For JavaScript/TypeScript LLM providers, prefer provider instrumentation over manual child spans. For Anthropic, add OpenInference in the same bootstrap and keep call sites native. This example uses
@vercel/otel@2.x
; if the installed types are v1, use
logRecordProcessor
singular instead.
ts
import Anthropic from "@anthropic-ai/sdk";
import { AnthropicInstrumentation } from "@arizeai/openinference-instrumentation-anthropic";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { registerOTel } from "@vercel/otel";

const anthropicInstrumentation = new AnthropicInstrumentation({
  traceConfig: {
    hideInputs: true,
    hideOutputs: true,
  },
});

anthropicInstrumentation.manuallyInstrument(Anthropic);

export function register() {
  registerOTel({
    serviceName: "mugline-web",
    instrumentations: [anthropicInstrumentation],
    logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
  });
}
对于Next.js应用,优先使用框架入口文件。
ts
// instrumentation.ts
import { registerOTel } from "@vercel/otel";

export function register() {
  registerOTel({
    serviceName: "mugline-web",
  });
}
除非项目不是常规的Next/Vercel应用,或者已经有必须扩展的自定义提供者,否则不要用自定义的
NodeSDK
启动引导替换此配置。
对于JavaScript/TypeScript LLM提供者,优先使用提供者插装而非手动子跨度。对于Anthropic,在同一启动引导中添加OpenInference,并保持调用站点原生。此示例使用
@vercel/otel@2.x
;如果安装的类型是v1版本,请改用单数形式的
logRecordProcessor
ts
import Anthropic from "@anthropic-ai/sdk";
import { AnthropicInstrumentation } from "@arizeai/openinference-instrumentation-anthropic";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { registerOTel } from "@vercel/otel";

const anthropicInstrumentation = new AnthropicInstrumentation({
  traceConfig: {
    hideInputs: true,
    hideOutputs: true,
  },
});

anthropicInstrumentation.manuallyInstrument(Anthropic);

export function register() {
  registerOTel({
    serviceName: "mugline-web",
    instrumentations: [anthropicInstrumentation],
    logRecordProcessors: [new BatchLogRecordProcessor(new OTLPLogExporter())],
  });
}

Route Handlers

路由处理器

Use native OTel APIs where auto-instrumentation is blind.
ts
import { withSpan } from "@superlog/otel-helpers";

const tracer = trace.getTracer("mugline.web");
const meter = metrics.getMeter("mugline.web");
const requests = meter.createCounter("mug.copy.generated");

export async function POST(request: Request) {
  const tenantId = request.headers.get("x-tenant-id") ?? "tenant_demo";
  return await withSpan("mug.copy.generate", async (span) => {
    span.setAttribute("tenant.id", tenantId);
    requests.add(1, { "tenant.id": tenantId, outcome: "success" });
    return Response.json({ ok: true });
  }, { tracer });
}
For TypeScript route handlers, use
@superlog/otel-helpers
withSpan
for bounded business spans and add
@superlog/otel-helpers
to
package.json
when it is not already present. This is required when the package can be installed. It keeps span lifecycle/error handling out of the handler body and avoids a large indentation diff. Do not expand the whole route into
tracer.startActiveSpan(...)
plus
try
/
catch
/
finally
unless the helper cannot be added or the span has a true cross-callback lifecycle.
If a route has an LLM call and OpenInference/provider instrumentation supports that SDK, do not wrap the provider call. Leave
client.messages.create(...)
/ equivalent in place and put business context on the active product span or structured log. Do not duplicate provider/model/token attributes in route-level spans, logs, or metrics when OpenInference already reports them. Do not calculate LLM cost in route handlers; Superlog derives estimated cost in the UI/query layer from OpenInference provider/model/token attributes. For Anthropic in Next.js/ESM, keep the instrumentation instance and
manuallyInstrument(Anthropic)
call at module scope so it runs once and before route code.
Match the
@vercel/otel
logs option to the installed package/types:
@vercel/otel@1.x
uses
logRecordProcessor
singular, while
@vercel/otel@2.x
uses
logRecordProcessors
plural. For normal Next.js/Vercel apps, do not guard
registerOTel(...)
behind
NEXT_RUNTIME
; Next calls
instrumentation.ts
in the appropriate runtime and
@vercel/otel
handles its own runtime differences.
console.info
is not OTLP log export. If there is no existing logger bridge, use
@opentelemetry/api-logs
for production log records. Remove pre-existing
console.*
calls that duplicate the same structured OTel log event:
ts
logger.emit({
  severityNumber: SeverityNumber.INFO,
  severityText: "INFO",
  body: "generated mug copy",
  attributes: {
    "tenant.id": tenantId,
    "gen_ai.provider.name": "anthropic",
    "gen_ai.request.model": model,
    "app.gen_ai.use_case": "web.mug_copy",
    outcome: "success",
  },
});
在自动插装无法覆盖的地方,使用原生OTel API。
ts
import { withSpan } from "@superlog/otel-helpers";

const tracer = trace.getTracer("mugline.web");
const meter = metrics.getMeter("mugline.web");
const requests = meter.createCounter("mug.copy.generated");

export async function POST(request: Request) {
  const tenantId = request.headers.get("x-tenant-id") ?? "tenant_demo";
  return await withSpan("mug.copy.generate", async (span) => {
    span.setAttribute("tenant.id", tenantId);
    requests.add(1, { "tenant.id": tenantId, outcome: "success" });
    return Response.json({ ok: true });
  }, { tracer });
}
对于TypeScript路由处理器,使用
@superlog/otel-helpers
withSpan
来创建有界业务跨度,若项目中尚未安装
@superlog/otel-helpers
,请将其添加到
package.json
中。当该包可安装时,这是必需的操作。它可以将跨度生命周期/错误处理从处理器主体中剥离,避免大量缩进差异。除非无法添加该辅助工具,或者跨度具有真正的跨回调生命周期,否则不要将整个路由展开为
tracer.startActiveSpan(...)
加上
try
/
catch
/
finally
的形式。
如果路由包含LLM调用,且OpenInference/提供者插装支持该SDK,则不要包装提供者调用。保留
client.messages.create(...)
或类似调用不变,并将业务上下文添加到活动产品跨度或结构化日志中。当OpenInference已报告提供者/模型/令牌属性时,不要在路由级跨度、日志或指标中重复这些属性。不要在路由处理器中计算LLM成本;Superlog会在UI/查询层从OpenInference的提供者/模型/令牌属性推导估算成本。 对于Next.js/ESM中的Anthropic,请将插装实例和
manuallyInstrument(Anthropic)
调用放在模块作用域中,使其仅运行一次且在路由代码之前执行。
使
@vercel/otel
的日志选项与已安装的包/类型匹配:
@vercel/otel@1.x
使用单数形式的
logRecordProcessor
,而
@vercel/otel@2.x
使用复数形式的
logRecordProcessors
。对于常规的Next.js/Vercel应用,不要在
NEXT_RUNTIME
条件下包裹
registerOTel(...)
;Next会在适当的运行时调用
instrumentation.ts
,且
@vercel/otel
会自行处理运行时差异。
console.info
并非OTLP日志导出。如果没有现有的日志桥接器,请使用
@opentelemetry/api-logs
生成生产环境日志记录。移除重复相同结构化OTel日志事件的原有
console.*
调用:
ts
logger.emit({
  severityNumber: SeverityNumber.INFO,
  severityText: "INFO",
  body: "generated mug copy",
  attributes: {
    "tenant.id": tenantId,
    "gen_ai.provider.name": "anthropic",
    "gen_ai.request.model": model,
    "app.gen_ai.use_case": "web.mug_copy",
    outcome: "success",
  },
});

Configuration And Smoke

配置与冒烟测试

Inline the endpoint and ingest key in
instrumentation.ts
— pass them explicitly to
registerOTel
. Don't rely on
OTEL_EXPORTER_OTLP_*
env vars: the Superlog ingest key is project-scoped + write-only and inline config sidesteps Vercel's env-propagation quirks during preview builds.
ts
const SUPERLOG_ENDPOINT = "https://intake.superlog.sh";
const SUPERLOG_KEY = "superlog_live_…"; // set by superlog-onboard skill on pairing

registerOTel({
  serviceName: "mugline-web",
  traceExporter: new OTLPTraceExporter({
    url: `${SUPERLOG_ENDPOINT}/v1/traces`,
    headers: { authorization: `Bearer ${SUPERLOG_KEY}` },
  }),
  // …same shape for log + metric exporters
});
Smoke checks should use tools already in the repo, e.g.
npm run typecheck
or
npm run build
, plus a real app request where practical. Do not invent fragile inline Node scripts that import TypeScript source files directly, and do not assume
ts-node
exists unless it is already installed.
instrumentation.ts
中内联端点和摄取密钥——将它们显式传递给
registerOTel
。不要依赖
OTEL_EXPORTER_OTLP_*
环境变量:Superlog摄取密钥是项目范围的且仅可写,内联配置可以避免Vercel在预览构建期间的环境变量传播问题。
ts
const SUPERLOG_ENDPOINT = "https://intake.superlog.sh";
const SUPERLOG_KEY = "superlog_live_…"; // set by superlog-onboard skill on pairing

registerOTel({
  serviceName: "mugline-web",
  traceExporter: new OTLPTraceExporter({
    url: `${SUPERLOG_ENDPOINT}/v1/traces`,
    headers: { authorization: `Bearer ${SUPERLOG_KEY}` },
  }),
  // …same shape for log + metric exporters
});
冒烟测试应使用项目中已有的工具,例如
npm run typecheck
npm run build
,并尽可能结合真实的应用请求。不要创建脆弱的内联Node脚本直接导入TypeScript源文件,除非
ts-node
已安装,否则不要假设它存在。