Desenvolvimento

31 mai, 2016

Enviando logs para um servidor remoto usando RabbitMQ

Publicidade

Tempo atrás eu escrevi um artigo para mostrar como enviar logs Silex para um servidor remoto. Hoje eu quero usar uma fila de mensagens para fazer isso. Normalmente, quando eu preciso de filas, eu uso Gearman, mas hoje eu quero brincar com o RabbitMQ.

Quando trabalhamos com aplicações web, é importante ter, de uma forma ou de outra, uma maneira de separar as operações do pedido principal. Filas de mensagens são ótimas ferramentas para executar essas operações. Elas ainda nos permitem criar nossos workers com linguagens diferentes do pedido principal. Recentemente, por exemplo, tenho trabalhado com dispositivos Modbus. Toda a lógica Modbus é escrita em Python, e eu quero usar uma interface com o PHP. Eu posso reescrever a lógica Modbus com PHP (existem bibliotecas PHP para se conectar com dispositivos Modbus), mas eu não sou tão louco. As filas são nossas amigas.

A ideia neste artigo é a mesma que a do anterior. Usaremos evento dispatcher para emitir eventos e vamos enviar esses eventos para uma fila RabitMQ. Vamos usar um provedor de serviços de chamada.

<?php
include __DIR__ . '/../vendor/autoload.php';
 
use PhpAmqpLib\Connection\AMQPStreamConnection;
use RabbitLogger\LoggerServiceProvider;
use Silex\Application;
use Symfony\Component\HttpKernel\Event;
use Symfony\Component\HttpKernel\KernelEvents;
 
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel    = $connection->channel();
 
$app = new Application(['debug' => true]);
$app->register(new LoggerServiceProvider($connection, $channel));
 
$app->on(KernelEvents::TERMINATE, function (Event\PostResponseEvent $event) use ($app) {
    $app['rabbit.logger']->info('TERMINATE');
});
 
$app->on(KernelEvents::CONTROLLER, function (Event\FilterControllerEvent $event) use ($app) {
    $app['rabbit.logger']->info('CONTROLLER');
});
 
$app->on(KernelEvents::EXCEPTION, function (Event\GetResponseForExceptionEvent $event) use ($app) {
    $app['rabbit.logger']->info('EXCEPTION');
});
 
$app->on(KernelEvents::FINISH_REQUEST, function (Event\FinishRequestEvent $event) use ($app) {
    $app['rabbit.logger']->info('FINISH_REQUEST');
});
 
$app->on(KernelEvents::RESPONSE, function (Event\FilterResponseEvent $event) use ($app) {
    $app['rabbit.logger']->info('RESPONSE');
});
 
$app->on(KernelEvents::REQUEST, function (Event\GetResponseEvent $event) use ($app) {
    $app['rabbit.logger']->info('REQUEST');
});
 
$app->on(KernelEvents::VIEW, function (Event\GetResponseForControllerResultEvent $event) use ($app) {
    $app['rabbit.logger']->info('VIEW');
});
 
$app->get('/', function (Application $app) {
    $app['rabbit.logger']->info('inside route');
    return "HELLO";
});
 
$app->run();

Aqui podemos ver o provedor de serviços:

<?php
namespace RabbitLogger;
 
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use Silex\Application;
use Silex\ServiceProviderInterface;
 
class LoggerServiceProvider implements ServiceProviderInterface
{
    private $connection;
    private $channel;
 
    public function __construct(AMQPStreamConnection $connection, AMQPChannel $channel)
    {
        $this->connection = $connection;
        $this->channel    = $channel;
    }
 
    public function register(Application $app)
    {
        $app['rabbit.logger'] = $app->share(
            function () use ($app) {
                $channelName = isset($app['logger.channel.name']) ? $app['logger.channel.name'] : 'logger.channel';
                return new Logger($this->connection, $this->channel, $channelName);
            }
        );
    }
 
    public function boot(Application $app)
    {
    }
}

E aqui o logger:

<?php
namespace RabbitLogger;
 
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Silex\Application;
 
class Logger implements LoggerInterface
{
    private $connection;
    private $channel;
    private $queueName;
 
    public function __construct(AMQPStreamConnection $connection, AMQPChannel $channel, $queueName = 'logger')
    {
        $this->connection = $connection;
        $this->channel    = $channel;
        $this->queueName  = $queueName;
        $this->channel->queue_declare($queueName, false, false, false, false);
    }
 
    function __destruct()
    {
        $this->channel->close();
        $this->connection->close();
    }
 
    public function emergency($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::EMERGENCY);
    }
 
    public function alert($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::ALERT);
    }
 
    public function critical($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::CRITICAL);
    }
 
    public function error($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::ERROR);
    }
 
    public function warning($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::WARNING);
    }
 
    public function notice($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::NOTICE);
    }
 
    public function info($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::INFO);
    }
 
    public function debug($message, array $context = [])
    {
        $this->sendLog($message, $context, LogLevel::DEBUG);
    }
    public function log($level, $message, array $context = [])
    {
        $this->sendLog($message, $context, $level);
    }
 
    private function sendLog($message, array $context = [], $level = LogLevel::INFO)
    {
        $msg = new AMQPMessage(json_encode([$message, $context, $level]), ['delivery_mode' => 2]);
        $this->channel->basic_publish($msg, '', $this->queueName);
    }
}

E, finalmente, o RabbitMQ Worker para processar os nossos registros

require_once __DIR__ . '/../vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('logger.channel', false, false, false, false);
echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";
$callback = function($msg){
    echo " [x] Received ", $msg->body, "\n";
    //$msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
};
//$channel->basic_qos(null, 1, null);
$channel->basic_consume('logger.channel', '', false, false, false, false, $callback);
while(count($channel->callbacks)) {
    $channel->wait();
}
$channel->close();
$connection->close();

Para executar o exemplo, devemos:

Iniciar o servidor RabbitMQ

rabbitmq-server

Iniciar o servidor Silex

php -S 0.0.0.0:8080 -t www

Iniciar o worker

php worker/worker.php

Você pode ver todo o projeto na minha conta no GitHub.

***

Gonzalo Ayuso faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://gonzalo123.com/2016/03/14/sending-logs-to-a-remote-server-using-rabbitmq