mongoose-studio / phobos-framework-logger
Phobos Framework Logger - Structured JSON logging with trace context (PSR-3)
Package info
github.com/mongoose-studio/phobos-framework-logger
pkg:composer/mongoose-studio/phobos-framework-logger
Requires
- php: >=8.4
- mongoose-studio/phobos-framework: ^3.1
- psr/log: ^3.0
Requires (Dev)
- phpunit/phpunit: ^11.0
README
Librería satélite opt-in de logging estructurado para Phobos Framework. Logs JSON line-delimited con contexto de traza automático (trace_id/request_id), redacción de datos sensibles y writers intercambiables. Implementa PSR-3 sin arrastrar Monolog ni ninguna otra dependencia pesada — fiel al minimalismo del núcleo, que a propósito no trae logger.
Características
- 📋 PSR-3 completo -
Psr\Log\LoggerInterface, interpola placeholders{clave}, valida niveles - 🧱 JSON estructurado - una línea = un objeto JSON, listo para Loki/agentes de logs (patrón K8s)
- 🔗 Contexto de traza -
trace_id/request_idinyectados en cada registro desde unLogContextrequest-scoped - 🌐 W3C Trace Context - adopta el
traceparententrante (Kong/OTel) y lo propaga saliente contraceParent() - 🕵️ Redacción de secretos - enmascara claves sensibles (
password,token,authorization, ...) y patrones sueltos (JWT, PAN) - 🚦 Access-log integrado -
request.handledcon método, path, status y duración; nivel según status (4xx=warning, 5xx=error) - 🖥️ Writers intercambiables -
stdout(prod),pretty(dev, coloreado),file,memory(tests),null - 💥 Excepciones expandidas -
context['exception']con unThrowablese expande a{class, message, file, line, trace} - 🧪 Testeable por diseño - DI por constructor en todo;
MemoryWriterpara afirmar logs en tests - 🚫 Cero magia - sin facades, sin estado estático: la forma canónica de Phobos
Instalación
composer require mongoose-studio/phobos-framework-logger
Dependencias
mongoose-studio/phobos-framework^3.1psr/log^3.0- PHP >= 8.4
Uso
1. Registrar el provider (módulo raíz)
public function providers(): array { return [AppServiceProvider::class, LoggerServiceProvider::class]; }
2. Middleware global (contexto de traza + access-log)
Primero en la cadena, para que todo lo demás ya loguee con contexto:
Phobos::init(ROOT, APPLICATION) ->loadEnvironment()->loadConfig() ->middleware(RequestLogContextMiddleware::class) ->middleware(CorsMiddleware::class) ->bootstrap(ApiModule::class) ->run()->send();
3. Loguear desde un service (inyección por constructor)
use Psr\Log\LoggerInterface; class PostService { public function __construct(private LoggerInterface $log) {} // autowiring public function publish(object $data): array { $this->log->info('post.publishing', ['kind' => $data->kind]); try { // ... } catch (\Throwable $e) { $this->log->error('post.publish_failed', ['exception' => $e]); throw $e; // nunca tragarse la excepción } return ['status' => 'ok']; } }
Depende de LoggerInterface (PSR-3), no de la clase concreta: testeable y reemplazable.
4. Excepciones no manejadas (entry point)
try { Phobos::init(ROOT, APPLICATION)->/* ... */->run()->send(); } catch (\Throwable $e) { inject(Psr\Log\LoggerInterface::class)->critical('unhandled.exception', ['exception' => $e]); // ... luego la traducción a JSON del template }
5. Propagar la traza hacia afuera
// A una llamada HTTP saliente (Guzzle) o al sobre de un evento del bus: $headers['traceparent'] = logContext()->traceParent();
Formato del registro
{
"timestamp": "2026-07-15T12:00:00.123Z",
"level": "info",
"service": "core-social",
"message": "request.handled",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"request_id": "0af7651916cd43dd8448eb211c80319c",
"tenant_id": null,
"actor_id": null,
"context": { "method": "GET", "path": "/v1/feed", "status": 200, "duration_ms": 42.1 }
}
tenant_id/actor_id se promueven a la raíz: se fijan una vez en el LogContext (p. ej. en el middleware de auth) y salen en todos los registros del request.
logContext()->setActorId($userId); logContext()->setTenantId($clientId);
Configuración (config/logging.php)
Todo es opcional; sin archivo, los defaults funcionan.
<?php return [ 'service' => env('SERVICE_NAME', 'app'), 'level' => env('LOG_LEVEL', 'info'), // umbral mínimo PSR-3 'writer' => env('LOG_WRITER', is_prod() ? 'stdout' : 'pretty'), 'access_log' => filter_var(env('LOG_ACCESS', true), FILTER_VALIDATE_BOOL), 'redact' => ['authorization', 'cookie', 'password', 'token', 'refresh_token', 'access_token', 'secret', 'pan', 'card'], 'file_path' => storage_path('logs/app.log'), // solo si writer=file ];
| Writer | Uso | Salida |
|---|---|---|
stdout |
Producción (K8s) | JSON line-delimited a stdout; el agente (Promtail/Grafana Agent) lo lleva a Loki |
pretty |
Desarrollo | Consola coloreada y legible |
file |
VPS/scripts | JSON append a archivo con lock (la rotación es de logrotate) |
memory |
Tests | Acumula en memoria (records(), last(), clear()) |
null |
Apagado | Descarta todo |
Redacción
Sin PII ni secretos en los logs, en ningún ambiente. Dos mecanismos:
- Por clave (recursivo, insensible a mayúsculas, por contención):
password,card_number,reset_token_hash... →•••••• - Por patrón (sobre strings sueltos): JWT y PAN de tarjeta se enmascaran aunque la clave no delate nada
$log->info('login.attempt', ['email' => $email, 'password' => $pass]); // → "context": {"email":"a@b.c","password":"••••••"}
Testing (en tu proyecto)
Bindea el writer de memoria y afirma sobre lo logueado:
$writer = new MemoryWriter(); container()->instance(WriterInterface::class, $writer); // ... ejercitar el código ... $this->assertSame('post.publishing', $writer->last()['message']);
Relación con el Observer del core
El Observer (trace(), Observer::dumpFormatted()) es una línea de tiempo en memoria para depurar un request puntual; no persiste ni sale del proceso. Este Logger es para producción: estructurado, persistente (stdout → Loki) y correlacionado por trace_id. Son complementarios.
Tests de la librería
composer test
Licencia
Este proyecto está licenciado bajo la Licencia MIT - ver el archivo LICENSE para más detalles.
Autor
Marcel Rojas
marcelrojas16@gmail.com
Mongoose Studio
Contribuciones
Las contribuciones son bienvenidas. Por favor:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
Phobos Framework by Mongoose Studio