Logging Messages to Scribe from PHP

Published on 2010-09-20 by John Collins.


In a previous tutorial, I showed you how to install Facebook Scribe on a Linux server. In this tutorial we will continue to look at Scribe, but this time from the client perspective. Note that this tutorial assumes that you already have your Scribe and Thrift environment set up as outlined in the previous tutorial.

You can log messages to Scribe from many different languages. In this tutorial I will show you how to use Thrift to generate a PHP client, and then use that client from your own PHP project to log messages.

Building the client with Thrift

Begin by copying all of the PHP library source files shipped with Thrift to the location where we want to use them from (in my case I'm using /opt/scribe/testapp):

[jcollins@redhat testapp]$ cp -R /opt/scribe/thrift-0.4.0/lib/php/src phpscribe

You should now have a phpscribe folder that looks something like this:

[jcollins@redhat testapp]$ ls -l phpscribe/
total 76
-rw-r--r-- 1 jcollins jcollins  1914 2010-09-20 14:58 autoload.php
drwxr-xr-x 3 jcollins jcollins  4096 2010-09-20 14:58 ext
drwxr-xr-x 2 jcollins jcollins  4096 2010-09-20 14:58 protocol
drwxr-xr-x 2 jcollins jcollins  4096 2010-09-20 14:58 server
-rw-r--r-- 1 jcollins jcollins 22870 2010-09-20 14:58 Thrift.php
drwxr-xr-x 2 jcollins jcollins  4096 2010-09-20 14:58 transport
[jcollins@redhat testapp]$

Next we will use Thrift to build the client libraries we will need for Scribe and fb303:

[jcollins@redhat testapp]$ /usr/local/bin/thrift -o ./phpscribe -I /usr/local/share \
--gen php /opt/scribe/scribe/if/scribe.thrift
[jcollins@redhat testapp]$ /usr/local/bin/thrift -o ./phpscribe -I /usr/local/share \
--gen php /usr/local/share/fb303/if/fb303.thrift

As Thrift will place the files in a folder named gen-php but our Scribe client will expect them to be in packages, a quick folder rename will sort that out:

[jcollins@redhat testapp]$ mv phpscribe/gen-php/ phpscribe/packages

You should now have a folder structure that looks like this:

[jcollins@redhat testapp]$ tree phpscribe/
|-- Thrift.php
|-- autoload.php
|-- ext
|   `-- thrift_protocol
|       |-- config.m4
|       |-- php_thrift_protocol.cpp
|       `-- php_thrift_protocol.h
|-- packages
|   |-- fb303
|   |   |-- FacebookService.php
|   |   `-- fb303_types.php
|   `-- scribe
|       |-- scribe.php
|       `-- scribe_types.php
|-- protocol
|   |-- TBinaryProtocol.php
|   `-- TProtocol.php
|-- server
|   |-- TServer.php
|   `-- TSimpleServer.php
`-- transport
    |-- TBufferedTransport.php
    |-- TFramedTransport.php
    |-- THttpClient.php
    |-- TMemoryBuffer.php
    |-- TNullTransport.php
    |-- TPhpStream.php
    |-- TServerSocket.php
    |-- TServerTransport.php
    |-- TSocket.php
    |-- TSocketPool.php
    |-- TTransport.php
    `-- TTransportFactory.php

Using the client libraries in PHP

Next up we will want to actually log something to Scribe. Here is the sample source code (sampleclient.php), please pay close attention to the comments:

// Set this to where you have the Scribe and Thrift libary files.  It MUST be set!
$GLOBALS['THRIFT_ROOT'] = './phpscribe';
// Include all of the lib files we need.
require_once $GLOBALS['THRIFT_ROOT'].'/packages/scribe/scribe.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TSocket.php';
require_once $GLOBALS['THRIFT_ROOT'].'/transport/TFramedTransport.php';
require_once $GLOBALS['THRIFT_ROOT'].'/protocol/TBinaryProtocol.php';
// A message in Scribe is made up of two parts: the category (a key) and the actual
// message.
$message = array();
$message['category'] = 'PHP';
$message['message'] = 'Hello to Scribe from PHP!';
// Create a new LogEntry instance to hold our message, then add it to a new $messages 
// array (we can submit multiple messages at the same time if we want to).
$entry = new LogEntry($message);
$messages = array($entry);
// These settings should really be in a config file somewhere.
$socket = new TSocket('localhost', 1463, true);
$transport = new TFramedTransport($socket);
$protocol = new TBinaryProtocol($transport, false, false);
// This is the actual Scribe client instance.
$scribeClient = new scribeClient($protocol, $protocol);
try {
    // Open the socket...
    // ...log the messages...
    // ...and close the socket.
}catch (TException $e) {
    // In reality, you would want something more robust here like logging to a text 
    // file if Scribe is down.
    echo $e->getMessage()."\n\n";

There are some important caveats here:

  1. The code should really be encapsulated into a function or a method.
  2. The host name and port number here are the defaults. These values should come from a configuration file in a real system.
  3. The error handling here is basic. Again in a real system, you would want something more robust.

Now run your PHP script and assuming there were no errors, you message should be logged to Scribe.

[jcollins@redhat testapp]$ php scribeclient.php

Checking the results

Assuming that your Scribe server is up-and-running, you should change directory to where it stores its log files:

[jcollins@redhat ~]$ cd /tmp/scribetest/

Now list the contents of the directory, and you should now see a PHP folder which maps to the category of our message:

[jcollins@redhat scribetest]$ ls -l
total 32
drwxr-xr-x 2 root root 4096 2010-09-20 15:35 PHP

Now drill into that folder...

[jcollins@redhat scribetest]$ cd PHP/
[jcollins@redhat PHP]$ ls -l
total 12
-rw-r--r-- 1 root root 104 2010-09-20 16:41 PHP_00000
lrwxrwxrwx 1 root root  29 2010-09-20 15:35 PHP_current -> /tmp/scribetest/PHP/PHP_00000

If you tail the current symbolic link ("sym link"), you will see your message:

[jcollins@redhat PHP]$ tail PHP_current 
Hello to Scribe from PHP!


If you are currently using flat files or database tables to store your logging information in your PHP project, and you are finding that it is causing you problems with performance bottle necks or even stability when your application is under heavy load, you might consider introducing Scribe into your architecture. The code above can be easily modified to slot into an existing Logger.write(...) method, for example, without interfering with existing application code.

Updated 2021 : note that the above post was originally published in 2010, but is left here for archival purposes. Sadly just a few years after I published this, work stopped on Scribe. It is a shame, as it was a very nice solution for log file aggregation, which is still a vital DevOps requirement today.