The following functions are good friends for logging. You can use format strings parsed by printf and several log levels.

Usage example : ( Download: example.php)

<?php
/**
 * Require l.php before parsing the ini file
 * so that L_* contants can be used in the ini file 
 * to define the L_LEVEL
 */
require_once 'l.php';
 
$config = parse_ini_file('config.ini');
define('l_reporting', intval($config['l_reporting']));
 
l ('test', L_NOTICE);
l (1);
l (3.4);
l (FALSE);
l (NULL, L_DEPRECATED);
l (array(1,2,3));
l (new DOMDocument()); 
lf ('file: %s', @file_get_contents('sada'), L_ERROR);
lf ('hello %s! The time is %s', 'thorsten', date('Y:m:i'));
lf ('%s in binary is %1$032b', rand());

Assuming this config file:

;...
l_reporting = L_ALL;
;...

The output will look like

[2011:10:03][L_NOTICE] test
[2011:10:03][L_ERROR] file: FALSE

When using the following config file:

 ; ...
 l_reporting = L_VDEBUG;
 ; ...

Then the output will look like:

[2011:10:29]test
[2011:10:29]1
[2011:10:29]3.4
[2011:10:29]FALSE
[2011:10:29]Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
 
[2011:10:29]DOMDocument Object
(
)
 
[2011:10:29]file: FALSE
[2011:10:29]hello thorsten! The time is 2011:10:29
[2011:10:29]1296685703 in binary is 01001101010010011101101010000111
<?php
/**
 *  Printf like log function for php
 *
 *  @example
 /**
 * Parse current log level from ini file. If doing 
 * is AFTER define('L_*', ..) contants, one can use
 * this constants in the ini file.
 *  
 *  Example ini file:
 *  ...
 *  l_reporting = E_ALL | DEBUG;
 *  ...
 *\/
 * $config = parse_ini_file('config.ini');
 * define('L_LEVEL', intval($config['l_reporting']));
 *
 * l ('test', L_NOTICE);
 * l (1);
 * l (3.4,);
 * l (FALSE);
 * l (NULL, L_DEPRECATED);
 * l (array());
 * l (new DOMDocument()); 
 * lf ('file: %s', @file_get_contents('sada'), L_ERROR);
 * lf ('hello %s! The time is %s', $argv[1], new DateTime());
 * lf ('SOME_INT_VALUE is %1$016b %1$s', SOME_INT_VALUE);
 *
 * @author Thorsten Heymann <info@metashock.net>
*/
 
/**
 *  Common log levels
 *  define them before parsing the ini file
 */
define('L_ERROR', 1);
define('L_WARNING', 2);
define('L_NOTICE', 4);
define('L_ALL', L_ERROR | L_WARNING | L_NOTICE);
define('L_DEBUG', 8 | L_ALL);
define('L_VDEBUG', 16 | L_DEBUG);
define('L_DEPRECATED', 32);
 
/**
 *  Custom log levels are after bit 8
 */
define('L_MODEL_DEBUG', 256);
 
/**
 *  A log function like printf. 
 * 
 * For the flexibiltity that $level is an optional parameter,
 * we have to pay the the price of "must parse the fmt string" 
 * to find out how many arguments in arglist are used as fmt args, 
 * and then detect if the log level was was specifed and get its value.
 * So try to use this method with $fmt strings as short as possible
 * to avoid overhead.
 *  
 *  l($mixed [, $level = L_DEBUG]);
 *  l($fmt_str, $fmt_arg0, ..., $fmt_arg [, $level = L_DEBUG]);
 *
 *  @return string | FALSE
 */
function lf ($message /* , $args ...,  $level = L_DEBUG*/) {
 
    // get number of arguments 
    $nargs = func_num_args();
 
    if($nargs < 1) {
        user_error(sprintf('%s expected at least one argument'));
        return FALSE;
    }
 
    $nfmtargs = 0;
    // check if first argument is a string
    if ( is_string ( func_get_arg (0) )) {
        // then check how much format arguments
        // the string consumes
        $fmt = func_get_arg(0);
        $args = array();
        for($i = 0; $i < strlen($fmt); $i++) {
            if($fmt[$i] !== '%') {
                continue;
            }
            $argindex = '';
            while(ord($fmt[$i+1]) > 0x29 && ord($fmt[$i+1]) < 0x40) {
                $argindex .= $fmt[$i+1];
                $i++;
            }
            if($argindex === ''
            || $fmt[$i+1] !== '$'
            ) {
                $nfmtargs++;
            } else if (!isset($args[$argindex])) {
                $nfmtargs++;
                $args[$argindex] = TRUE;
            }
        }        
    }
 
    // $level is the last argument in list
    // if $level was not set we assume L_DEBUG as the default value
    if($nargs < $nfmtargs + 2) {
        $level = L_DEBUG;
    } else {
        $level = func_get_arg($nargs -1);
    }
 
    $printfargs = array();
    // check if there are arguments to the ftm string
    for($i = 0; $i < max(1, $nargs); $i++) {
        $arg = func_get_arg($i);
        // convert none printf arguments to printf arguments. 
        // if they are willing or not!
        if(is_string($arg) 
        || is_float($arg)
        || is_int($arg)
        || (is_object($arg) && method_exists($arg, '__toString'))
        ) {
            $printfargs []= $arg;           
        } else if(is_bool($arg)) {
            $printfargs []= $arg ? 'TRUE' : 'FALSE';
        } else if(is_null($arg)) {
            $printfargs []= 'NULL';
        } else {
            $printfargs []= print_r($arg, TRUE);
        }
    }
 
    // call printf
    $msg = call_user_func_array ('sprintf', $printfargs);
    return l($msg, $level);
}
 
 
/**
 *  Logs a message to stderr
 *
 *  @param mixed $message
 *  @param integer $level=L_DEBUG
 *  @return string the log message
 */
function l($message, $level = L_DEBUG) {
 
    // check if $level passes current L_LEVEL filter
    if(($level & l_reporting) !== $level) {
        return '';
    }
 
    // convert $message to a string - willing or not!
    if(is_bool($message)) {
        $message = $message ? 'TRUE' : 'FALSE';
    } else if(is_null($message)) {
        $message = 'NULL';
    } else if(is_object($message) || is_array($message)) {
        $message = print_r($message, TRUE);
    }
 
    $prefix = '[' . date('Y:m:i') . '][' .
        l_reporting_to_string($level) . '] ';
    $message = $prefix . $message;
 
    // just using STDERR here. Use your own logger ..
    fwrite(STDERR, $message . PHP_EOL);
    return $message . PHP_EOL;
}
 
 
/**
 *  Gives a string representation of the common 
 *  log levels
 *
 *  @param integer 
 *  @return string
 */
function l_reporting_to_string($level) {
    $switches = array();
    if(($level & L_ERROR) === L_ERROR) {
        $switches []= 'L_ERROR';
    }
    if(($level & L_WARNING) === L_WARNING) {
        $switches []= 'L_WARNING';
    }
    if(($level & L_NOTICE) === L_NOTICE) {
        $switches []= 'L_NOTICE';
    }
    if(($level & L_DEBUG) === L_DEBUG) {
        $switches []= 'L_DEBUG';
    }
    if(($level & L_VDEBUG) === L_VDEBUG) {
        $switches []= 'L_VDEBUG';
    }
    if(($level & L_DEPRECATED) === L_DEPRECATED) {
        $switches []= 'L_DEPRECATED';
    }
    return implode(' | ', $switches);
}

Play around with various combinations of l_reporting levels. (And report bugs :) )

Leave a Reply