Creating a Logging Mechanism

Creating a Logging Mechanism

The primary purpose of logging is to allow you to see what the application was doing, or how it was performing, at some time in the past. A properly written log will allow you to do a historical analysis of system behavior to determine whether changes need to be made. Three basic mechanisms exist for capturing this information.

Simple File Logging

The easiest way to handle logging is to write the information to a file. Usually, the user ID from which your Web server process is running does not have the access needed to write to the file system, except for a temporary directory (/tmp on most UNIX systems), which isn't necessarily the best location in which to keep important application log files. To allow the Web server user to be able to write to a file, create a dedicated directory in which to store your log files.

Note 

Create a folder above the document root of your application called logs and ensure that the Web server user has write permissions to this folder (chmod 744 and chown nobody on UNIX systems).

   /www    |    |    +---./mysite        |        |        +---htdocs        |   |        |   |        |   + ---images        |   |        |   + ---css        |        +---logs 

Example File System Layout

To write to a file in PHP, use fopen() to get a file handle and specify that you want to open the file for writing. Use fwrite() to write data to the file and fclose() to close the file. The following code defines and then demonstrates a simple method called logMessage(), which uses these basic methods to write to a pre-defined log file.

   <?php    function logMessage($message) {      $LOGDIR = '/www/mysite/logs/'; //chmod 744 and chown nobody      $logFile = $LOGDIR . 'mysite.log';      $hFile = fopen($logFile, 'a+'); //open for appending, create                                     //the file if it doesn't exist      if(! is_resource($hFile)) {        printf("Unable to open %s for writing. Check file permissions.",    $logFile);        return false;      }      fwrite($hFile, $message);      fclose($hFile);      return true;     }    logMessage("Hello, log!\n");    ?> 

If you run this code and you have all the permissions correctly set on the $LOGDIR directory, you should see the message Hello, log! in the mysite.log file. For the most rudimentary needs, this function might be sufficient, but for a real application, you'll want to track more than just the message. The log should be a machine-parsable file, meaning that you should be able to process the log data using a separate application that analyzes the information held within it. The log should keep track of the date and time of each message, some sense of the severity or importance of the message, and some remark about which module of the application generated the message.

The Logger Class

The class in the following code sample provides an OO mechanism for generating a more detailed log file than that created using the code in the previous section. Create a file called class.Logger.php. This file will contain the Logger class, which will allow you to write information in a tab-delimited format to a text file on the server, tracking the timestamp, log level (which is the severity of the message), the message itself, and an optional module name. The module name can be any string that helps to identify the part of the application that generated the message. The code for the Logger class follows.

   <?php     //Log Levels.  The higher the number, the less severe the message     //Gaps are left in the numbering to allow for other levels     //to be added later     define('LOGGER_DEBUG', 100);     define('LOGGER_INFO', 75);     define('LOGGER_NOTICE', 50);     define('LOGGER_WARNING', 25);     define('LOGGER_ERROR', 10);     define('LOGGER_CRITICAL', 5);                class Logger {       private $hLogFile;       private $logLevel;       //Note: private constructor.  Class uses the singleton pattern       private function __construct() {         global $cfg;  //system configuration info array from some external file         $this->logLevel = $cfg['LOGGER_LEVEL'];         $logFilePath = $cfg['LOGGER_FILE'];         if(! strlen($logFilePath)) {           throw new Exception('No log file path was specified ' .                               'in the system configuration.');         }         //Open a handle to the log file.  Suppress PHP error messages.         //We'll deal with those ourselves by throwing an exception.         $this->hLogFile = @fopen($logFilePath, 'a+');         if(! is_resource($this->hLogFile)) {           throw new Exception("The specified log file $logFilePath " .                               'could not be opened or created for ' .                               'writing.  Check file permissions.');         }       }       public function __destruct() {         if(is_resource($this->hLogFile)) {           fclose($this->hLogFile);         }       }       public static function getInstance(){         static $objLog;         if(!isset($objLog)) {           $objLog = new Logger();         }         return $objLog;       }       public function logMessage($msg, $logLevel = LOGGER_INFO, $module = null) {         if($logLevel <= $this->logLevel) {           $time = strftime('%x %X', time());           $msg = str_replace("\t", '    ', $msg);           $msg = str_replace("\n", ' ', $msg);           $strLogLevel = $this->levelToString($logLevel);           if(isset($module)) {             $module = str_replace("\t", '    ', $module);             $module = str_replace("\n", ' ', $module);           }           //logs: date/time loglevel message modulename           //separated by tabs, new line delimited           $logLine = "$time\t$strLogLevel\t$msg\t$module\n";           fwrite($this->hLogFile, $logLine);         }       }       public static function levelToString($logLevel) {         switch ($logLevel) {           case LOGGER_DEBUG:             return 'LOGGER_DEBUG';             break;           case LOGGER_INFO:             return 'LOGGER_INFO';             break;           case LOGGER_NOTICE:             return 'LOGGER_NOTICE';             break;           case LOGGER_WARNING:             return 'LOGGER_WARNING';             break;           case LOGGER_ERROR:             return 'LOGGER_ERROR';             break;           case LOGGER_CRITICAL:             return 'LOGGER_CRITICAL';             default:             return '[unknown]';         }       }     }    ?> 

This class should seem straightforward enough. You'll note you create a private constructor to prevent having to open the file handle several times during the execution of a page request. The getInstance() method allows code that will use this class to get an instance of it. This is known as the singleton pattern, which you first saw in Chapter 8, "Database Abstraction Layers."

The first few lines of the file establish a number of logging level constants. These constants allow you to control what information gets logged. While developing the first version of an application, for debugging purposes you'll probably want to log quite a bit of information to help you track down bugs. When the application is finally put into production, you'll want to log less information to save on system resources. Logging less information also increases the signal-to-noise ratio, meaning there is more useful information in a given number of lines of log information. When an application is running in a production environment, much of the debugging information you used during development will be of little use when trying to track down problems and will make finding those problems in the log more difficult.

In the constructor, two pieces of information are pulled from a global array of configuration information; the name of the log file, and the current debugging level. For this example, you can assume that there is only one log file per application. Later in this chapter you'll look at ways to extend this class to provide even more functionality and greater flexibility in where information is logged.

Store the logging level in a private member variable that will be used later to determine which information is written to the log file. The log filename is checked to make sure it has a value and that the Web server user can actually open or create the log file for appending. The a+ parameter to fopen() means "open this file for appending and if it doesn't exist, create it." Store the file handle as another private member variable. If for any reason you can't create a valid file handle, the constructor throws an exception.

Any attempt to call Logger::getInstance() should be wrapped in a try...catch block because of the possibility of failure arising from incorrect permissions on the log file, or lack of disk space on the volume on which it resides. In the event that this happens, the entire application should not cease to work because of the logger. You should catch this exception and take appropriate action, perhaps by sending an e-mail to a systems administrator (see Chapter 14, "Communicating with Users," for a robust e-mailing class you can use).

This class also has a destructor (lines 41 45) that closes the file handle if it is open. If the file path wasn't valid, the file handle will not be a valid resource. Even though the exception was thrown in the constructor, the destructor will still be called when the object is deallocated from memory, so you need to make sure that yet another error isn't generated by trying to close a file handle that was never opened successfully in the first place.

Just as with the Database class from Chapter 8, the getInstance() method in the Logger class has a static variable that will store an instance of the class in between invocations of the getInstance() method. The first time the method is called, that variable will be null, resulting in the creation of a new instance of Logger. If the log file path is invalid, the exception will bubble up to this method. On subsequent calls to this method, the variable will store the reference created during the first invocation, and that original instance will be returned. Using the singleton pattern here allows you to save the system resources required to open a file handle.

The logMessage() method does all the hard work. It takes the message text, the logging level, and an optional module name. If the current system-wide logging level configuration parameter (assigned to the private member variable $logLevel in the constructor) is at least as large as the log level specified in the second parameter to this function, an entry in the log file is created. If the current application logging level is less than the level specified in the second parameter, no entry is created. This allows you to control what is or is not entered into the log. For each message sent to the logMessage() method, a log level should be supplied indicating the severity of the message. For example, if you're printing just the contents of some variable to the log for debugging purposes, the second parameter to logMessage() should be LOGGER_DEBUG. Then, when deploying the code to the production server, you should set the $cfg['LOGGER_LEVEL'] parameter to LOGGER_WARNING or higher. This will stop debug messages from being printed to the log, saving CPU cycles and restricting the content in the log to errors of varying severity.

logMessage() generates a time stamp on line 60 using PHP's strftime() function, which takes a format string as its first parameter and a UNIX timestamp as the second. The format string used in the code sample uses the system default short date representation (%x) and the system default time representation (%X). On my server, which is located in the United States, an example output from strftime('%x %X', time()) is "03/17/2004 03:55:25" (meaning March 17, 2004, at 3:55:25 in the morning). If your computer is located in other parts of the world, the date representation would be shown appropriately for your location (in this case that would likely mean 17/03/2004). If you want more control over the formatting of the timestamp in the log, see the documentation for the strftime() function at http://www.php.net/strftime. If your log requires higher time resolution than one second, you can use the microtime() function to get milliseconds. Again, see the documentation for more information on how to use this function.

After creating a textual representation of the timestamp, logMessage() then needs to do some formatting of the message. Because you want to create a log file that is easily machine readable, separate the fields on each line of the log with a tab (\t). Each log entry is separated by a newline character (\n). Because of this, you need to remove any tabs or new lines that might exist in the message because these would corrupt the log file and make it difficult to parse with a log analyzer. Replace the tabs with four spaces (the default number of characters occupied by a tab in most text editors) and replace new lines with a space. If the $module parameter has a value, the same replacements should be made.

The only other thing of interest that happens here is the conversion of the numeric log level constant to a string. Because PHP sees the constants as their actual numeric value, there is no easy way to convert the name of a constant to a string representation of that constant name. As a result, the string representations are hard-coded into the method levelToString(). The switch statement loops through the constants and returns the name of the constant. Although the break statements are not technically necessary, they are added for clarity's sake. Also not actually necessary is the declaration of this method as static, but because it doesn't depend on any of the member variables and because you may find a need to print the log level in code outside this class, declaring the method static makes it easy to write code like echo Logger::levelToString($cfg['LOGGER_LEVEL']), for example.

The following code sample shows how you might be able to use the Logger class in a real application.

   <?php    require_once('class.Logger.php');    $cfg['LOGGER_FILE'] = '/var/log/myapplication.log';    $cfg['LOGGER_LEVEL'] = LOGGER_INFO;    $log = Logger::getInstance();    if(isset($_GET['fooid'])) {      //not written to the log - the log level is too high      $log->logMessage('A fooid is present', LOGGER_DEBUG);      //LOG_INFO is the default so this would get printed      $log->logMessage('The value of fooid is ' . $_GET['fooid']);    } else {      //This will also be written, and includes a module name      $log->logMessage('No fooid passed from ' . $_SERVER['HTTP_REFERER'],                       LOGGER_CRITICAL,                       "Foo Module");      throw new Exception('No foo id!');    }    ?> 

If $_GET['fooid'] is set, two calls to logMessage() are made, but only one message will actually be written to the file because of the configured logging level. The contents of the log will look something like the following:

   03/17/04  03:58:42      LOGGER_INFO      The value of fooid is 25 

Here you see the timestamp, the logging level of the message, and the message itself. If $_GET['fooid'] is not set, you will see the following log entry:

   03/19/04  05:30:07    LOGGER_CRITICAL  No fooid passed from            http://localhost/testLogger.php     Foo Module 

In this case, the testLogger.php page failed to pass fooid to the page that invoked the logger, so the LOGGER_CRITICAL error was written to the file.

Extending the Logger Class

Logging to a text file is an incredibly useful way to store log data. There are literally thousands of utilities you can use to parse and analyze this data from simple UNIX utilities such as sed and grep to fancy commercial log analysis software packages costing tens of thousands of dollars. But sometimes a text file isn't the most convenient storage medium. You might want to use a relational database or integrate with your platform's system logger to centralize all your application logs. Additionally, the Logger class as developed thus far is capable of writing only to a single file no matter where in the application it is invoked. There are several circumstances in which you might want to keep separate logs for different sections of an application or of different tasks performed. In this section, you'll extend the Logger class to be able to integrate with any data storage medium and support multiple logs in a single application.

In Chapter 8, "Database Abstraction Layers," you learned how the PEAR DB class can connect to a completely different database backend by a simple change to its connection string. By connecting with "mysql://root@localhost/mydb", you can connect to a MySQL database running on the local machine using the username "root" and connecting to the database called "mydb". To connect to a PostgreSQL database, the connection string looks like "pgsql://postgres@localhost/yourdb", which connects to a PostgreSQL database cluster on the local machine, authenticates as the user postgres, and connects to the database called "yourdb". After a connection has been established using this common connection string syntax, everything else about the use of the DB class is identical, regardless of which RDBMS you're connected to. You can use a very similar construct to allow your Logger class to connect to any kind of storage medium.

The goal of the enhanced Logger class is to use a similar syntax for establishing a connection to a storage medium for log data. Because you'll be able to use different storage media, you should also be able to store different logs for different parts of the application. The new Logger class should support some sort of "registry" of log connections. For example, you should be able to have a log called "errors" and one called "queries," which will store error messages and SQL statements, respectively. The following sample code represents what you should be able to do with the redesigned class:

   <?php    Logger::register('errors', 'file:///var/log/error.log');    Logger::register('app,                     'pgsql://postgres@db/errors?table=applog&timestamp=dtlog&' .                                                 'msg=smesg&level=slevel&module                                                     =smod');    $objQLog = Logger::getInstance('queries');    $sql = "SELECT * FROM foo";    $objQLog->logMessage("Selecting all foos");    try {      Database->getInstance()->select($sql);    } catch (DBQueryException $e) {         $objErrLog = Logger::getInstance('errors');         $objErrLog->logMessage($e->getMessage(), LOGGER_CRITICAL);    }    ?> 

Showing this code before showing the new class that drives it may be a little confusing, but sometimes it's easier to design a class by first knowing how you want to be able to use it. In a real application you would put the first two lines in a globally included file. These two lines establish two different logs, called app and errors. The app log will store its log messages in a PostgreSQL database, in a table called applog using the field names specified for the timestamp, message, logging level, and module. The error log will be a text file located in /var/log/error.log. In the main part of the application, you would store most messages in the app log for later analysis. Any errors that occur will be stored in the errors log, which is a text file. We use a text file for the errors because one of the errors may be an inability to connect to the database.

Parsing the Connection String

The first step toward creating the new Logger class is to be able to parse strings of the form scheme://user:password@host:port/path?query#fragment. Luckily, PHP provides us with an incredibly handy function for doing just that. The parse_url() function takes a string in this general form and returns an array containing the elements from the URL that exist (the array keys are the same as the names used in the previous string). If one or more of the elements do not exist, no item with that key is added to the array (as opposed to having a null entry for that key). For example:

   $url = "ftp://anonymous@ftp.gnu.org:21/pub/gnu/gcc"    $arParts = parse_url($url);    var_dump($arParts);    //print out:    Array (        [scheme] => ftp        [user] => anonymous        [host] => ftp.gnu.org        [port] => 21        [path] => /pub/gnu/gcc    ) 

Note that the password, query, and fragment keys do not appear in the array, because they do not appear in the URL.

Redesigning Logger to Use the Connection String

As does PEAR DB, Logger will use the scheme part of this array to determine which logging backend to use. The register() method will be used to establish a new connection and will take a canonical name and a URL as parameters. Because the register() method was statically invoked in the sample code, you'll need to use some sort of intermediate function that will allow both the register() and getInstance() methods to talk to the same set of information. The following code shows the new register() method and the reworked getInstance() and constructor. In fact, this code sample represents the entirety of the new Logger class. The parts that have changed are highlighted.

   <?php    //Log Levels.  The higher the number, the less severe the message    //Gaps are left in the numbering to allow for other levels    //to be added later    define('LOGGER_DEBUG', 100);    define('LOGGER_INFO', 75);    define('LOGGER_NOTICE', 50);    define('LOGGER_WARNING', 25);    define('LOGGER_ERROR', 10);    define('LOGGER_CRITICAL', 5);    class Logger {      private $hLogFile;      private $logLevel;      //Note: private constructor.  Class uses the singleton pattern      private function __construct() {      }      public static function register($logName, $connectionString) {        $urlData = parse_url($connectionString);      if(! isset($urlData['scheme'])) {        throw new Exception("Invalid log connection string $connectionString");      }      include_once('Logger/class.' . $urlData['scheme'] . 'LoggerBackend.php');      $className = $urlData['scheme'] . 'LoggerBackend';      if(! class_exists($className)) {        throw new Exception('No logging backend available for ' .                            $urlData['scheme']);      }      $objBack = new $className($urlData);      Logger::manageBackends($logName, $objBack);     }     public static function getInstance($name) {       return Logger::manageBackends($name);     }     private static function manageBackends($name, LoggerBackend        $objBack = null) {static $backEnds;       if(! isset($backEnds)) {         $backEnds = array();       }       if(! isset($objBack)) {         //we must be retrieving         if(isset($backEnds[$name])) {           return $backEnds[$name];          } else {           throw new Exception("The specified backend $name was not " .                               "registered with Logger.");          }       } else {         //we must be adding        $backEnds[$name] = $objBack;       }     }      public static function levelToString($logLevel) {       switch ($logLevel) {         case LOGGER_DEBUG:           return 'LOGGER_DEBUG';           break;         case LOGGER_INFO:           return 'LOGGER_INFO';           break;         case LOGGER_NOTICE:           return 'LOGGER_NOTICE';           break;         case LOGGER_WARNING:           return 'LOGGER_WARNING';           break;         case LOGGER_ERROR:           return 'LOGGER_ERROR';           break;         case LOGGER_CRITICAL:           return 'LOGGER_CRITICAL';         default:           return '[unknown]';       }     }    }    ?> 

In the preceding code, Logger pulls in the file that defines an abstract class called LoggerBackend, which serves as the base class for the classes that will do the actual work of writing to a given logging mechanism. Logger's constructor is now empty but is still declared to be private to prevent its instantiation outside the getInstance() method.

The new static method register() is responsible for instantiating a LoggingBackend object based on the scheme part of the URL specified in the second parameter. To do so, it makes a call to parse_url() to determine the scheme. If no scheme was present in the URL, register() throws an exception. If the scheme is present, an attempt is made to include_once a filed called Logger/class .[scheme]LoggerBackend.php, and the class contained therein is instantiated by passing the $urlData array.

If the specified LoggerBackend class is properly instantiated, the new instance is passed, along with the canonical name specified in the first parameter to register() to the private function manageBackends(). This function exists because all public methods of Logger are now static. As a result, the class can't use any member variables to store the instantiated backend objects. manageBackends() contains a static variable that takes the place of a class member. If the function has two parameters, it stores the LoggerBackend object in the $backEnds array using the $name parameter as a key. If only one parameter is specified (the $name parameter), manageBackends() returns the LoggerBackend object stored in $name, if one exists.

The new getInstance() method no longer returns a Logger object but instead returns an instantiated LoggerBackend. It does so by calling manageBackends() with only one parameter, the canonical name of the particular log requested. If the named log wasn't previously registered, or was unable to be instantiated during register(), manageBackends() throws an exception that will bubble up to getInstance().

The LoggerBackend Class

The manageBackends(), register(), and getInstance() methods all interact with LoggerBackend objects. This class provides a concrete constructor (a construct that exists and is callable) and an abstract method called logMesage(), which you probably remember from the original Logger class. The latter method is unchanged in number and type of parameters from the code we saw previously. However, in the LoggerBackend class, the logMessage() method is abstract no actual function body exists, just the declaration of a function that must be implemented by classes that inherit from LoggerBackend. The constructor of the class takes the array output from parse_url() and stores it in a protected member variable. No other methods are defined by the LoggerBackend class. Save the following as Logger/class.LoggerBackend.php.

   <?php    abstract class LoggerBackend {      protected $urlData;      public function __construct($urlData) {        $this->urlData = $urlData;      }      abstract function logMessage($message, $logLevel = LOGGER_INFO, $module);     }     ?> 

The class itself should be declared abstract because it contains one abstract method and because there is no practical use for an instance of LoggerBackend. Only the subclasses of LoggerBackend will do any real work.

Subclassing LoggerBackend

To make the new and improved Logger class be able to do anything, you'll need to create at least one subclass of LoggerBackend. Because we already had some code that will log information to a file, this will be the easiest LoggerBackend subclass to create first.

Create a file called class.fileLoggerBackend.php (pay attention to capitalization in the filename) and enter the following code:

   <?php    require_once('Logger/class.LoggerBackend.php');    class fileLoggerBackend extends LoggerBackend {      private $logLevel;      private $hLogFile;      public function __construct($urlData) {        global $cfg;  //system configuration info array from some external file        parent::__construct($urlData);        $this->logLevel = $cfg['LOGGER_LEVEL'];        $logFilePath = $this->urlData['path'];        if(! strlen($logFilePath)) {          throw new Exception('No log file path was specified ' .                              'in the connection string.');        }        //Open a handle to the log file. Suppress PHP error messages.        //We'll deal with those ourselves by throwing an exception.        $this->hLogFile = @fopen($logFilePath, 'a+');        if(! is_resource($this->hLogFile)) {          throw new Exception("The specified log file $logFilePath " .                              'could not be opened or created for ' .                              'writing. Check file permissions.');        }      }      public function logMessage($msg, $logLevel = LOGGER_INFO, $module = null) {        if($logLevel <= $this->logLevel) {          $time = strftime('%x %X', time());          $msg = str_replace("\t", '    ', $msg);          $msg = str_replace("\n", ' ', $msg);          $strLogLevel = Logger::levelToString($logLevel);          if(isset($module)) {            $module = str_replace("\t", '    ', $module);            $module = str_replace("\n", ' ', $module);          }          //logs: date/time loglevel message modulename          //separated by tabs, new line delimited          $logLine = "$time\t$strLogLevel\t$msg\t$module\n";          fwrite($this->hLogFile, $logLine);        }      }    }    ?> 

Much of this code should look familiar to you. It comes nearly verbatim from the original Logger class. The fileLoggerBackend class will respond to logs registered with the file:// scheme. It writes the log to the file specified in the path component of the parse_url() array. To register a log of this type, the URL should be something like file:///var/log/app.log. Note that these components are file:// (ending in two forward slashes) and /var/log/app.log (starting with one forward slash). There are three initial forward slashes in the file scheme.

To use this backend, you can write code like the following (just like what you saw at the beginning of thissection):

   <?php    Logger::register('app', 'file:///var/log/applog.log');    $log = Logger::getInstance('app');    $log->logMessage('This is a new log message!', LOGGER_CRITICAL, 'test');    ?> 

This registers a fileLoggerBackend with the Logger class called 'app', which writes to the file/var/log/applog.log.

Logging to a Database Table

We can use this same process of subclassing LoggerBackend to create a mechanism for logging to any conceivable data repository. The following code shows a PostgreSQL backend, but the same principles can be used to log to any database platform.

This class has a significantly longer constructor. The connection parameters and the names of the fields in the database to which the information gets written should all be representable in the connection string with sensible defaults for those items that can be optional. As a result, the code must check to see whether certain values are set before building up connection strings and field names. None of this code is very interesting but we include all of it here to show how to parse the connection string to allow a flexible PostgreSQL logging mechanism. This code should be saved in Logger/class.pgsqlLoggerBackend .php.

   <?php    require_once('Logger/class.LoggerBackend.php');    class pgsqlLoggerBackend extends LoggerBackend {      private $logLevel;      private $hConn;      private $table = 'logdata';      private $messageField = 'message';      private $logLevelField = 'loglevel';      private $timestampField = 'logdate';      private $moduleField = 'module';      public function __construct($urlData) {        global $cfg; //system configuration info array from some external file        parent::__construct($urlData);        $this->logLevel = $cfg['LOGGER_LEVEL'];        $host = $urlData['host'];        $port = $urlData['port'];        $user = $urlData['user'];        $password = $urlData['password'];        $arPath = explode('/', $urlData['path']);        $database = $arPath[1];       if(!strlen($database)) {         throw new Exception('pgsqlLoggerBackend: Invalid connection string.' .                             ' No database name was specified');       }       $connStr = '';       if($host) {         $connStr .= "host=$host ";       }       if($port) {         $connStr .= "port=$port ";       }       if($user) {         $connStr .= "user=$user ";       }       if($password) {         $connStr .= "password=$password ";       }       $connStr .= "dbname=$database";       //Suppress native errors. We'll handle them with an exception       $this->hConn = pg_connect($connStr);       if(! is_resource($this->hConn)) {         throw new Exception("Unable to connect to the database using $connStr");       }       //Take the query string in the form var=foo&bar=blah       //and convert it to an array like       // array('var' => 'foo', 'bar' => 'blah')       //Be sure to convert urlencoded values       $queryData = $urlData['query'];       if(strlen($queryData)) {         $arTmpQuery = explode('&',$queryData);         $arQuery = array();         foreach($arTmpQuery as $queryItem) {           $arQueryItem = explode('=', $queryItem);           $arQuery[urldecode($arQueryItem[0])] = urldecode($arQueryItem[1]);         }       }       //None of these items is mandatory.  The defaults are established in the       //private member declarations at the top of the class.       //These variables establish the name of the table and the names of         the fields       //within that table that store the various elements of the log entry.       if(isset($arQuery['table'])) {         $this->table = $arQuery['table'];       }       if(isset($arQuery['messageField'])) {         $this->messageField = $arQuery['messageField'];       }       if(isset($arQuery['logLevelField'])) {         $this->logLevelField = $arQuery['logLevelField'];       }       if(isset($arQuery['timestampField'])) {         $this->timestampField = $arQuery['timestampField'];       }       if(isset($arQuery['moduleField'])) {         $this->logLevelField = $arQuery['moduleField'];       }     }     public function logMessage($msg, $logLevel = LOGGER_INFO, $module = null) {       if($logLevel <= $this->logLevel) {         $time = strftime('%x %X', time());         $strLogLevel = Logger::levelToString($logLevel);         $msg = pg_escape_string($msg);         if(isset($module)) {           $module = "'" . pg_escape_string($module) . "'";         } else {           $module = 'NULL';         }         $arFields = array();         $arFields[$this->messageField] = "'" . $msg . "'";         $arFields[$this->logLevelField] = $logLevel;         $arFields[$this->timestampField] = "'". strftime('%x %X', time()) . "'";         $arFields[$this->moduleField] = $module;         $sql = 'INSERT INTO ' . $this->table;         $sql .= ' (' . join(', ', array_keys($arFields)) . ')';         $sql .= ' VALUES (' . join(', ', array_values($arFields)) . ')';         pg_exec($this->hConn, $sql);       }     }    }    ?> 

In the constructor, the class parses out the query part of the URL to determine the table name and the names of the fields in which the log information is stored. The assumption is that the table will be created using a SQL statement like the following. The field names don't matter because they're configurable in the connection string, but the data types in your table should be the same (or should be compatible data types).

   create table logdata (       message text,       loglevel smallint,       logdate timestamp,       module varchar(255)    ); 

To use the new PostgreSQL logger, the code should look something like the following:

   <?php    $cfg['LOGGER_LEVEL'] = LOGGER_INFO;    Logger::register('app', 'pgsql://steve@localhost/mydb?table=logdata');    $log = Logger::getInstance('app');    if(isset($_GET['fooid'])) {      //not written to the log   the log level is too high      $log->logMessage('A fooid is present', LOGGER_DEBUG);      //LOG_INFO is the default, so this would get printed      $log->logMessage('The value of fooid is ' . $_GET['fooid']);    } else {      //This will also be written and includes a module name      $log->logMessage('No fooid passed from ' . $_SERVER['HTTP_REFERER'],                       LOGGER_CRITICAL,                       "Foo Module");      throw new Exception('No foo id!');    }    ?> 

The connection string passed to Logger::register() specifies a log called app that connects to a PostgreSQL database running on localhost. The LoggerBackend connects as the user steve to the database called mydb. The table into which the log entries are written is called logdata.Had we not specified any of these parameters, the defaults declared as the class's private member variables would have been used. Of course, this backend can easily be modified to support any type of database for which there is a PHP module, including MySQL and SQL Server.



Professional PHP5 (Programmer to Programmer Series)
Professional PHP5 (Programmer to Programmer Series)
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 182
BUY ON AMAZON

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net