Writing logs with ZendLog

Sometimes is a web application is demanded to have logging for purposes like debugging or user-actions logs .
There are many sollutions for adding logging abilities to your site , starting with the built-in php functions for writing in log files and ending with special libraries of this purpose . One of them is ZendLog library from Zend Framework.
One of the advantages of ZendLog is that it enables to define additional levels of logging , beside the ones that are embedded in the class.
In is also usefull that it supports multiple file writing , for example you can write error-level logs in a file and debug-level logs in another file , or having an user-action logging in a separate file from the debug logs.
In this example we’ll create a class that will extend ZendLog and add several usefull capabilities.
For the start , we’ll include the ZendLog files:

require_once ‘Zend/Log.php’;
require_once ‘Zend/Log/Writer/Stream.php’;
require_once ‘Zend/Log/Filter/Suppress.php’;

You can change the paths depending where your ZendFr classes are located.
Now we create our class:

class Logger extends Zend_log{

First method that we’ll define it , it will be init() . We’ll assume we have some global array which will contain files paths are several settings . Those can be modified according to you envoirement .

private function init(){
global $conf_log_settings;

Lets assume we want a user-actions loggins , we can name this level USERACT and give it a 8 priority number

$this->addPriority(‘REPORT’, 8);

Now we start to define the first streaming for debugging logs . We give the path of the log file and also we define a custom formatting :

$debug_writer = new Zend_Log_Writer_Stream($conf_log_settings[‘debug_logfile’]);
$formatter = new Zend_Log_Formatter_Simple(‘%timestamp%|%priorityName%|(%priority%)|%message%’ . PHP_EOL);
$debug_writer->setFormatter($formatter);

We can define a surpress filter if we want for example to disable the logging in the production envoirenment

$bansher = new Zend_Log_Filter_Suppress();
$bansher->suppress($conf_log_settings[‘debug_enable’]);
$debug_writer->addFilter($bansher);

To disable the debug loggigng ,$conf_log_settings[‘debug_enable’] must be set to TRUE .
Now we can define a level of debugging , we can let all debug messages or limit only to Emergency level . ZendLog levels start from 0 to 7 as priority. So setting to 7 will allow all messages to be written .

$filter = new Zend_Log_Filter_Priority($conf_log_settings[‘debug_level’],’<='); $debug_writer->addFilter($filter);
$this->addWriter($debug_writer);

And we are done with the debugging messages . Now we can do the separate logging for our user-actions . We’ll need to define a new stream :

$action_writer = new Zend_Log_Writer_Stream($conf_log_settings[‘action_logfile’]);
$formatter2 = new Zend_Log_Formatter_Simple(‘%timestamp%|%priorityName%|(%priority%)|%message%’ . PHP_EOL);
$action_writer->setFormatter($formatter2);

We set set same costum formatting as we had for debugging , but we could eliminate for example the priorityName , because our file we’ll have only one type of messages.
Next is to tell ZendLog to log on this stream only the USERACT level messages ( which we defined earlier to have priority 8 )

$filter2 = new Zend_Log_Filter_Priority(8,’=’);
$action_writer->addFilter($filter2);
$this->addWriter($action_writer);

And we are done with the init function .
Because we’ll use this class in various places and to not initiate all the time and run init() function , we can use a Singleton pattern to ensure init() will be called only one time . This is done with the classical getInstance() function , except we’ll run init() also in it :

public static function getInstance()
{
if (self::$instance == NULL) {
self::$instance = new Logger();
self::$instance->init();
}

return self::$instance;
}

Last function that we’ll define is a nice introduction in php5 : the __call function . __call function role is to catch situations when we call a method which is not defined . ZendLog use this function to create a method for each of the defined levels of logging . We’ll extend ZendLog __call to add an improvment

public function __call($method, $params){
$param=””;
foreach ($params as $par){$param.=”$par|”;}
$param = rtrim($param,’|’);
parent::__call($method,array($param));
}

Normally ZendLog __call reveices an array of araguments . Our modification is to add a | between the parameters , because we used a custom formatting to the line . Otherwise ZendLog will use it’s separator , but we want the message to look unitary . We’ll explain this in how to use our class.
To write a log line we need to make a call like this :

Logger::getInstance()->debug($a_message,$an_user_id);

The line written in the log will be in the following format :

2007-11-05T18:57:59-05:00|DEBUG|(7)|Hello world|14

where “Hello world” is the message and 14 it’s an user id .
The good thing about our __call is that we can add how many parameters we want , and those will be inserted in the log file.
If we are doing debbuging sometimes it is usefull to know where the log was made . For this we can include the magic constants __LINE__ and __FILE__
__FILE__ will add the file were the log was made and __LINE__ will give us the line number in that file

Logger::getInstance()->err(__FILE__,__LINE_$a_message,$an_user_id);

The log line will look like :

2007-11-05T18:57:59-05:00|DEBUG|(7)|helloworld.php|10|Hello world|14

So Logger can be called with debug(), err(), warn() . These are the name of the priorities . See inside ZendLog class for the whole list from 0 to 7.
Finally , let’s show an example for our user-actions logging :

Logger::getInstance()->useract(‘user logging’,$uid);

The line in the log file for those actions will look like :

2007-11-05T18:57:59-05:00|USERACT|(8)|user logging|13

I hope this class can help on tracking debugging , user-actions or any events that you need to track .
Many thanks to Dario Sande for reminding me about the magic constants __FILE__ and __LINE__ . An implementation of this class can be done for CodeIgniter framework (thanks Fernando Varesi ) .
The only difference is that you don’t need anymore getInstance() , because CodeIgniter implements the Singleton , and you need to declare the contructor as

function __construct() {
parent::__construct();
$this->init();
}