[slf4j-dev] Re: Beta 4 and the new method signatures

Ceki Gülcü listid at qos.ch
Fri Jul 8 22:14:38 CEST 2005


Greg,

This may be becoming a dull habit but I would like to thank you for
your original and thought provoking message. One does not stumble upon
gems every day.

More below.

At 09:44 AM 7/8/2005, Greg Wilkins wrote:

>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.

Good summary. Although I mostly agree, the second point "not a 
one-size-fits-all" API, is not addressed by the simplified log API. I'll 
elaborate more on this below.

>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.

Agreed.


>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.

I see where you are going.

>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 );
>  }

All right, the above raises a fundamental question but (in my humble
opinion) also makes a false assumption. Take for example request
logs. Do you think any of the methods in interface Log is appropriate
for requests logs? I think none of them are, not a single one! Don't
get me wrong, your question touches one the core issues in the problem
domain. It's just that Log interface doesn't provide an answer.

As for the second use case, servlet context logs are a degenerate case
of logging. It's degenerate in the sense that the user cannot specify
a logger nor a level. Otherwise, as far as I can tell, there is
nothing special about servlet context logging.

>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.

This is a rather sweeping but also an insightful statement.

>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.

It's indeed workable but is it better? As mentioned previously, I think 
that the simplified Log interface does not solve the extra use cases.

If "info" is a logger, then

   info.log("doing something now");

is not exactly the same as

   logger.info(""doing something now");

While both statements are likely to be allowed to print on the output 
devices, the first statement would leave no clue about the origin of the 
log whereas the second form would print the logger name (hence the class 
name). You could use a localization extraction facility, but they are slow 
and unreliable.

The bottom line is that class scopes logging (for levels INFO, WARN,
ERROR) yeilds a little more functionality and flexibility at the cost of a
fatter API.


>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) { ... }
>}


By the way, if you have markers attached to log statements instead of
loggers, then there is no need for subdomains. The factory classes
become:

public class LoggerFactory {
   static Logger getLogger(Class clazz) {...}
   static Logger getLogger(String name) {...}
}

public class MarkerFactory {
   static Marker getMarker(String name) {...}
}

I'd like to add a few more things, but I really should go.

Cheers,

-- 
Ceki Gülcü

   The complete log4j manual: http://www.qos.ch/log4j/





More information about the slf4j-dev mailing list