views:

125

answers:

5

Best suggestion will be the accepted answer

Class:

<?php 

class LogInfo {
    private $first_run;         // Flag to add line break at the beginning of script execution
    private $calling_script;    // Base name of the calling script
    private $log_file;          // log file path and name
    private $log_entry;         // information to be logged
    private $log_level;         // Log severity levels: error, warning, notice, info (default)
    private $log_level_arr;     // Array of accepted log levels
    private $fh;                // File handle

    private $file_name;         // File path and name
    private $file_parts;        // Array of $file_name
    private $script_name;       // Script Name
    private $script_parts;      // Array of $script_name    

    function __construct() {
        $this->first_run        = true;
        $this->log_level_arr    = array(
                                    'info'=>'info', // default
                                    'error'=>'error',
                                    'warning'=>'warning',
                                    'notice'=>'notice',
                                    );
        $this->calling_script   = '';       
        $this->log_file         = '';
        $this->log_entry        = '';
        $this->log_level        = '';
        $this->fh               = '';
        $this->file_name        = '';
        $this->file_parts       = '';
        $this->script_name      = '';
        $this->script_parts     = '';                   
    }

    /**
     * log detailed information into calling script log files
     */
    public function logInfo($info, $level = 'info') {

        if(array_key_exists($level,$this->log_level_arr)) {
            $this->log_level = $level;
        } else {
            $this->log_level = 'undefined';
        }

        $this->calling_script = $this->getScriptBaseName();
        $this->log_file = dirname(__FILE__)."/logs/".$this->calling_script.".log";
        $this->fh = fopen($this->log_file, 'a') or die("Can't open log file: ".$this->log_file);

        if($this->first_run) {
            $this->log_entry = "\n[" . date("Y-m-d H:i:s", mktime()) . "][" .$this->log_level."]:\t".$info."\n";
        } else {
            $this->log_entry = "[" . date("Y-m-d H:i:s", mktime()) . "][" .$this->log_level."]:\t".$info."\n";
        }

        fwrite($this->fh, $this->log_entry);
        fclose($this->fh);

        $this->first_run = false;
    }

    /**
     * return the base name of the calling script
     */
    private function getScriptBaseName() {
        $this->file_name    = $_SERVER["SCRIPT_NAME"];
        $this->file_parts   = explode('/', $this->file_name);
        $this->script_name  = $this->file_parts[count($this->file_parts) - 1];
        $this->script_parts = explode('.', $this->script_name);

        return $this->script_parts[0];
    }
}

?>

How to call:

$log = new LogInfo();

$txt = "This is comment";
$log->logInfo($txt." 1",'error');
$log->logInfo($txt." 2",'new');
$log->logInfo($txt." 3",'warning');
$log->logInfo($txt." 4",'notice');
$log->logInfo($txt." 5",'info');
$log->logInfo($txt." 6");
+6  A: 

I would use separate methods for each log level, that would be easier to use.

$log->error($msg) for example.

jishi
so you would recommend creating a function for each log level? error(), info(), notice() and warning()? instead of just passing a flag? I do like the syntax on that as it would make more sense. $log->info($msg);
Phill Pafford
using this suggestion, thnx
Phill Pafford
To add to this, you could call the `logInfo()` function from the `error()` function and use the flags there. That way you condense all your writing to a single function, but the syntax makes more sense. Also, if you do this, make the `logInfo()` function `private`.
Joseph
@Phill - Use of the magic __call method would allow you to call your different log levels without needing to explicitly define a separate method for each. You could validate against an array of acceptable levels, and provide a method allowing you to extend the list of levels dynamically at run time
Mark Baker
Thanks for this answer as it provided the best solution I was looking for
Phill Pafford
+1  A: 

As jishi already stated, I would also separate the log-message-levels into own methods. These log-methods would be protected and only get invoked through one log-method which is public to use for the programmer.

Also I would add the possibility to add different types of writers. So that you can log the data into a file, database, send via email, and so on. I would realize this with dependecy-injection. If you need an example, about what I'm thinking of, just say it :)

EDIT

I'm home right now, and as promised I'm gonna provide some code:

/**
 * This is an interface for the writers. All writers must implement this,
 * to ensure, that they work together with the logger-class.
 */
interface logWriter
{
    public function log($message, $type);
}

/**
 * This is your logger-class. You call this class, if you want to log something,
 * just as you do it now.
 */
class logger
{
    /** This is the most important var. All writers that shall be used, are registered
     * in this array.
     */
     protected $_writers = array();

     // Here the other vars go, that you need for your logger, like config-vars and stuff
     // ...
     // ...

     /**
      * Your constructor, probably you'll use it to configure the logger...
      */
      public function __construct($config)
      {
        //.... do stuff...
      }

      /**
       * Okay with this method you register the different writers with your logger
       */
       public function registerWriter(logWriter $writer)
       {
            // Store the write into the array with all writers.
            $this->_writers[] = $writer;
       }

       /**
        * This is your normal log-method, just as you have one now
        */
       public function log($message, $type)
       {
            // Now you iterate over all registered writers and call the log-method of the writer...
            foreach($this->_writers AS $writer)
            {
                $writer->log($message, $type);
            }
       }
}

/**
 * This is a writer that sends the log message via email to you
 */
class emailWriter implements logWriter
{
    /**
     * Implement log-method from the logWriter-interface
     */
     public function log($message, $type)
     {
        // This is simplified right now. Should be more complex in the final version
        $to = '[email protected]';
        $from = '[email protected]';
        $subject = 'Log-Message of type' . $type;
        $body = $message;
        $headers = $from . "\r\n";
        mail($to, $subject, $body, $headers);
     }
}

/**
 * This is a writer that writes the message into a log-file
 */
class fileWriter implements logWriter
{
    /**
     * Implement log-method from the logWriter-interface
     */
     public function log($message, $type)
     {
        //... Here you just do the stuff that you would do: open file, write content, close, etc...
     }
}

// ...
// Here you can develop more writers like an SMS-Writer, Jabber-IM-Writer and so on
// ...

Okay now using this stuff:

$logger = new logger();

// Register the writers with the logger
$logger->registerWriter(new emailWriter());
$logger->registerWriter(new fileWriter());

// Now log something
$logger->log('This is a test-log-message', 'info');

Since we've registered the email- and fileWriter to the logger, the message will be written into a file andadditionally you'll get an email.If you only want to get an email and don't write the message into a file, only register the emailWriter with the logger-class

faileN
yes examples would be nice and thanx
Phill Pafford
I am currently at the office and don't have the time to provide an example right now. I'll post one in about 2 hours, when I am home. Hope you can stand it this long :P
faileN
log4php (based on log4j) implemented just such logging, so a FATAL in class XYZ could be SMSd to support staff for immediate attention, while FATAL in class ABC could be e-mailed to somebody, or ERROR in any class could be written to a straight log, and INFO could be written to a cyclic log, etc... all user configurable
Mark Baker
@Phill Pafford: Okay I've updated my answer. Check it out. If you're common with OOP, it shouldn't be a problem to understand the basic approach ;)@Mark Baker: There are several frameworks providing the idea of different writers. I haven't head about "log4php", but I can say, that the Zend-Framework also includes this feature.
faileN
+1  A: 

I will often add some static functions to a logger to make it more easily accessible without an instantiated object. Building on jishi's suggestion of different methods for each level:

public static function info($message) {
   // do logging here
}

LogInfo::info("I can log from anywhere");

Of course, there are certainly times and places where you don't want to access your logger using static functions. I generally find it easier though.

Another suggestion is to read in a configuration variable from somewhere that specifies a logging level. When you're building and very curious about exactly what is happening where, you can turn your logging level up from a central location (i.e. config.php or something).

Change your main log method to check that config variable before doing anything...

public static function info($message, $logLevel) {
    if(check_config("logLevel") >= $logLevel) {
        // do logging here
    }
}
thetaiko
What is the benefits of using this: LogInfo::info("I can log from anywhere"); instead of this: $log->info("I can log from anywhere");
Phill Pafford
The benefit is not having to declare a `$log` object. If you want to add logging functionality to 5 different functions, you will have to either pass in the already instantiated `$log` object or create a new one in each function. Instead, you can simply call your static log methods without declaring an object.
thetaiko
I see, but how would I get the first_run functionality using this method? so each time the script runs I want to add a line break but keep all the other logging grouped together, any thoughts as I do like not having to declare a $log object all the time
Phill Pafford
Make the first_run variable static as well. This variable will then belong to the class rather than to an instance. After you call a log function for the first time set the `$first_run` flag using `self::first_run = false;`
thetaiko
Oops - `self::first_run` should be `self::$first_run`
thetaiko
Hmm lets say I have 2 scripts that both call the LogInfo::info() but each script should have its own log file (Thinking MVC), so the model might log something and then the controller might log something but each should have it's own log. (It's not what I'm doing, I'm just asking to ask)
Phill Pafford
+3  A: 

You may want to make this class implement an observable interface so you can attach it to other objects which can then use it to log events using the Observer pattern.

interface Observer
{
    function notify($sender);
}

interface Observable
{
    function addObserver(Observer $observer);
}

class LogInfo implements Observer
{
    // ...

    public function notify($obj)
    {
        $this->logInfo($obj->getMessage());
    }

    // ...
}

class DoesSomething implements Observable
{
    // ...

    protected $observers = array();
    protected $message;

    public function addObserver(Observer $observer)
    {
        $this->observers[] = $observer;
    }

    protected function notify($message)
    {
        $this->message = $message;
        if (count($this->observers))
        {
            foreach($this->observers as $observer)
            {
                $observer->notify($this);
            }
        }
    }

    public function getMessage()
    {
        return $this->message;
    }

    public function doSomething()
    {
         // ...
         $this->notify('We did something important');
    }

    // ...
}

$obj = new DoesSomething();
$obj ->addObserver(new LogInfo());
$obj ->doSomething(); // Uses LogInfo object to log event
John Conde
interesting, I've never used the Observer functionality before. Thanks
Phill Pafford
+1 Clever use of Observer pattern
Mark Baker
+1  A: 

Very similar question from earlier this week: http://stackoverflow.com/questions/3187219/how-do-i-save-logs-in-php/3187389#3187389

Sam Bisbee
Hmm I do like the STATIC functionality of this, maybe incorporate the two
Phill Pafford