Shortly after the release of Tawny-OWL 1.5.0 (http://www.russet.org.uk/blog/3110), I noticed a strange message being printed to screen, which looks like this:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further
details.

What strangeness is this? I had never heard of SLF4J before, and could not see why this was happening; so I googled around, and found out. The reason is that I had switched versions of the OWL API (http://dl.acm.org/citation.cfm?id=2019471), and this now uses the SLF4J API as a logging API. By default, it prints out this error message.

Slightly irritated by this, I tweeted

#slf4j I mean, who decided to print a message to tell me I am using a no-op logger. How is that no-op?

To which the author of SLF4J Ceki Gülcü kindly replied:

@phillord if you place slf4j-nop.jar on the class path the nop message should not appear.

But this seems unsatisfying to me. The reason for the message is that SLF4J is a facade, and needs a logging backend. It prints this error message when it cannot find one. The way to switch it off is to include the slf4j-nop dependency (which, ironically, does not contain the NOP logger — that’s in slf4j-api).

Normally, I would say that a library should not print to stderr in normal operation. But I can appreciate SLF4J logic here; it has to do something by default. For most libraries, my response would be “use a proper logger then!”, but, of course, SLF4J does not have that option — it cannot do this during bootstrap, and it either prints to stderr or just dumps all logs.

Okay, no worries, just include slf4j-nop in your project? Unfortunately, SLF4J say explicitly not to do this.

If you are packaging an application and you do not care about logging, then placing slf4j-nop.jar on the class path of your application will get rid of this warning message. Note that embedded components such as libraries or frameworks should not declare a dependency on any SLF4J binding but only depend on slf4j-api

Now, for a library/application/textual UI like Tawny-OWL (1303.0213), the distinction between “library” and “end-user” is not that clear, so this advice is not that useful. SLF4J justify this by saying:

When a library declares a compile-time dependency on a SLF4J binding, it imposes that binding on the end-user, thus negating SLF4J’s purpose.

Of course, imposing a binding of the end-user is a negative thing, but then is not forcing users to choose a binding also an imposition? It is made worse in my case, because I also get this message from ELK, another dependency of Tawny-OWL:

log4j:WARN No appenders could be found for logger
(org.semanticweb.elk.config.ConfigurationFactory).
log4j:WARN Please initialize the log4j system properly.

A similar error message for a similar reason. I now have to explicitly pacify the logging from two libraries in my project!

The curious thing about the whole situation is that everyone is behaving entirely reasonably. I can fully appreciate the logic of SLF4J, I can fully understand why the OWL API uses it, and I hope that others appreciate why I do not want print outs to stderr. It’s another one of those situations which show that software is difficult, and that often there are only compromises; whether something is a solution or a problem depends on where you sit.

For what it is worth, my own advice is that libraries and framework developers should always include the slf4j-nop if they use slf4j-api. If their end-users want logging, they can always use dependency exclusions to enable it.

I’d like to thank the members of the SLF4J users list, and Ceki for their helpful and polite responses to my questions. You can read the full thread on gmane. This article was, however, written without their endorsement.

  • Update

I have written a followup to this article.

Bibliography

19 Comments

  1. Ignazio says:

    I had a design problem with a simple solution once. Now I have two design problems and a legacy interface to maintain.

  2. verisimilidude says:

    The Java dependency hierarchy was an interesting idea that did not work in practice. Later languages do not seem to have problems like this.

  3. Daniel Lyons says:

    The problem with your advice is that when I begin to depend on a library that follows it, I have to create the exclusion or wonder why I’m not getting any logging. I may wind up with one of these nop-including libraries as a transitive dependency of something else that I actually care about, which may not have documented that I need the exclusion. The effect is that I add a dependency on your library and *poof* my logging disappears. Logging is often missin-critical; interfering with it nonchalantly is going to piss people off in a hurry.

    The right thing to do is know whether your main artifact is a library or some kind of executable, and only include slf4j-nop if it’s an executable. If you have something that wants to be both, well, you should split it into two Maven packages, one for the library which doesn’t include it and one for the executable which does. More packaging work for you, but that’s always been life with Maven. You’ll get fewer downstream users showing up with torches and pitchforks.

  4. Phillip Lord says:

    Actually, what you say is not quite true. If someone else includes a backend dependency, they will get an error message to stderr (that’s the behaviour of slf4j-api). But, yes, I know, including the nop dependency is a kludge. But leaving non-error messages to stderr is also a kludge, and I have no other way to fix it. I figure that someone who actively wants logging is in a better state of fix the logging system (i.e. to make it log) than someone who does not even know that there is a logging system there.

    Others have suggested this “library” and “executable” distinction, but it really makes no sense for my Tawny-OWL library. It’s a library, but it comes with a REPL because that’s part of Clojure. Besides, why should I enforce on downstream users of my code a choice that I am not prepared to make? That includes me, BTW. I have about 10 downstream projects, all of which would need “slf4j-nop” adding.

  5. Phillip Lord says:

    Ugly isn’t it?

  6. lmm says:

    > Others have suggested this “library” and “executable” distinction, but it really makes no sense for my Tawny-OWL library. It’s a library, but it comes with a REPL because that’s part of Clojure.

    It does make sense. If my application depends on 100 libraries should I be including 100 different REPL classes in my final jar? Put your REPL in its own maven module, it’s really not at all hard.

    > I have about 10 downstream projects, all of which would need “slf4j-nop” adding.

    So put the dependency in a parent pom that you use for executable projects. Having distinct parents (or distinct profiles) for executables and libraries is a good idea anyway, they are quite different and have different requirements.

  7. Phil Lord says:

    The REPL is a standard part of clojure. This is standard for any lisp. I can’t put the REPL in it’s own maven module. Have you developed in a REPL based language before?

    I could put the dependency into a parent pom. But then should the downstream projects use the version with the slf4j-nop or not? Because, they too can be used directly through a REPL, or as dependencies. Or do I make two versions of those as well? My solution — include nop and let people exclude it is far, far simpler.

  8. Mikael Ståldal says:

    Your advise is useless and will make life harder for me and a lot of application developers.

    Please organize your project properly instead of spreading bogus advice.

  9. lmm says:

    > The REPL is a standard part of clojure. This is standard for any lisp. I can’t put the REPL in it’s own maven module. Have you developed in a REPL based language before?

    Yes, I use Scala full-time. If you’re talking about a custom REPL class then that’s (necessarily) an executable and it belongs in its own module. If you’re talking about just using the library from a standard REPL, set up your *REPL* so that it always runs with slf4j-nop on its classpath, and then whatever libraries you load in it you’ll have the non-logging that you want.

    > I could put the dependency into a parent pom. But then should the downstream projects use the version with the slf4j-nop or not? Because, they too can be used directly through a REPL, or as dependencies.

    Again, everything should be split into library and application. Each module may be one or another. Any module that’s depended on by a module must be a library. Any module that contains a main() must be an application. (It follows that it’s entirely normal for an application module to consist *only* of a 2-or-3-line main() method; that’s fine). This is good design practice anyway.

    > My solution — include nop and let people exclude it is far, far simpler.

    Anything that requires users to do exclusions is extremely antisocial. So is silently suppressing the logging from completely unrelated libraries, which is what you’re doing by including that.

  10. Phil Lord says:

    Thanks. My project works fine for me, and expecting me to re-organise it for a upstream dependency seems unreasonable.

  11. Phil Lord says:

    Neither my project, nor downstream projects have main methods. All are end-user tools though. All could be used by downstream projects themselves.

    I agree exclusions are anti-social. I looked extensively for a better solution, including reading a significant part of the SLF4J code base, and discussing it extensively with the authors.

    We do have another solution, but that is a terrible kludge as well.

  12. lmm says:

    > Neither my project, nor downstream projects have main methods. All are end-user tools though. All could be used by downstream projects themselves.

    Then where is the thing that has the main method? To be executing on the JVM there has to be somewhere that has a main method. That somewhere, wherever it is, is the appropriate place to add the slf4j-nop (or slf4j-whatever) dependency.

  13. Phil Lord says:

    It depends how you launch it. I normally use leiningen within Emacs (i.e. it’s the build tool), but it would also work with boot or maven, or indeed, clojure itself if I turned it into a uberjar.

    So, yes, I could add slf4j-nop as a dev or REPL dependency (kind of equivalent to a maven test dependency). But, also, so would be downstream projects. Should they all have to find out about slf4j-nop also!

    The only clean solution is, AFAICT, to re-code slf4j-api so it does not print it’s start up error message. But they don’t want to (for understandable, but I think, incorrect reasons).

  14. Mikael Ståldal says:

    You can do whatever you want with your project. If you think it is appropriate to include slf4j-nop, then do so.

    But please do not give general advices about how other developers should do their projects based on particular (and uncommon) properties of your project.

  15. Daniel Lyons says:

    I think your advice leads to more harm than whatever is wrong about SLF4J logging when there is no backend driver.

    It may be the case that SLF4J and log4j are predicated on Maven which is itself predicated on there being a straightforward distinction between libraries and applications, and that may not hold in Clojure-land. Furthermore, SLF4J’s mechanism of operation is complex; on at least three occasions I’ve had to mask a library’s dependency on slf4j-log4j due to the upstream not understanding it. But I don’t think these facts excuse you. This advice is bad engineering practice, and to follow it as a form of protest would be very shortsighted.

    It is antisocial, because you’re both ignoring what the SLF4J project dictates and because you’re creating more work for your downstream dependents. Worse, if your project (or your advice) becomes popular, it will be difficult to know a priori whether there is something there to be masked—you’ll just have to notice that after adding some dependency your project mysteriously stopped logging. This response seems out of proportion to the trivial problem of a few ugly lines of output.

    I’m actually a lot more worried about other people following this advice on your example than you doing it. I’m probably never going to use your library. You have reasons for making this decision on this project. But if other people do as you do without thinking, it’s going to create a lot of harm.

  16. lmm says:

    > So, yes, I could add slf4j-nop as a dev or REPL dependency (kind of equivalent to a maven test dependency). But, also, so would be downstream projects. Should they all have to find out about slf4j-nop also!

    No, whoever’s setting up something that they ship as a REPL (leiningen or whatever) should find out about slf4j and set up an slf4j config that’s appropriate to the operation of a REPL (whether that be nop, directing logs to some kind of logging window, or whatever’s appropriate for that REPL). The REPL is the thing that’s in the position to know what kind of logging is appropriate.

  17. Phillip Lord says:

    I’m creating less work for my downstream dependents, not more, since they no longer need to include the nop dependency. I am not *ignoring* what the SLF4J suggest. In fact, I talked to them extensively. I am discounting it, even if I respect their decision.

    I agree, that people should not follow my advice with thinking about it. I’d hope that people think about most things, and one of those is the consequences of including a logging library which by prints an error message by default. It’s not a big consequence, of course. But, then, I think it will happen to a lot of people.

  18. Phillip Lord says:

    Why is it everyone elses decision? Besides, I think you are contradicting the logic you gave earlier. I have also embedded my library (and in turn the REPL) in an application! If the REPL includes slf4j-nop they will now turn off the logging for the application. Solution: I will exclude the nop dependency when embedding tawny-owl in other applications.

  19. lmm says:

    > I’m creating less work for my downstream dependents, not more, since they no longer need to include the nop dependency.

    But most downstream dependencies don’t want nop logging. You’re creating a lot more work for them since they have to add an exclusion, or more likely spend hours debugging why their program isn’t working because someone’s silently turned off logging so they don’t see some important error messages from one of the libraries they’re using.

    > Besides, I think you are contradicting the logic you gave earlier. I have also embedded my library (and in turn the REPL) in an application!

    FFS. So *split the REPL into library and application*, and use the library piece when embedding. This isn’t hard.

    > If the REPL includes slf4j-nop they will now turn off the logging for the application. Solution: I will exclude the nop dependency when embedding tawny-owl in other applications.

    If you know it’s common to not want the nop dependency then you really shouldn’t add it by default.

Leave a Reply