LoginSignup
1
1

More than 3 years have passed since last update.

React + winston で デバッグ用の汎用ロガーを書いてみた

Last updated at Posted at 2019-09-13

コンソール出力用(主にデバッグ用途)で React + winston でロガーを書いてみました。Typescript で書いていますが、ほぼ依存してないです

winston はそれなりに機能持ってるので良いのですが、オリジナルサイトやら他の実装例が凝りすぎていて、雛形とするには、ちょっと整理しないといけなかったので、この記事を書きました

インストールは

npm install -S winston moment @types/winston @types/moment

moment は UTC 時間をログ時刻に直すのに使ってます。UTC 文字列そのままで良いなら不要、@types/... も Typescript 使わないなら不要です

私はアプリケーションを React で普通書いているので、props のダンプ出力が主用途です。これにかなり特化はしていますが、ログとしてメッセージと関連するオブジェクトのログを出すだけなら、かなり汎用につかえます

以下の Logging.tsx 実装例だと、log というロガーができているので、

import { log } from "./Logging"

// log.(level)( message [, object ] ); が基本的な使い方です
log.debug( "This Container's props :", props );

これだけで、タイムスタンプ・レベル・メッセージと、props の JSON ダンプがログに出力されます。

winston には format.splat() という printf フォーマッタもどきな引数の指示の仕方もあるのですが、手軽にダンプ取るために、「メッセージ(と、オブジェクトのダンプ)」という形にしてあります。

なお、JSON.stringify() の動作が気に食わなかった(undefined/function のプロパティは削除される)ので、 replacer を定義してあります。

実装例では、ログレベルと日時フォーマットは他の定数定義から持ってきていますが、

  • ログレベルの文字列 'debug'
  • moment の format 文字列 ’YYYY-MM-DD HH-mm-SS'

という string そのものですので、適宜置き換えてください

transports(log4j なら appenders) を Console に向けていますが、そのあたりは用途に応じてストリームなりファイルなりへ変更してください

以下、実装例

Logging.tsx

//
// Common/Logging - ロギング処理を行うパッケージ
//
// This software is released under the MIT License
//
import  { format, transports, createLogger }    from 'winston';
import  moment                                  from 'moment';

import  { DateFormat, Logging }                 from "Common/Constants";

// ログのカスタムフォーマッタ
const                   formatter               = format.printf(

    // ログ情報をフォーマットした文字列に展開する関数
    ( info : any ) : string => {

        // 引数を展開する
        const {
            level,                      // デフォルトで level と message が渡る
            message,
            timestamp,                  // format.combine() で format.timestamp() 指定されている
            ...etc                      // その他の内容は JSON で表示する
        }               = info;

        // フォーマットした文字列を返す
        return `${ moment( timestamp ).format( DateFormat.YYYYMMDDHHMMSS ) } [${ level }] - ${ message }` +
               `${ etc && Object.keys( etc ).length ? "\n" + JSON.stringify( etc, replacer, 2 ) : "" }`
    }
);

// JSON.stringify のオブジェクトの翻訳関数
const                   replacer                = ( key : any, value : any ) : any => {

    // 関数なら "= function" と返す
    if ( value instanceof Function ) {
        return "= function";

    // undefined なら "= undefined" と返す
    } else if ( value === undefined ) {
        return "= undefined";
    }

    // 他のオブジェクト・配列はそのまま次へ
    return value;
};

// export dev - デバッグ用のロガー
export const            log                     = createLogger(

    // ロガーオプション
    {
        level           : Logging.Level,
        format          : format.combine(
            format.timestamp(),                 // ログ情報に timestamp を付加する
            format.simple(),                    // テキスト行としてログを出す
            formatter,                          // テキスト化するフォーマッタ
        ),
        transports      : [
            new transports.Console(),           // コンソールへ出力する
        ],
    }
);

実行例

log.debug( 'TitleText : props : ', props );
2019-09-14 04:45:55 [debug] - TitleText : props : 
{
  "title": "Welcome to ようこそ React パーク @ よこはまちほー",
  "font": "large",
  "color": "white"
}

TODO

  • やっぱりログ発火点のファイル名・行番号が欲しいが、stacktrace いじるくらいじゃ、ブラウザの差異もあるし、簡単には無理そう。
  • これをきちんとやるなら、console に直接出すロガー書いた方が早いんじゃないかとは思うが、プロジェクトで使う以上は、ファイルやストリームへの機能を諦めるのもなあ
  • というわけで, React を FC でコンポーネント化してあれば、スコープ小さいし(どうせデバッグ用途だし)この程度で実用上はいいのかなと。コンポーネント入口でログ取れていれば、再描画も含めてほぼ内容全部わかるし
  • ま、production にする時に、ログレベルを 'warn' とかにしとけば抑制されるのは、やっぱり便利だよね

みっしりコードを書く風潮が強い中、かなり「スペーシングと空行を多用」なコードですが、ワタシ、大昔からこのスタイルなのでご容赦。お好きに適当にコードフォーマッタかけてくだされば

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1