メインコンテンツまでスキップ

ロギング

Zeltは、構造化ログ、設定可能なtransport、コンテキスト伝播を備えた組み込みの Logger モジュールを提供します。

基本的な使い方

Logger をserviceやcontrollerにinjectします:

import { Injectable, inject, Logger } from '@zeltjs/core';

@Injectable()
export class OrderService {
  constructor(private logger = inject(Logger)) {}

  processOrder(orderId: string) {
    this.logger.info(`Processing order: ${orderId}`);

    try {
      // ... 注文を処理する
      this.logger.debug('Order validation passed');
    } catch (error) {
      this.logger.error(`Failed to process order: ${orderId}`);
      throw error;
    }
  }
}

ログレベル

Loggerは、深刻度の順に4つのログレベルをサポートします:

レベルメソッド説明
debuglogger.debug()詳細なデバッグ情報
infologger.info()一般的な情報メッセージ
warnlogger.warn()警告メッセージ
errorlogger.error()エラーメッセージ

メッセージは、設定されたレベル以上の場合にのみ出力されます。例えば level: 'info' の場合、debug() メッセージは抑制されます。

構造化ログ

構造化データを含めるには、第2引数としてコンテキストを渡します:

logger.info('Order processed', { orderId, userId, duration: 150 });
// 出力: 13:45:23 INFO  Order processed {"orderId":"123","userId":"456","duration":150}

子Logger

すべてのログ呼び出しにわたって永続する、バインドされたコンテキストを持つ子loggerを作成します:

@Injectable()
export class OrderService {
  private logger: Logger;

  constructor(baseLogger = inject(Logger)) {
    this.logger = baseLogger.child({ service: 'OrderService' });
  }

  processOrder(orderId: string) {
    const orderLogger = this.logger.child({ orderId });
    orderLogger.info('Processing started');
    // 出力に含まれる: {"service":"OrderService","orderId":"123"}
  }
}

withLogContextによるグローバルコンテキスト

AsyncLocalStorage を使ってasyncの境界をまたいでコンテキストを伝播するには withLogContext を使います:

const logger = inject(Logger);

withLogContext({ requestId: 'abc-123' }, () => {
  logger.info('Request received');
  // このスコープ内のすべてのログには自動的にコンテキストが含まれる
  someService.process();
});

設定

基本設定

LoggerConfig を使ってLoggerを設定します:

@Config
export class AppLoggerConfig extends LoggerConfig {
  constructor(
    private env = inject(Env),
    private consoleTransport = inject(ConsoleTransport),
    private jsonlFormatter = inject(JsonlFormatter),
  ) {
    super();
  }

  override get level(): LogLevel {
    return (this.env.getString('LOG_LEVEL') as LogLevel) ?? 'info';
  }

  override get transports(): readonly TransportBinding[] {
    return [{ transport: this.consoleTransport, formatter: this.jsonlFormatter }];
  }
}

PrettyFormatterの利用

開発環境で人間が読みやすい出力にするには、PrettyFormatter を使います:

@Config
export class DevLoggerConfig extends LoggerConfig {
  constructor(
    private consoleTransport = inject(ConsoleTransport),
    private prettyFormatter = inject(PrettyFormatter),
  ) {
    super();
  }

  override get level() {
    return 'debug' as const;
  }

  override get transports(): readonly TransportBinding[] {
    return [{ transport: this.consoleTransport, formatter: this.prettyFormatter }];
  }
}

PrettyFormatter はTTY環境で色付きのログを出力します:

13:45:23 INFO  Order processed {"orderId":"123"}
13:45:23 ERROR Failed to process {"error":"timeout"}

アプリ作成時にconfigを登録します:

const app = createApp([http({
    controllers: [AppController],
  })], { configs: [AppLoggerConfig] });

TransportとFormatter

Loggerはpluggableなtransport/formatterアーキテクチャを使用します:

コンポーネント説明
ConsoleTransportstdout/stderrへ書き込む
JsonlFormatterJSON Linesフォーマット(1行に1つのJSONオブジェクト)
PrettyFormatter色をオプションとする人間が読みやすいフォーマット

カスタムTransport

カスタム出力先には LoggerTransport を実装します:

import type { LoggerTransport } from '@zeltjs/core';

export class FileTransport implements LoggerTransport {
  write(message: string): void {
    // ファイルに書き込む
  }
}

カスタムFormatter

カスタム出力フォーマットには LoggerFormatter を実装します:

import type { LoggerFormatter, LogEntry } from '@zeltjs/core';

export class CustomFormatter implements LoggerFormatter {
  format(entry: LogEntry): string {
    return `[${entry.level}] ${entry.message}`;
  }
}

デフォルトの挙動

カスタム設定がない場合:

  • レベル: 'info'(debugメッセージは抑制される)
  • Transport: ConsoleTransport
  • Formatter: JsonlFormatter