Which pattern to use for logging? Dependency Injection or Service Locator?

26,392

Solution 1

I personally do a mixture of both.

Here are my conventions:

  • From a static context - Service Location
  • From an instance context - Dependency Injection

I feel this gives me the right balance of testability. I find it a little harder to setup tests against classes that use Service Location than use DI, so this is why Service Location ends up being the exception rather than the rule. I'm consistent in its use, though, so it's not hard to remember what type of test I need to write.

Some have raised the concern that DI tends to clutter constructors. I don't feel this is a problem, but if you feel this way, there are a number of alternatives that use DI, but avoid constructor parameters. Here is a list of Ninject's DI methods: http://ninject.codeplex.com/wikipage?title=Injection%20Patterns

You'll find that most Inversion of Control containers have the same features as Ninject. I chose to show Ninject because they have the most concise samples.

Hopefully this is helpful.

Edit: To be clear, I use Unity and Common Service Locator. I have a singleton instance of my Unity container for DI and my implementation of IServiceLocator is simply a wrapper around that singleton Unity container. This way I don't have to do any type mappings twice or anything like that.

I also don't find AOP to be particularly helpful beyond tracing. I like manual logging better simply for its clarity. I know that most AOP logging frameworks are capable of both, but I don't need the former (AOP's bread and butter) most of the time. This is just personal preference, of course.

Solution 2

The logger is clearly a service that your business logic depends upon, and should thus be treated as a dependency the same way you do with IDependency. Inject the logger in your constructor.

Note: even though AOP is mentioned as the way to inject logging I do not agree that it is the solution in this case. AOP works great for execution tracing, but will never be a solution for logging as part of business logic.

Solution 3

Maybe this will be little offtopic, but why do we need injecting logger at all, when we can just type at the beggining of the class:

Logger logger = LogManager.GetLogger("MyClassName");

Logger doesn't change during development and later during maintenance. Modern loggers are highly customizable, so argument

what if I want to replace text logger with database?

is missed.

I don't negate using dependency injection, I'm just curious about your mind.

Solution 4

My little rule of thumb:

  • If it's in a class library, use either constructor injection or property injection with a null-object pattern.

  • If it's in a main application, use the service locator (or singleton).

I find this applies pretty well when using log4net. You don't want class libraries reaching out to things that might not be there, but in an application program, you know that the logger is going to be there, and libraries like log4net are based heavily around the service-location pattern.

I tend to think of logging as something sufficiently static that it doesn't really need DI. It's extremely unlikely that I'll ever change the logging implementation in an application, especially since every logging framework out there is incredibly flexible and easy to extend. It's more important in class libraries when your library might need to be used by several applications which already use different loggers.

YMMV, of course. DI is great but that doesn't mean everything needs to be DI'ed.

Solution 5

We switched all our Logging/Tracing to PostSharp (AOP framework) attributes. All you need to do to create logging for a method is add the attribute to it.

Benefits:

  • Easy use of AOP
  • Clear separation of concerns
  • Happens at compile time -> Minimal performance impact

Check out this.

Share:
26,392
CodingInsomnia
Author by

CodingInsomnia

Updated on September 13, 2020

Comments

  • CodingInsomnia
    CodingInsomnia over 3 years

    Consider this scenario. I have some business logic that now and then will be required to write to a log.

    interface ILogger
    {
        void Log(string stuff);
    }
    
    interface IDependency
    {
        string GetInfo();
    }
    
    class MyBusinessObject
    {
        private IDependency _dependency;
    
        public MyBusinessObject(IDependency dependency)
        {
            _dependency = dependency;
        }
    
        public string DoSomething(string input)
        {
            // Process input
            var info = _dependency.GetInfo();
            var intermediateResult = PerformInterestingStuff(input, info);
    
            if (intermediateResult== "SomethingWeNeedToLog")
            {
                // How do I get to the ILogger-interface?
            }
    
            var result = PerformSomethingElse(intermediateResult);
    
            return result;
        }
    }
    

    How would you get the ILogger interface? I see two main possibilities;

    1. Pass it using Dependency Injection on the constructor.
    2. Get it via a singleton Service Locator.

    Which method would you prefer, and why? Or is there an even better pattern?

    Update: Note that I don't need to log ALL method calls. I only want to log a few (rare) events that may or may not occur within my method.

  • CodingInsomnia
    CodingInsomnia about 14 years
    Well, unfortunately I cannot use a simple AOP approach here. I don't want to log ALL method calls, only some events that may or may not happen inside the method.
  • Admin
    Admin about 14 years
    DI != cluttered constructors.
  • M4N
    M4N about 14 years
    @Will: can you please explain?
  • CodingInsomnia
    CodingInsomnia about 14 years
    @Will The downvote seems a bit harsh. If using constructor injection the ILogger interface does indeed "clutter" the constructor with a something that isn't really a core dependency.
  • CodingInsomnia
    CodingInsomnia about 14 years
    Well, yes - but only when you need to log all calls (I do that too already). But in this case, I need to log things that only happen occasionally (event logging rather than call logging if you see what I mean).
  • CodingInsomnia
    CodingInsomnia about 14 years
    Kind of interesting solution, but it seems like a bit too much coding for a relatively simple task. It also only allows for logging things based on the result of the method (I realize that my example does exactly that though..)
  • CodingInsomnia
    CodingInsomnia about 14 years
    +1 one for a solution that allows me to unit test MyBusinessObject without having to mock the ILogger.
  • M4N
    M4N about 14 years
    But using this pattern, you can only log before or after calling the base class' method. You will still not be able to log in the middle of methods.
  • James
    James about 14 years
    @Martin, fair point, however based on the example provided the logging only happens based on the result of the DoSomething so the above solution would fit that nicely.
  • Anderson Imes
    Anderson Imes about 14 years
    @andlju: I'm guessing @Will is referring to the fact (rightly so) that DI doesn't necessarily require contructor parameters. DI that involves setting a property or another method is just as valid. Here is a list of Ninject's DI methods: ninject.codeplex.com/wikipage?title=Injection%20Patterns
  • Peter Lillevold
    Peter Lillevold about 14 years
    The logger is clearly a service that the code depends on. Why treat it differently than other dependencies? AOP doesn't work in this case.
  • Peter Lillevold
    Peter Lillevold about 14 years
    -1, because: 1) Singleton is an anti-pattern. 2) clutter the constructor? explain yourself! 3) AOP does not work with logging as part of business logic.
  • duffymo
    duffymo about 14 years
    IF it's occasionally done, I'd say that DI would be overkill as well. I'd have a static class member for Logger and be done with it.
  • CodingInsomnia
    CodingInsomnia about 14 years
    Thank you, this aligns quite well with my own feelings!
  • Mauricio Scheffer
    Mauricio Scheffer almost 14 years
    -1, no need to have the logger in the constructor, you can have it (and most people do so) as an optional dependency (property).
  • Thorkil Holm-Jacobsen
    Thorkil Holm-Jacobsen about 7 years
    The logger is clearly a service that your business logic depends upon - I disagree with this. Your business logic should in no way depend on logging, and your application should work just as well without it. Your debugging sessions, however...
  • Peter Lillevold
    Peter Lillevold about 7 years
    @ThorkilHolm-Jacobsen in the OP's case it is: business logic that now and then will be required to write to a log . I agree that it is not always the case. But I do not agree with your business logic should in no way depend on logging . I have had such business requirements on several occasions.
  • Jepzen
    Jepzen over 6 years
    Totally agree. Example, log4net can be configured to log to basically anything and you can also write your own adapter. And in this way you know which logger is used that can be hard to figure out when injected
  • Shiljo Paulson
    Shiljo Paulson about 6 years
    Change field/variable _logger to readyonly so that new instance can't be created inside LoggableBusinessObject
  • Dan
    Dan over 2 years
    I agree. I believe for the vast majority of applications, dependency injection for logging is overkill. Usually, logging frameworks can be configured where to log to (and more) which gives them the flexibility you want. Plus, I have never seen the the need to mock logging functionality for tests . When you want for example change the loglevel by namespace in the configuration, DI will even make even your life more complicated. How would the logging framework know in which namespace it is? (There are solutions but I don't find them elegant)
  • Dan
    Dan over 2 years
    However, where I would strongly recommend to use DI when you are creating a Library. This is the one common use case for DI and logging