🦫

VercelのログをCloud Loggingに送りたい!! with Cloudflare Workers

2023/12/08に公開

この記事は モニクル Advent Calendar 2023 の 8 日目の記事です。

@beaverjrです。
モニクルでSREをしています。

はじめに

Vercelでは、出力したログを各プロジェクトのFunctions画面でリアルタイム表示させることができますが、保存はされません。
https://vercel.com/guides/how-do-i-store-logs-on-vercel

ログを永続的に保持しておくためには

Vercelが連携している外部loggerサービス
https://vercel.com/integrations#logging

または

LogDrainの機能
https://vercel.com/docs/observability/log-drains-overview/log-drains

が利用できます。

連携している外部loggerサービスはIntegrationの設定で簡単に利用できるようになるため便利です。
しかし、Google Cloudにログを集約したい!など、Cloud Loggingにログを送りたいケースもあると思います。

本記事ではLogDrainの機能を使用し、VercelのログをCloudflare Workersを経由してCloud Loggingに転送する方法を紹介します。

構成

全体の構成は下記の図の通りです。
LogDrainの機能だけでは直接Cloud Loggingにログを転送することはできず、ヘッダーの追加とログの整形を行う必要があります。

Cloudflare Workersを採用した理由は

これらに加え、ちょうどCloudflare使ってみたい熱も高まっていたためです。

設定手順

※Cloudflare Workers, KVの作成方法は公式の手順を参考にしてください
https://developers.cloudflare.com/workers/get-started/guide/
https://developers.cloudflare.com/kv/get-started/

1.Google Cloudのトークン取得用のCloudflare Workersを作成

  • Google Cloudのアクセストークンを取得し、Cloudflare Workers KVに保存する(スコープはlogging.write
  • トークンの有効期限が切れる前に、定期的に新しいトークンを取得する
    • Cron Triggersで定期的に実行するようにします。
index.ts
import GoogleAuth, { GoogleKey } from 'cloudflare-workers-and-google-oauth'

export interface Env {
	GCP_SERVICE_ACCOUNT: string;
	<KV>: KVNamespace;
}

export default {
	async scheduled(
		Event: ScheduledEvent,
		env: Env,
		ctx: ExecutionContext
	): Promise<Response> {
		// https://developers.google.com/identity/protocols/oauth2/scopes
		const scopes: string[] = ['https://www.googleapis.com/auth/logging.write']
		const googleAuth: GoogleKey = JSON.parse(env.GCP_SERVICE_ACCOUNT)

		// 取得したトークンをKVに保存する
		const oauth = new GoogleAuth(googleAuth, scopes)
		const token: any = await oauth.getGoogleAuthToken()
		await env.<KV>.put('key', token);
		const key: string | null = await env.<KV>.get('key');
	    return new Response(key);
	},
};

2.ヘッダー追加・ログ整形用のCloudflare Workersを作成

  • Vercel→Cloudflare Workersの認証
  • 手順1でKVに保存したトークンを取得
  • ヘッダー追加・ログ整形
  • Cloud Logging APIを使用して、整形したログを送信
  • Vercelではエンドポイントの所有権を確認するため、Vercelが指定するヘッダーを返す

を行います。

index.ts
// VercelからCloudflare Workersへのアクセスを認証するために、認証ヘッダーを使用
// 認証ヘッダーは、Vercel側で指定
// Cloudflare Workers側では認証ヘッダーを検証して、認証されたリクエストのみ処理
export default {
	async fetch(request: Request, env: any, ctx: any) {
		const AUTH_HEADER_KEY = env.AUTH_HEADER_KEY;
		const AUTH_HEADER_VALUE = env.AUTH_HEADER_VALUE;
		const psk = request.headers.get(AUTH_HEADER_KEY);

		if (psk === AUTH_HEADER_VALUE) {
			return handleRequest(request, env);
		}

		return new Response('Sorry, you have supplied an invalid key.', {
			status: 403,
		});
	},
};


async function handleRequest(request: Request, env: any) {
	// ログの形式をJSON形式に変換する
	// ログに必要な情報を追加する
	const logData1: any = await request.json();
	const logEntries = logData1.map((entry: any) => {
		const timestamp = new Date(entry.timestamp).toISOString();
		return {
			logName: 'projects/<プロジェクト名>/logs/<logname>',
			resource: {
				type: 'global',
			},
			severity: 'INFO', 
			timestamp: timestamp,
			jsonPayload: {
				id: entry.id,
				message: entry.message,
				source: entry.source,
				requestId: entry.requestId,
				statusCode: entry.statusCode,
				proxy: entry.proxy,
				projectId: entry.projectId,
				deploymentId: entry.deploymentId,
				host: entry.host,
				path: entry.path,
			},
		};
	});

	// KVからトークンを取得する

	const newAccessToken = await env.<KV>.get('key');

	// Cloud Logging APIを使用して、ログを送信
	// ログは、指定したプロジェクトとログ名のログに書き込まれる
	const response = await fetch('https://logging.googleapis.com/v2/entries:write', {
		method: 'POST',
		headers: {
			Authorization: `Bearer ${newAccessToken}`,
			'Content-Type': 'application/json',
		},
		body: JSON.stringify({ entries: [logEntries] }),
	});
	if (!response.ok) {
		return new Response('Sorry, an error occurred.', {
			status: 500,
		});
	}

	return returnResponse200(env);
}

// Vercelが指定するヘッダーを返す
function returnResponse200(env: any) {
	const response = new Response('Hello from Cloudflare Workers!', {
		status: 200,
		headers: {
			'x-vercel-verify': env.X_VERCEL_VERIFY,
		},
	});
	return response;
}

3.VercelでLogDrainを作成する

Configure a log drainの手順に沿って設定します。

  • 希望のSource、Delivery Format(今回はJSONを選択しました)、Projectを選択します。

  • Custom Headersで認証用のヘッダーを追加します。

  • エンドポイントに手順2のCloudflare WorkersのURLを指定します。

  • 手順2のCloudflare側で環境変数(例:X_VERCEL_VERIFY)にvercel-verification-stringを設定します。

  • Verifyをクリックし、検証します。
  • Test Log Drainで実際にテストログを送信します。
  • 問題なければ、Add Log Drainをクリックして完了です。
  • ログがCloud Loggingに転送されはじめます🎉

まとめ

Cloudflare Workersを使用して、VercelのログをCloud Loggingに転送することができました。
転送されたログは、構造化ログとして書き込まれるため、検索やデータの活用が容易になります。
個人的には今年Cloudflareを初めて業務で使い、こちらの記事を書くことができたので感慨深いです。
アドバイスいただいた@k2さん、ありがとうございました!!!

明日は @FAMAsoon さんです!よろしくお願いします!

参考

https://zenn.dev/msy/articles/4c48d9d9e06147
https://dev.to/mannieschumpert/setting-up-a-vercel-log-drain-with-aws-4a9c
https://zenn.dev/mr_ozin/articles/645502f4a621d6

株式会社モニクル

Discussion