❓
Next.jsにてpinoに入門し、ログ設計を見直したいがしかし
はじめに
ログ設計について何もわからない。
pinoを利用する。
"pino": "^9.2.0",
個人開発で利用しているpinoのログ設定ファイルは以下なのだが、やっつけ感が否めない。
logger.ts
import pino from "pino";
/**
* error: アプリケーションのクラッシュや致命的なエラーが発生した場合のログ出力に利用されます。 \
* warn: アプリケーションのクラッシュや致命的なエラーが発生した場合のログ出力に利用されます。 \
* info: 一般的な動作ログやAPIリクエストの正常な処理についてのログ出力に利用されます。 \
* debug: 開発時のみ役立つ細かい情報についてのログ出力に利用されます。
*/
type LogLevel = "error" | "warn" | "info" | "debug";
type ErrorOptions = {
/**
*エラーオブジェクト(スタックトレース含む、エラー時のみ)
*/
error: Error;
};
type DebugOptions = {
/**
*デバッグ用の追加データ(配列やオブジェクト)
*/
debugData?: unknown;
/**
*リクエストヘッダ(デバッグ用)
*/
requestHeaders?: Record<string, string>;
/**
* リクエストボディ(デバッグ用)
*/
requestBody?: Record<string, unknown>;
/**
* レスポンスボディ(デバッグ用)
*/
responseBody?: Record<string, unknown>;
};
type LogOptions = {
/**
*ログメッセージ
*/
message: string;
/**
*呼び出し元の関数名+ファイル名
*/
caller: string;
/**
*同一リクエストの識別ID
*/
requestId?: string;
/**
*ユーザー情報(オプション)
*/
user?: {
/**
*ユーザーのIPアドレス(マスク済み)
*/
ip: string;
/**
*認証済みのユーザーID(オプション)
*/
userId?: string;
};
/**
*リクエスト情報(オプション)
*/
route?: {
/**
*リクエストパス
*/
path: string;
/**
*HTTPメソッド
*/
method: string;
};
/**
*タイミング情報(オプション)
*/
timing?: {
/**
*処理時間(ミリ秒)
*/
durationMs?: number;
};
/**
*HTTPステータスコード(オプション)
*/
status?: number;
/**
*レスポンス情報(オプション)
*/
response?: {
/**
*ステータスコード
*/
statusCode: number;
/**
*レスポンスボディ(オプション)
*/
body?: Record<string, unknown>;
};
};
type LogOptionsGenerics<T extends LogLevel = "info"> =
T extends "error"
? LogOptions & ErrorOptions
: T extends "debug"
? LogOptions & DebugOptions
: LogOptions;
const formatters = {
level: (label: string) => {
return {
level: label.toUpperCase(),
};
},
};
const pinoConfig = {
level:
process.env.NODE_ENV === "production"
? "info"
: "debug", // 環境によりログレベルを切り替え
timestamp: pino.stdTimeFunctions.isoTime,
browser: {
asObject: true,
formatters,
write: (o: object) => {
const typedO = o as LogOptions & {
level: LogLevel;
} & { time: string };
const { level, time, ...rest } = typedO;
const sortedO = {
level,
time,
...rest,
};
// eslint-disable-next-line no-console
console.log(JSON.stringify(sortedO));
},
},
formatters,
};
const logger = pino(pinoConfig);
const unpackError = (
option: LogOptionsGenerics<"error">
) => {
option.error = {
name: option.error.name,
message: option.error.message,
stack: option.error.stack,
cause: option.error.cause,
};
return option;
};
export const loggerError = (
option: LogOptionsGenerics<"error">
) => {
return logger.error(unpackError(option));
};
export const loggerWarn = (
option: LogOptionsGenerics<"warn">
) => {
return logger.warn(option);
};
export const loggerInfo = (
option: LogOptionsGenerics<"info">
) => {
return logger.info(option);
};
export const loggerDebug = (
option: LogOptionsGenerics<"debug">
) => {
return logger.debug(option);
};
以下のような形式のログが出力される。
{"level":"INFO","time":"2025-02-10T13:05:38.051Z","message":"middleware start.","caller":"middleware.ts","requestId":"5f0c8321-5c43-445e-b047-23e5ea5bb9dc","user":{"ip":"::1"},"route":{"path":"/ja-JP","method":"GET"}}
ちなみに
ブラウザにおけるformattersは
から対応となったので注意。
Edge RuntimeであるMiddlewareでのフォーマットに必要。
そもそも、ログ設計とは?
終わりに
なにもわからない。フロントエンドにおけるログ設計の調査を継続しなければ。
参考文献
Discussion