なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

Cloudflare Workers で Mackerel へ OpenTelemetry のトレースを送信したい

以前の記事で、 Vercel から OpenTelemetry のトレースを Mackerel へ送信した記事を書きました。

tech.natsuneko.blog

今回は、プラットフォームを変え 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 トレースを送る記事でした。

参考: