/**
* 起動時の初期化ログと、捕捉漏れエラーの記録。
*
* `register()` / `onRequestError()` は Next.js 16.3.3 のファイル規約
* (node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/instrumentation.md)
* に従う。`onRequestError` は v15.0.0 で安定。
*
* logger を関数の中で動的 import しているのは、公式ガイドが
* 「副作用のある import は register() の内側に置く」ことを推奨しており、
* かつ `register()` が全ランタイムで呼ばれるため。
* pino は Node.js API に依存するので、トップレベル import にしない。
*
* 外部送信(Slack 通知等)は本設計のスコープ外なので追加しない。
*/
import type { Instrumentation } from "next";
/**
* サーバーインスタンス起動時に1回だけ走る。
* 「ログ機構が生きていること」と「どの設定で動いているか」を1行残す。
* これが無いと、ログが出ない原因が LOG_LEVEL なのか未起動なのか切り分けられない。
*/
export async function register(): Promise<void> {
const { logEvent, logger } = await import("@/lib/logger");
logEvent("info", {
event: "app.start",
log_level: logger.level,
node_env: process.env.NODE_ENV ?? "unknown",
next_runtime: process.env.NEXT_RUNTIME ?? "unknown",
});
}
/**
* ルートの catch 節で拾えなかったサーバーエラー。
*
* `api.error` ではなく `app.uncaught` で記録する。
* 意図的に処理したエラーと捕捉漏れが同じイベント名で混ざると、
* 障害調査で「ハンドリング済みか否か」を区別できなくなる。
*/
export const onRequestError: Instrumentation.onRequestError = async (
error,
request,
context
) => {
const {
logEvent,
normalizeRequestId,
runWithRequestId,
REQUEST_ID_HEADER,
} = await import("@/lib/logger");
// request.path はクエリ文字列を含む(例: /api/clean-logs?facility_name=...)。
// 施設名などの値が載るため、パス部分だけにする(§4 のマスキング規約)
const route = request.path.split("?")[0] ?? request.path;
const digest =
typeof error === "object" && error !== null && "digest" in error
? String((error as { digest: unknown }).digest)
: undefined;
const event = {
event: "app.uncaught",
route,
method: request.method,
route_path: context.routePath,
router_kind: context.routerKind,
route_type: context.routeType,
revalidate_reason: context.revalidateReason,
error_digest: digest,
} as const;
// onRequestError はルートの AsyncLocalStorage の外で走るため、
// ヘッダから相関IDを取り直す。無ければ mixin が "-" にする
// (ここで新規生成しても紐づく先が無く、偽の相関を作るだけ)
const requestId = normalizeRequestId(request.headers[REQUEST_ID_HEADER]);
if (requestId) {
runWithRequestId(requestId, () => logEvent("error", event, error));
return;
}
logEvent("error", event, error);
};