Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SLF4J - Bindings are overwritten by other applications on the same application server

A project of mine is packed as an EAR file which contains SLF4J API (1.7.5) as well as the logback libraries as its implementation (logback-core 1.0.13 and logback-classic 1.0.13).

When I (used to) deploy my project SLF4J's LoggerFactory finds logback as possible binding and the correct logger (i.e. logback) is used.

Now I have a resource connector (activemq-rar-5.8.0.rar) which is deployed before my own EAR file (as the EAR files requires the RAR). Unfortunately this RAR contains its own SLF4J implementation (slf4j-api-1.6.6.jar slf4j-log4j12-1.6.6.jar log4j-1.2.17.jar). The RAR files uses the log4j implementation.

When I deploy my EAR file the LoggerFactory inside my application's code suddenly uses the log4j implementation (org.slf4j.impl.Log4jLoggerAdapter) - even though I expected the classpath to be separated from the RAR.

This doesn't seem to be the case - so what am I doing wrong (the RAR should use log4j, my EAR should use logback)?


Update 1: It doesn't look as if I am alone, but unfortunately an answer is missing..


Update 2:

According to this table, GlassFish loads the connector module before the EAR/WAR libs (which are the last libs to be loaded).


Update 3:

I managed to fix the "binding": If I put the slf4j-api-1.7.5.jar and the logback implementation (logback-core-1.0.13.jar and logback-classic-1.0.13.jar) inside the domains/<myDomain>/lib folder in GlassFish, logback will be used as logging implementation (see Update 2 - "Common Classloader" comes before "Connector Classloader").

Unfortunately my configuration files aren't found anymore as they are inside the WAR/EAR - which will be loaded later by a different classloader ("Archive Classloader").

So this is not really a solution for me as I would like to keep the logback configuration files inside the EAR/WAR (as every application uses a different configuration).


Kind regards

stupidSheep

like image 517
stupidSheep Avatar asked Aug 08 '13 10:08

stupidSheep


1 Answers

I finally figured out an acceptable solution.

GlassFish loads the Connector Module before the EAR/WAR, see "Update 2". By providing the SLF4J implementation BEFORE the connector module is being loaded, my provided SLF4J implementation is used.

To do this I've copied the following JARS to the directory domains/<myDomain>/lib (see "Update 3").

  • logback-core-1.0.13.jar
  • logback-classic-1.0.13.jar
  • slf4j-api-1.7.5.jar

Unfortunately logback didn't find its own configuration file (logback.xml) anymore, which must be on the classpath (which it is, as I packed it inside a JAR).

The solution is to manually configure logback. I've done this using the following CDI producer:

package com.example;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;

@ApplicationScoped
public class LoggerProducer {
    @PostConstruct
    public void initialize() {
        // The following is logback specific. Unfortunately logback doesn't find its XML configuration
        // as the logback implementation gets loaded by a different ClassLoader than this code.
        // See http://docs.oracle.com/cd/E19226-01/820-7695/6niugesfp/index.html#indexterm-28
        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        JoranConfigurator joranConfigurator = new JoranConfigurator();
        joranConfigurator.setContext(lc);
        lc.reset();

        try {
            // The logback configuration is now being loaded from the classpath (by the "Archive Classloader")
            joranConfigurator.doConfigure(this.getClass().getClassLoader().getResource("logback.xml"));
        } catch (JoranException e) {
            e.printStackTrace();
        }
    }

    @Produces
    @ApplicationLogger
    Logger createLogger(InjectionPoint injectionPoint) {
        return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass());
    }
}

This will configure logback. Please note that I've used logback specific code to do that, so if you change the SLF4J implementation you must change the LoggerProducer as well.

I think logback doesn't find its configuration file as the "Common Classloader" doesn't have the EAR/WAR on its classpath. But later, when the application is loaded, the "Archive Classloader" has the logback.xml on its classpath (as it's provided by the EAR/WAR file), so it's possible to configure logback once everything is in place.

Regards stupidSheep

like image 142
stupidSheep Avatar answered Oct 28 '22 08:10

stupidSheep