[slf4j-dev] Re: Beta 4 and the new method signatures
Greg Wilkins
gregw at mortbay.com
Fri Jul 8 09:44:55 CEST 2005
Niclas Hedhman wrote:
> public interface Logger
> {
> String getName();
>
> boolean isEnabled();
> void log( String message );
> void log( Throwable throwable );
> void log( String message, Throwable throwable );
> void log( String format, Object arg );
> void log( String format, Object arg1, Object arg2 );
>
> Logger addChildLogger( String name );
> }
Niclas,
A revolutionary idea.... but before we man the baracades let's
just review how we got here.....
I like the current API. It's not perfect, but simple and usable
and with the help of a LogSupport class I can get the trace, verbose
and ignore behaviour that I like.
If there was no change to the API - I'd be happy.
If the ignore use-case was better met - I think the world would be better.
If the trace use-case was better met - I think more people would switch from commons.
But I don't see any of these things as drivers for significant change
to the API.
However in discussing solutions to these use-cases it does become
clear that the current "fat" interface with methods for a fix set
of log levels is:
- not a flexible design
- not a one-size-fits-all API
+ very familiar to 95% of users
+ easy to use and understand.
+ meets the majority of use-cases.
If we simply added trace, verbose and/or ignore log levels, we would not
fundamentally change any of this. We would meet a few more use-cases at
the small risk of "fattening" the API and increasing the risk of poor usage.
There have been a few other suggestions to meet these log levels:
* Use nested/custom Loggers.
This has always been an options, but I think the current "fat" API
works against this. If I create a Logger for "ignore" or "verbose", then
what are all the warn, error, info methods for?
I don't think this is workable - which is a pity as I have extra
use-cases (see below).
* The Marker API.
This ideas allows arbitrary markers to be passed with log. This greatly
increases the flexibility of the API while working within the current
log levels fixed in the API. It is a good addition, but is really
aimed at advanced users and will not directly assist the ignore and trace
use-cases. This is definitely workable solution.
Now Niclas has proposed a completely different API. I'm not sure
the ignore/verbose/trace use-cases warrant such a huge change. But
you have to ask yourself - since all API discussions get bogged down
on number and type of log levels - wouldn't it be great to avoid them?!?!?
So I'd like to explore Niclas's simple Logger idea a little bit.
Specially as I have a number of extra use cases that could really
use a thin logger API:
+ Logging the requests in Jetty. I should be able to
use the logging mechanism for rolling over files etc.etc.
but I don't want to create a logger with warn, error, fatal,etc
because none of those makes sense for a request log.
+ Creating a log for the servlet context, which has a log API
in it. Jetty can make a context log and route servlet logs to
it and defer the decision of what to do with it to the logging
configuration. We already do this a bit, but again it makes
little sense to have an API with warn, error, fatal etc.
This reveals that the current API works against creating
custom loggers because the lack of a generic log method and
the existence of the warn, error, info etc. methods do not
make sense for 99% of custom loggers.
So there is a use for a very simple logger like:
public interface Log
{
boolean isEnabled();
void log( String message );
void log( Throwable throwable );
void log( String message, Throwable throwable );
void log( String format, Object arg );
void log( String format, Object arg1, Object arg2 );
}
I think it even makes sense for traditional debug, error, warn
type logging. The reason for this is that I think that class
scoped Loggers only really make sense for debug.
The fact that warn, error and info are given same scope as
debug is just an artefact of the "fat" API. For warn, error and
info package scope or application scope loggers make
a lot more sense. With a thin logger API you could do:
package com.acme;
public interface Logs
{
final static Log warn = LogFactory.getLogger(Loggers.class,"warn");
final static Log error = LogFactory.getLogger(Loggers.class,"error");
final static Log info = LogFactory.getLogger(Loggers.class,"info");
final static Log ignore = LogFactory.getLogger(Loggers.class,"ignore");
}
package com.acme;
public class MyClass implements Logs
{
final static Log debug = LogFactory.getLog(MyClass.class,"debug");
final static Log requests = LogFactory.getLog("requestlog");
public void myMethod()
{
debug.log("I can see my house from here");
try
{
// ...
info.log("doing something now");
// ...
requests.log(httpRequest.toNCSA());
}
catch(Exception e)
{
error.log("Shit happens ",e);
}
}
}
To me, that is a very workable solution. But the problem is that
it is a big step away from the current API and would make porting
difficult.
So my final thought for this Marathon email - what if we did EVERYTHING?
Added trace/ignore! Added Markers1 Added thin logs!
Then we might end up with the current Logger API being a facade over
a thin Log API like:
public interface Marker
{ ... }
/* core log interface */
public interface Log
{
boolean isEnabled();
void log( String message );
void log( Throwable throwable );
void log( String message, Throwable throwable );
void log( String format, Object arg );
void log( String format, Object arg1, Object arg2 );
}
/* conveniance log interface */
public interface Logger
{
/* top level markers - may have nested marker defined */
public final static Marker ERROR = Marker.getMarker("ERROR");
public final static Marker WARN = Marker.getMarker("WARN");
public final static Marker INFO = Marker.getMarker("INFO");
public final static Marker DEBUG = Marker.getMarker("DEBUG");
public final static Marker TRACE = Marker.getMarker("TRACE");
public final static Marker IGNORE= Marker.getMarker("IGNORE");
public Log getLog(Marker marker);
/* Conveniance methods where
* Logger.debug(s) is equivalent to Logger.getLog(DEBUG).log(s)
*/
void debug(String msg);
void debug(String format, Object arg);
void debug(String format, Object arg1, Object arg2);
void debug(String msg, Throwable t);
void trace(String msg);
void trace(String format, Object arg);
void trace(String format, Object arg1, Object arg2);
void trace(String msg, Throwable t);
void error(String msg);
void error(String format, Object arg;)
void error(String format, Object arg1, Object arg2);
void error(String msg, Throwable t);
void info(String msg);
void info(String format, Object arg);
void info(String format, Object arg1, Object arg2);
void info(String msg, Throwable t);
void warn(String msg);
void warn(String format, Object arg);
void warn(String format, Object arg1, Object arg2);
void warn(String msg, Throwable t);
void ignore(Throwable t);
}
public class LoggerFactory
{
static Logger getLogger(Class clazz) {...}
static Logger getLogger(Class clazz, String subDomain) { ... }
static Logger getLogger(String name) {...}
static Logger getLogger(String name, String subDomain) { ... }
static Log getLog(Class clazz, Marker m) {...}
static Log getLog(Class clazz, String subDomain, Marker m) { ... }
static Log getLog(String name, Marker m) {...}
static Log getLog(String name, String subDomain, Marker m) { ... }
}
Is this the grand unification theory? probably not.
But it is not tooooo ugly - althought the s in slf4j might need
to be dropped.
BUT I REPEAT.... I'm 95% happy with the current API and am not
really advocating for radical change.
cheers all.
More information about the slf4j-dev
mailing list