
Why a Logging Framework Should Not Log - phillord
http://www.russet.org.uk/blog/3112
======
paulmd
No, this guy is 100% wrong. The distinction is absolutely clear. If someone
else is expected to build on top of a framework he provides then he is
building a library, and it would be inappropriate to tie future consumers to a
back-end. This is relatively simple to fix - you mark his library as a
dependency and add an additional <exclude> for slf4j-nop to that dependency,
but it's not the right way to do this. Those messages aren't meant for him,
they're meant for future consumers, and now after including his library they
have to dig through and figure out why SLF4J suddenly broke.

I can't find exactly what this Tawny-OWL thing is (his link is broken), but it
sounds like it might have some kind of interpreter application provided. If he
is offering a finished application within the same build unit as his UI
library, this is the "big ball of mud" architecture. The fix is to break his
application out into a separate build unit that depends on his library. The
application can then include slf4j-nop without messing up SLF4J for all his
library's consumers. Maven Reactor can automatically build his dependencies
when his main application builds, if desired.

The thing about having to deal with 2 different logging frameworks is exactly
what SLF4J is meant to fix. SLF4J lets you route the messages from multiple
sources to a single sink/backend defined by the final consumer application.
Yeah, it sucks to deal with multiple logging frameworks, but that's simply how
it is and you don't help anything by having a whinge about it. You include the
routing/bridge modules (JUL-to-SLF4J, Commons-to-SLF4J, etc) in the final-
consumer application and call it a day. If you don't want to do that, find
some other dependency that doesn't use those logger frameworks.

~~~
phillord
My framework is built in Clojure. The interpreter is always there. Think of it
like a statistics library with R.

The idea of a "main application" is itself not a clear one. There are a set of
functions you can use. Whether you choose to launch directly over the library
or import it is a matter of convenience.

~~~
paulmd
I would say that the responsibility to define a backend falls either on the
Clojure interpreter or the user of the interpreter, then. Java is not
typically run on an interpreter shell and the defaults that normally are
sensible for Java probably aren't appropriate there.

It's a perfectly sensible default for a framework to warn you at runtime that
it's not configured properly, and stderr is the appropriate channel for that
to go out over. I would even go so far as to say that it should default to
stderr output on warn/error messages unless explicitly muted, instead of
merely switching to NOP output after the initial misconfiguration warning. An
interpreter might prefer another default but I think that's probably the best
default for most Java projects.

The way I see it here, Clojure built on top of a stack without understanding
the caveats that go with the stack. It's simply a fact that Java logging is a
mess, there are four major competing standards and you need to tell Java which
one you prefer, because any project will eventually need to interact with all
of them if it continues growing. It's a fact of building on that stack.

~~~
agentgt
The problem is that it is a Facade/API that is initializing and printing to
STDERR.

I completely disagree with the idea of making slf4j-nop a required exclusion
dependency (that is a I agree with @paulmd and not the OP) but a facade/api
should have very little interaction with the boot process of Java. The default
should be to do nothing and require either a java.util.ServiceLoader like
mechanism or some other explicit configuration (or in SL4J's case adding a
bridge).

That is slf4j-nop _behavior_ should be the default (not the dependency). I
understand that would confuse users but I think it would be the correct
behavior. (also see my other comment for more reasons)

------
theanomaly
Whoa. The author recommends including slf4j-nop as an explicit dependency of
libraries?! This means that when a developer writes an application and forgets
to include an slf4j backend, instead of getting a warning that no backend is
configured, that developer will get absolutely no output. This is really bad
advice.

~~~
phillord
It's ugly advice. Wish there was a better alternative. Ideas on a post-card
please.

~~~
devonkim
The JVM works this way after C programs popularized the precedent - refuse to
run at all with a dynamic link error stating the shared library that was
expected without any mention of what version of the library should be present
in the LD_LIBRARY_PATH or that the shared library version expected actually
doesn't match the ABI in some corner cases and silently fails during runtime.
That's part of why customizable classloaders in the JVM exist.

~~~
phillord2
That is not what is happening with SLF4J. The nop logger is present in the API
artifact. The API artifact chooses to warn when using it. The nop dependency,
despite the name, contains no logger at all. It just explicitly forces the
usage of the nop logger.

It's pretty clever actually, and works in a straight-forward way.

------
setheron
This is a well understood problem and I think the design pattern SLF4J uses
has become quite popular.

The ability to allow consumers to bind at runtime the implementation they want
by providing an api jar is kind of a neat idea.

The thing I dislike the most is when libraries force me to use their logging
runtime. If they need it just for tests they should add the 'test' scope and
just use the 'api' jar as a 'compile' dependency.

~~~
phillord
It doesn't force anything though, unless you statically refer to the runtime.
It does force you to exclude their logging backend, but that is straight-
forward enough.

~~~
duaneb
Dependency exclusions is a code smell everywhere but java. I don't understand
why someone is actively giving this advice.

~~~
phillord2
The alternative is _not_ specifying a nop dependencies and then inflicting on
downstream uses the choice that I was not prepared to make.

------
agentgt
My feeling is that SLF4J and in general the boot strap initialization is
fundamentally flawed in Java because of the plethora of libraries that do
static initialization. I have been meaning to blog about this for sometime.
The reason is simple. Bootstrap configuration should load before logging but
that is not the case.

In a large application it can be very difficult to control what boots up and
the overall order of things being initialized particularly with SL4J,
configuration frameworks like commons-configuration (archaius) and DI
frameworks like Spring and Guice.

I understand that SLF4Js binding method is a static dependency to enable the
very small case of Java JIT elision of method calls when using the NOOP
logger.

But that is such a small case. If you have a library that is extremely hi
performance do not use SLF4J for god sakes.

The reality is most large application projects should really create their
__own __SLF4J binding so that they can control the initialization process
particularly if you use a networking logging infrastructure such as AMQP. And
this is fairly annoying because you will have to copy and paste the bridge
library (ie because of the static nature it is impossibly to intercept the
logging framework).

SLF4J should really do its binding dynamically (one option would be to use
java.util.ServiceLoader). This is so that you could wrap and load existing
bridges with out essentially having to copy and paste existing bridges.

Overall I would say if you have either a hi performance library, networking
library, configuration library, or any other low level library please do not
link to SLF4J. It is trivial to make your __own __one class logger that either
uses system properties or what not to do its dispatching.

Some example libraries that do this correctly are the Kryo serializer,
RESTEasy, and Tomcat (although flawed Tomcat does try).

Spring and most Apache projects some of the worse because they rely on Commons
logging and the use static loggers on initialization (commons-config and most
of Spring).

------
drostie
Caches:

[https://web.archive.org/web/20160505153552/http://www.russet...](https://web.archive.org/web/20160505153552/http://www.russet.org.uk/blog/3112)

[http://webcache.googleusercontent.com/search?q=cache:9BEl9sv...](http://webcache.googleusercontent.com/search?q=cache:9BEl9svPfs0J:www.russet.org.uk/blog/3112&num=1&hl=en&gl=us&strip=1&vwsrc=0)

~~~
ninjakeyboard
ty!

------
chvid
I really wish Java developers would just use java.util.logging and be done
with it.

~~~
tadfisher
That's an all-or-nothing solution. As third-party libraries often use
alternative loggers, and java.util.logging cannot be substituted on the
classpath (because of the java.* namespace), then using JUL locks consumers
out of using their own logging facade as it cannot be bridged to SLF4J.

~~~
electrum
java.util.logging can be bridged into SLF4J using the jul-to-slf4j bridge.

------
xg15
I always found SLF4J's reasoning quite simple and logical and not at all an
example of "those situations which show that software is difficult, and that
often there are only compromises".

I think the core problem in this case is that the author is not clear if they
are writing a library or a (command line) application.

The following workflow has always worked well for me:

1) Do you expect users of your project to use it by putting it in their
classpath and calling its methods from their code? Then it's a library. You
have no idea what else will be on the classpath and which context your library
will be loaded in. (In fact, the user might not even be aware they are using
your library thanks to transitive dependencies)

Libraries should never (if they can avoid it in any way) modify application-
wide settings. So: Don't include any logger implementation at all,
_especially_ not the nop logger. (Please. Seriously. I've had to deal with
misbehaving libraries in that regard and finding out why your logger settings
are suddenly overwritten is a serious PITA.)

2) Do you expect your project to be used as a command line/standalone utility
via java -jar ... or java -cp ...? Then you're writing an application. _Do_
include a logger implementation and all necessary configuration. You can do it
safely here as no one else should be using your project as a dependency.

3) Your project is a mix of 1) and 2) : Find out which parts of your project
are library and which are application and split into sub-projects accordingly.

There are a few special cases I see, but not many:

4) You're writing unit tests for a library project: Do include a logger
implementation, but make sure it's only visible to the test system. E.g., in
Maven, you would add the logger implementation as a test-scoped dependency and
put all configuration in src/test/resources.

5) You're writing a plugin/servlet/etc: Read up on your host system and find
out if they provide logging or if it's your responsibillity. Proceed
accordingly.

6) You're not using SLF4J but one of your dependencies does and so you get
that message: Think about using SLF4J in your own project. Really. It's
becoming almost a standard and usually you can keep using your old logging
framework below it - but it will make things much easier later on. But if you
don't want to, for some reason... 6a) If you're writing a library: Don't do
anything at all. Do not include any logger implementation. See 1). The user of
your library will be in a much better position to deal with the problem than
you are. 6b) If you're writing an application: Include the slf4j-xxx
implementation that corresponds to your logging framework: The log events from
your dependency will be routed to the logging framework you're using and you
can continue like you did before. If you're very certain you don't want _any_
logging from _any library using SLF4J_ , use the nop logger - however that is
a very unusual case and a very inflexible solution.

~~~
phillord2
It's a set of clojure code that I expect people to use in an interactive shell
by, yes, calling methods. Whether people do that within an IDE, directly at a
REPL or by running a script is up to them.

I'm still not understanding why I should perform major surgery on my tool for
the sake of a logging library which is a transitive dependency.

Nor am I understanding why I should pass the error message that confused me on
to downstream users. I'm happy to make the decision rather than sit on the
fence.

~~~
xg15
My point is that "not passing on the error message" could make life harder for
downstream users too, depending on who those users are. (Sorry if my reply was
arrogant or harsh - I got burned by this in the past, that's why I'm telling
it)

If you're developing a project and use SLF4J by yourself, and use a third-
party-dependency that already includes a logger implementation, this
dependency can override your logging and cause all sorts of havoc. This can be
a frustrating and hard to find bug.

(I'd say the fact that this can happen at all is a design flaw in it's own,
partly caused by Maven's way of dependency resolution, partly by SLF4J. But I
don't see that changing in the near future...)

I think it's important to consider how your project will be used. if it's
really just for exploratory use in a REPL/a few scripts, then including a
logger is fine. If you think this project could be used in larger projects,
you should consider leaving it as it is or restructuring.

If you do include a logger, leave a note about it in the documentation. This
way, users can at least exclude it again if they have to.

------
puppetmaster3
The logging in java is supper idiotic. This is because everyone agrees that
Java's logging is shit.

So every library uses something else. I have a small project and have 4
logging systems (that I had to integrate).

ps: if you are a business person and have an option on this, I don't care for
it regardless of your pov.

~~~
paulmd
Yeah, it's the exact situation described in that XKCD: "There are 14 competing
standards! Let's create one that is perfect for everyone's use-case!" "There
are 15 competing standards..."

[https://xkcd.com/927/](https://xkcd.com/927/)

And, as you noticed, pretty much every Java project will reach a point where
it has to deal with all four-ish of the major logging frameworks. Some module,
somewhere, will include an Apache dependency and then you get JCL, and some
other purist will insist on java.util.logging and away you go.

With that said, it really isn't that bad to fix. You can use the SLF4J bridge
modules and dump all 4 of the logging systems to a single output system. This
doesn't actually have to be via SLF4J either - there are modules to dump SLF4J
out to JCL or JUL or whatever.

[http://www.slf4j.org/legacy.html](http://www.slf4j.org/legacy.html)

This is one of those things about Java that's annoying as hell to get set up,
but you plug through it for an hour and then you never need to touch it again.
And the next time you can crib from your working build and 99% of it will be
the same.

~~~
puppetmaster3
I doubt that you have used the SLF4J bridge in any realsitic situation, else
you would not say that it works.

~~~
paulmd
I have and it works fine.

~~~
puppetmaster3
I don't believe you.

Even the docs say it does not work w/ multiple loggers. But I'm not here to
teach, good luck.

~~~
paulmd
You're quite simply wrong - the docs do not say that anywhere.

What they do say is that you are not allowed to route loggers _circularly_ \-
if you route (for example) JUL through SLF4J, you are not allowed to route
that output back to JUL, otherwise messages would never be dumped to output.

~~~
puppetmaster3
That's why I said that you have not used it in anger in production
environments. Anyway, good luck.

------
azeirah
Archive.org cached version:
[https://web.archive.org/web/20160505153552/http://www.russet...](https://web.archive.org/web/20160505153552/http://www.russet.org.uk/blog/3112)

