Логирование в Laravel
Laravel использует библиотеку Monolog для логирования, предоставляя удобную абстракцию с поддержкой множества каналов и драйверов. Конфигурация логирования находится в config/logging.php.
Конфигурация каналов
// config/logging.php
return [
'default' => env('LOG_CHANNEL', 'stack'),
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
'channels' => [
// Stack - combines multiple channels
'stack' => [
'driver' => 'stack',
'channels' => explode(',', env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
// Single file
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
// Daily rotation
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
// Slack notifications
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'critical'),
'replace_placeholders' => true,
],
// Papertrail
'papertrail' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'),
],
'processors' => [PsrLogMessageProcessor::class],
],
// Standard error output
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'debug'),
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [PsrLogMessageProcessor::class],
],
// Syslog
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
'replace_placeholders' => true,
],
// Error log
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
// Null channel (discard all logs)
'null' => [
'driver' => 'monolog',
'handler' => NullHandler::class,
],
// Emergency fallback
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];
Основные драйверы каналов
single
Записывает все логи в один файл:
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
daily
Создаёт новый файл каждый день с автоматической ротацией:
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14, // Keep logs for 14 days
],
// Creates files: laravel-2026-02-22.log, laravel-2026-02-21.log, etc.
stack
Комбинирует несколько каналов. Лог-сообщение отправляется во ВСЕ каналы стека:
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
'ignore_exceptions' => false,
],
slack
Отправляет логи в Slack-канал через webhook:
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Production Alerts',
'emoji' => ':rotating_light:',
'level' => 'critical', // Only critical and above
],
syslog
Отправляет логи в системный syslog:
'syslog' => [
'driver' => 'syslog',
'level' => 'warning',
'facility' => LOG_USER,
],
Использование логирования
Фасад Log
use Illuminate\Support\Facades\Log;
// PSR-3 log levels
Log::emergency('System is unusable.');
Log::alert('Action must be taken immediately.');
Log::critical('Critical conditions.');
Log::error('Runtime errors.');
Log::warning('Exceptional occurrences that are not errors.');
Log::notice('Normal but significant events.');
Log::info('Interesting events.');
Log::debug('Detailed debug information.');
Контекстные данные
Второй аргумент - массив контекстных данных:
Log::info('User logged in.', [
'user_id' => $user->id,
'email' => $user->email,
'ip' => request()->ip(),
]);
Log::error('Payment failed.', [
'order_id' => $order->id,
'gateway' => 'stripe',
'error_code' => $e->getCode(),
'message' => $e->getMessage(),
]);
Log::warning('Slow query detected.', [
'query' => $query,
'time_ms' => $executionTime,
'connection' => $connectionName,
]);
Запись в конкретный канал
// Write to specific channel
Log::channel('slack')->critical('Server disk space is low.', [
'disk_usage' => '95%',
'server' => gethostname(),
]);
// Write to multiple channels (stack on-the-fly)
Log::stack(['daily', 'slack'])->error('Payment gateway down.', [
'gateway' => 'stripe',
'downtime_start' => now()->toIso8601String(),
]);
// Write to custom channel
Log::channel('orders')->info('Order placed.', [
'order_id' => $order->id,
'total' => $order->total,
]);
Хелпер logger()
// Quick logging
logger('Debug message');
logger()->info('User action performed.', ['user_id' => $user->id]);
logger()->error('Something failed.', ['error' => $e->getMessage()]);
Контекст логирования (Laravel 11+)
Laravel 11 добавил механизм контекста, который автоматически включается во все последующие записи лога:
use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Context;
// Add context that persists across all log entries in this request
Context::add('request_id', (string) Str::uuid());
Context::add('user_id', auth()->id());
// Now ALL log entries will include request_id and user_id
Log::info('Processing order.'); // Includes request_id and user_id
Log::info('Order completed.'); // Also includes request_id and user_id
Контекст в middleware
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
final class AddLogContext
{
public function handle(Request $request, Closure $next): mixed
{
Context::add('request_id', (string) Str::uuid());
Context::add('url', $request->fullUrl());
Context::add('method', $request->method());
if ($request->user()) {
Context::add('user_id', $request->user()->id);
Context::add('user_email', $request->user()->email);
}
return $next($request);
}
}
Stacked контекст
// Push values to a stack within context
Context::push('breadcrumbs', 'homepage');
Context::push('breadcrumbs', 'category');
Context::push('breadcrumbs', 'product');
// Retrieve stacked values
$breadcrumbs = Context::get('breadcrumbs');
// ['homepage', 'category', 'product']
Контекст через очереди
Контекст автоматически передаётся в queued jobs:
// In controller
Context::add('request_id', $requestId);
Context::add('user_id', auth()->id());
// Dispatch job - context is automatically passed
ProcessOrder::dispatch($order);
// In the job, the context is available
// Log entries in the job will include request_id and user_id
Создание кастомных каналов
Кастомный канал через Monolog
// config/logging.php
'channels' => [
'orders' => [
'driver' => 'daily',
'path' => storage_path('logs/orders.log'),
'level' => 'info',
'days' => 30,
],
'api' => [
'driver' => 'daily',
'path' => storage_path('logs/api.log'),
'level' => 'debug',
'days' => 7,
],
'security' => [
'driver' => 'stack',
'channels' => ['daily-security', 'slack'],
],
'daily-security' => [
'driver' => 'daily',
'path' => storage_path('logs/security.log'),
'level' => 'warning',
'days' => 90,
],
'performance' => [
'driver' => 'single',
'path' => storage_path('logs/performance.log'),
'level' => 'info',
],
],
Кастомный Monolog Handler
// config/logging.php
'channels' => [
'telegram' => [
'driver' => 'monolog',
'handler' => \App\Logging\TelegramHandler::class,
'handler_with' => [
'token' => env('TELEGRAM_BOT_TOKEN'),
'chatId' => env('TELEGRAM_CHAT_ID'),
],
'level' => 'critical',
],
],
declare(strict_types=1);
namespace App\Logging;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\LogRecord;
final class TelegramHandler extends AbstractProcessingHandler
{
public function __construct(
private readonly string $token,
private readonly string $chatId,
int|string $level = 'DEBUG',
bool $bubble = true,
) {
parent::__construct($level, $bubble);
}
protected function write(LogRecord $record): void
{
$text = sprintf(
"*%s* [%s]\n```\n%s\n```",
$record->level->name,
$record->datetime->format('Y-m-d H:i:s'),
$record->formatted ?? $record->message,
);
Http::post("https://api.telegram.org/bot{$this->token}/sendMessage", [
'chat_id' => $this->chatId,
'text' => $text,
'parse_mode' => 'Markdown',
]);
}
}
Кастомизация канала через tap
// config/logging.php
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
'tap' => [\App\Logging\CustomizeFormatter::class],
],
declare(strict_types=1);
namespace App\Logging;
use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;
final class CustomizeFormatter
{
/**
* Customize the given logger instance.
*/
public function __invoke(Logger $logger): void
{
foreach ($logger->getHandlers() as $handler) {
$handler->setFormatter(new LineFormatter(
format: "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
dateFormat: 'Y-m-d H:i:s',
allowInlineLineBreaks: true,
ignoreEmptyContextAndExtra: true,
));
}
}
}
Уровни логирования (PSR-3)
emergency → alert → critical → error → warning → notice → info → debug
Когда вы устанавливаете level в конфигурации канала, записываются только сообщения этого уровня и выше:
// level: 'warning' → will log: warning, error, critical, alert, emergency
// level: 'debug' → will log: ALL levels
// level: 'error' → will log: error, critical, alert, emergency
Логирование запросов к базе данных
// In AppServiceProvider::boot()
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
DB::listen(function ($query) {
if ($query->time > 100) { // Slow queries (>100ms)
Log::channel('performance')->warning('Slow query detected.', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time_ms' => $query->time,
]);
}
});
Практический пример: структурированное логирование
declare(strict_types=1);
namespace App\Services;
use App\Models\Order;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Log;
final class OrderService
{
public function processOrder(Order $order): void
{
// Set context for all subsequent log entries
Context::add('order_id', $order->id);
Context::add('customer_id', $order->customer_id);
Log::channel('orders')->info('Starting order processing.');
try {
$this->validateInventory($order);
Log::channel('orders')->info('Inventory validated.');
$this->chargePayment($order);
Log::channel('orders')->info('Payment charged.', [
'amount' => $order->total,
'currency' => $order->currency,
]);
$this->fulfillOrder($order);
Log::channel('orders')->info('Order fulfilled.');
} catch (\Throwable $e) {
Log::channel('orders')->error('Order processing failed.', [
'error' => $e->getMessage(),
'step' => $this->getCurrentStep(),
]);
// Also alert via Slack for critical orders
if ($order->total > 1000) {
Log::channel('slack')->critical('High-value order failed!', [
'order_id' => $order->id,
'total' => $order->total,
'error' => $e->getMessage(),
]);
}
throw $e;
}
}
}