Configuración del canal de registro y autoconexión
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeÉste es nuestro objetivo... y el resultado final va a ser muy bueno: aprovechar nuestro middleware -y el hecho de que estamos añadiendo este identificador único a cada mensaje- para registrar todo el ciclo de vida de un mensaje en un archivo. Quiero ver cuándo se despachó originalmente un mensaje, cuándo se envió al transporte, cuándo se recibió del transporte y cuándo se gestionó.
Añadir un gestor de registros
Antes de entrar en el tema del middleware, vamos a configurar un nuevo canal de registro que registre en un nuevo archivo. Abre config/packages/dev/monolog.yaml
y añade una nueva clave channels
. Espera... eso no es correcto. Un canal de registro es, en cierto modo, una "categoría", y puedes controlar cómo se gestionan los mensajes de registro de cada categoría. No queremos añadirlo aquí porque entonces ese nuevo canal sólo existiría en el entorno de desarrollo. No, queremos que el canal exista en todos los entornos... aunque decidamos dar un tratamiento especial a esos mensajes sólo en dev
.
Para ello, directamente dentro de config/packages
, crea un nuevo archivo llamadomonolog.yaml
... aunque... recuerda: los nombres de estos archivos de configuración no son importantes. Lo que es importante es añadir una clave monolog
, y luego channels
establecer una matriz con uno nuevo - ¿qué tal messenger_audit
.
monolog: | |
channels: [messenger_audit] |
Gracias a esto, ahora tenemos un nuevo servicio de registro en el contenedor para este canal. Vamos a encontrarlo: en tu terminal, ejecuta:
php bin/console debug:container messenger_audit
Ahí está: monolog.logger.messenger_audit
- lo utilizaremos en un momento. Pero antes, quiero hacer que cualquier registro de este canal se guarde en un nuevo archivo en el entornodev
. Retrocede en config/packages/dev/monolog.yaml
, copia el manejador demain
, pégalo y cambia la clave a messenger
... aunque podría ser cualquier cosa. Actualiza el archivo para que se llame messenger.log
y -aquí está la magia- en lugar de decir: registrar todos los mensajes excepto los del canal event
, cámbialo para que sólo registre los mensajes que están en ese canal messenger_audit
.
monolog: | |
handlers: | |
// ... lines 3 - 7 | |
messenger: | |
type: stream | |
path: "%kernel.logs_dir%/messenger.log" | |
level: debug | |
channels: ["messenger_audit"] | |
// ... lines 13 - 25 |
Autoconexión del registrador de canales
¡Genial! Para utilizar este servicio, no podemos simplemente autocablear el canal normalLoggerInterface
... porque eso nos dará el registrador principal. Este es uno de esos casos en los que tenemos varios servicios en el contenedor que utilizan todos la misma clase o interfaz.
Para hacerlo deseable, de nuevo en services.yaml
, añade un nuevo bind global:$messengerAuditLogger
que apunte al id del servicio: cópialo del terminal, y pégalo como @monolog.logger.messenger_audit
.
// ... lines 1 - 7 | |
services: | |
// ... line 9 | |
_defaults: | |
// ... lines 11 - 12 | |
bind: | |
// ... lines 14 - 15 | |
$messengerAuditLogger: '@monolog.logger.messenger_audit' | |
// ... lines 17 - 34 |
Gracias a esto, si utilizamos un argumento llamado $messengerAuditLogger
en el constructor de un servicio o en un controlador, Symfony nos pasará ese servicio. Por cierto, a partir de Symfony 4.2, en lugar de vincularse sólo al nombre del argumento, también puedes vincularte al nombre y al tipo diciendoPsr\Log\LoggerInterface $messengerAuditLogger
. Eso sólo hace las cosas más específicas: Symfony nos pasaría este servicio para cualquier argumento que tenga este nombre y el tipo-indicación LoggerInterface
.
En cualquier caso, tenemos un nuevo canal de registro, ese canal registrará en un archivo especial, y el servicio de registro para ese canal es deseable. ¡Es hora de ponerse a trabajar!
Cierra los archivos de configuración del monolog y ve a AuditMiddleware
. Añade unpublic function __construct()
con un argumento LoggerInterface $messengerAuditLogger
- el mismo nombre que usamos en la configuración. Llamaré a la propiedad en sí $logger
, y terminaré esto con $this->logger = $messengerAuditLogger
.
// ... lines 1 - 4 | |
use Psr\Log\LoggerInterface; | |
// ... lines 6 - 10 | |
class AuditMiddleware implements MiddlewareInterface | |
{ | |
private $logger; | |
public function __construct(LoggerInterface $messengerAuditLogger) | |
{ | |
$this->logger = $messengerAuditLogger; | |
} | |
// ... lines 19 - 40 | |
} |
Configurar el contexto
Abajo, en handle()
, elimina el dump()
y crea una nueva variable llamada $context
. Además del mensaje de registro propiamente dicho, es un hecho poco conocido que puedes pasar información extra al registrador... ¡lo cual es súper útil! Vamos a crear una clave llamada id
configurada con el id único, y otra llamada class
configurada con la clase del mensaje original. Podemos conseguirlo conget_class($envelope->getMessage())
.
// ... lines 1 - 10 | |
class AuditMiddleware implements MiddlewareInterface | |
{ | |
// ... lines 13 - 19 | |
public function handle(Envelope $envelope, StackInterface $stack): Envelope | |
{ | |
// ... lines 22 - 28 | |
$context = [ | |
'id' => $stamp->getUniqueId(), | |
'class' => get_class($envelope->getMessage()) | |
]; | |
// ... lines 33 - 39 | |
} | |
} |
A continuación, ¡hagamos el registro! Es un poco más interesante de lo que cabría esperar. ¿Cómo podemos averiguar si el mensaje actual se acaba de enviar o se acaba de recibir de forma asíncrona desde un transporte? Y si acaba de ser despachado, ¿cómo podemos averiguar si el mensaje será tratado ahora mismo o enviado a un transporte para más tarde? La respuesta... ¡está en los sellos!
I'm working on a project with Symfony 6 and I have two questions:
(1) I spent more time than I even want to admit struggling to configure a logger (or do I mean channel? or handler? or all the above?) that would write to its own file and only there, with a view to recording certain application events for posterity -- not errors or debugging stuff. Might eventually want to store these messages in a database. But I digress. I called the handler "app" and the channel "app", and it simply would not work no matter how hard I tried -- even banging my head on the table didn't work. When I ran
container:debug monolog
, the servicemonolog.handler.app
would appear, but notmonolog.logger.app
. I managed to bind this thing to a variable so that__construct(LoggerInterface $appLogger)
looked like it might work -- but $appLogger turned out to be an instance of the handler, not the logger. (In retrospect this doesn't seem so surprising.) So Symfony got upset and exploded. I then changed the name "app" to something else wherever it occured, and lo and behold, everything works! So, the question: is there something sacred about the word "app"?(2) And now that I have it working pretty well, I wonder if I can make my logger write exclusively to my file, or other destination that I decide on later -- because I see that my nice new logger has three handlers: two Streamhandlers and one ConsoleHandler. One Streamhandler logs to my custom path, one writes to dev.log, and the console handler? Is that for use with a
Symfony\Component\Console\Command\Command
? Anyway, the question is: how do you configure a logger to write to a particular destination exclusively?