typescript-logging

TypeScript専用のロギングライブラリ(23,478週間ダウンロード、98 GitHub スター)。TypeScriptの特性を活かした型安全なロギングAPIを提供。ニッチな用途向けだが、純粋なTypeScript環境での特化機能を搭載。

ロギングライブラリTypeScriptカテゴリスタイルLog4TSAngularReactSPA設定不要

ロギングライブラリ

typescript-logging

概要

typescript-logging は、TypeScript専用に設計されたロギングライブラリです。Angular、React、Emberなどのフレームワークで構築される**シングルページアプリケーション(SPA)**に特化し、最小限の設定で使用できる使いやすいロギングメカニズムを提供します。動的な構築が可能で、アプリケーションがデプロイされた後でもロギングの有効化や変更を簡単に行えるという特徴があります。

詳細

typescript-logging は、モダンなTypeScriptアプリケーション開発の課題を解決するために設計されました。従来のJavaScript ロギングライブラリでは対応しきれないTypeScriptの型システムを活用し、開発時の型安全性実行時の柔軟性を両立させています。ライブラリはカテゴリスタイルLog4TS スタイルの2つの主要なスタイルを提供し、プロジェクトの要件に応じて選択できます。

技術的特徴

  • TypeScript完全対応: TypeScriptで書かれ、TypeScriptのためのロギングライブラリ
  • 2つのスタイル: Category-style と Log4ts-style の選択式アーキテクチャ
  • SPA最適化: Angular、React、Ember等のフレームワークに特化した設計
  • 動的設定: アプリケーション実行中の設定変更とロガー制御
  • 最小設定: 複雑な設定なしですぐに使用開始可能
  • デプロイ後制御: 本番環境でのロギングレベル変更が容易
  • TypeScript型安全: 完全な型チェックとIntelliSense サポート

アーキテクチャスタイル

  • Category-style: プロバイダーを設定し、他のモジュールが使用するgetLogger関数を公開
  • Log4ts-style: プロバイダーとgetLogger関数による類似の設定アプローチ

主要コンポーネント

  • LoggerFactory: ロガーインスタンスの作成と管理
  • Category: ログメッセージのカテゴリ化とフィルタリング
  • LogLevel: ログレベルの定義と制御
  • LogFormat: ログメッセージのフォーマット設定

メリット・デメリット

メリット

  • TypeScript特化: TypeScriptプロジェクトでの完全な型安全性
  • SPA最適化: モダンなフロントエンドフレームワークとの優れた統合
  • 簡単設定: 複雑な設定ファイルなしで即座に使用開始可能
  • 動的制御: 実行時のロギング設定変更と動的な有効化
  • 2つのスタイル: プロジェクトに応じた柔軟なアーキテクチャ選択
  • デプロイ後調整: 本番環境でのロギングレベル調整が容易
  • 軽量: 必要最小限の依存関係と小さなバンドルサイズ
  • IntelliSense: TypeScriptの恩恵による優れた開発体験

デメリット

  • TypeScript限定: JavaScript プロジェクトでは十分に活用できない
  • コミュニティサイズ: Winston、Pinoと比較して小さなユーザーベース
  • ドキュメント: 日本語ドキュメントと使用例が限定的
  • エコシステム: 周辺ツールやプラグインの選択肢が少ない
  • 学習コスト: 独自のアーキテクチャスタイルの理解が必要
  • バージョン非互換: version 1とversion 2間で非互換性あり
  • メンテナンス: アクティブな開発とアップデートの頻度が不明確

参考ページ

書き方の例

基本的なセットアップ(Category-style)

// パッケージのインストール
// npm install --save typescript-logging
// npm install --save typescript-logging-category-style

import { Category, CategoryLogger, CategoryServiceFactory, CategoryConfiguration, LogLevel } from 'typescript-logging';

// ロガーカテゴリの設定
const category = new Category('MyApp');
const logger: CategoryLogger = CategoryServiceFactory.getLogger(category);

// 基本的なログ出力
logger.info('アプリケーションが開始されました');
logger.warn('警告: 設定ファイルが見つかりませんでした');
logger.error('エラー: データベース接続に失敗しました');

// 構造化されたログ
logger.info('ユーザーログイン', { userId: 12345, sessionId: 'abc123' });
logger.debug('API応答時間', { endpoint: '/api/users', responseTime: '150ms' });

高度なカテゴリ設定

import { 
  Category, 
  CategoryLogger, 
  CategoryServiceFactory, 
  CategoryConfiguration, 
  LogLevel,
  DateFormat,
  LogFormat
} from 'typescript-logging';

// 複数カテゴリの定義
const rootCategory = new Category('MyApp');
const authCategory = new Category('Auth', rootCategory);
const apiCategory = new Category('API', rootCategory);
const databaseCategory = new Category('Database', rootCategory);

// カスタムログ設定
const config = new CategoryConfiguration(
  LogLevel.Debug,
  LogLevel.Info,
  new LogFormat(new DateFormat()),
  true // StackTrace を含める
);

// 設定をカテゴリに適用
CategoryServiceFactory.setDefaultConfiguration(config);

// 各カテゴリのロガー取得
const authLogger: CategoryLogger = CategoryServiceFactory.getLogger(authCategory);
const apiLogger: CategoryLogger = CategoryServiceFactory.getLogger(apiCategory);
const dbLogger: CategoryLogger = CategoryServiceFactory.getLogger(databaseCategory);

// カテゴリごとのログ出力
class AuthService {
  constructor() {
    authLogger.info('AuthService初期化完了');
  }
  
  login(username: string): boolean {
    authLogger.debug(`ログイン試行: ${username}`);
    
    try {
      // 認証ロジック
      const isValid = this.validateCredentials(username);
      
      if (isValid) {
        authLogger.info(`ログイン成功: ${username}`);
        return true;
      } else {
        authLogger.warn(`ログイン失敗: ${username}`);
        return false;
      }
    } catch (error) {
      authLogger.error('ログイン処理エラー', error);
      return false;
    }
  }
  
  private validateCredentials(username: string): boolean {
    authLogger.trace(`認証情報検証中: ${username}`);
    // 認証ロジック
    return username.length > 0;
  }
}

class APIService {
  async fetchUserData(userId: number) {
    apiLogger.info(`ユーザーデータ取得開始: ${userId}`);
    
    try {
      const startTime = Date.now();
      // API呼び出し
      const userData = await fetch(`/api/users/${userId}`);
      const endTime = Date.now();
      
      apiLogger.info('ユーザーデータ取得成功', {
        userId,
        responseTime: `${endTime - startTime}ms`,
        status: userData.status
      });
      
      return userData;
    } catch (error) {
      apiLogger.error(`ユーザーデータ取得失敗: ${userId}`, error);
      throw error;
    }
  }
}

Log4ts-style設定

// パッケージのインストール
// npm install --save typescript-logging
// npm install --save typescript-logging-log4ts-style

import { 
  LFService, 
  LoggerFactory, 
  LoggerFactoryOptions, 
  LogLevel, 
  LogGroupRule, 
  LogFormat, 
  DateFormatEnum 
} from 'typescript-logging-log4ts-style';

// Log4ts スタイルの設定
const options = new LoggerFactoryOptions()
  .addLogGroupRule(new LogGroupRule(new RegExp('.+'), LogLevel.Debug))
  .setDateFormat(DateFormatEnum.YearMonthDayTime)
  .setFormat(new LogFormat());

// ロガーファクトリーの作成
const factory = LFService.createNamedLoggerFactory('MyAppFactory', options);

// 各モジュール用のロガー取得
const appLogger = factory.getLogger('MyApp');
const authLogger = factory.getLogger('MyApp.Auth');
const apiLogger = factory.getLogger('MyApp.API');

// Log4j スタイルのログ出力
class UserController {
  private logger = factory.getLogger('MyApp.Controller.User');
  
  constructor() {
    this.logger.info('UserController初期化');
  }
  
  async createUser(userData: any) {
    this.logger.debug('ユーザー作成開始', userData);
    
    try {
      // バリデーション
      if (!this.validateUserData(userData)) {
        this.logger.warn('無効なユーザーデータ', userData);
        throw new Error('Invalid user data');
      }
      
      // ユーザー作成
      const user = await this.saveUser(userData);
      
      this.logger.info('ユーザー作成成功', { userId: user.id });
      return user;
    } catch (error) {
      this.logger.error('ユーザー作成エラー', error);
      throw error;
    }
  }
  
  private validateUserData(userData: any): boolean {
    this.logger.trace('ユーザーデータ検証中');
    return userData && userData.email && userData.name;
  }
  
  private async saveUser(userData: any) {
    this.logger.debug('データベース保存中');
    // データベース操作
    return { id: Date.now(), ...userData };
  }
}

動的ログレベル制御

import { CategoryServiceFactory, LogLevel, Category } from 'typescript-logging';

// 実行時のログレベル変更
class LoggerManager {
  private categories: Map<string, Category> = new Map();
  
  registerCategory(name: string, parent?: Category): Category {
    const category = new Category(name, parent);
    this.categories.set(name, category);
    return category;
  }
  
  // 動的にログレベルを変更
  setLogLevel(categoryName: string, level: LogLevel): void {
    const category = this.categories.get(categoryName);
    if (category) {
      CategoryServiceFactory.setConfigurationCategory(category, level);
      console.log(`カテゴリ '${categoryName}' のログレベルを ${LogLevel[level]} に変更しました`);
    }
  }
  
  // デバッグモードの有効化
  enableDebugMode(): void {
    this.categories.forEach((category, name) => {
      CategoryServiceFactory.setConfigurationCategory(category, LogLevel.Debug);
    });
    console.log('全カテゴリでデバッグモードを有効化しました');
  }
  
  // 本番モードの設定
  setProductionMode(): void {
    this.categories.forEach((category, name) => {
      if (name.includes('Debug') || name.includes('Trace')) {
        CategoryServiceFactory.setConfigurationCategory(category, LogLevel.Warn);
      } else {
        CategoryServiceFactory.setConfigurationCategory(category, LogLevel.Info);
      }
    });
    console.log('本番モードのログレベルを設定しました');
  }
  
  // 環境変数による自動設定
  configureFromEnvironment(): void {
    const logLevel = process.env.LOG_LEVEL || 'info';
    const level = this.parseLogLevel(logLevel);
    
    this.categories.forEach((category) => {
      CategoryServiceFactory.setConfigurationCategory(category, level);
    });
    
    console.log(`環境変数に基づいてログレベルを ${logLevel} に設定しました`);
  }
  
  private parseLogLevel(levelString: string): LogLevel {
    switch (levelString.toLowerCase()) {
      case 'trace': return LogLevel.Trace;
      case 'debug': return LogLevel.Debug;
      case 'info': return LogLevel.Info;
      case 'warn': return LogLevel.Warn;
      case 'error': return LogLevel.Error;
      case 'fatal': return LogLevel.Fatal;
      default: return LogLevel.Info;
    }
  }
}

// 使用例
const loggerManager = new LoggerManager();

// カテゴリ登録
const appCategory = loggerManager.registerCategory('App');
const authCategory = loggerManager.registerCategory('Auth', appCategory);
const apiCategory = loggerManager.registerCategory('API', appCategory);

// 環境に応じた設定
if (process.env.NODE_ENV === 'development') {
  loggerManager.enableDebugMode();
} else {
  loggerManager.setProductionMode();
}

// 実行時のレベル変更(例:管理画面からの操作)
function changeLogLevel(category: string, level: string) {
  const logLevel = LogLevel[level as keyof typeof LogLevel];
  loggerManager.setLogLevel(category, logLevel);
}

Angular統合例

// Angular サービスでの使用例
import { Injectable } from '@angular/core';
import { Category, CategoryLogger, CategoryServiceFactory } from 'typescript-logging';

@Injectable({
  providedIn: 'root'
})
export class LoggingService {
  private appCategory = new Category('AngularApp');
  private serviceCategory = new Category('Service', this.appCategory);
  private componentCategory = new Category('Component', this.appCategory);
  
  getServiceLogger(serviceName: string): CategoryLogger {
    const category = new Category(serviceName, this.serviceCategory);
    return CategoryServiceFactory.getLogger(category);
  }
  
  getComponentLogger(componentName: string): CategoryLogger {
    const category = new Category(componentName, this.componentCategory);
    return CategoryServiceFactory.getLogger(category);
  }
}

// コンポーネントでの使用
import { Component, OnInit } from '@angular/core';
import { LoggingService } from './logging.service';
import { CategoryLogger } from 'typescript-logging';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html'
})
export class UserListComponent implements OnInit {
  private logger: CategoryLogger;
  
  constructor(private loggingService: LoggingService) {
    this.logger = this.loggingService.getComponentLogger('UserList');
  }
  
  ngOnInit(): void {
    this.logger.info('UserListComponent初期化');
    this.loadUsers();
  }
  
  async loadUsers(): Promise<void> {
    this.logger.debug('ユーザー一覧読み込み開始');
    
    try {
      // ユーザーデータ取得
      const users = await this.userService.getUsers();
      
      this.logger.info('ユーザー一覧読み込み完了', { count: users.length });
      this.users = users;
    } catch (error) {
      this.logger.error('ユーザー一覧読み込みエラー', error);
    }
  }
  
  onUserSelect(user: any): void {
    this.logger.debug('ユーザー選択', { userId: user.id, userName: user.name });
    // ユーザー選択処理
  }
}

React統合例

// React Hookでの使用例
import React, { useEffect, useState } from 'react';
import { Category, CategoryLogger, CategoryServiceFactory } from 'typescript-logging';

// ロギング用カスタムフック
function useLogger(componentName: string): CategoryLogger {
  const [logger] = useState(() => {
    const category = new Category(`React.${componentName}`);
    return CategoryServiceFactory.getLogger(category);
  });
  
  return logger;
}

// ユーザーリストコンポーネント
interface User {
  id: number;
  name: string;
  email: string;
}

const UserListComponent: React.FC = () => {
  const logger = useLogger('UserList');
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(false);
  
  useEffect(() => {
    logger.info('UserListComponent マウント');
    loadUsers();
    
    return () => {
      logger.info('UserListComponent アンマウント');
    };
  }, []);
  
  const loadUsers = async () => {
    logger.debug('ユーザー一覧読み込み開始');
    setLoading(true);
    
    try {
      const response = await fetch('/api/users');
      const userData = await response.json();
      
      logger.info('ユーザー一覧読み込み完了', { count: userData.length });
      setUsers(userData);
    } catch (error) {
      logger.error('ユーザー一覧読み込みエラー', error);
    } finally {
      setLoading(false);
    }
  };
  
  const handleUserClick = (user: User) => {
    logger.debug('ユーザークリック', { userId: user.id, userName: user.name });
    // ユーザー詳細画面への遷移など
  };
  
  if (loading) {
    logger.debug('ローディング状態表示');
    return <div>Loading...</div>;
  }
  
  return (
    <div>
      <h2>ユーザー一覧</h2>
      {users.map(user => (
        <div key={user.id} onClick={() => handleUserClick(user)}>
          {user.name} ({user.email})
        </div>
      ))}
    </div>
  );
};

export default UserListComponent;