What is the best way to inject a logger?

I’m working on several projects right now that I’d like to be able to generate logs from.

The problem is that logging isn’t required for the code to work, so how do I configure it?

I see three possibilities:

Required in Constructor

Pros

  • Cleanest implementation

Cons

  • Requires user to pass in a logger even if they don’t want to log anything

Optional in Constructor

Pros

  • No longer requires the user to pass in a logger

Cons

  • Clutters up the constructor
  • I consider using null a code smell

Optional setLogger Method

Pros

  • Clean constructor
  • No nulls

Cons

  • Instantiating with a logger requires two statements

Summary

I’m leaning toward the third method for my packages even though it doesn’t feel like an ideal solution. It just feels better than the other two.

How are you handling injecting an optional logger? Let me know in the comments.

Author: Andrew Shell

Madison, WI developer, Co-Founder and CTO of Pinpoint Software, founder of Madison PHP.

23 thoughts on “What is the best way to inject a logger?”

    1. That certainly looks interesting. I’d be nervous about using it as part of a public library. Could certainly work as part of my application assuming it didn’t produce a huge performance hit.

    1. It doesn’t seem to be possible to have a new statement as a default value in the method parameters.

      PHP Fatal error: Constant expression contains invalid operations

    1. How many loggers are you willing to add? Iterating over a larger number of loggers will increase the number of calls to the logging method, also increase the number of log messages that need to be created. Having multiple back ends for logging probably should be the task of the one logger to be injected.

        1. While I do use Monolog, I don’t like to rely on proprietary features – it makes it harder to switch to a different logging framework, and the freedom to easily migrate to a different implementation is part of the value proposal of using PSR-3 in the first place.

          Either way, what I proposed is an alternative to setter-injection, and has two important advantages over it: avoiding the null-check, and avoiding potential side-effects from accidentally overriding an existing logger. I will never personally get to “a large number of loggers” – this concern seems like a bit of a stretch? But I work in a large team, and I’d like to avoid breaking the logging-features by accidentally overwriting somebody else’s logger, or vice-versa. Works for me 🙂

    1. Nice and simple, although not really my style. I try to stay away from global static methods. It’s nice that it’s just a facade so you can easily swap out what is being returned. Similar to what they do in Laravel.

      1. I would also stay away from global static methods if I can, but not in this case 😉 To me a logger is not a dependency but rather part of your infrastructure. Thus I would not inject it in my classes.

  1. What about Getter Injection?

    http://symfony.com/blog/new-in-symfony-3-3-getter-injection

    A similar solution would be this facade function (in combination with a dependency injection container). Example:

    function logger()
    {
    $logger = container()->get(Logger::class);
    if (!$logger) {
    $logger = new Logger(‘app’);
    $logFile = config()->get(‘log_file’);
    $handler = new RotatingFileHandler($logFile, 0, Logger::ERROR, true, 0775);
    $logger->pushHandler($handler);
    container()->set(Logger::class, $logger);
    }
    return $logger;
    }

    Usage:

    logger()->error(‘My error message);

    There are pros and cons, of course.

  2. I guess I don’t understand the problem you’re trying to solve. In case #1 you have the minimum code and if you don’t what to log anything you pass a NullLogger instance. Your con is solved. For case #2 and case #3 you add a punch of code/complexity for no real benefit. In both cases it clutters the creation of the class.

    However, why is the developer making the choice whether or not the class should be logging. The is a runtime choice and therefore should be a configuration file so it can adjusted by the end-user.

      1. Of course, so initialize $this->logger = new NullLogger() in your constructor.

        Or use an array like I suggested – using foreach eliminates the need to check if a logger was added.

        Honestly, my personal preference these days is to just use constructor-injection, even for optional dependencies like this one – you can make the $logger constructor argument default to null, and initialize with $this->logger = $logger ?: new NullLogger() in the constructor.

        IMO, that’s the simplest, most obvious solution – treat the logger like you would treat any other dependency; in my general experience, regarding any component as “infrastructure” (or any other special status) leads only to less obvious or more complex solutions. (That said, I do tend to use the addLogger() approach, since, at the moment, I don’t know of a good PSR-3 filtering/aggregation library and, as said, would prefer not to rely on e.g. Monolog handlers or other proprietary architecture.)

        1. I didn’t mean create one of their own. Just they need to instantiate and inject a library that literally doesn’t do anything. I’d rather have the class take care of that.

  3. As a developer that consumes libraries, I would would like to wrap MyClass with MyClassLogger. Then I can handle logging on my terms. I see two problems with this, but i think it will keep the library code cleaner and concise:

    1) I don’t have fine grained control of logging at very specific points in the code, only before or after calls.
    2) private methods would not be available to wrap, easily fixed by marking all private methods as protected.

    That being said, within my own apps I have borrowed Laravel’s static proxy idea and actually use it for logging when I need it for diagnostics, but don’t want to inject the logger just for that.

  4. I think the simplest way to go is to create a setter and do a simple if statement to check if there is a logger before logging (you can create a private log method that wraps the if statement to avoid putting if’s all over your code or even create a trait for that), as the logger is not necessary to make things work. I use the constructor only for things that are really necessary for a class to work. I would do the same for something like a EventManager, that it is optional.

Leave a Reply