Monthly Log Driver en Laravel 13: Rotación Automática
Introducción
Uno de los desafíos más comunes en aplicaciones en producción es gestionar adecuadamente los archivos de logs. Cuando tu aplicación genera miles de eventos diarios, los logs pueden crecer exponencialmente, consumiendo espacio en disco y dificultando la búsqueda de errores críticos.
Laravel 13 introduce el monthly log driver, una solución elegante que automatiza la rotación de logs agrupándolos por mes. A diferencia del driver daily que crea un archivo por día, el driver monthly consolida todos los logs de un mes en un único archivo, reduciendo la fragmentación y mejorando la organización de tus registros.
En este artículo aprenderás a configurar, personalizar y aprovechar al máximo esta nueva funcionalidad para optimizar tu estrategia de logging en producción.
Qué es el Monthly Log Driver
El monthly log driver es un gestor de canales de logging que automáticamente rota los archivos de logs en base a períodos mensuales. Cada mes genera un nuevo archivo de log, lo que resulta en:
- Menos fragmentación: Un archivo por mes vs. uno por día
- Mejor organización: Logs agrupados por período contable o administrativo
- Menor consumo de inodos: Crucial en sistemas de archivos con límites
- Facilita auditoría: Acceso rápido a logs históricos por período
Diferencias con otros drivers
daily: app-2026-01-15.log (1 archivo/día)
single: laravel.log (1 archivo único)
monthly: laravel-2026-01.log (1 archivo/mes) ← NUEVO
Configuración Básica del Monthly Driver
Paso 1: Actualizar a Laravel 13.23+
Primero, asegúrate de tener la versión correcta:
composer update laravel/framework
Verifica que tengas Laravel 13.23.0 o superior:
php artisan --version
Paso 2: Configurar en config/logging.php
Abre tu archivo de configuración de logging y añade el nuevo canal:
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'daily'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
// ← NUEVO DRIVER MONTHLY
'monthly' => [
'driver' => 'monthly',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
],
Paso 3: Activar el Monthly Driver
Cambia tu variable de entorno para usar el nuevo driver:
# .env
LOG_CHANNEL=monthly
LOG_LEVEL=debug
Configuración Avanzada del Monthly Driver
Personalizar la ruta de almacenamiento
Es recomendable usar rutas más descriptivas para identificar fácilmente el período:
// config/logging.php
'monthly' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'permission' => 0644,
],
Esto generará archivos como:
storage/logs/monthly/laravel-2026-01.log
storage/logs/monthly/laravel-2026-02.log
storage/logs/monthly/laravel-2026-03.log
Configurar múltiples canales con stack
Una estrategia común es mantener un archivo de todos los logs y otro separado para errores:
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['monthly', 'monthly_errors'],
'ignore_exceptions' => false,
],
'monthly' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'monthly_errors' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/errors.log'),
'level' => 'error',
],
],
Casos de Uso Prácticos
Caso 1: Logging Diferenciado por Nivel
Mantén logs de depuración separados de los críticos:
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['monthly_debug', 'monthly_errors'],
],
'monthly_debug' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/debug.log'),
'level' => 'debug',
],
'monthly_errors' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/errors.log'),
'level' => 'error',
],
],
Caso 2: Logging por Módulo
Separa logs de diferentes partes de tu aplicación:
// config/logging.php
'channels' => [
'payments' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/payments.log'),
'level' => 'info',
],
'api' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/api.log'),
'level' => 'debug',
],
'auth' => [
'driver' => 'monthly',
'path' => storage_path('logs/monthly/auth.log'),
'level' => 'info',
],
],
Luego usa en tu código:
// En un controlador de pagos
Log::channel('payments')->info('Pago procesado', [
'order_id' => $orderId,
'amount' => $amount,
]);
// En un controlador de API
Log::channel('api')->debug('Request recibido', [
'endpoint' => $request->path(),
'method' => $request->method(),
]);
// En un controlador de autenticación
Log::channel('auth')->warning('Intento fallido de login', [
'email' => $email,
'ip' => $request->ip(),
]);
Caso 3: Stack Híbrido (Daily + Monthly)
Mantén logs del día actual en archivos diarios pero guardacopia mensual:
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'monthly_archive'],
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'days' => 7,
],
'monthly_archive' => [
'driver' => 'monthly',
'path' => storage_path('logs/archive/laravel.log'),
'level' => 'notice',
],
],
Limpiar Logs Antiguos
Con el monthly driver, es menos crítico, pero aún es buena práctica limpiar logs mensuales antiguos. Crea un comando personalizado:
php artisan make:command CleanOldMonthlyLogs
// app/Console/Commands/CleanOldMonthlyLogs.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Carbon\Carbon;
use File;
class CleanOldMonthlyLogs extends Command
{
protected $signature = 'logs:clean-old-monthly {--months=12 : Mantener logs de últimos N meses}';
protected $description = 'Elimina logs mensuales más antiguos que N meses';
public function handle()
{
$monthsToKeep = $this->option('months');
$cutoffDate = Carbon::now()->subMonths($monthsToKeep);
$logPath = storage_path('logs/monthly');
if (!is_dir($logPath)) {
$this->info('No hay directorio de logs mensuales.');
return;
}
$files = File::files($logPath);
$deleted = 0;
foreach ($files as $file) {
$filename = $file->getFilename();
// Extrae el período del nombre: laravel-2026-01.log
if (preg_match('/(\d{4})-(\d{2})\.log$/', $filename, $matches)) {
$logDate = Carbon::createFromFormat('Y-m', $matches[1] . '-' . $matches[2]);
if ($logDate->lessThan($cutoffDate)) {
File::delete($file->getRealPath());
$deleted++;
$this->line("Eliminado: {$filename}");
}
}
}
$this->info("Se eliminaron {$deleted} archivos de log.");
}
}
Programa su ejecución automática:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
// Limpiar logs mensuales mayores a 12 meses el primer día de cada mes
$schedule->command('logs:clean-old-monthly --months=12')
->monthlyOn(1, '02:00');
}
Monitorear Uso de Disco
Crea un comando para vigilar el espacio ocupado por logs:
php artisan make:command CheckLogDiskUsage
// app/Console/Commands/CheckLogDiskUsage.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use File;
class CheckLogDiskUsage extends Command
{
protected $signature = 'logs:disk-usage';
protected $description = 'Muestra uso de disco de logs';
public function handle()
{
$logsPath = storage_path('logs');
$totalSize = 0;
$files = [];
foreach (File::allFiles($logsPath) as $file) {
$size = $file->getSize();
$totalSize += $size;
$files[] = [
'Archivo' => $file->getFilename(),
'Tamaño' => $this->formatBytes($size),
'Ruta' => $file->getRelativePath(),
];
}
$this->table(
['Archivo', 'Tamaño', 'Ruta'],
$files
);
$this->info("\nTamaño total: " . $this->formatBytes($totalSize));
}
private function formatBytes($bytes)
{
$units = ['B', 'KB', 'MB', 'GB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= (1 << (10 * $pow));
return round($bytes, 2) . ' ' . $units[$pow];
}
}
Integración con Servicios Externos
El monthly driver es ideal para exportar logs a servicios externos:
// app/Console/Commands/ExportMonthlyLogs.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use File;
use AWS\S3\S3Client;
class ExportMonthlyLogs extends Command
{
public function handle()
{
$logsPath = storage_path('logs/monthly');
$s3 = new S3Client([
'version' => 'latest',
'region' => env('AWS_DEFAULT_REGION'),
]);
foreach (File::files($logsPath) as $file) {
$s3->putObject([
'Bucket' => env('AWS_BUCKET'),
'Key' => 'logs/archive/' . $file->getFilename(),
'Body' => fopen($file->getRealPath(), 'r'),
]);
$this->info("Exportado: " . $file->getFilename());
}
}
}
Troubleshooting y Mejores Prácticas
Permisos de archivos
Asegúrate de que el directorio tiene permisos correctos:
chmod -R 775 storage/logs/monthly
chown -R www-data:www-data storage/logs/monthly
Rotación no ocurre
Si los logs no están rotando mensualmente:
// Verifica la configuración
php artisan config:show logging.channels.monthly
Performance con logs muy grandes
Para aplicaciones con alto volumen, usa múltiples canales:
'stack' => [
'driver' => 'stack',
'channels' => ['monthly', 'monthly_errors', 'monthly_slow'],
],
Puntos clave
- El monthly log driver automatiza la rotación de logs por período mensual
- Reduce fragmentación y mejora organización frente al driver
daily - Ideal para aplicaciones con alto volumen de logs que requieren auditoría
- Configurable en
config/logging.phpcon múltiples canales personalizados - Combina con stacks para logging diferenciado por nivel, módulo o propósito
- Implementa limpieza automática de logs antiguos con scheduled commands
- Monitorea uso de disco regularmente para evitar saturación
- Intégrable con servicios cloud (AWS S3, Google Cloud Storage, etc.)
- Soporta múltiples canales simultáneamente para casos de uso complejos
- Requiere Laravel 13.23.0 o superior para acceso a esta funcionalidad