13

I try to build and application based on Java.

For dependency injection I use Google Guice.

Now I came up with the problem of logging some information during the application. I do not talk about general logging in a way of method calls etc. I know about AOP and that I can do like method call tracing etc. with that.

What I look for is manual logging. I need some way of logging in nearly each class in my application. So I thought about two options:

  1. getting the logger by using the Guice injection framework doing this for me through the constructor (or setter or private ...) but it feels like adding the logging concern really to each class and pollutes my constructor
  2. using a global service locator in the method where I want to call the log. Uhh but all DI fans will hate me for doing that

So what is the best way from a practical point of view?

Steven
  • 166,672
  • 24
  • 332
  • 435
SimFirehawk
  • 157
  • 1
  • 9
  • What kind of information do you log when you do 'manual logging'? – Steven Jul 12 '13 at 11:33
  • 2
    All logging frameworks I know use a singleton factory for concrete logger instances. If you want to be flexible w.r.t the logger implementation you can use a logging facade like slf4j. – Pyranja Jul 12 '13 at 11:38
  • 2
    I disagree with @Pyranja. If you want more flexibility, define your own `ILogger` abstraction (one that contains just one member), hide your logging framework behind that abstraction and inject that interface into the classes that need it. That allows you to unit test that code properly and hides a third party dependency. Take a look at [this answer](http://stackoverflow.com/questions/5646820/logger-wrapper-best-practice). – Steven Jul 12 '13 at 11:54
  • 1
    I think we have to agree to disagree. In my opinion, logging is such a generic task, that using a custom abstraction does not add considerable benefit. Established logging facades like slf4j already offer such an abstraction (which could also be injected if one dislikes static factories). Unless there are specific requirements, I expect a custom logger interface to be so similar to the ones already available, that they are interchangeable. And the heavy lifting of connecting the facade to concrete logging implementations has already been done! – Pyranja Jul 12 '13 at 12:09
  • 1
    @Steven: debug information, for instance file parsing (from which file). But as more as I think about I realize that some of it collides with SRP – SimFirehawk Jul 12 '13 at 12:48

2 Answers2

24

I need some way of logging in nearly each class in my application.

Think again. If you think you need logging in nearly every class, your design might be sub optimal and might cause maintenance issues in the long run. This Stack Overflow answer talks about what's the issue and how to improve it. It's answered in the context of .NET, but the answer is applicable to Java as well.

That answer mainly talks about exception logging, for non-exception logging I would say: Prevent logging too much information at too many places. For each info or warning that you want to log, question whether this shouldn't have been an exception in the first place. For instance, don't log things like "we shouldn't be in this branch", but throw an exception!

And even when you want to log debug information, does anyone ever going to read this? You'll end up with log files with thousands and thousands of lines that nobody ever reads. And if they read it, they have to wade through all those lines of text and do complicated regex searches through it to get the information they were looking for.

Another reason I see developers do this is to cover up for their used coding practices. Just as comments are used in this way. I see developers log things like "we have executed this block" or "this if branch skipped". This way they can trace through the code and big methods.

However, instead of writing big methods, we all know by now that methods should be small. No, even smaller. Besides, if you unit test your code thoroughly, there is not much reason to debug the code and you have verified that it does what it is supposed to do.

And again good design can help here. When you use a design as described in that Stack Overflow answer (with command handlers), you can again create a single decorator that can serialize any arbitrary command message and log it to disk before the execution starts. This gives you an amazingly accurate log. Just add some context information (such as execution time and user name) to the log and you have an audit trail that could even be used to replay commands during debugging or even load testing.

I use this type of application design for a couple of years now, and since then, I hardly ever have any reason to do extra logging within the business logic. It is needed now and then, but those cases are pretty rare.

but it feels like adding the logging concern really to each class and pollutes my constructor

It does, and you'll end up with constructors with too many parameters. But don't blame the logger, blame your code. You are violating the Single Responsibility Principle here. You can 'hide' this dependency by calling it through a static facade, but that doesn't lower the number of dependencies and overall complexity of a class.

using a global service locator in the method where I want to call the log. Uhh but all DI fans will hate me for doing that

In the end, you will hate yourself for that, because every class is still has an extra dependency (a well hidden dependency in this case). This makes each class more complicated, and will force you to have more code: more code to test, more code to have bugs, more code to maintain.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    Thank you very much for the link. It helped a lot to understand the design problem I have. So I understand for exceptions it does not make sense and it is much better with a decorator. For logging debug information, there is still the question from above. – SimFirehawk Jul 12 '13 at 12:44
  • 1
    @SimFirehawk, while engineers may not read through the logs, other systems may process them so as to make more accessible the information contained in the logs. In such environments, it makes sense to write unit tests for the more important logging (eg to ensure that something is logged under specific scenarios). Such tests may need to double out the logger. – Noel Yap Dec 15 '16 at 00:47
0

The topic of logging and how one should go about it is actually a more complex topic than one might at first think.

As with many questions, the answer to how one should approach logging is "It depends". There are certainly some use cases which can be mitigated without the need of components taking on a logging dependency. For example, a need to uniformly log all method calls within a library can be addressed with the Decorator Pattern and superfluous uses of logging exceptions can be addressed by centralizing such logging at the top of a call stack. Such use cases are important to consider, but they don't speak to the essence of the question which really is "When we would like to add detailed logging to a component in strongly-typed languages such as Java and C#, should the dependency be expressed through the component's constructor?"

Use of the Service Locator pattern is considered to be an anti-pattern due to the fact that it's misuse leads to opaque dependencies. That is to say, a component which obtains all its dependencies through a Service Locator doesn't express everything that's needed without knowledge of the internal implementation details. Avoiding the Service Locator pattern is a good rule-of-thumb, but adherents of this rule should understand the when and why so as not to fall into the cargo cult trap.

The goal of avoiding the Service Locator pattern is ultimately to make components easier to use. When constructing a component, we don't want consumers to guess at what is needed for the component to function as expected. Developers using our libraries shouldn't have to look at the implementation details to understand which dependencies are needed for the component to function. In many cases, however, logging is an ancillary and optional concern and serves only to provide tracing information for library maintainers to diagnose issues or to keep an audit log of usage details for which the consumers are neither aware or interested. In cases where consumers of your library must provide dependencies not needed for the primary function of the component, expressing such dependencies as invariant (i.e. constructor parameters) actually negates the very goal sought by avoiding the Service Locator pattern. Additionally, due to the fact that logging is a cross-cutting concern (meaning such needs may be widely desired across many components within a library), injecting logging dependencies through the constructor further amplifies usage difficulty.

Still another consideration is minimizing changes to a library's surface-area API. The surface-area API of your library is any public interfaces or classes required for construction. It's often the case that libraries are constructed through a DI container, particularly for internally-maintained libraries not meant for public consumption. In such cases, registration modules may be supplied by the library for specific DI containers or techniques such as convention-based registration may be employed which hide the top level types, but that doesn't change the fact that they are still part of the surface-area API. A library which can easily be used with a DI container, but can also be used without one is better than one which must be used with a DI container. Even with a DI container, it's often the case with complex libraries to reference implementation types directly for custom registration purposes. If a strategy of injecting optional dependencies is employed, the public interface is changed each time a developer wants to add logging to a new type.

A better approach is to follow the pattern established by most logging libraries such as Serilog, log4net, NLog, etc. and obtain loggers through a logger factory (e.g. Log.ForContext<MyClass>();). This has other benefits as well, such as utilizing filtering capabilities by each respective library. For more discussion on this topic, see this article.

Derek Greer
  • 15,454
  • 5
  • 45
  • 52