2
\$\begingroup\$

I asked a question about an implementation of a logger using syslog and error_log as fallback that made me re-evaluate my Logger interface. Before it simply forced a log($timestamp, $message) to be implemented but after reading over the answer to the question I'm not sure that this is the best API possible to work with all the different logging options available.

Here's my new interface that allows logs to be opened and closed.

<?php

/**
 * @file
 * @brief Holds the interface to be implemented by objects responsible for logging
 * messages and data.
 */

namespace SprayFire\Logger;

interface Logger {

    /**
     * @brief We have left the parameters for this method blank on purpose; the
     * implementations should be responsible for passing the appropriate arguments.
     *
     * @details
     * It should be noted that if your implementation defines the method signature
     * with parameters you may assign a default value to that parameter and your
     * implementation will satisfy this method signature.
     *
     * @return true if log opened, false if didn't
     */
    public function openLog();

    /**
     * @param $timestamp the timestamp for the given message
     * @param $message The string message that should be appended to the end
     *        of the log
     * @return true if the message was logged, false if it didn't
     */
    public function log($timestamp, $message);

    /**
     * @return true if the log was closed, false if it didn't.
     */
    public function closeLog();

}

My problem is keeping the interface generic enough while allowing the various different options needed in opening a log to be satisfied. For example, NullLogger wouldn't really need any parameters passed to openLog() where as SysLogLogger would probably need some parameters passed. How would I accomplish this?

What I'm thinking now

That parameters will be passed as variable arguments to the function and the implementation uses func_get_args() to retrieve the necessary information to open the log, if necessary.

Alternatively, the implementation could simply define the parameters needed and ensure there are default values for all parameters. This would ultimately supply the needs of the interface while still communicating to the user that parameters are needed.

What I could be doing

I could declare the parameter passed to openLog() as an array that holds the appropriate option information. This means though that I would need to create an array for every Logger, whether it is needed or not. I'm not one to micro-optimize but at the same time I don't want to be creating unused/needless resources.

I could declare the three parameters passed to openlog in Logger::openLog, and then declare default values for those in the method signature signaling that you could simply not provide these arguments. But, this really doesn't "feel" right, you'd also be signaling to the user that you could pass arguments to say, NullLogger, that might actually influence the way the implementation works and it really wouldn't.

I'm not sure what would be the best solution here and would appreciate any feedback on the interface's API and the solutions proposed.

\$\endgroup\$
2
\$\begingroup\$

NullLogger wouldn't really need any parameters passed to openLog() where as SysLogLogger would probably need some parameters passed. How would I accomplish this?

Pass these parameters to the constructor, store in a private field and use it in the openLog (or any other) function.

If you use func_get_args() you wouldn't be able to replace the logging system without modifying the client code.

SLF4J is a great example for a logging framework. Check it's Logger interface. It contains some methods but none of them depend on any implementation. Logback, Log4j, JCL and other implementations implements this simple interface and their external configuration determines the details.

A note: maybe timestamp shouldn't be passed to the log function, it should be the internal of the logger implementation.

\$\endgroup\$
  • \$\begingroup\$ Ultimately I really liked the SLF4J logging implementation you linked and is the reason I accepted your answer. \$\endgroup\$ – Charles Sprayberry Jan 26 '12 at 23:44
2
\$\begingroup\$

When you have a log object the Log in the method name seems redundant.

    // Option 1
    $log = new Logger();
    $log->openLog();

    // Option 2    
    $log = new Logger();
    $log->open();

I think the second is clearer and it also removes confusion between openlog and openLog.

The main function of a logger is to log messages. I would argue that you could do away with the open and close methods all-together. Whether the appropriate resources are opened can be handled internally in the Logger class. The Logger class is also in a good position to know whether resources should be freed after the writing of a log is done.

private $isOpen;

private function open()
{
   \openlog(); // etc.
   $this->isOpen = true;
}

public function log($message) // I agree with palacsint on removing timestamp.
{
   if (!$this->isOpen)
   {
      $this->open();
   }

   // Write your log message.
}

Quite often you find that you don't need to close. For syslog closelog is optional. I wouldn't bother calling it. Let it close for itself. The same would go for logging to a file. Open it once, and then lock/write/unlock. I don't see a need to close the file unless you are doing something exotic with massive amounts of open files. If so, make is so that your logger opens and closes it on each write.

 // lock/write/unluck
 flock($file, LOCK_EX);
 fwrite($file, $message);
 flock($file, LOCK_UN);

I would be interested as to any reasons to have open and close logic to be controlled from outside of the logger. In my own framework I have not run into any problems without open and close methods.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.