debug (TypeScript)

軽量デバッグ専用ライブラリのTypeScript対応版。型定義によりTypeScriptプロジェクトでの開発体験が向上。環境変数による柔軟な制御と、開発時のデバッグ情報表示に特化。多くのTypeScriptライブラリで内部使用。

debug

debugは、Node.jsとブラウザの両方で動作する軽量なデバッグユーティリティです。名前空間ベースのフィルタリング、自動的な色分け、環境変数による制御など、開発時のデバッグを効率化する機能を提供します。

主な特徴

  • 名前空間ベースのデバッグ - モジュール単位でのログ制御
  • 自動色分け - 名前空間ごとに自動的に色を割り当て
  • 環境変数制御 - DEBUG環境変数で出力を制御
  • 軽量 - 最小限の依存関係
  • ユニバーサル - Node.jsとブラウザの両方で動作

インストール

npm install debug

# TypeScript型定義
npm install --save-dev @types/debug

基本的な使い方

シンプルな例

import debug from 'debug';

// デバッガーの作成
const log = debug('myapp:server');
const error = debug('myapp:error');

// メッセージの出力
log('サーバーが起動しました');
error('エラーが発生しました');

// 環境変数でデバッグを有効化
// DEBUG=myapp:* node app.js

名前空間の階層

import debug from 'debug';

// 階層的な名前空間
const dbDebug = debug('myapp:database');
const dbQuery = debug('myapp:database:query');
const dbConnection = debug('myapp:database:connection');

dbDebug('データベース初期化');
dbQuery('SELECT * FROM users');
dbConnection('接続プール作成');

// 環境変数での制御例
// DEBUG=myapp:database:* - データベース関連のみ
// DEBUG=myapp:* - アプリ全体
// DEBUG=* - すべて

名前空間の拡張

import debug from 'debug';

const log = debug('auth');

// extendを使用した名前空間の拡張
const logSign = log.extend('sign');
const logLogin = log.extend('login');
const logLogout = log.extend('logout');

log('認証モジュール初期化');          // auth 認証モジュール初期化
logSign('サインアップ処理開始');      // auth:sign サインアップ処理開始
logLogin('ログイン試行');            // auth:login ログイン試行
logLogout('ログアウト完了');         // auth:logout ログアウト完了

高度な使い方

フォーマッター

import debug from 'debug';

const log = debug('myapp:formatter');

// printf スタイルのフォーマット
log('ユーザー %s がログインしました', 'user123');
log('処理時間: %d ms', 150);

// オブジェクトの整形
const user = { id: 1, name: '田中', role: 'admin' };
log('ユーザー情報: %O', user);  // 1行で表示
log('ユーザー詳細: %o', user);  // 複数行で表示

// JSONフォーマット
log('JSON: %j', { status: 'ok', count: 42 });

カスタムフォーマッター

import debug from 'debug';

// カスタムフォーマッターの追加
debug.formatters.h = (v: any) => {
  return v.toString('hex');
};

const log = debug('myapp:custom');
const buffer = Buffer.from('hello');
log('Buffer内容: %h', buffer);  // 16進数で表示

条件付きデバッグ

import debug from 'debug';

const log = debug('myapp:conditional');

// デバッグが有効か確認
if (log.enabled) {
  // 重い処理(デバッグが有効な場合のみ実行)
  const expensiveData = calculateExpensiveOperation();
  log('計算結果:', expensiveData);
}

// より簡潔な方法
log.enabled && log('デバッグモードで実行中');

出力先のカスタマイズ

import debug from 'debug';
import fs from 'fs';

const log = debug('myapp:custom-output');

// ファイルに出力
const logStream = fs.createWriteStream('debug.log', { flags: 'a' });
log.log = (...args: any[]) => {
  logStream.write(args.join(' ') + '\n');
};

// または console.log を使用(デフォルトは console.error)
log.log = console.log.bind(console);

log('このメッセージはファイルに保存されます');

TypeScriptでの型安全な実装

型定義付きラッパー

import createDebug, { Debugger } from 'debug';

interface Logger {
  debug: Debugger;
  info: Debugger;
  warn: Debugger;
  error: Debugger;
}

function createLogger(namespace: string): Logger {
  const base = createDebug(namespace);
  
  return {
    debug: base.extend('debug'),
    info: base.extend('info'),
    warn: base.extend('warn'),
    error: base.extend('error')
  };
}

// 使用例
const logger = createLogger('myapp:service');
logger.info('サービス開始');
logger.error('エラーが発生しました');

構造化ログ

import debug from 'debug';

class StructuredDebugger {
  private debug: debug.Debugger;

  constructor(namespace: string) {
    this.debug = debug(namespace);
  }

  log(level: string, message: string, meta?: Record<string, any>): void {
    if (!this.debug.enabled) return;

    const timestamp = new Date().toISOString();
    const logEntry = {
      timestamp,
      level,
      message,
      ...meta
    };

    this.debug('%j', logEntry);
  }

  info(message: string, meta?: Record<string, any>): void {
    this.log('INFO', message, meta);
  }

  error(message: string, error?: Error, meta?: Record<string, any>): void {
    this.log('ERROR', message, {
      ...meta,
      error: error ? {
        name: error.name,
        message: error.message,
        stack: error.stack
      } : undefined
    });
  }
}

// 使用例
const structuredLog = new StructuredDebugger('myapp:structured');
structuredLog.info('ユーザーログイン', { userId: 123, ip: '192.168.1.1' });

環境変数

DEBUG環境変数

# 特定の名前空間のみ有効化
DEBUG=myapp:server node app.js

# ワイルドカードの使用
DEBUG=myapp:* node app.js

# 複数の名前空間
DEBUG=myapp:server,myapp:database node app.js

# 除外パターン(-を使用)
DEBUG=myapp:*,-myapp:verbose node app.js

# すべて有効化
DEBUG=* node app.js

その他の環境変数

// 色を無効化
process.env.DEBUG_COLORS = 'false';

// 日付を非表示(非TTY環境)
process.env.DEBUG_HIDE_DATE = 'true';

// オブジェクトの検査深度
process.env.DEBUG_DEPTH = '10';

// 隠しプロパティを表示
process.env.DEBUG_SHOW_HIDDEN = 'true';

ブラウザでの使用

基本設定

// ブラウザ環境での使用
import debug from 'debug';

const log = debug('myapp:browser');

// localStorage でデバッグを有効化
localStorage.debug = 'myapp:*';

// または特定の名前空間のみ
localStorage.debug = 'myapp:browser';

log('ブラウザでのデバッグメッセージ');

Web Workerでの使用

// Web Worker内
import debug from 'debug';

const log = debug('myapp:worker');

// メインスレッドから設定を受け取る
self.addEventListener('message', (event) => {
  if (event.data.debug) {
    localStorage.debug = event.data.debug;
  }
});

log('Worker内でのデバッグ');

パフォーマンスの最適化

遅延評価

import debug from 'debug';

const log = debug('myapp:performance');

// 悪い例: 常に文字列が構築される
function badExample(data: any[]) {
  log(`配列サイズ: ${data.length}, 内容: ${JSON.stringify(data)}`);
}

// 良い例: デバッグが有効な場合のみ実行
function goodExample(data: any[]) {
  if (log.enabled) {
    log(`配列サイズ: ${data.length}, 内容: ${JSON.stringify(data)}`);
  }
}

// より良い例: 関数を使用
function betterExample(data: any[]) {
  log(() => `配列サイズ: ${data.length}, 内容: ${JSON.stringify(data)}`);
}

バッチ処理

import debug from 'debug';

class BatchDebugger {
  private debug: debug.Debugger;
  private buffer: string[] = [];
  private flushInterval: number = 1000;
  private timer?: NodeJS.Timeout;

  constructor(namespace: string) {
    this.debug = debug(namespace);
  }

  log(message: string): void {
    if (!this.debug.enabled) return;

    this.buffer.push(message);
    
    if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), this.flushInterval);
    }
  }

  flush(): void {
    if (this.buffer.length > 0) {
      this.debug('バッチログ:\n%s', this.buffer.join('\n'));
      this.buffer = [];
    }
    this.timer = undefined;
  }
}

ベストプラクティス

1. 意味のある名前空間

// 良い例
const log = debug('myapp:http:request');
const dbLog = debug('myapp:db:query');
const authLog = debug('myapp:auth:jwt');

// 悪い例
const log1 = debug('log1');
const debugger = debug('debug');

2. モジュール単位での設定

// logger.ts
import debug from 'debug';

export function createModuleLogger(moduleName: string) {
  return {
    trace: debug(`myapp:${moduleName}:trace`),
    debug: debug(`myapp:${moduleName}:debug`),
    info: debug(`myapp:${moduleName}:info`),
    warn: debug(`myapp:${moduleName}:warn`),
    error: debug(`myapp:${moduleName}:error`)
  };
}

// userService.ts
import { createModuleLogger } from './logger';

const logger = createModuleLogger('userService');

export class UserService {
  async createUser(data: any) {
    logger.debug('ユーザー作成開始', data);
    try {
      // 処理
      logger.info('ユーザー作成成功');
    } catch (error) {
      logger.error('ユーザー作成失敗', error);
      throw error;
    }
  }
}

3. 開発/本番環境の切り替え

import debug from 'debug';

const isDevelopment = process.env.NODE_ENV === 'development';

// 開発環境では詳細なログを有効化
if (isDevelopment) {
  debug.enable('myapp:*');
} else {
  // 本番環境ではエラーのみ
  debug.enable('myapp:*:error');
}

まとめ

debugライブラリは、シンプルながら強力なデバッグツールです。名前空間ベースのフィルタリング、環境変数による制御、自動的な色分けなど、開発効率を向上させる機能を提供します。特に大規模なアプリケーションでは、モジュール単位でのログ制御が可能なため、必要な情報だけを効率的に取得できます。TypeScriptとの組み合わせにより、型安全なロギングシステムを構築することも可能です。