以前の記事で、 Vercel から OpenTelemetry のトレースを Mackerel へ送信した記事を書きました。
今回は、プラットフォームを変え Cloudflare Workers から Mackerel へ OpenTelemetry のトレースを送ってみようと思います。
まず、 Cloudflare Workers で OpenTelemetry トレースを自動計装して送信したい場合、 @microlabs/otel-cf-workers を使います。
これは、 Cloudflare Workers 向けの OpenTelemetry 互換のライブラリで、 Cloudflare Workers 上で使える様々な API に対して、自動計装を実装してくれているパッケージです。 基本の使い方は、次のようにするだけで、簡単に使えます。
// @index.ts import { instrument, ResolveConfigFn } from '@microlabs/otel-cf-workers'; export interface Env { } const handler = { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { await fetch('https://cloudflare.com'); const greeting = "Welcome to Cloudflare Workers instrumentation!"; return new Response(`${greeting}!`); }, }; // エクスポーターとサービス名の設定をして const config: ResolveConfigFn = (env: Env, _trigger) => { return { exporter: { url: '送信先', headers: { // 必要に応じてヘッダーを追加 }, }, service: { name: 'cloudflare-workers-example' }, }; }; // instrument 関数でラップする export default instrument(handler, config);
多くのサービスではこのままで使えるのですが、デフォルト実装では OTLP JSON フォーマットのみ対応なので、 Protobuf を使用しているサービスでは使えません。 なので、 OpenTelemetry SDK をベースに、カスタムエクスポーターを実装することで、 Mackerel やその他の Protobuf 使用サービスでも使えるようにしましょう。
カスタムエクスポーターの実装は次のような感じになります。
// @./opentelemetry/protobuf.ts // ref: https://github.com/evanderkoogh/otel-cf-workers/issues/68#issuecomment-3214465723 import { __unwrappedFetch } from "@microlabs/otel-cf-workers"; import { type ExportResult, ExportResultCode } from "@opentelemetry/core"; import { ProtobufTraceSerializer } from "@opentelemetry/otlp-transformer"; import type { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base"; export interface OTLPExporterProtoConfig { url: string; headers?: Record<string, string>; } const defaultHeaders = { "Content-Type": "application/x-protobuf", }; export class OTLPTraceExporterProto implements SpanExporter { private readonly url: string; private readonly headers: Record<string, string>; constructor(config: OTLPExporterProtoConfig) { this.url = config.url; this.headers = { ...defaultHeaders, ...config.headers }; } export(items: ReadableSpan[], resultCallback: (result: ExportResult) => void): void { try { const serializedTraces = ProtobufTraceSerializer.serializeRequest(items); const body = serializedTraces ? new ArrayBuffer(serializedTraces.length) : new ArrayBuffer(0); if (serializedTraces) { new Uint8Array(body).set(serializedTraces); } __unwrappedFetch(this.url, { method: "POST", headers: this.headers, body, }) .then((response) => { if (response.ok) { resultCallback({ code: ExportResultCode.SUCCESS }); } else { console.error(`OTLP/protobuf trace exporter failed with status code: ${response.status}`); resultCallback({ code: ExportResultCode.FAILED }); } }) .catch((error) => { console.error("Error exporting OTLP/protobuf traces:", error); resultCallback({ code: ExportResultCode.FAILED, error: error instanceof Error ? error : new Error(String(error)) }); }); } catch (error) { console.error("Error serializing traces:", error); resultCallback({ code: ExportResultCode.FAILED, error: error instanceof Error ? error : new Error(String(error)) }); } } async shutdown(): Promise<void> { // No-op } }
次に、このカスタムエクスポーターを使って送信するようにします。
export interface Env { MACKEREL_API_KEY: string; // 追記 }; const config: ResolveConfigFn<Env> = (env, _trigger) => { const exporter = new OTLPTraceExporterProto({ url: "https://otlp-vaxila.mackerelio.com/v1/traces", headers: { Accept: "*/*", "Mackerel-Api-Key": env.MACKEREL_API_KEY, }, }); return { exporter, service: { name: 'cloudflare-workers-example' }, }; };
この状態で Workers ページにアクセスすると、 Mackerel へトレースが送信され、次の画像のように Mackerel 上で確認できます。

ということで、 Cloudflare Workers から Mackerel へ OpenTelemetry トレースを送る記事でした。
参考: