Nuxt3 ベストプラクティス01: ログ出力

公開日 2025年1月31日
更新日 2025年1月31日
12 分で読める
Vue

1. はじめに

Nuxt 3 では、デフォルトで Nuxt Kit の useLogger を使用してログを出力します。基本的な機能は十分ですが、各 HTTP リクエストごとに一意のリクエスト ID を生成する機能がないため、ログの追跡が難しくなります。

本記事では、各 HTTP リクエストごとに一意の ID を生成し、ログ分析やデバッグをより効率的に行う方法を紹介します。

2. 実装方法

2.1 pino のインストール

ログ管理には pino を使用します。以下のコマンドでインストールしてください。

npm install pino pino-http pino-pretty

2.2 一意のリクエスト ID の生成

まず、各 HTTP リクエストごとに一意の ID を生成します。server/middleware/ ディレクトリに 01.tracing-id.ts を作成します。ファイル名の 01 は、このミドルウェアが最初に実行されるようにするためです。

注意: Nuxt 3 では、ミドルウェアの実行順序はファイル名のアルファベット/数字の順に決まります。

// server/middleware/01.tracing-id.ts
export default fromNodeMiddleware((req, _res, next) => {
  const tracingId = req.headers['x-tracing-id'] || Math.random().toString(36).slice(2, 12);
  req.headers['x-tracing-id'] = tracingId;
  next();
});

ここでは Math.random() を使用して長さ10のランダムな文字列を生成し、それをリクエストヘッダーに保存します。

2.3 pino の設定

次に、server/middleware/ ディレクトリに 02.log-register.ts を作成し、pino-http ログミドルウェアを登録します。

// server/middleware/02.log-register.ts
import logger from 'pino-http';

export default fromNodeMiddleware(
  logger({
    level: 'info',
    transport: {
      target: 'pino-pretty',
      options: {
        colorize: process.env.NODE_ENV !== 'production',
        translateTime: 'SYS:standard',
        ignore: 'req,res,responseTime,pid,hostname',
        messageFormat: '[reqId:{req.id}] - {msg}',
      },
    },
    genReqId: (req) => req.headers['x-tracing-id'] || req.id,
    autoLogging: false,
  }),
);

注意: fromNodeMiddleware を使用し、pino-http を適用することで req オブジェクトに logpino.Logger インスタンス)を注入できます。

この設定により、ログ出力時に reqId が含まれるようになり、ログの関連付けが容易になります。

2.4 API でのログ利用

上記の設定を行うことで、API ハンドラー内で pino を使用してログを記録できます。

// server/api/test.ts
export default defineEventHandler((event) => {
  const logger = event.node.req.log;
  logger.info('Hello, world!');
  return { message: 'Hello, world!' };
});

API にアクセスすると、次のようなログが出力されます。

[2025-01-31 09:52:11.217 +0900] INFO: [reqId:b0v9ldkiv1] - Hello, world!

3. カスタムログユーティリティの作成

pino によりログのフォーマットが整理されましたが、API コード内での logger の使用には以下の課題があります。

  1. event.node.req.log から logger を手動で取得する必要がある。
  2. ユーティリティ関数内でログを使用する場合、logger を明示的に渡す必要がある。

理想的には、どこからでも logger.info('xxx'); を実行するだけでログ出力ができるようにしたいです。

この問題を解決するために、カスタムログユーティリティを作成します。

3.1 AsyncLocalStorage の活用

AsyncLocalStorage を利用し、logger をリクエストごとにスコープ化することで、どこからでも logger を取得可能にします。

まず、server/utils/ ディレクトリに async-storage.ts を作成します。

// server/utils/async-storage.ts
import { AsyncLocalStorage } from 'async_hooks';
import type pino from 'pino';

type StorageT = { logger: pino.Logger };
export const asyncLocalStorage = new AsyncLocalStorage<StorageT>();

これにより、リクエストごとに logger を保持できます。

3.2 グローバル logger の作成

次に、server/utils/ ディレクトリに logger.ts を作成します。

// server/utils/logger.ts
const getLogger = () => asyncLocalStorage.getStore()?.logger ?? console;

export const logger = {
  info: (msg: string) => getLogger().info(msg),
  error: (msg: string) => getLogger().error(msg),
  warn: (msg: string) => getLogger().warn(msg),
  debug: (msg: string) => getLogger().debug(msg),
};

これで、どこからでも logger.info('message') を呼び出すだけでログを出力できます。

3.3 defineEventHandler をラップする

server/utils/ ディレクトリに handler.ts を作成し、defineEventHandler をラップして logger をリクエストごとに適用します。

// server/utils/handler.ts
import type { EventHandler, EventHandlerRequest } from 'h3';

export const defineWrappedEventHandler = <T extends EventHandlerRequest, D>(
  handler: EventHandler<T, D>,
): EventHandler<T, D> =>
  defineEventHandler<T>(async (event) => {
    const logger = event.node.req.log;
    return asyncLocalStorage.run({ logger }, async () => handler(event));
  });

3.4 API での利用

defineWrappedEventHandler を使用すると、API 内で logger を簡単に使用できます。

// server/api/test.ts
export default defineWrappedEventHandler(() => {
  logger.info('Hello, world!');
  return { message: 'Hello, world!' };
});

注意: ここで使用しているのは defineEventHandler ではなく defineWrappedEventHandlerです。

また、ユーティリティ関数の内部でも logger を直接使用できます。

// server/util/some-util.ts
export const someUtil = () => {
  logger.info('use some util');
};

// server/api/test.ts
export default defineWrappedEventHandler(() => {
  someUtil();
  return { message: 'Hello, world!' };
});

このように、明示的に logger を取得せずに、どこでもログを出力できるようになります。

この方法を採用することで、ログ管理がシンプルになり、可読性やメンテナンス性が向上します。


次の記事:Nuxt3 ベストプラクティス02: Request のバリデーション

概要

技術的洞察、経験、思考を共有する個人ブログ

クイックリンク

お問い合わせ

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 CODE赤兎. 無断転載禁止