otel-nextjs-style について
Next.js アプリケーションに OpenTelemetry の規約を適用するコーディングスキルです。フレームワーク設定、ルートのスパン、ログ、LLM プロバイダーの計装を扱い、計測の重複を避けます。
主な機能
- 01Next.js フレームワーク入口の設定
- 02ルートハンドラーのスパン指針
- 03LLM プロバイダー計装の指針
- 04OTLP ログ設定
- 05設定とスモークチェックの指針
活用シーン
- 01instrumentation.ts でテレメトリーを初期化
- 02TypeScript のルートに業務スパンを追加
- 03対応する LLM プロバイダーを計装
- 04構造化 OTLP ログを設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ superloglabs/ skills --skill otel-nextjs-styleインストール後、Claude Code で/otel-nextjs-styleを入力して読み込まれたか確認します。
詳細
OTel Next.js スタイル
Next.js アプリでは、フレームワークのエントリーポイントを優先してください。
// instrumentation.ts
import { registerOTel } from "@vercel/otel";
export function register() {
registerOTel({
serviceName: "mugline-web",
});
}
リポジトリが一般的な Next/Vercel アプリではない場合、または拡張が必要なカスタムプロバイダーがすでにある場合を除き、これをカスタムの NodeSDK ブートストラップに置き換えないでください。
JavaScript/TypeScript の LLM プロバイダーでは、手動で子スパンを作成するより、プロバイダーのインストルメンテーションを優先してください。Anthropic では、同じブートストラップに OpenInference を追加し、呼び出し箇所はネイティブのままにしてください。この例では @vercel/[email protected] を使用しています。インストール済みの型定義が v1 の場合は、単数形の logRecordProcessor を使用してください。
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
自動インストルメンテーションでは検出できない箇所では、ネイティブの OTel API を使用してください。
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 は OpenInference のプロバイダー/モデル/トークン属性を基に、UI/クエリレイヤーで推定コストを導出します。
Next.js/ESM で Anthropic を使用する場合は、インストルメンテーションのインスタンスと manuallyInstrument(Anthropic) の呼び出しをモジュールスコープに置き、ルートコードの実行前に一度だけ実行されるようにしてください。
インストール済みのパッケージ/型定義に合わせて @vercel/otel のログオプションを指定してください。
@vercel/[email protected] では単数形の logRecordProcessor を、@vercel/[email protected] では複数形の logRecordProcessors を使用します。一般的な Next.js/Vercel アプリでは、registerOTel(...) を NEXT_RUNTIME による条件分岐で囲まないでください。Next.js は適切なランタイムで instrumentation.ts を呼び出し、@vercel/otel がランタイムの違いを処理します。
console.info は OTLP ログのエクスポートではありません。既存のロガーブリッジがない場合は、本番環境のログレコードに @opentelemetry/api-logs を使用してください。同じ構造化 OTel ログイベントを重複して記録する既存の console.* 呼び出しは削除してください。
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
instrumentation.ts 内で、otel-onboarding-style のソースレベルの公開 Superlog 設定パターンを使用し、それらの定数を registerOTel に明示的に渡してください。公開プロジェクトトークンは書き込み専用で、PostHog のプロジェクトトークンや Sentry の DSN と同様に、セットアップブロック内のエンドポイントと一緒に配置します。
const SUPERLOG_ENDPOINT = "https://intake.superlog.sh";
const SUPERLOG_PUBLIC_TOKEN = "sl_public_...";
// The token MUST be sent as the `x-api-key` header. Ingest only reads
// `x-api-key` or `Authorization: Bearer <token>`; any other header name 401s.
function superlogHeaders(token: string): Record<string, string> {
return { "x-api-key": token };
}
registerOTel({
serviceName: "mugline-web",
traceExporter: new OTLPTraceExporter({
url: `${SUPERLOG_ENDPOINT}/v1/traces`,
headers: superlogHeaders(SUPERLOG_PUBLIC_TOKEN),
}),
// …same shape for log + metric exporters
});
スモークチェックでは、たとえば npm run typecheck や npm run build など、リポジトリ内ですでに使われているツールに加え、可能であれば実際のアプリへのリクエストを使用してください。TypeScript のソースファイルを直接インポートする、壊れやすいインライン Node スクリプトを作成しないでください。また、すでにインストールされていない限り、ts-node が存在すると想定しないでください。