コンソール出力用(主にデバッグ用途)で 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 に向けていますが、そのあたりは用途に応じてストリームなりファイルなりへ変更してください
以下、実装例
//
// 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' とかにしとけば抑制されるのは、やっぱり便利だよね
で
みっしりコードを書く風潮が強い中、かなり「スペーシングと空行を多用」なコードですが、ワタシ、大昔からこのスタイルなのでご容赦。お好きに適当にコードフォーマッタかけてくだされば