Logging in PHP Applications

I recently took over a product stream whose code was approximately two years old. I was shocked to see that it didn’t have any logging mechanism in place, which scared the hell out of me. In this article, I’ll explain why it scared me, and the best ways to implement logging in PHP applications.

If there is no logging mechanism, then if there’s a goof-up in a production environment, you have absolutely no idea what went wrong. The only thing which a support developer can do in this case is to reproduce the issue at the developer end, which sometimes work and sometimes don’t.

In a Lamp stack (PHP environment), logs are being generated at many places. Apache creates its logs, PHP creates its logs and MySQL also creates its logs. You can get a lot of information from these logs, especially when there are errors. The problem with these logs is that they are server-specific rather than application-specific, so they would not log anything specific to the application.

Application logs are very important, and they must be implemented in even trivial applications. Some of the very basic reasons for which an application log is important are debugging during development, troubleshooting in a production environment, gathering statistics, fixing security loopholes, and so forth.

Basically, application logs can be divided into the following categories.


  1. Trace logs: Trace logs are for troubleshooting. They contain the traces of what was executed when from which page and line. So trace logs are meant for developers and can be technical in nature. An example would look like this:


02:49:06 05/07/08 [critical] 1210108746 Could not create instance with dsn mysql://:@/ C:xampphtdocsicore4includeISms.class.php 43 superadmin


In this log the values are in the following order: server time, date, priority, timestamp, log message, filename, line number and the user who issued this.


  1. Audit logs: Audit logs keep track of logins and logouts of the users. This helps in security breaches.


  1. User logs / History: User logs are meant for end user consumption. These logs are not for troubleshooting and generally don’t have priority attached to them. They serve as a history for the application. These logs should not be technical in nature. A typical example for this would be:


02:49:06 05/07/08 A new user foo was created by superadmin

{mospagebreak title=Basic Design}

Before we move on to how we can implement logs, let’s take a look at what should be the basic design consideration for logs.

  1. File system or database: To store logs, a database as well as a file can be used. The disadvantage with using a database is that the write time is longer, causing an undue load on the database server. The disadvantage with a file system is that the read and searching time is longer than the database server.

    The advantage of using a database server is better organization of the logs and better sorting, searching and display capability. So, the rule of thumb is that if the logging is being done excessively, then use a file system to log it; otherwise, you can use a database. Generally, trace logs are more frequently logged, so it would be a good idea to log them to a file system. Audit logs and user logs can be logged to a database.


  1. Priority: There should be an option for logging according to the priority, and this priority should be configurable. Sometimes logging slows the application. In that case, there should be an option of restricting the logs to higher priority issues or stop the application from logging altogether.


  1. Size limit: There should be an option to restrict the total size of the logs, otherwise they may flood the disk (in the worst case). I have seen one application which only had an option to restrict the logs by the number of days — i.e., you could have logs for 1 day , 2 days … n days. This application always had this problem in which it would flood the disk with logs and slow the application, because sometimes even when using a one-day window, gigabytes of logs would get created. So there has to be a configurable upper size limit if you’re using a file system and a maximum number of rows if you’re using a database to contain the total size of logs.


  1. Log file location: The log file location should be configurable, because in some cases you want the log to be in other drives or some location which is other than the install location.


  1. Timestamp: Log messages must contain a timestamp even though they contain the server date time. In some cases you will need to compare the logs of two application instances whose server time could be different from each other.

{mospagebreak title=Trace Logs}

Now that we’ve completed the design considerations, let’s see how we can implement trace logs for an application. PHP provides a native function to log necessary information: error_log, but it is very basic and lacks a few of the basic design features we discussed above.

What we will do is write our MyLog class using the Pear Log. Writing our own class will give us the flexibility to append and prepend strings to the log message centrally.

The MyLog class’s definition looks like this:


*********************** MyLog.php***************************

<?php

/**

* MyLog -> This class is subclass of pear::log

*/

 

require_once ‘Log.php’ ; // pear Log class

 

class MyLog

{

 

var $logger = Null ; // Log class singleton instance

 

function MyLog() // constructor

{

global $myGlobal ;

$conf = array(‘mode’ => 0600, ‘timeFormat’ => ‘%X %x’);


$this->logger = &Log::singleton(‘file’, $myGlobal['log']['logfile'], ”, $conf, $myGlobal['log']['level']);

 

} // end of function MyLog

 

/**

* @desc logs to the specified file

* @param

* below are the valid levels , always use nos for specifying log levels

 

0 System is unusable

1 Immediate action required

2 Critical conditions

3 Error conditions

4 Warning conditions

5 Normal but significant

6 Informational

7 Debug-level messages

 

*/

 

function writelog($logmsg, $priority=7, $file_name = "" , $line_no = "")

{

global $myGlobal ;

 

// Check the current file size

 

// we have two log files -> trace.last.log ang trace.log

// both of these should not be greater than specified size limit

// suppose specified size limit is 100 MB

// in this case if trace.log (current log file) increases more than 50 MB

// trace.last.log is deleted , trace.log is renamed to trace.log.last and

// a new trace.log is created for logging

 

$lsize = filesize ( $myGlobal['log']['logfile'] ) ; // in bytes

$fsize = $lsize/2 ;

 

if ($lsize >= $myGlobal['log']['size'] *1024*1024/2){ // exceeded the limit

// delete if there’s already some old file like .last

unlink($myGlobal['log']['logfile']."last") ;

// rename the current log file to filenaame.last.log

rename($myGlobal['log']['logfile'], $myGlobal['log']['logfile'].".last") ;

 

}

 

// prepend the log message with unix timestamp

$logmsg = mktime()." ".$logmsg ;

 

// append file name and line no

$logmsg .= " ".$file_name ;

$logmsg .= " ".$line_no ;

 

//append user login

$logmsg .= " ".$_SESSION[current_login_id] ;

 

$this->logger->log($logmsg, $priority) ;

 

// mail logs if required

if ($priority <= $myGlobal['log']['email_level'] ){

 

// send email

$this->sendemail($logmsg, $priority) ;

 

}

 

 

 

 

}

 

function sendemail($logmsg, $priority) // implementation for sending notification through mail

{

// implement yourself

}

 

/**

* Destructor

*/

function _MyLog()

{

if ($this->_opened) {

$this->close();

}

}

}

 

?>


{mospagebreak title=Code Explained} 

Here, we used a custom class MyLog which uses Pear::Log package (http://pear.php.net/package/Log/) through aggregation.

We used MyLog because we might want to further customize the class according to our logging needs; in fact, we did.

We used a global variable to configure several parameters; this global variable could be your application’s global variable. The variables are:

$myGlobal['log']['logfile'] : specifies the log file location like "f:trace.log"

$myGlobal['log']['level'] : specifies the log level above which the logging should be done. The levels are :

0 System is unusable

1 Immediate action required

2 Critical conditions

3 Error conditions

4 Warning conditions

5 Normal but significant

6 Informational

7 Debug-level messages

These levels are from Pear::Log library.


$myGlobal['log']['size']: specifies the total log size. For example if you specify 32, it means 32 Mb can be the total size of the log files. Our class works by making two files of 16 MB each; once the second file nears completion, it deletes the first one. In this way, continuity is maintained in the logs.


$ myGlobal ['log']['email_address'] = ‘info@example.com’ : critical log messages will be sent to this mail id.


$ myGlobal ['log']['email_level'] = 1: log level above which (inclusive) the messages will be mailed to the email_address specified above.


In the application, you will have to invoke the MyLog instance before using it:


// logging object

$objMyLog = new MyLog() ;


Now, with this object you can log information in the application anywhere. Do make sure that the scope of this instance is global.


// logging in a script

$GLOBALS['objMyLog']->writelog("Could not create instance with dsn $dsn", 2, __FILE__, __LINE__) ;


In this statement the first argument is the log message; the second argument is the priority, which is 2 here; and the third and fourth arguments are filename and line number respectively.

{mospagebreak title=The writelog() function}

Now, I will walk you through the MyLog :: writelog() function briefly. Once the instance invokes this function with arguments, the function will write the message to the log file specified. But before that it goes through the following steps:


  1. Check the total log file size: see the code snippet for logic and implementation.


  1. Prepend the log message with unix timestamp:
    $logmsg = mktime()." ".$logmsg ;


  1. Append file name and line no to the log message :
    $logmsg .= " ".$file_name ;
    $logmsg .= " ".$line_no ;


  1. Append user login to the log message :
    $logmsg .= " ".$_SESSION[current_login_id] ;


  1. Log the message through Pear::Log::log function
    $this->logger->log($logmsg, $priority) ;


  1. Send any email if required :
    if ($priority <= $myGlobal['log']['email_level'] ){
    // send email
    $this->sendemail($logmsg, $priority) ;

    }



Application level logging is very important and should be adapted to the application. There are no hard and fast rules for how to implement them. Furthermore, logging is an overhead to the application, so it should be implemented in a way which costs the least and is able to give you most of the important information.

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan