🪵

MonologでContextにObjectを渡すべきなのか?

2024/04/18に公開

PHPのログのあらゆるケースでMonologはつかわれていますが、MonologのContext(第二引数の配列)に何を指定すべきなのか。

というか、指定したらどうなるのかはあまり理解がされていないケースが多い様に思います。

前提としてこんな感じのコードがあるとする

require_once "vendor/autoload.php";
$logger = new \Monolog\Logger('my_logger');
$errorHandler = new \Monolog\Handler\StreamHandler('php://stderr', \Monolog\Logger::DEBUG);
$logger->pushHandler($errorHandler);

これで、とりあえずLoggerが標準エラー出力にエラーを出し始める

$logger->info('info');

[2024-04-18T04:10:08.499738+00:00] my_logger.INFO: info [] []

Contextは、配列である

php > $logger->info("context test", ['message'=>"context!!!"]);

[2024-04-18T04:01:51.160986+00:00] my_logger.INFO: context test {"message":"context!!!"} []

そして、これの展開はハンドラやその他に任せられるため、ある程度自動展開される

が、デフォルトのハンドラなら所詮出力テキストなので構造化の展開は乏しい。

php > $logger->info('info', ['text'=>["some text", ["another text"]]]);

[2024-04-18T04:12:30.039995+00:00] my_logger.INFO: info {"text":["some text",["another text"]]} []

JSONにしてもまあ乏しいは乏しい、まあログなんてそんなものではあるが。

ポイント、Objectは渡すべきなのか?

以下はDateTimeImmutableを渡した例であるが、なんかいい感じになっている風にみえる

php > $dt = new DateTimeImmutable();
php > $logger->info('info', ['dt'=>$dt]);

[2024-04-18T04:11:50.548522+00:00] my_logger.INFO: info {"dt":"2024-04-18T04:11:41+00:00"} []

だが、これは$dt Objectが暗黙的に日時に変換されているだけで、普通のObjectを渡すとちょっと様子がことなる。

例外をいれてみよう。

php > $e = new Exception("some exception");
php > $logger->info("context test", ['exception'=>$e]);

[2024-04-18T03:59:00.974360+00:00] my_logger.INFO: context test {"exception":"[object] (Exception(code: 0): some exception at php shell code:1)"} []

一見したところ、よさそうにみえなくもない。が、Exceptionはもっと情報を持っている

php > var_dump($e);
object(Exception)#4 (7) {
  ["message":protected]=>
  string(14) "some exception"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(14) "php shell code"
  ["line":protected]=>
  int(1)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}

たとえば、fileやlineは必要なことが多いだろう、それが落ちている。

まとめ、Contextに適当にものをいれるな

一見してContextにいれておけば全部がダンプされるように思えるが、実際に試すとそうでもないことがわかる。

Contextはデフォルトでは万能ではない。

しかしmonologは多機能

色々なプロセッサ、ハンドラ、その他諸々がある。

たとえば、PSR-3準拠のPlaceholder機能があったりする

$handler->pushProcessor(new \Monolog\Processor\PsrLogMessageProcessor()); // 追加で必要

php > $logger->info('context test: {text}', ['text'=>"context!!!"]);

[2024-04-18T04:07:52.047156+00:00] my_logger.INFO: context test: context!!! {"text":"context!!!"} []

こうすると、Contextの情報がエラーメッセージにインラインで展開されるようになる。

文字列連結でエラーログを書くと言う不細工な(?)ことから解放される。

(…といっても、 "context text {$text}" と、そんなに差があるか?といわれると…だが…まあ、定数とか?メソッド返値とか?あるし?)

自作のProcessorもつくれる

たぶん、ContextにObjectを入れる人は、Dumperがほしいのだと思う

php > class DumpProcessor
{
    public function __invoke(array $record)
    {
        if (isset($record['context']) && !empty($record['context'])) {
            $record['extra']['dump'] = print_r($record['context'], true);
        }
        return $record;
    }
}
php > $logger->pushProcessor(new DumpProcessor());
php > $logger->info('info', ['dt'=>$dt]);

[2024-04-18T04:23:06.634801+00:00] my_logger.INFO: info {"dt":"2024-04-18T04:11:41+00:00"} {"dump":"Array\n(\n    [dt] => DateTimeImmutable Object\n        (\n            [date] => 2024-04-18 04:11:41.457097\n            [timezone_type] => 3\n            [timezone] => UTC\n        )\n\n)\n"}

まあ、とっても不格好だが。
(勿論Jsonでもよいが、ここでは所詮文字列に変換しているんだ、という事を伝えたいわかりやすさのために print_r をつかっている)

もう一度まとめ

  • Monologは多機能であり、ちゃんと設定すれば万能である
  • しかし、設定しないと思い通りにはならないだろう
  • というか、設定されまくったMonologは「オレオレMonolog」なので、現場で挙動の乖離が激しい
  • っていうか、ログに本当に欲しい情報がでているか確認した???

Discussion