Logging to Amazon’s DynamoDB from Zend Framework 2

I recently had to write an app that would run in Amazon Beanstalk. The thing with Beanstalk is that you can set some server metric to trigger automatic scaling (up or down) for your EC2 instances so you cannot count on any particular instance to be there at any time. So, any logging you do to a local file will be lost when the instance is destroyed. This is why I had to write something that could log to DynamoDB so that my logs would be untouched if the instances ceased to exist.

I found that doing this in ZF2 was incredibly easy. This pretty much took me 10 minutes and the best thing was that it required almost no code change at all other than instanciating my new writer and passing it to my file logger before using it.

Here’s the code:

<?php
namespace JulianVidal\Amazon\DynamoDb;

use Zend\Log\Writer\AbstractWriter;
use Aws\DynamoDb\DynamoDbClient;

class LogWriter extends AbstractWriter
{

    /**
     * Amazon DynamoDB Client
     *
     * @var DynamoDbClient
     */
    protected $client = null;

    /**
     * Table name
     * 
     * @var string
     */
    protected $table;

    /**
     * A string identifying this system useful when logging from multiple
     * systems into the same DynamoDB.
     * 
     * @var string
     */
    protected $origin;

    /**
     * Constructor
     *
     * @param DynamoDbClient $client DynamoDb client
     * @param string $table The DynamoDb table to log to
     * @param string $origin A string identifying this system
     * @throws Exception\RuntimeException
     */
    public function __construct(DynamoDbClient $client, $table, $origin = null)
    {
        $this->client = $client;
        $this->table  = $table;
        $this->origin = $origin;
    }

    /**
     * Write a message to the log.
     *
     * @param array $event event data
     * @return void
     * @throws Exception\MissingClientException
     */
    protected function doWrite(array $event)
    {
        if ($this->table == '') {
            throw new Exception\TableNotSetException('Logger needs a table to log to');
        }

        $message = array(
            'TableName' => $this->table,
            'Item' => $this->client->formatAttributes(array(
                'id'           => sprintf('%s_%s', time(), rand(10000, 99999)),
                'timestamp'    => $event['timestamp']->format('Y-m-d H:i:s'),
                'priority'     => $event['priority'],
                'priorityName' => $event['priorityName'],
                'message'      => $event['message'],
                'origin'       => $this->origin,
             )),
        );

        $this->client->putItem($message);
    }

    /**
     * Close the stream resource.
     *
     * @return void
     */
    public function shutdown()
    {
        $this->client = null;
    }

    /**
     * Get table
     * 
     * @return string
     */
    public function getTable()
    {
        return $this->table;
    }

    /**
     * Set the DynamoDb table to log to
     *
     * @param string $table
     */
    public function setTable($table)
    {
        $this->table = $table;
    }

    /**
     * Get Client
     *
     * @return DynamoDbClient
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * Set Client
     *
     * @param \Aws\DynamoDb\DynamoDbClient $client
     */
    public function setClient(DynamoDbClient $client)
    {
        $this->client = $client;
    }

    /**
     * Get origin
     *
     * @return string
     */
    public function getOrigin()
    {
        return $this->origin;
    }

    /**
     * Set origin
     *
     * @param string $origin
     */
    public function setOrigin($origin)
    {
        $this->origin = $origin;
    }

}

You might be wondering what the $origin bit is all about. This is not necessary in order to log to DynamoDb but in my case I had to identify each instance somehow so I ended up passing an $origin string which would contain some of that particular EC2 instance’s identification (I think it was the instance ID plus the public DNS or something).