Last week I read an interesting post about logging your messages into a Mongo database (I must admite that I didn’t know what a Mongo DB was until that day). This made me think about an experiment, and this is it.

The experiment
According to the post intro and the post title, you don’t need to be a genius to guess what the experiment is about. Yes, as you should probably notice, this is about developing a logger which update a twitter account with the symfony log messages. The twitter account is @fTwittyLogger (wich is the nickname I gave it).

sfLogger class
First off, some theory. Every logger you develop must inherit from the abstract class sfLogger. As clients of this code we are only concerned about implementing the sfLogger::doLog($message, $priority) method which takes two arguments, the message to log and the priority of this message, which can be one of the next constants: EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, INFO and DEBUG.

The methods sfLogger:initialize() and sfLogger::shutdown() are executed at the begining and the end of the process, and may be useful to set some variables, close database connections, etc.

Our logger, fTwittyLogger

// lib/fTwittyLogger.class.php
<?php
 
class fTwittyLogger extends sfLogger
{
  protected
    $format     = '[%priority%] %message%';
 
  public function initialize(sfEventDispatcher $dispatcher, $options = array())
  {
 
    parent::initialize($dispatcher, $options);
 
    if (!$this->hasOption('user'))
    {
      throw new sfInitializationException(sprintf('You must provide the twitter username account'));
    }
 
    if (!$this->hasOption('pass'))
    {
      throw new sfInitializationException(sprintf('You must provide the twitter password account'));
    }
 
    $this->user = $this->getOption('user');
    $this->pass = $this->getOption('pass');
 
    return true;
  }
 
  protected function doLog($message, $priority)
  {
    $this->tweet(strtr($this->format, array(
      '%message%'  => $message,
      '%priority%' => $this->getPriority($priority))
    ));
 
  }
 
  protected function getPriority($priority)
  {
    return sfLogger::getPriorityName($priority);
  }
 
  public function hasOption($option)
  {
    return array_key_exists($option, $this->options);
  }
 
  private function tweet($message)
  {
    $context = stream_context_create(array(
      'http' => array(
      'method'  => 'POST',
      'header'  => sprintf("Authorization: Basic %s\r\n", base64_encode($this->user.':'.$this->pass)).
                   "Content-type: application/x-www-form-urlencoded\r\n",
      'content' => http_build_query(array('status' => $message)),
      'timeout' => 5,
      ),
    ));
 
    $ret = file_get_contents('http://twitter.com/statuses/update.xml', false, $context);
 
    return false !== $ret;
  }
}

From top to bottom:

  • initialize() check there is an username and password for the Twitter account
  • doLog() twits the message.tweet() method is a copy of this post

Using fTwittyLogger, modifying factories.yml
The last thing to do is to notify symfony that we are no longer using the default logger and we want to use our new one:

// app/config/factories.yml
prod:
  logger:
    class:   fTwittyLogger
    param:
      loggers: ~
      user: fTwittyLogger
      pass: symfony

NOTE: As you can see, the password is there. It's not a slip. I did it so you don't have to create "zombie" accounts just in case you want to give it a try.

... this is nonsense
Before someone attack me without mercy, I would like to say I am aware about how useless and unpractical this is. Twitter just let you write 140 characters and the data is public domain, but this post is about learning something new rather than something practical. Hope you enjoy it!

Hace poco leía un interesante artículo en el que explicaban como desarrollar un logger que guardase la información en una base de datos mongo (reconozco que no sabía qué era Mongo hasta que lo leí). Este post me “iluminó” para hacer un experimento simple y de paso aprender algo sobre los logs de symfony.

El experimento
Después de la mini-introducción y el título de este post no habrá que ser muy listo para darse cuenta de que el experimento consiste en conectar el logger de symfony con una cuenta de Twitter de manera que cada vez que hay un mensaje de log se haga un update en una cuenta de twitter, en este caso, esta cuenta es @fTwittyLogger, que es el nombre cariñoso que le he dado a la criatura.

La clase sfLogger
Antes de empezar, un poco de teoría. Cualquier logger en symfony deberá heredar de una otra manera de la clase abstracta sfLogger. Como clientes de esta clase nuestra única preocupación será implementar el método sfLogger::doLog($message, $priority) que recibe dos parámetros, el mensaje que queremos que aparezca en el log y la prioridad del mensaje que puede ser una de las siguientes constantes: EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, INFO y DEBUG.

Además, existen dos métodos que también nos pueden resultar útiles, sfLogger:initialize() y sfLogger::shutdown(), que se ejecutan al principio y el fin del proceso.

Nuestra logger, fTwittyLogger

// lib/fTwittyLogger.class.php
<?php
 
class fTwittyLogger extends sfLogger
{
  protected
    $format     = '[%priority%] %message%';
 
  public function initialize(sfEventDispatcher $dispatcher, $options = array())
  {
 
    parent::initialize($dispatcher, $options);
 
    if (!$this->hasOption('user'))
    {
      throw new sfInitializationException(sprintf('You must provide the twitter username account'));
    }
 
    if (!$this->hasOption('pass'))
    {
      throw new sfInitializationException(sprintf('You must provide the twitter password account'));
    }
 
    $this->user = $this->getOption('user');
    $this->pass = $this->getOption('pass');
 
    return true;
  }
 
  protected function doLog($message, $priority)
  {
    $this->tweet(strtr($this->format, array(
      '%message%'  => $message,
      '%priority%' => $this->getPriority($priority))
    ));
 
  }
 
  protected function getPriority($priority)
  {
    return sfLogger::getPriorityName($priority);
  }
 
  public function hasOption($option)
  {
    return array_key_exists($option, $this->options);
  }
 
  private function tweet($message)
  {
    $context = stream_context_create(array(
      'http' => array(
      'method'  => 'POST',
      'header'  => sprintf("Authorization: Basic %s\r\n", base64_encode($this->user.':'.$this->pass)).
                   "Content-type: application/x-www-form-urlencoded\r\n",
      'content' => http_build_query(array('status' => $message)),
      'timeout' => 5,
      ),
    ));
 
    $ret = file_get_contents('http://twitter.com/statuses/update.xml', false, $context);
 
    return false !== $ret;
  }
}

De arriba a abajo:

  • El método initialize() asegurá que se han pasado el nombre de usuario y la contraseña de la cuenta de twitter
  • El método doLog() twittea el mensaje. El método tweet() es una copia de lo que se hizo en este post

Utilizando fTwittyLogger, modificando factories.yml
Lo último que quedaría sería informar a symfony de que ya no queremos utilizar su logger por defecto y en su lugar queremos utilizar nuestra nueva creación:

// app/config/factories.yml
prod:
  logger:
    class:   fTwittyLogger
    param:
      loggers: ~
      user: fTwittyLogger
      pass: symfony

NOTA: No ha sido un descuido dejar la contraseña, lo he puesto adrede por si alguien quiere hacer alguna prueba que no tenga que andar creando cuentas “zombie” en Twitter.

… pues menuda tontería
Antes de que alguien me ataque de forma despiada, soy consciente de que mostrar tus logs en una cuenta de twitter no tiene ninguna utilidad práctica. Twitter sólo te deja escribir 140 caracteres y además es público pero no se trata de eso sino de explorar y hacer cosas nuevas con el único fin de aprender algo nuevo.

Hace poco posteé un artículo sobre cómo tweetar con PHP. Normalmente estos updates van acompañas de un enlace con información más completa, y para eso tenemos tinyurl.

El siguiente código se conecta a la API de tinyurl y te devuelve el valor de la URL empequeñecida, así de sencillo:

    function tinyURL($url)
    {
        $context = stream_context_create(array(
            'http' => array(
              'method'  => 'GET',
              'timeout' => 5
            ),
        ));
        $ret = file_get_contents('http://tinyurl.com/api-create.php?url='.urlencode($url), false, $context);
        return $ret;
    }

Y ésta sería la forma de utilizar en un caso “práctico”:

tweet('Ya tengo blog, '.tinyURL('http://www.loalf.com'), 'loalf', 'Pa$$');

Hace poco vi esta pequeña joya de código que permite tweetear (¿?) directamente desde código PHP.

function tweet($message, $username, $password)
{
  $context = stream_context_create(array(
    'http' => array(
      'method'  => 'POST',
      'header'  => sprintf("Authorization: Basic %s\r\n", base64_encode($username.':'.$password)).
                   "Content-type: application/x-www-form-urlencoded\r\n",
      'content' => http_build_query(array('status' => $message)),
      'timeout' => 5,
    ),
  ));
  $ret = file_get_contents('http://twitter.com/statuses/update.xml', false, $context);
 
  return false !== $ret;
}

La forma de utilizarlo no puede ser más sencilla

tweet('From PHP, yeah...', 'fabpot', 'Pa$$');

Muy útil hoy en día cuando muchas aplicaciones se conectan a Twitter para hacer actualizaciones automáticas.

Post original: Tweeting from PHP