Playing with event dispatcher and Silex. Sending logs to a remote server.


Today I continue playing with event dispatcher and Silex. Now I want to send a detailed log of our Kernel events to a remote server. We can do it something similar with Monolog, but I want to implement one working example hacking a little bit the event dispatcher. Basically we’re going to create one Logger class (implementing PSR-3 of course)

namespace G;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class Logger implements LoggerInterface
{
    private $socket;

    public function __construct($socket)
    {
        $this->socket = $socket;
    }

    function __destruct()
    {
        @fclose($this->socket);
    }

    public function emergency($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::EMERGENCY);
    }

    public function alert($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::ALERT);
    }

    public function critical($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::CRITICAL);
    }

    public function error($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::ERROR);
    }

    public function warning($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::WARNING);
    }

    public function notice($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::NOTICE);
    }

    public function info($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::INFO);
    }

    public function debug($message, array $context = array())
    {
        $this->sendLog($message, $context, LogLevel::DEBUG);
    }

    public function log($level, $message, array $context = array())
    {
        $this->sendLog($message, $context, $level);
    }

    private function sendLog($message, array $context = array(), $level = LogLevel::INFO)
    {
        $data = serialize([$message, $context, $level]);
        @fwrite($this->socket, "{$data}\n");
    }
}

As you can see our Logger class send logs to a remote server, with a socket passed within the constructor.
We also need one Service Provider called LoggerServiceProvider to integrate our Logger instance into our Silex application.

namespace G;

use Silex\Application;
use Silex\ServiceProviderInterface;

class LoggerServiceProvider implements ServiceProviderInterface
{
    private $socket;

    public function __construct($socket)
    {
        $this->socket = $socket;
    }

    public function register(Application $app)
    {
        $app['remoteLogger'] = $app->share(
            function () use ($app) {
                return new Logger($this->socket);
            }
        );
    }

    public function boot(Application $app)
    {
    }
}

And now the last part is our Silex application:

use G\LoggerServiceProvider;
use G\Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel;
use Symfony\Component\HttpKernel\Event;

$app = new Application();
$app->register(new LoggerServiceProvider(stream_socket_client('tcp://localhost:4000')));

$app->on(HttpKernel\KernelEvents::REQUEST, function (Event\GetResponseEvent $event) use ($app) {
        $app->getLogger()->info($event->getName());
    }
);

$app->on(HttpKernel\KernelEvents::CONTROLLER, function (Event\FilterControllerEvent $event) use ($app) {
        $app->getLogger()->info($event->getName());
    }
);

$app->on(HttpKernel\KernelEvents::TERMINATE, function (Event\PostResponseEvent $event) use ($app) {
        $app->getLogger()->info($event->getName());
    }
);

$app->on(HttpKernel\KernelEvents::EXCEPTION, function (Event\GetResponseForExceptionEvent $event) use ($app) {
        $app->getLogger()->critical($event->getException()->getMessage());
    }
);

$app->get('/', function () {
    return 'Hello';
});

$app->run();

As we can see the event dispacher send each event to a remote server (in this example: tcp://localhost:4000). Now we only need a tcp server to handle those sockets. We can use different tools and libraries to do that. In this example we’re going to use React.

use React\EventLoop\Factory;
use React\Socket\Server;

$loop   = Factory::create();
$socket = new Server($loop);

$socket->on('connection', function (\React\Socket\Connection $conn){
    $unique = uniqid();
    $conn->on('data', function ($data) use ($unique) {
            list($message, $context, $level) = \unserialize($data);
            echo date("d/m/Y H:i:s")."::{$level}::{$unique}::{$message}" . PHP_EOL;
        });
});

echo "Socket server listening on port 4000." .PHP_EOL;
echo "You can connect to it by running: telnet localhost 4000" . PHP_EOL;

$socket->listen(4000);
$loop->run();

Now we only need to start our servers:
our silex one

php -S 0.0.0.0:8080 -t www

and the tcp server

php app/server.php

One screencast showing the prototype in action:

You can see the full code in my github account.

4 thoughts on “Playing with event dispatcher and Silex. Sending logs to a remote server.

    1. I try to avoid to extend abstract classes as a plague. I’ve used a lot of abstract classes in the past, but nowadays I realized that they normally turn into a hodgepodge, violating the single responsibility principle. As a general rule I prefer to work with interfaces instead of abstract classes (and simple inheritance). In fact I prefer to work with traits than extending abstract classes.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.