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

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

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

インストールは

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

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

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

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

```js
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(),           // コンソールへ出力する
        ],
    }
);
```

実行例

```js
log.debug( 'TitleText : props : ', props );
```

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

TODO

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

で

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