🦓

[Express.js]WinstonとMorganでロギングを導入する

2024/12/17に公開

はじめに

ExpressとTypeScriptで作成されたAPIサーバーに、WinstonとMorganを使用してロギング管理を実装します。
WinstonはNode.js向けの多機能ロギングライブラリ、MorganはExpress専用のHTTPリクエストロギングミドルウェアです。
ロガーを導入することで、デバッグのスピードが大きく向上します。構造化されたログにより、エラーの発生場所や原因を素早く特定でき、問題解決までの時間を短縮できます。
アプリケーションの運用、開発、保守においてロガーを導入することがおすすめです。

ロガーとconsole.logの違い

  • console.logでは単純な出力のみですが、ロガーでは情報の重要度に応じて適切なログレベル(error、warn、info、debug等)を設定できます。
  • console.logはコンソールにのみ出力されますが、ロガーは様々な出力先(ファイル、外部サービス、データベースなど)に柔軟に出力できます。
  • console.logは単純なテキスト出力ですが、ロガーは構造化された形式(JSON等)でログを出力でき、後の検索が容易です。
  • 開発環境と本番環境で異なるログ設定を適用できます。
  • console.logは同期的な処理となりますが、ロガーは非同期処理やバッファリングが可能で、アプリのパフォーマンスへの影響を最小限に抑えられます。

前提条件

  • Node.js と npm がインストールされていること
  • Express + TypeScriptで作成されたプロジェクト

tl:dr;

  1. 必要なパッケージをインストールする
  2. Winstonの設定ファイルを作成
  3. Morganの設定ファイルを作成
  4. アプリへの統合
  5. ログの使用例
  • 通常のログ出力
  • 非同期処理でのログ出力
  1. ログローテーションの追加

1. 必要なパッケージをインストールする

npm install winston morgan
npm instsll -D @types/morgan

https://www.npmjs.com/package/winston
https://www.npmjs.com/package/morgan
https://www.npmjs.com/package/@types/morgan

2. Winstonの設定ファイルを作成

src/config/logger.tsにWinstonの設定を作成します。

src/config/logger.ts
import winston from 'winston'

// ログレベル
const levels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  debug: 4,
};

// ログカラー
const colors = {
  error: 'red',
  warn: 'yellow',
  info: 'green',
  http: 'magenta',
  debug: 'white',
};

// ログフォーマットの定義
const logFormat = winston.format.combine(
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
  winston.format.errors({ stack: true }),
  winston.format.splat(),
  winston.format.json()
)

// ロガーの作成
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: logFormat,
  transports: [
    // コンソールログ
    new winston.transports.Console(),
  ]
})

// 開発環境の場合はコンソールにも出力
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    )
  }))
}

export default logger

2. Morganの設定

src/middleware/morgan.tsにMorganの設定を作成します。

src/middleware/morgan.ts
import morgan from 'morgan'
import logger from '../config/logger'

// Morganのストリームを定義
const stream = {
  write: (message: string) => {
    logger.info(message.trim())
  }
}

// カスタムフォーマットの定義
const format = ':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" - :response-time ms'

// Morganミドルウェアの作成
const morganMiddleware = morgan(format, { stream })

export default morganMiddleware

ログのフォーマットは要件に合わせてカスタマイズすることができます。
デフォルトでは以下のように出力されます。

morgan(':method :url :status :res[content-length] - :response-time ms')

定義したMorganのフォーマット文字列は、HTTPリクエストのログを以下のような形式で出力します:

192.168.1.1 - john [10/Oct/2023:13:55:36 +0000] "GET /api/users HTTP/1.1" 200 2890 "http://example.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" - 50.3 ms
  • :remote-addr192.168.1.1 (クライアントのIPアドレス)
  • :remote-userjohn (認証されたユーザー名、ない場合は -)
  • :date[clf]10/Oct/2023:13:55:36 +0000 (リクエスト時刻)
  • :methodGET (HTTPメソッド)
  • :url/api/users (リクエストURL)
  • :http-version1.1 (HTTPバージョン)
  • :status200 (HTTPステータスコード)
  • :res[content-length]2890 (レスポンスのサイズ[バイト])
  • :referrerhttp://example.com (リファラー)
  • :user-agentMozilla/5.0... (ユーザーエージェント)
  • :response-time50.3 (レスポンス時間[ミリ秒])

3. アプリケーションへの統合

src/app.tsでミドルウェアを統合します。

import express from 'express'
import morganMiddleware from './middleware/morgan'
import logger from './config/logger'

const app = express()

// Morganミドルウェアの適用
app.use(morganMiddleware)

export default app

ログの使用例

1. 通常のログ出力

// 情報ログ
logger.info('User logged in', { userId: user.id })

// エラーログ
logger.error('Database connection failed', { error: err })

2. 非同期処理でのログ出力

async function fetchUserData(userId: string): Promise<User>{
  try {
    logger.info('Fetching user data', { userId })
    const user = await User.findById(userId)
    logger.info('User data retrieved successfully', { userId })
    return user
  } catch (error) {
    logger.error('Failed to fetch user data', { 
      userId,
      error: error.message
    })
    throw error
  }
}

ログローテーションの追加

ログローテーションとはログファイルを一定の基準(サイズ、日付など)で分割・管理する仕組みです。
これにより、古いログの自動アーカイブ/削除が実現できます。

winston-daily-rotate-file をインストールして、ロガーに Transport を追加します。

npm install winston-daily-rotate-file
src/config/logger.ts
import winston from 'winston'
+ import DailyRotateFile from 'winston-daily-rotate-file';

// ロガーの作成
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: logFormat,
  transports: [
    // コンソールログ
    new winston.transports.Console(),

+    // エラーログ
+    new DailyRotateFile({
+      filename: 'error-%DATE%.log',
+      level: 'error',
+      ...rotateFileConfig,
+    }),
+
+    // 全ログ
+    new DailyRotateFile({
+      filename: 'combined-%DATE%.log',
+      ...rotateFileConfig,
+    }),
  ]
})

export default logger

主な設定オプション

  • filename: ログファイルの名前パターン
  • datePattern: 日付フォーマット
  • maxSize: ファイルの最大サイズ
  • maxFiles: 保持する最大ファイル数/期間
  • zippedArchive: 古いログの圧縮有無
  • auditFile: ログファイルの管理情報を保存

APIを叩いてログがログファイルに出力されたことを確認します。
また、logs/.gitignoreに追加することも忘れないようにしましょう。

まとめ

ExpressアプリにWinstonとMorganを導入してロギング管理を実装してみました。
誰かの参考になれば嬉しいです。

https://github.com/winstonjs/winston
https://github.com/expressjs/morgan

Discussion