
Log Drainsを使用してVercelのログをAmazon CloudWatchに転送する方法
リテールアプリ共創部のるおんです。
Vercel上でホスティングしているNext.js v15で作成したアプリケーションのログを、Log Drains 機能を使用してAmazon CloudWatch Logsに転送する仕組みを構築したので、実装手順をまとめました。
前提・やりたいこと
- Next.js v15で作成したアプリケーションをVercel上にデプロイしている
- Vercelのログを長期保存したい
- Amazon CloudWatch Logsで一元管理し、メトリクスやアラートを設定したい
今回のブログでは、Vercel上のログをAmazon CloudWatch Logsに転送するところまでを解説しますが、他のAWSサービスと組み合わせると以下のようなことができるようになります。
先に結論
- Vercelからのログを処理するAWS Lambdaを作成し、VercelのダッシュボードからLog Drainsを使用してLambda関数と連携。
- Vercelのダッシュボードから検証リクエストを送信し、Lambda関数が正常に動作することを確認
- アプリケーション上のログをLog Drains経由でLambda関数で受信し、CloudWatch Logsに転送されることを確認
これにより、Vercelのログ保持期間の制約を超えて、長期間のログ保存とCloudWatch上での高度な分析・アラートが可能になります。
構成図

Vercelのログ保持期間の制約
Vercelのログ保持期間はプランによって以下のように制限されています。
| Plan | Retention time |
|---|---|
| Hobby | 1 hour of logs |
| Pro | 1 day of logs |
| Pro with Observability Plus | 30 days of logs |
| Enterprise | 3 days of logs |
| Enterprise with Observability Plus | 30 days of logs |
Proプランでは1日、Enterpriseプランでも3日しかログが保存されず、Observability Plusを追加しても30日までです。
法令対応や長期的な分析のために60日以上ログを保持したい場合、Log Drains機能を使って外部サービスにログを転送する必要があります。
Log Drainsとは
Vercelが提供する、観測データ(ログ 、トレース、Speed Insights、Web Analyticsなど)をカスタムHTTPエンドポイントやネイティブ統合に転送する機能です。
Pro/Enterpriseプランのみで利用可能で、API Routesなどの Vercel Functions やEdge Functions、静的ファイルやビルドログ、その他複数のデータソースに対応しています。また複数のデータフォーマットにも対応しています。
今回は Functions の ログ データを NDJSON 形式でログを受信し、AWS Lambda経由でCloudWatch Logsに転送します。
やってみた
以下、簡単な手順です。
- AWS Lambda関数の準備
- Vercelダッシュボード上でのLog Draisの設定
- 動作確認
① AWS Lambda関数の準備
まず、ログを処理するLambda関数を作成します。マネジメントコンソール上の Lambda サービスにおいて、以下の内容で関数を作成します。
一から作成 を選択し、適当な関数名を入力します。今回はvercel-log-drainとして作成しました。また、適切なNode.jsのバージョンを指定してください。

次に、その下にある「 その他の構成 」欄から 関数URLをEnable にします。また、今回はVercelからリクエストを送信したいのでIAMでの認証ではなく、Lambda関数内でカスタムヘッダーを使った独自認証を実装(後ほど解説)するので、認証タイプをNone に設定し、Corsも有効 にします。

次に、実際のVercelからのリクエストを処理するLambda関数内のコードを実装します。コードソース内 のindex.mjsに以下のコードを貼り付けてください。
export const handler = async (event) => {
// Check if authorization header is present
if (
!event.headers.authorization ||
event.headers.authorization !== `Bearer ${process.env.AUTH_TOKEN}`
) {
console.log("🔴 Unauthorized request");
return {
statusCode: 401,
};
}
if (event.body === undefined) {
// No body i.e. check request
console.log("Verify request");
return {
statusCode: 200,
headers: {
"x-vercel-verify": process.env.INTEGRATION_SECRET,
},
};
}
/**
* We convert the request body from Base64,
* then split the resulting string by new lines.
* (The NDJSON format sends line-delimited JSON logs.)
*/
try {
const logs = Buffer.from(event.body, "base64")
.toString("utf-8")
.split("\n");
// We create a log entry for each item received.
for (const log of logs) {
/**
* The split function will leave an empty item
* at the end of the array. We don't want to log this.
*/
if (log !== "") {
// Parse the log to determine the appropriate log level
try {
const logData = JSON.parse(log);
const logLevel = logData.level || logData.logLevel;
// Output with appropriate console method based on log level
switch (logLevel) {
case "error":
console.error(logData);
break;
case "warn":
console.warn(logData);
break;
case "debug":
console.debug(logData);
break;
case "info":
console.info(logData);
break;
default:
console.log(logData);
break;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (parseError) {
console.log(log);
}
}
}
} catch (e) {
console.log("🔴 Error decoding body", e);
// Continue to return 200 to avoid retries
}
return {
statusCode: 200,
headers: {
"x-vercel-verify": process.env.INTEGRATION_SECRET,
},
};
};
上記コードを貼り付けたら Deploy ボタンを実行します。
実装のポイント
- 認証チェック(3~11行目): カスタムヘッダー
authenticationでBearer認証を実装。これによりVercelからのリクエストであることを担保します。この後Vercel側でヘッダーの設定をします。 - 検証リクエスト対応(13~22行目): bodyが空の場合はVercelからの検証リクエストなので
x-vercel-verifyヘッダーを返却 - Base64デコード(30~32行目): Vercelから送られてくるbodyはBase64エンコードされているためデコード
- ログレベル別出力: CloudWatch側でフィルタリングしやすいように
console.error、console.warnなどを使い分け
以上で、AWS Lambdaの準備は完了です。この後環境変数などを追加する設定もまだありますので、タブを閉じずに進めることをお勧めします。
ちなみに、ここまでの設定をAWS CDKを用いて構築するとこんな感じ
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as aws_logs from "aws-cdk-lib/aws-logs";
import type { Construct } from "constructs";
export class LogDrainStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// CloudWatch Log Group
const functionLogGroup = new aws_logs.LogGroup(this, "LogGroup", {
removalPolicy: cdk.RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE,
retention: aws_logs.RetentionDays.SIX_MONTHS, // 6ヶ月保持
logGroupName: "/aws/lambda/vercel-log-drain",
});
// Lambda function
const logDrainFunction = new nodejs.NodejsFunction(
this,
"LogDrainFunction",
{
functionName: "vercel-log-drain",
entry: "lambda/log-drain-handler/index.js",
handler: "handler",
runtime: lambda.Runtime.NODEJS_LATEST,
timeout: cdk.Duration.seconds(10),
memorySize: 1769,
environment: {
AUTH_TOKEN: process.env.AUTH_TOKEN || "",
INTEGRATION_SECRET: process.env.INTEGRATION_SECRET || "",
},
logGroup: functionLogGroup,
},
);
// Function URL
logDrainFunction.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
cors: {
allowedOrigins: ["*"],
allowedMethods: [lambda.HttpMethod.ALL],
allowedHeaders: ["*"],
},
});
}
}
CDKでのポイント
logGroupで明示的にロググループを指定し、保持期間を設定addFunctionUrlでFunction URLを追加- 環境変数として
AUTH_TOKENとINTEGRATION_SECRETを設定
② Vercelダッシュボード上でのLog Drainsの設定
Log Drainsのセットアップをします。
Vercelのダッシュボードにアクセスし、「Settings」→「Drains」タブに移動します。「Add Drain」から今回設定するLog Drainsを設定します。
「Add Drain」を押すと、画像のような設定画面が表示されます。まずは観測データを選択します。
今回は「Logs」を選択します

次に、対象プロジェクトを選択し、データのソースを設定します。今回はAPI Routesなどのログが出力される「Functions」を選択します。EnvironmentにはProductionを選択しました。

次に、Lambdaの 関数URL をマネジメントコンソールから取得して、取得した関数URLをLog Drainsのエンドポイントとして設定します。データフォーマットは、NDJSON であることに注意してください


最後に、Custom Headersのトグルをオンにして、インプット欄に以下の文字列を入力します。
Authorization: Bearer sample-random-key-hogehoge
パスワード生成ツール等で 安全なランダム文字列 を生成し、「sample-random-key-hogehoge」の部分は置き換えてください

そして、今設定した安全なランダム文字列を「AUTH_TOKEN」として、そしてその上に表示されているSignature verification secretを「INTEGRATION_SECRET」としてLambdaの環境変数に設定します。

こうすることで、先ほど作成したLambda関数の以下の部分で環境変数から値を取得し、VercelのLog Drains経由のリクエストだと判定することができます。
Vercelからのリクエストであることを確認
// Check if authorization header is present
if (
!event.headers.authorization ||
event.headers.authorization !== `Bearer ${process.env.AUTH_TOKEN}`
) {
console.log("🔴 Unauthorized request");
return {
statusCode: 401,
};
}
検証リクストをPassするためのコード(検証の場合bodyがnullで入ってくる)
if (event.body === undefined) {
// No body i.e. check request
console.log("Verify request");
return {
statusCode: 200,
headers: {
"x-vercel-verify": process.env.INTEGRATION_SECRET,
},
};
}
「Create Drain」を押して、問題なくテストが通ったら成功です。
③ 動作確認
Amazon CloudWatch Logsに移動し、/vercel-log-drain ロググループが見つかるはずなので、そこにログが転送されていることを確認します。
Next.jsのアプリケーションのappディレクトリ配下にtestディレクトリとpage.tsxを作成し、以下のコードを追加しました。
export const dynamic = "force-dynamic";
export default function TestPage() {
console.info("🚀Log Drain Test!!");
return <div>TestPage</div>;
}
アプリのurlで/testにアクセスするとサーバーコンポーネント上で実行されるコンソールのログがVercelのログに出力され、さらにそれがAmazon CloudWatchのログストリームに転送されていることが確認できました。
また、Vercel上のメタデータもログとして付与されていることがわかります。
Vercel Logs

Amazon CloudWatch Logs

終わりに
Vercel Log DrainsとAmazon CloudWatchを使用すると、Vercelのログを長期保存することができ、AWS上で高度な分析やアラート設定が可能になります。
特に、ログレベルに応じた適切な出力方法を採用することで、CloudWatch側でのフィルタリングやメトリクス化がスムーズになります。
今回のブログではログの転送の解説に留まりましたが、他のサービスを組み合わせることでエラーアラートをSlackへ連携して通知を送信することなどが可能です。詳しくは以下のブログなどが参考になります。
自分自身4ヶ月ほど前に本機能を実装しましたが、やり方を残しておきたいなと思っていたので今回ブログにしました。
以上、どなたかの参考になれば幸いです。
参考








