Laravelのログ管理とエラーハンドリング

Laravelでの開発において、適切なログ管理とエラーハンドリングは非常に重要です。これらは問題のデバッグ、アプリケーションの健全性の監視、そしてエンドユーザーに対する適切なエラー表示に役立ちます。この記事では、Laravelのログ機能とエラーハンドリングの仕組みについて詳しく解説します。

Laravelのログシステム

基本設定

Laravelのログ設定は config/logging.php ファイルで管理されています。デフォルトでは、複数のログチャネルが定義されており、環境に応じて使い分けることができます。

// config/logging.php の一部
return [
    // デフォルトのログチャネル
    'default' => env('LOG_CHANNEL', 'stack'),

    // 利用可能なログチャネル
    'channels' => [
        'stack' => [
            'driver' => 'stack',
            'channels' => ['single'],
            'ignore_exceptions' => false,
        ],
        'single' => [
            'driver' => 'single',
            'path' => storage_path('logs/laravel.log'),
            'level' => env('LOG_LEVEL', 'debug'),
        ],
        // 他のチャネル設定...
    ],
];

ログレベルについて

Laravelでは、以下のログレベルが使用できます(緊急度の高い順):

  1. emergency – システムが使用不可
  2. alert – すぐに対応が必要
  3. critical – 致命的な状況
  4. error – エラーの発生
  5. warning – 警告(エラーではないが注意が必要)
  6. notice – 通常だが重要なイベント
  7. info – 情報メッセージ
  8. debug – デバッグ情報

ログの書き込み方法

Laravelでは、Log ファサードを使用してログを記録できます:

use Illuminate\Support\Facades\Log;

// 基本的な使い方
Log::info('ユーザーがログインしました', ['id' => $user->id]);

// 様々なレベルでのログ記録
Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);

チャネルの切り替え

特定のログチャネルを使用したい場合は、channel メソッドを使います:

// 'slack' チャネルにエラーを記録
Log::channel('slack')->error('Something went wrong!');

// 複数のチャネルに同時に記録
Log::stack(['single', 'slack'])->info('重要な処理が完了しました');

コンテキスト情報の追加

ログメッセージにコンテキスト情報を追加することで、デバッグがより容易になります:

Log::info('注文が作成されました', [
    'order_id' => $order->id,
    'amount' => $order->amount,
    'customer' => $order->customer->email
]);

カスタムログチャネルの作成

特定の要件に合わせてカスタムログチャネルを作成することも可能です:

// config/logging.php に追加
'channels' => [
    // 既存のチャネル...
    
    'custom' => [
        'driver' => 'monolog',
        'handler' => MyCustomLogHandler::class,
        'with' => [
            'foo' => 'bar',
        ],
    ],
],

カスタムハンドラーの例:

namespace App\Logging;

use Monolog\Handler\AbstractProcessingHandler;

class MyCustomLogHandler extends AbstractProcessingHandler
{
    protected function write(array $record): void
    {
        // カスタムログの処理ロジック
    }
}

エラーハンドリング

エラーの設定

Laravelのエラーハンドリング設定は app/Exceptions/Handler.php で管理されています。このクラスは、アプリケーション内で発生したすべての例外を処理します。

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;

class Handler extends ExceptionHandler
{
    /**
     * 報告しない例外のタイプ
     */
    protected $dontReport = [
        \Illuminate\Auth\AuthenticationException::class,
        \Illuminate\Auth\Access\AuthorizationException::class,
        // その他の例外...
    ];

    /**
     * 例外をレポートする
     */
    public function register(): void
    {
        $this->reportable(function (Throwable $e) {
            // カスタムのレポートロジック
        });
    }
}

カスタムエラーページの作成

HTTPエラーコードに応じてカスタムエラーページを表示するには、resources/views/errors ディレクトリに対応するビューファイルを作成します:

  • 404.blade.php – ページが見つからない
  • 500.blade.php – サーバーエラー
  • 403.blade.php – アクセス禁止

例えば、404エラーページは次のように作成できます:

<!-- resources/views/errors/404.blade.php -->
@extends('layouts.app')

@section('content')
<div class="error-page">
    <h1>404</h1>
    <h2>ページが見つかりません</h2>
    <p>お探しのページは存在しないか、移動した可能性があります。</p>
    <a href="{{ url('/') }}" class="btn">ホームに戻る</a>
</div>
@endsection

特定の例外の処理

特定の例外を独自に処理するには、Handler クラスの register メソッド内でレンダラーを定義します:

public function register(): void
{
    $this->renderable(function (\App\Exceptions\CustomException $e, $request) {
        return response()->view('errors.custom', [], 500);
    });
}

API例外のハンドリング

APIリクエストの場合は、JSONレスポンスを返すように設定できます:

public function register(): void
{
    $this->renderable(function (Throwable $e, $request) {
        if ($request->is('api/*') || $request->wantsJson()) {
            return response()->json([
                'message' => $e->getMessage(),
                'code' => $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500,
            ], $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500);
        }
    });
}

実践的なエラー処理パターン

例外クラスの作成

アプリケーション固有のエラーを表現するためのカスタム例外クラスを作成することができます:

namespace App\Exceptions;

use Exception;

class InsufficientFundsException extends Exception
{
    protected $account;
    
    public function __construct($account, $message = "口座残高不足です", $code = 0, Exception $previous = null)
    {
        $this->account = $account;
        parent::__construct($message, $code, $previous);
    }
    
    public function getAccount()
    {
        return $this->account;
    }
}

これを使用する例:

public function withdraw($amount)
{
    if ($this->balance < $amount) {
        throw new InsufficientFundsException($this);
    }
    
    $this->balance -= $amount;
    return $this->balance;
}

トランザクションとエラー処理

データベーストランザクション中のエラー処理の例:

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

try {
    DB::beginTransaction();
    
    // トランザクション内の処理
    $user->update(['balance' => $user->balance - $amount]);
    $recipient->update(['balance' => $recipient->balance + $amount]);
    Transfer::create(['from' => $user->id, 'to' => $recipient->id, 'amount' => $amount]);
    
    DB::commit();
    
    Log::info('送金が完了しました', [
        'user' => $user->id,
        'recipient' => $recipient->id,
        'amount' => $amount
    ]);
    
} catch (\Exception $e) {
    DB::rollBack();
    
    Log::error('送金処理中にエラーが発生しました', [
        'user' => $user->id,
        'recipient' => $recipient->id,
        'amount' => $amount,
        'error' => $e->getMessage()
    ]);
    
    throw $e;
}

ログの設定ベストプラクティス

日ごとのログローテーション

大規模なアプリケーションでは、日ごとにログファイルをローテーションさせることで管理が容易になります:

'daily' => [
    'driver' => 'daily',
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'days' => 14,  // 保持する日数
],

Slack通知の設定

重要なエラーが発生した場合にSlackに通知を送ることができます:

'slack' => [
    'driver' => 'slack',
    'url' => env('LOG_SLACK_WEBHOOK_URL'),
    'username' => 'Laravel Log',
    'emoji' => ':boom:',
    'level' => env('LOG_LEVEL', 'critical'),
],

使用例:

// criticalレベル以上のエラーのみSlackに通知される
Log::channel('slack')->critical('データベース接続エラー発生');

本番環境でのログ設定

本番環境では、デバッグ情報を最小限に抑えるべきです:

// .env.production
LOG_CHANNEL=stack
LOG_LEVEL=error  # errorレベル以上のみ記録

パフォーマンスモニタリングとログ

処理時間の記録

処理にかかった時間をログに記録する例:

$startTime = microtime(true);

// 長時間の処理
processLargeDataset();

$endTime = microtime(true);
$executionTime = $endTime - $startTime;

Log::info('データセット処理が完了しました', [
    'execution_time' => $executionTime,
    'records_processed' => $count
]);

クエリのログ記録

開発中にSQLクエリをログに記録する設定:

// AppServiceProvider.php の boot メソッド内
if (app()->environment('local')) {
    DB::listen(function ($query) {
        Log::channel('queries')->info(
            $query->sql,
            [
                'bindings' => $query->bindings,
                'time' => $query->time
            ]
        );
    });
}

外部サービスを使ったエラー追跡

Laravel アプリケーションのエラー追跡には外部サービスを統合することも可能です:

Sentry の統合

composer require sentry/sentry-laravel

設定:

// config/logging.php
'channels' => [
    // 他のチャネル...
    
    'sentry' => [
        'driver' => 'sentry',
        'level'  => env('LOG_LEVEL', 'error'),
    ],
],

app/Exceptions/Handler.php の修正:

public function register(): void
{
    $this->reportable(function (Throwable $e) {
        if (app()->bound('sentry')) {
            app('sentry')->captureException($e);
        }
    });
}

まとめ

Laravelのログシステムとエラーハンドリングは、アプリケーションの健全性と信頼性を維持するために重要な役割を果たします。適切に設定することで、問題の素早い発見と解決が可能になります。

ポイントをまとめると:

  1. 環境に応じた適切なログレベルの設定
  2. コンテキスト情報を含めた詳細なログ記録
  3. カスタム例外クラスを活用した明確なエラー表現
  4. ユーザーフレンドリーなエラーページの提供
  5. APIリクエストに対する適切なJSON応答
  6. 重要なエラーの通知システムの導入
  7. 外部サービスを活用した高度なエラー追跡

これらのプラクティスを組み合わせることで、堅牢なLaravelアプリケーションを構築することができます。エンドユーザーにとっては使いやすさが向上し、開発者にとってはデバッグがより容易になります。

皆さんのLaravelプロジェクトにも、ぜひ適切なログ管理とエラーハンドリングを導入してみてください!

コメント

タイトルとURLをコピーしました