The last couple of days I've been learning about SLF4J and Logback. IMHO 
this is a great piece of software.

One of my needs was to configure logging to a JNDI shared database 
inside tomcat 5.5. I followed the directions outlined in 
http://logback.qos.ch/manual/appenders.html for configuring the 
DBAppender with a JNDIConnectionSource.

The example code in the manual is like this:

<connectionSource class="ch.qos.logback.core.db.JNDIConnectionSource">
   <param name="jndiLocation" value="jdbc/MySQLDS" />
   <param name="username" value="myUser" />
   <param name="password" value="myPassword" />
</connectionSource>

(BTW, the "username" param in the documentation is wrong, it must be 
"user").

I will outline the configuration I needed for tomcat5.5 in case you want 
to add a samples section and as documentation in the list archives. 
After some headaches the configuration I got working was this simple one:

In logback.xml:

...
<connectionSource class="ch.qos.logback.core.db.JNDIConnectionSource">
     <param name="jndiLocation"
            value="java:comp/env/jdbc/myDataSource"/>
</connectionSource>
...

In context.xml:

...
<Resource name="jdbc/myDataSource"
           auth="Container"
           type="javax.sql.DataSource"
           driverClassName="org.postgresql.Driver"
           url="jdbc:postgresql://localhost/mydb"
           username="myuser"
           password="mypass"
           maxActive="150"
           maxIdle="50"
           maxWait="10000"
           removeAbandoned="true"
           removeAbandonedTimeout="60"
           logAbandoned="true"
           validationQuery="SELECT COUNT(*) FROM PingTable"/>
...

In web.xml:

...
<resource-ref>
   <description>sample datasource</description>
   <res-ref-name>jdbc/myDataSource</res-ref-name>
   <res-type>javax.sql.DataSource</res-type>
   <res-auth>Container</res-auth>
</resource-ref>
...

(In my experience the <resource-ref> definition is **not** required but 
it is referenced as mandatory in the tomcat docs 
http://tomcat.apache.org/tomcat-5.5-doc/jndi-resources-howto.html & 
http://tomcat.apache.org/tomcat-5.5-doc/jndi-datasource-examples-howto.html)

Using this configuration and only including the slf4j-api-1.5.2.jar, 
logback-classic-0.9.9.jar and logback-core-0.9.9.jar jars in WEB-INF/lib 
(removed commons-logging.jar also) I got the DB logging working.

Comments: using tomcat5.5, username/password can't be specified when 
trying to use a JNDI-shared DataSource. The following configuration:

<connectionSource class="ch.qos.logback.core.db.JNDIConnectionSource">
     <param name="jndiLocation"
            value="java:comp/env/jdbc/myDataSource"/>
     <param name="user" value="myuser"/>
     <param name="password" value="mypass"/>
</connectionSource>

did not work and I got a "java.lang.UnsupportedOperationException: Not 
supported by BasicDataSource" exception. The reason is outlined in 
http://www.bedework.org/pipermail/bedework-users/2007-December/000890.html, 
but long story short, when using pooled data sources the user and 
password version of getConnection method can't be used. I don't know if 
this is tomcat-specific behaviour. Related lines in 
ch.qos.logback.core.db.JNDIConnectionSource:

...
   public Connection getConnection() throws SQLException {
     Connection conn = null;
     try {

       if (dataSource == null) {
         dataSource = lookupDataSource();
       }
       if (getUser() == null) {
         conn = dataSource.getConnection();
       } else {
         conn = dataSource.getConnection(getUser(), getPassword());
       }
     } catch (final NamingException ne) {
       addError("Error while getting data source", ne);
       throw new SQLException("NamingException while looking up 
DataSource: "
           + ne.getMessage());
     } catch (final ClassCastException cce) {
       addError("ClassCastException while looking up DataSource.", cce);
       throw new SQLException("ClassCastException while looking up 
DataSource: "
           + cce.getMessage());
     }

     return conn;
   }
...

The "if (getUser() == null)" test is the source of this problem. Maybe 
some sort of "if jndi then don't use user/pass" testing could be done 
for better error reporting (at least better than "not supported method 
exception"), but I'm not an expert on logback architecture so I don't 
know what implications this could have.

Added to that, the JNDI name used in the "jndiLocation" must be 
prepended by "java:comp/env", as this is the standard context for java 
objects and the JNDIConnectionSource does not prepend the searched 
values with it. From ch.qos.logback.core.db.JNDIConnectionSource:

...
   private DataSource lookupDataSource() throws NamingException, 
SQLException {
     DataSource ds;
     Context ctx = new InitialContext();
     Object obj = ctx.lookup(jndiLocation);

     // PortableRemoteObject was introduced in JDK 1.3. We won't use it.
     // ds = (DataSource)PortableRemoteObject.narrow(obj, DataSource.class);
     ds = (DataSource) obj;

     if (ds == null) {
       throw new SQLException("Failed to obtain data source from JNDI 
location "
           + jndiLocation);
     } else {
       return ds;
     }
   }
...

Another thing I got problems with was slf4j's jcl-over-slf4j-1.5.2.jar. 
At first I included this jar as I thought it would be harmless to still 
get JCL compatibility in my app. But including this jar made my 
application miserably fail. The exception I got was that the JNDI 
context for my data source did not exist, which was weird since I got it 
working before including jcl-over-slf4j-1.5.2.jar.

After checking stack traces, I concluded that this is due to logback 
being initialized too early when using jcl-over-slf4j, before tomcat 
makes the JNDI resources available to the web application. Check this 
stack trace:

12:44:45,287 |-ERROR in 
[EMAIL PROTECTED] - Error while getting 
data source javax.naming.NameNotFoundException:
javax.naming.NameNotFoundException: 'comp' can't be found in this context
         at org.apache.naming.NamingContext.lookup(NamingContext.java:770)
         at org.apache.naming.NamingContext.lookup(NamingContext.java:153)
         at 
org.apache.naming.SelectorContext.lookup(SelectorContext.java:137)
         at javax.naming.InitialContext.lookup(InitialContext.java:351)
         at 
ch.qos.logback.core.db.JNDIConnectionSource.lookupDataSource(JNDIConnectionSource.java:104)
         at 
ch.qos.logback.core.db.JNDIConnectionSource.getConnection(JNDIConnectionSource.java:62)
         at 
ch.qos.logback.core.db.ConnectionSourceBase.discoverConnnectionProperties(ConnectionSourceBase.java:42)
         at 
ch.qos.logback.core.db.JNDIConnectionSource.start(JNDIConnectionSource.java:50)
         at 
ch.qos.logback.core.joran.action.NestedComponentIA.end(NestedComponentIA.java:142)
         at 
ch.qos.logback.core.joran.spi.Interpreter.callEndAction(Interpreter.java:310)
         at 
ch.qos.logback.core.joran.spi.Interpreter.endElement(Interpreter.java:173)
         at 
ch.qos.logback.core.joran.spi.Interpreter.endElement(Interpreter.java:154)
         at 
ch.qos.logback.core.joran.spi.EventPlayer.play(EventPlayer.java:48)
         at 
ch.qos.logback.core.joran.spi.Interpreter.play(Interpreter.java:336)
         at 
ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:96)
         at 
ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:74)
         at 
ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:38)
         at 
ch.qos.logback.classic.util.ContextInitializer.configureByResource(ContextInitializer.java:23)
         at 
ch.qos.logback.classic.util.ContextInitializer.autoConfig(ContextInitializer.java:34)
         at 
ch.qos.logback.classic.util.ContextInitializer.autoConfig(ContextInitializer.java:43)
         at 
org.slf4j.impl.StaticLoggerBinder.initialize(StaticLoggerBinder.java:62)
         at 
org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:53)
         at 
org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:48)
         at org.slf4j.LoggerFactory.<clinit>(LoggerFactory.java:60)
         at 
org.apache.commons.logging.impl.SLF4JLogFactory.getInstance(SLF4JLogFactory.java:155)
         at 
org.apache.commons.logging.LogFactory.getLog(LogFactory.java:704)
         at 
org.apache.catalina.core.ContainerBase.getLogger(ContainerBase.java:381)
         at 
org.apache.catalina.core.StandardContext.start(StandardContext.java:4119)
         at 
org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:760)
         at 
org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:740)
         at 
org.apache.catalina.core.StandardHost.addChild(StandardHost.java:544)
         at 
org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:626)
         at 
org.apache.catalina.startup.HostConfig.deployDescriptors(HostConfig.java:553)
         at 
org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:488)
         at 
org.apache.catalina.startup.HostConfig.start(HostConfig.java:1149)

As you can see, tomcat references a JCL logger in 
"org.apache.catalina.core.ContainerBase.getLogger" just when first 
loading the webapp. This triggers logback initialization, and when the 
JNDIConnectionSource tries to get a reference to the DataSource, it 
finds nothing.

I found a reference to a similar problem with JNDI contexts in the old 
bugzilla tracker (http://marc.info/?l=logback-dev&m=119005116732589&w=2) 
but although I tried locating this bug report in the new JIRA tracker I 
could not find it. Maybe it got lost during the migration?

At the moment I'm still trying to fully understand the codebase. Do you 
figure out any solution to the problem?

I'm sorry for such a lengthy message. Hope this info helps the project.
Regards and keep up the good work.

Diego.
_______________________________________________
Logback-user mailing list
[email protected]
http://qos.ch/mailman/listinfo/logback-user

Reply via email to