Viewed   102 times

I would like to implement logging mechanism to file in PHP:

  1. log file path will be in config file config.php
  2. in several classes I would like to log some events into the log file

For example:

    Class A {

        public function f_A {
            log_to_file($message);
        }

    }

    Class B {

        public function f_B {
            log_to_file($message);
        }

    }

I will be very grateful for any tips. I would like to implement some easy and elegant solution.

I was thinking about it (thank you for your answers) and I think I will do it this way (maybe, there are some errors, I was writing it from scratch):

interface Logger {
    public function log_message($message);
}

class LoggerFile implements Logger {
    private $log_file;

public function __construct($log_file) {
    $this->log_file = $log_file;
}
public function log_message($message) {
        if (is_string($message)) {
            file_put_contents($this->log_file, date("Y-m-d H:i:s")." ".$message."n", FILE_APPEND);
        }
    }
}

//maybe in the future logging into database

class LoggerDb implements Logger {
    private $db;

    public function __construct($db) {
        //some code
    }
public function log_message($message) {
        //some code
    }
}

Class A {
    private $logger;

public function __construct(Logger $l) {
        $this->logger = $l;
    }


public function f_A {
    $this->logger->log_message($message);
}
}

Class B {
    private $logger;

public function __construct(Logger $l) {
        $this->logger = $l;
    }


public function f_B {
    $this->logger->log_message($message);
}
}

//usage:

//in config.php:

define("CONFIG_LOG_FILE", "log/app_log.log");

//in the index.php or some other files

$logger = new LoggerFile(CONFIG_LOG_FILE);

$instance_a = new A($logger);
$instance_b = new B($logger);

 Answers

2

Where are loggers used?

In general there are two major use-cases for use of loggers within your code:

  • invasive logging:

    For the most part people use this approach because it is the easiest to understand.

    In reality you should only use invasive logging if logging is part of the domain logic itself. For example - in classes that deal with payments or management of sensitive information.

  • Non-invasive logging:

    With this method instead of altering the class that you wish to log, you wrap an existing instance in a container that lets you track every exchange between instance and rest of application.

    You also gain the ability to enable such logging temporarily, while debugging some specific problem outside of the development environment or when you are conducting some research of user behaviour. Since the class of the logged instance is never altered, the risk of disrupting the project's behaviour is a lot lower when compared to invasive logging.

Implementing an invasive logger

To do this you have two main approaches available. You can either inject an instance that implements the Logger interface, or provide the class with a factory that in turn will initialize the logging system only when necessary.

Note:
Since it seems that direct injection is not some hidden mystery for you, I will leave that part out... only I would urge you to avoid using constants outside of a file where they have been defined.

Now .. the implementation with factory and lazy loading.

You start by defining the API that you will use (in perfect world you start with unit-tests).

class Foobar 
{
    private $loggerFactory;

    public function __construct(Creator $loggerFactory, ....)
    {
        $this->loggerFactory = $loggerFactory;
        ....
    }
    .... 

    public function someLoggedMethod()
    {
        $logger = $this->loggerFactory->provide('simple');
        $logger->log( ... logged data .. );
        ....
    }
    ....
}

This factory will have two additional benefits:

  • it can ensure that only one instance is created without a need for global state
  • provide a seam for use when writing unit-tests

Note:
Actually, when written this way the class Foobar only depends on an instance that implements the Creator interface. Usually you will inject either a builder (if you need to type of instance, probably with some setting) or a factory (if you want to create different instance with same interface).

Next step would be implementation of the factory:

class LazyLoggerFactory implements Creator
{

    private $loggers = [];
    private $providers = [];

    public function addProvider($name, callable $provider)
    {
        $this->providers[$name] = $provider;
        return $this;
    }

    public function provide($name)
    {
        if (array_key_exists($name, $this->loggers) === false)
        {
            $this->loggers[$name] = call_user_func($this->providers[$name]);
        }
        return $this->loggers[$name];
    }

}

When you call $factory->provide('thing');, the factory looks up if the instance has already been created. If the search fails it creates a new instance.

Note: I am actually not entirely sure that this can be called "factory" since the instantiation is really encapsulated in the anonymous functions.

And the last step is actually wiring it all up with providers:

$config = include '/path/to/config/loggers.php';

$loggerFactory = new LazyLoggerFactory;
$loggerFactory->addProvider('simple', function() use ($config){
    $instance = new SimpleFileLogger($config['log_file']);
    return $instance;
});

/* 
$loggerFactory->addProvider('fake', function(){
    $instance = new NullLogger;
    return $instance;
});
*/

$test = new Foobar( $loggerFactory );

Of course to fully understand this approach you will have to know how closures work in PHP, but you will have to learn them anyway.

Implementing non-invasive logging

The core idea of this approach is that instead of injecting the logger, you put an existing instance in a container which acts as membrane between said instance and application. This membrane can then perform different tasks, one of those is logging.

class LogBrane
{
    protected $target = null;
    protected $logger = null;

    public function __construct( $target, Logger $logger )
    {
        $this->target = $target;
        $this->logger = $logger;
    }

    public function __call( $method, $arguments )
    {
        if ( method_exists( $this->target, $method ) === false )
        {
            // sometime you will want to log call of nonexistent method
        }

        try
        {
            $response = call_user_func_array( [$this->target, $method], 
                                              $arguments );

            // write log, if you want
            $this->logger->log(....);
        }
        catch (Exception $e)
        {
            // write log about exception 
            $this->logger->log(....);

            // and re-throw to not disrupt the behavior
            throw $e;
        }
    }
}

This class can also be used together with the above described lazy factory.

To use this structure, you simply do the following:

$instance = new Foobar;

$instance = new LogBrane( $instance, $logger );
$instance->someMethod();

At this point the container which wraps the instance becomes a fully functional replacement of the original. The rest of your application can handle it as if it is a simple object (pass around, call methods upon). And the wrapped instance itself is not aware that it is being logged.

And if at some point you decide to remove the logging then it can be done without rewriting the rest of your application.

Wednesday, August 3, 2022
5

Background: You asked for a "simple explanation" which suggests:

  1. You want a no-nonsense overview without jargon
  2. You want something that will help you learn from the beginning
  3. You have discovered that no two people ever answer the question the same way, and it's confusing. That's the reason you are here asking for a simple explanation. Yes?

Short No-Jargon Answer:

  1. Many introductory explanations jump quickly into "OOP real world" examples. Those can tend to confuse more than help, so feel free to ignore that for now.
  2. You can think of source code simply as "chunks" of functionality, that just happen to be saved to individual files.
  3. There are different ways of organizing those "chunks"; depending on things like conventions of the programming language, the background and training of the developer(s), or just plain old personal preference.
  4. OOP and Procedural programming are simply two main, generally-recognized methodologies, for how to organize and arrange those "chunks" of code.

Long No-Jargon Answer:

Procedural vs OOP is just one aspect of a fundamental issue of computer programming: how to make your code easy to understand and a piece of cake to professionally maintain. You can actually write "Procedural" code that follows some of the principles of OOP, so the two are not necessarily opposites.

Your understanding will really grow once you learn other object-oriented programming languages, among which, PHP is a "new kid on the block".

Here is a quick overview of what you will learn as you build experience:

  • You can write PHP source code that does useful tasks

  • You can organize useful tasks into "chunks" of code

  • You can think of "chunks" of code independently of the individual files where they are saved

  • Sometimes those "chunks" of code will behave differently based on parameters you pass in

  • Chunks of code that accept parameters are called "Functions"

  • Functions can be "chunked" together, and there are different ways of doing this:

    • For example: you could have just one big PHP file with all the functions you have ever written in your entire life, listed in alphabetical order by function name
    • For example: you could have multiple PHP files with functions that are chunked together by subject matter [e.g., functions for doing basic string manipulation, functions for processing arrays, functions for file input/output, etc]
  • OOP is a special way of "chunking" Functions together into a "Class"

  • A Class is just another level of "chunking" code together so that you can treat it as a unified whole

  • A Class can be thought of as a "chunking" of methods and properties

    • methods are simply functions that are logically related to one another in some meaningful way. The words "method" and "function" are basically two different terms for the same thing.
    • properties are simply data values that are related to the class. These are values that are intentionally non-isolated to any individual function, because more than one of the functions in the class should have access to them.
      • For example: if your class has a bunch of methods for doing astronomy, properties of the class might be the values for certain famous numbers that all astronomy methods need to know about (like Pi, the speed of light, the distance between specific planets, etc.).
    • This is where most OOP explanations get confusing because they branch off into "real world examples" which can quickly get off-topic. Often, "real world" is a euphemism for the ontological perspectives of a particular individual or group. That tends to be useful only once you already understand the concept well enough to teach it to someone else.
    • To understand OOP without confusion, you can skip the "real world" examples for now, and just focus on the code. A Class is simply a way to store functions (aka methods) and properties (aka data) as PHP code in one or more related "chunks" where each individual "chunk" deals with a specific topic or piece of functionality. That's all you need to know in order to get started.
  • A Class is useful because it allows you to organize your code at a very high level in a way that makes it easy for you to understand, use, and maintain.

  • When someone has written a lot of functions, and organized them into a lot of Classes, and gotten those to work together in some cool way, they package the whole thing together and call it a "Framework".

  • A Framework is just the next-highest level of "chunking" (including coding style and conventions) that one or more people agree on because they like the way the code is organized and it suits their working style, preferences, values, plans for world domination, etc.

See also

  • OOP appeal
Thursday, September 15, 2022
2

You could execute the php code and collect the output like this:

ob_start();
include "template.phtml";
$out1 = ob_get_clean();

http://www.php.net/manual/de/function.ob-get-contents.php

Thursday, November 10, 2022
 
2

In a lot of scenarios, procedural programming is just fine. Using OO for the sake of using it is useless, especially if you're just going to end up with POD objects (plain-old-data).

The power of OO comes mainly from inheritance and polymorphism. If you use classes, but never use either of those two concepts, you probably don't need to be using a class in the first place.

One of the nicest places IMO that OO shines in, is allowing you to get rid of switch-on-type code. Consider:

function drive($the_car){

    switch($the_car){

      case 'ferrari':
          $all_cars->run_ferrari_code();
          break;

      case 'mazerati':
          $all_cars->run_mazerati_code();
          break;

      case 'bentley':
          $all_cars->run_bentley_code();
          break;
    }
}

with its OO alternative:

function drive($the_car){

    $the_car->drive();
}

Polymorphism will allow the proper type of "driving" to happen, based on runtime information.


Notes on polymorphism:

The second example here has some premisses: That is that all car classes will either extend an abstract class or implement an interface.

Both allow you to force extending or implementing classes to define a specific function, such as drive(). This is very powerful as it allows you to drive() all cars without having to know which one you're driving; that is because they're extending an abstract class containing the drive() method or implementing an interface forcing the drive() method to be defined.

So as long as you make sure that all your specific cars either extend the abstract class car or implement an interface such as canBeDriven (both of which must declare the drive() method) you can just call the drive() method on an object which you know is a car (but not what type of car) without fear of it not being defined, as PHP will throw fatal errors at you until you define those methods in your specific car classes.

Thursday, September 1, 2022
 
3

So you use "com.novus" %% "salat" % "1.9.9" which depends on salat-util % 1.9.9, which depends on org.slf4j % slf4j-api % 1.7.2. Akka "com.typesafe.akka" %% "akka-slf4j" % "2.4.1" depends on 1.7.12. Make sure that you need the whole salat package. Probably you could find a way of using only part of it, or configure logging in different way, or wait for update from Salat developers.

Monday, September 12, 2022
 
jpspesh
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :