Daniel Kulp wrote:
[...]
How is this for an idea/comprimise:
For all logging, we use JDK Logger objects. However, instead of calling
Logger.getLogger(....), we create a utility class and call:
LoggerFactory.getInstance().getLogger(.....). (several varieties of
getLogger calls). The default factory just passes them on to JDK Logger
getLogger calls. However, if you want, you can replace the
LoggerFactory with one that will wrapper log4j Loggers or whatever else
you want. The JDK Logger object is not final (or have any final
methods) so we can use it as the API for logging, the underlying logging
can be just about anything. I COULD be convinced to do something in
similar in Celtix as this would JUST affect the Logger creation
mechanisms, not any of the actual logging.
What are peoples thoughts about that?
Daniel, sorry for not responding earlier (moved to a new house). First
of all, thanks for trying to find a consensus.
My first reaction to the above suggestion was "If it was that simple,
then why are people still messing around with jakarta commons-logging,
slf4j and friends?" But I wanted to try it out and started to create a
prototype of a j.u.l.Logger subclass that delegates to a corresponding
log4j Logger. Here are my observations so far:
* j.u.l.Logger.getLogger() hardcodes "new Logger()" for logger
creation. We absolutely *must* use our LoggerFactory everywhere or
things will break. This means that there is a high probability for
errors, because the proposed design deviates from normal usage of
the API.
* If we want to work around that issue, we could implement our own
LogManager as well. Unfortunately the actual instance is
controlled by a System property that is evaluated only in the
static initializer of the LogManager class. This practically means
it's impossible to control from our code, only from the command
line, because some low level JDK class might already have logged
something to j.u.l before our configuration code even runs.
* I couldn't find a solution that handles j.u.l specific Levels like
Level.CONFIG which do not exist in log4j. Same with user defined
levels.
* The combination of getLevel() and setLevel() is hard to implement.
I think they should delegate to the log4j logger, but then
setLevel() followed by getLevel() would not always return the same
result because FINER and FINEST are both mapped to the log4j TRACE
level.
* The implementation of addHandler() is unclear, probably that
operation should not be supported. Delegating to log4j means that
j.u.l Handlers will be ignored. This violates the API contract of
j.u.l.Logger - logging messages are no longer forwarded to
registered Handlers instead they are forwarded to log4j.
There are probably many more points in the same spirit. To summarize,
the design does not follow the substitution principle: the subclass is
not a replacement for j.u.l.Logger because many methods of j.u.l.Logger
do not make sense for a log4j backend. The only good news is that I
managed to get some log messages out of log4j.
To me, the code of the j.u.l.Logger subclass seems like a fragile hack.
Yes, I could probably bridge to log4j and get some yoko logging output,
but the code will continue to work only if the yoko dev team is
extremely disciplined and it is very clear which parts of j.u.l must not
be used.
I've also taken a look at j.u.l.Logging package itself. Let me say it
politely: I'm still not convinced.
* throwing() encourages an idiom very similar to the "log and throw"
style that is discouraged by our coding guidelines
* Naming is horrible. Quick, what's the purpose of Logger.logrb()?
* The implementation quality is rather low. Example: Level.equals()
throws a ClassCastException in the normal code path.
I still think that using our own interface would be the best solution.
No external dependencies and maximum flexibility. I could live with
slf4j, but it seems that j.u.l is not a viable option for those of us
who need to integrate with log4j, even when using a LoggerFactory as
suggested above.
Regards, and thanks for reading this rather lengthy email,
Lars