【Laravel11】最低限のログ設計について

## はじめに
システムのログってめちゃくちゃ大事。
システムのバグの早期発見からの修正だったり、システムで何が起きているか可視化できる。

そんなある日、あるプロジェクトのフレームワーク設計を行うことがあった。
ただ単に環境を用意して渡すと、書き方がそれぞれ異なる可能性もあるため、先に準備した方がいいなと思った時の出来事である。

**ログ周りはできるだけ処理を共通にしてメンバーにおろそう**

特にLaravelは書き方に自由性があるため、それぞれ異なる書き方ができやすいフレームワーク（だと個人的には思っている）。

荒削りな部分もあるが、全体のログ設計として最低限を網羅できるように設計したことを忘れないように備忘録としてメモしておく。


## ログ実装

+ **前提**
前提としてデフォルトのLaravelのフローを崩さず、ログの出力、書き出しの部分のみをオーバライドしたい。
また、標準出力も開発効率向上のため、対応する。
<br>

+ **ログ関数**
まずログを出力するServiceクラスを作成。

    **基本ログ項目**
    ・IPアドレス
    ・URL
    ・メソッド

```LogService.php
<?php

namespace App\Services;

use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LogService
{
    protected $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function log($level, $message, array $addCon = [])
    {
        // 基本ログ
        $context = [
            'ip_address' => $this->request->ip(),
            'url' => $this->request->fullUrl(),
            'method' => $this->request->method(),
        ];

        if ($level == "error") { $cont["request_body"] = $this->request->all(); }
        // カスタムログと基本ログをマージ
        $context = array_merge($context, $addCon);

        try {
            Log::stack(['daily', 'stdout'])->log($level, $message, $context);
        } catch (\Exception $e) {}
    }
}

```

+ **ログフォーマットを整える**
```Logging/CustomizeFormatter.php
<?php

namespace App\Logging;

use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\AbstractProcessingHandler;

class CustomizeFormatter
{
    public function __invoke(Logger $logger): void
    {
        foreach ($logger->getHandlers() as $handler) {
            if ($handler instanceof AbstractProcessingHandler) {
                $handler->pushProcessor(function ($record) use ($handler) {
                    if ($record['level'] >= \Monolog\Logger::ERROR) {
                        // エラーレベル以上の場合、カスタムフォーマットを使用
                        $handler->setFormatter(new LineFormatter(
                            "[%datetime%] %channel%.%level_name%: %message% %context% \n"
                        ));
                    } else {
                        // 通常のフォーマットに戻す
                        $handler->setFormatter(new LineFormatter(null, null, true, true));
                    }
                    return $record;
                });
            }
        }
    }
}

```

+ **ログ設定**
下記情報を追記または修正する
```config/logging.php
        'stack' => [
            'driver' => 'stack',
            'ignore_exceptions' => false,
        ],
        'daily' => [
            'driver' => 'daily',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
            'days' => env('LOG_DAILY_DAYS', 30),
            'replace_placeholders' => true,
            'tap' => [App\Logging\CustomizeFormatter::class],
        ],
        'stdout' => [
            'driver' => 'monolog',
            'level' => env('LOG_LEVEL', 'debug'),
            'tap' => [App\Logging\CustomizeFormatter::class],
            'handler' => StreamHandler::class,
            'formatter' => env('LOG_STDOUT_FORMATTER'),
            'with' => [
                'stream' => 'php://stdout',
            ],
        ],
```

+ **エクセプション設定**
エクセプション時は共通で設定する。
```bootstrap/app.php
    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->reportable(function (Throwable $e)
        {
            $logService = app()->make(LogService::class);
            $logService->log("error", $e->getMessage(), ["file" => $e->getFile(), "line" => $e->getLine()]);
        });
    })
```

これで勝手にエクセプションをキャッチしたら勝手に書き込まれる。

+ **バリデーションログ**
バリデーションは一律にしたかったが、各フォームリクエストファイルに記述するように今回はした。

```FormRequest.php

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Validation\ValidationException;
use App\Services\LogService;

protected function failedValidation(Validator $validator)
{
    $logService = app()->make(LogService::class);
    $logService->log("warning", "Validation Error", [
        "errors" => $validator->errors(),
        "input" => $this->all(),
    ]);

    throw new ValidationException($validator);
}
```


+ **呼び出し**
```controller.php
        $this->log_service->log('info', 'ログだよ');
```


## まとめ
ログって忘れがちだけどめっちゃ大事ですなぁ