PHP Logger with stacktrace

November 17, 2009

Logging in php isn’t fun, lets face it. There are quite a few php loggers out there, but we’ve built our own over the years and we felt why not share this with the community? So, why did we build our own? Well, firstly I personally like log files that are actually readable and secondly I really need a stack trace, I need to know not only what part of the code fell over, but what called that piece of code? And, then what called that piece of code. Sometimes I like to use the system log file, and just have a terminal open (yeah, we’re a Mac shop here!) and just watch the log using tail -f php_errors.log. But, sometimes I like to view the log in html for small test scripts. And, another thing is that sometimes I want to setup the logger to email me any fatal errors, this is very handy as sometimes these can go under the radar especially on really busy sites.

Our Logger class is static, which is great for speed and means there’s only ever one instantiation floating around. It also initializes itself, so you don’t need to worry.


require_once("Logger.class.php");

// This tells the logger to echo html
Logger::echoLog();

// Tells the Logger to catch and log system errors
Logger::catchSysErrors();

// This tells the logger to email you fatal errors. The email will contain the error
// message as well as a dump of the POST, GET and SESSION variables
Logger::setupEmail($email, $projectName, $logLevel = self::$FATAL)  

// You can set the logging level (the default is debug)
// Level can be (in order);
// Logger::$DEBUG, Logger::$INFO, Logger::$ERROR, Logger::FATAL
// e.g., if you set the level to ERROR, only ERROR and FATAL messages would be outputted
// e.g., if you set the level to INFO, only INFO, ERROR & FATAL messages would be outputted
Logger::setLevel(Logger::$DEBUG);

// To use, call any of these
Logger::fatal("Some error message");
Logger::error("Some error message");
Logger::debug("Some error message");
Logger::info("Some error message");

// You can also dump a variable, array or object's contents with
Logger::dump($variable);

Here is the Logger class (apologies that the indenting is a little screwy);

/**
* Logging class, use for all trace commands
*
*/
class Logger {

	public static $DEBUG	 	= 0;
	public static $INFO 		= 1;
	public static $WARNING 		= 2;
	public static $ERROR	 	= 3;
	public static $FATAL	 	= 4;

	private static $debugLevel = 0;

	private static $echoLog = false;

	private static $email		= "dev@adastrasystems.com";
	private static $project     = "Adastra Project";
	private static $sendEmail   = true;
	private static $emailLevel  = 4;

	// //////////////////////////////////////////////////////////////////////////////////////

	/**
	* Class constructor
	*/
	public function __construct(){
		self::init();
	}

	// //////////////////////////////////////////////////////////////////////////////////////

	public static function echoLog(){
		self::$echoLog = true;
	}

    // //////////////////////////////////////////////////////////////////////////////////////

    /**
     * Let the Logger catch any system errors
     */
    public static function catchSysErrors(){
        set_error_handler("Logger::sysErrorHandler");
    }

	// //////////////////////////////////////////////////////////////////////////////////////

	/**
	*
	*/
	private static function trace($level, $msg){

		if ($level < self::$debugLevel){
			return;
		}

		$bt = debug_backtrace();

		$class = "";
		$function = "";

		// get class, function called by caller of caller of caller
		if (isset($bt[2]['class'])){
			$class = $bt[2]['class'];
			$function = "." . $bt[2]['function'];
		}

		// get file, line where call to caller of caller was made
		$file = $bt[1]['file'];
		$line = $bt[1]['line'];

		$file_name = basename($file);

		if (self::$echoLog){

			switch($level){
				case self::$DEBUG: 	$levMsg = "debug"; break;
				case self::$INFO:  	$levMsg = "info"; break;
				case self::$WARNING:$levMsg = "warning"; break;
				case self::$ERROR: 	$levMsg = "error"; break;
				case self::$FATAL: 	$levMsg = "fatal"; break;
			}

			$msg = "[$levMsg $class$function] $msg";
			$msg .= " on line $line of $file_name";
			if (isset($bt[2]['file'])){
				$fname = basename($bt[2]['file']);
				$msg .= ", called from line ".$bt[2]['line']." of $fname";
			}
			$msg .= "\n";

			echo $msg;

			flush();

		}
		else {

			// Get IP address....

			// Translate level into text...
			$levMsg = "????";
			switch($level){
				case self::$DEBUG: 	$levMsg = "DEBUG"; break;
				case self::$INFO:  	$levMsg = "INFO"; break;
				case self::$WARNING:$levMsg = "WARNING"; break;
				case self::$ERROR: 	$levMsg = "ERROR"; break;
				case self::$FATAL: 	$levMsg = "FATAL"; break;
			}

			$msg = "[$levMsg] $class$function $msg";
			$msg .= " {on line $line of $file_name";
			if (isset($bt[2]['file'])){
				$fname = basename($bt[2]['file']);
				$msg .= ", called from line ".$bt[2]['line']." of $fname";
			}
			if (isset($bt[3]['file'])){
				$fname = basename($bt[3]['file']);
				$msg .= ", called from line ".$bt[3]['line']." of $fname";
			}
			$msg .= "}";

			error_log($msg);
		}

        if (self::$sendEmail){
			self::sendEmail($msg, $level);
        }		

	}

	// //////////////////////////////////////////////////////////////////////////////////////

	private static function sendEmail($msg, $level){

		if ($level < self::$emailLevel){
			return;
		}

		$headers = 'From: ' . self::$email;
		$message = $msg . "\n\nPOST VARIABLES \n\n" . print_r($_POST, true) . "\nGET VARIABLES\n\n" . print_r($_GET, true) . "\nSESSION VARIABLES\n\n" . print_r($_SESSION, true);
		mail(self::$email, self::$project, $message, $headers);
   	}

	// //////////////////////////////////////////////////////////////////////////////////////

    /**
     * Use to replace system error handler, if desired. Use Logger::catchSysErrors() to
     * activate
     */
    public static function sysErrorHandler($errno, $errstr, $errfile, $errline){

        switch ($errno) {
            case E_USER_ERROR:
                $levMsg = "SYS_ERROR";
                break;

            case E_USER_WARNING:
                $levMsg = "SYS_WARNING";
                break;

            case E_USER_NOTICE:
                $levMsg = "SYS_NOTICE";
                break;

            default:
                $levMsg = "SYS_UNKNOWN";
               break;
        }

        if (self::$echoLog){

			switch($errno){
				case E_USER_NOTICE:  $levMsg = "info"; break;
				case E_USER_WARNING:$levMsg = "warning"; break;
				case E_USER_ERROR: 	$levMsg = "error"; break;
				default: $levMsg = "debug"; break;
			}

			$msg = "[$levMsg #$errno] $errstr";
			$msg .= " on line $errline of ".basename($errfile)."";
			$msg .= "\n";

			echo $msg;

			flush();

        }
        else {

            $msg = "[$levMsg] No: $errno Msg: $errstr";
            $msg .= " {on line $errline of ".basename($errfile)."}";

            error_log($msg);
        }

        /* Don't execute PHP internal error handler */
        return true;
    }

    // //////////////////////////////////////////////////////////////////////////////////////

	public static function setupEmail($email, $projectName, $logLevel = self::$FATAL) {
		self::$sendEmail = true;
		self::$emailLevel = $logLevel;
		self::$project = $projectName;
	}

    // //////////////////////////////////////////////////////////////////////////////////////

	public static function setLevel($newLevel) { $debugLevel = $newLevel; }

	public static function setLevelDebug() { self::$debugLevel = self::$DEBUG; }
	public static function setLevelInfo() { self::$debugLevel = self::$INFO; }
	public static function setLevelWarning() { self::$debugLevel = self::$WARNING; }
	public static function setLevelError() { self::$debugLevel = self::$ERROR; }
	public static function setLevelFatal() { self::$debugLevel = self::$FATAL; }

	// //////////////////////////////////////////////////////////////////////////////////////

	public static function dump($var) { self::debug(print_r($var, true)); }
	public static function debug($msg){ self::trace(self::$DEBUG, $msg);	}
	public static function warning($msg){ self::trace(self::$WARNING, $msg);	}
	public static function warn($msg){ self::trace(self::$WARNING, $msg);	}
	public static function error($msg){ self::trace(self::$ERROR, $msg);	}
	public static function info($msg){  self::trace(self::$INFO, $msg);	}
	public static function fatal($msg){ self::trace(self::$FATAL, $msg);	die(); }

	// //////////////////////////////////////////////////////////////////////////////////////
}

Categories: php
Tags: ,
Comments (0)