Logging con Context en Laravel 12¶
Este documento describe el sistema de logging con context automático usando Laravel Context, incluyendo request_id y user_id para correlación y debugging mejorado.
Laravel Context Overview¶
Laravel Context permite agregar datos contextuales que se incluyen automáticamente en todos los logs escritos durante una request. Esto mejora significativamente el debugging y monitoring.
Implementación en RequestId Middleware¶
Configuración Automática¶
// app/Http/Middleware/RequestId.php
use Illuminate\Support\Facades\Context;
public function handle(Request $request, Closure $next): Response
{
$requestId = $request->headers->get('X-Request-Id') ?: (string) Str::uuid();
// Compartir en request attributes
$request->attributes->set('request_id', $requestId);
// Agregar al Laravel Context para logging automático
Context::add([
'request_id' => $requestId,
'user_id' => $request->user()?->id,
]);
$response = $next($request);
$response->headers->set('X-Request-Id', $requestId);
return $response;
}
Datos de Context Incluidos¶
- request_id: UUID único por request para correlación
- user_id: ID del usuario autenticado (null para guests)
Beneficios del Context¶
Correlación de Logs¶
Todos los logs de una misma request comparten el mismo request_id
:
// En cualquier parte del código
Log::info('User attempted login', ['email' => $email]);
Log::error('Login failed', ['reason' => 'invalid_credentials']);
// Logs resultantes:
// [2024-01-15 10:30:15] INFO: User attempted login {"email":"user@example.com","request_id":"550e8400-e29b-41d4-a716-446655440000","user_id":123}
// [2024-01-15 10:30:15] ERROR: Login failed {"reason":"invalid_credentials","request_id":"550e8400-e29b-41d4-a716-446655440000","user_id":123}
Debugging Distribuido¶
Con microservicios o workers, el request_id
permite seguir una operación completa:
// Controller
Log::info('Starting export process');
// → request_id: abc-123
// Job/Queue
Log::info('Processing export in background');
// → request_id: abc-123 (si se pasa el context)
// External API
Log::info('Calling external service', ['api_endpoint' => $url]);
// → request_id: abc-123
Configuración de Logging¶
Canales de Log Compatibles¶
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
'replace_placeholders' => true,
],
// Context se incluye automáticamente en Laravel 12
// No se necesita configuración adicional
],
Formato de Logs con Context¶
Los logs incluyen automáticamente el context:
{
"message": "User login successful",
"context": {
"user_email": "user@example.com"
},
"level": 200,
"level_name": "INFO",
"channel": "local",
"datetime": "2024-01-15T10:30:15.123456+00:00",
"extra": {
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": 123
}
}
Casos de Uso Avanzados¶
Context Temporal¶
// Agregar context temporal para una operación específica
Context::add(['operation' => 'bulk_import']);
try {
$this->processImport($file);
Log::info('Import completed successfully');
} finally {
Context::forget('operation');
}
Context en Jobs¶
// Pasar context a jobs en background
class ProcessExport implements ShouldQueue
{
public function handle(): void
{
// Restaurar context desde la request original si es necesario
Context::add([
'request_id' => $this->requestId,
'user_id' => $this->userId,
]);
Log::info('Processing export in background');
// Los logs mantienen correlación con la request original
}
}
Context Personalizado por Controller¶
class UserController extends Controller
{
public function __construct()
{
$this->middleware(function ($request, $next) {
Context::add(['controller' => 'UserController']);
return $next($request);
});
}
public function update(Request $request, User $user)
{
Context::add(['target_user_id' => $user->id]);
Log::info('Updating user profile');
// → Incluye controller, target_user_id, request_id, user_id
}
}
Integración con Inertia¶
Request ID en Frontend¶
El request_id está disponible en el frontend vía Inertia shared data:
// Layout o componente React
import { usePage } from '@inertiajs/react';
const Layout = ({ children }) => {
const { requestId } = usePage().props;
// Incluir en reportes de error del frontend
useEffect(() => {
window.addEventListener('error', (event) => {
console.error('Frontend error', {
message: event.message,
request_id: requestId,
});
});
}, [requestId]);
};
Debugging Cross-Stack¶
// En el frontend, enviar request_id en errores
const reportError = (error: Error) => {
fetch('/api/errors', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-Id': requestId, // Del shared data de Inertia
},
body: JSON.stringify({
message: error.message,
stack: error.stack,
request_id: requestId,
}),
});
};
Monitoring y Alertas¶
Queries por Request ID¶
# Buscar todos los logs de una request específica
grep "550e8400-e29b-41d4-a716-446655440000" storage/logs/laravel.log
# Con herramientas como ELK Stack
GET /logs/_search
{
"query": {
"term": {
"extra.request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
Alertas por Usuario¶
# Monitorear errores por usuario específico
GET /logs/_search
{
"query": {
"bool": {
"must": [
{"term": {"level_name": "ERROR"}},
{"term": {"extra.user_id": 123}}
]
}
}
}
Testing Context¶
Verificar Context en Tests¶
/** @test */
public function context_includes_request_id_and_user_id(): void
{
$user = User::factory()->create();
Route::get('/test-context', function () {
$context = Context::all();
Log::info('Test log message');
return response()->json(['context' => $context]);
})->middleware('web');
$response = $this->actingAs($user)->get('/test-context');
$response->assertOk();
$data = $response->json();
$this->assertArrayHasKey('request_id', $data['context']);
$this->assertArrayHasKey('user_id', $data['context']);
$this->assertEquals($user->id, $data['context']['user_id']);
}
Mock Context en Tests¶
protected function setUp(): void
{
parent::setUp();
// Context limpio para cada test
Context::flush();
}
/** @test */
public function can_add_custom_context(): void
{
Context::add(['test_context' => 'test_value']);
$this->assertEquals('test_value', Context::get('test_context'));
}
Best Practices¶
Context Apropiado¶
- Siempre incluir: request_id, user_id
- Por operación: operation_type, target_resource_id
- Para debugging: component, method, step
Context Inapropiado¶
- Datos sensibles: passwords, tokens, PII
- Datos grandes: response bodies, file contents
- Datos dinámicos: timestamps (ya incluidos por Laravel)
Performance¶
- Context es muy eficiente en Laravel 12
- Se serializa una vez por log entry
- Uso mínimo de memoria adicional
Limpieza¶
// Limpiar context específico cuando sea necesario
Context::forget('temporary_operation');
// O limpiar todo el context (raramente necesario)
Context::flush();
Integración con APM¶
New Relic¶
// Agregar request_id como atributo personalizado
if (extension_loaded('newrelic')) {
newrelic_add_custom_parameter('request_id', Context::get('request_id'));
newrelic_add_custom_parameter('user_id', Context::get('user_id'));
}
DataDog¶
// Tags personalizados para DataDog
app('datadog')->increment('requests.total', 1, [
'request_id' => Context::get('request_id'),
'user_id' => Context::get('user_id'),
]);