Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

separate logger for each instance of a library

We have a plain old java library that is instantiated from many different applications. In this case each application is a web application that all live in the same tomcat container.

Each application logs to its own log file, using its own logger. We want the logs generated by the library, that are pertinent to a specific application, to also go to that applications separate log file.

For this, one way is to allow the application to pass in its logger to the library:

library = new library(Logger applicationsVeryOwnLogger);

And then use that logger, to log all statements in the library. However, this means that the logger is now a class variable in the library, and every class in the library needs a reference to the library to use the right logger.

Are there better ways to do this?

like image 967
rouble Avatar asked Dec 13 '11 18:12

rouble


Video Answer


2 Answers

You've marked your question with log4j tag, so I assume that's what you are using.

I hope that your library is using a unique package name.

If that's the case, you can actually just setup a logger for that package.

log4j.category.my.lib.package = INFO, libFileAppender
log4j.rootLogger = INFO, rootFileAppender

Doing so will log messages from your library to both libFileAppender and rootFileAppender.

If you don't want to messages from your library to appear in rootFileAppender you may turn off that logger additivity, like this:

log4j.category.my.lib.package = INFO, libFileAppender
log4j.additivity.my.lib.package = false
log4j.rootLogger = INFO, rootFileAppender

With this, you will only see message in libFileAppender

like image 145
Alexander Pogrebnyak Avatar answered Sep 28 '22 08:09

Alexander Pogrebnyak


We had a similar need in one of our older applications. The solution we came up with was a ResourceManager that would retrieve resources(Logger, Config files etc) by (context)ClassLoader.

Usually every application deployed as an EAR gets its own ClassLoader and the library can then just call ResourceManager.getLogger() to get the Logger associated with the current Thread/Application. That way you dont need to pass it with every method call in the library (it requires that you can change the library tho).

import java.util.*;
import java.util.logging.*;

public class ResourceManager 
{
    private static final Map<ClassLoader, Map<String, Object>> resources = 
        Collections.synchronizedMap(new WeakHashMap<ClassLoader, Map<String, Object>>());
    public static final String LOGGER = Logger.class.getName();

    static
    {
        // adjust for log4j or other frameworks
        final Logger logger = Logger.getLogger("logging.default");
        logger.setLevel(Level.ALL);
        logger.addHandler(new ConsoleHandler() 
        {
            {
                setOutputStream(System.out);
                setLevel(Level.ALL);
            }
        });
        registerResource(null, LOGGER, logger);
    }

    private static ClassLoader getApplicationScope()
    {
        return Thread.currentThread().getContextClassLoader();
    }

    public static void registerResource(final String name, final Object resource)
    {
        registerResource(getApplicationScope(), name, resource);
    }

    public static synchronized void registerResource(final ClassLoader scope, final String name, final Object resource)
    {
        Map<String, Object> hm = null;
        hm = resources.get(scope);
        if (hm == null)
        {
            hm = Collections.synchronizedMap(new HashMap<String, Object>());
            resources.put(scope, hm);
        }
        hm.put(name, resource);
    }

    public static Object getResource(final String name)
    {
        for(ClassLoader scope = getApplicationScope();;scope = scope.getParent())
        {
            final Map<String, Object> hm = resources.get(scope);
            if ((hm != null) && hm.containsKey(name)) 
            {
                return hm.get(name);
            }
            if (scope == null) break;
        }
        return null;
    }

    public static void registerLogger(final Logger logger)
    {
        registerResource(LOGGER, logger);
    }

    public static Logger getLogger()
    {
        return (Logger)getResource(LOGGER);
    }       
}

Register Logger in the init phase of EJB/WebApp(needs to be registered before any call to getLogger):

Logger logger = Logger.getLogger([Application Logger Name]);
ResourceManager.registerLogger(logger);

Retrieve Logger in library(utility method):

private Logger getLogger()
    {
        return ResourceManager.getLogger();     
    }

This will return the logger for the application(EAR) that is associated with the current thread.

Not limited to Loggers it also works for other resources that you want to share.

Limitations:

  • wont work if you package multiple applications/EJBs per deployed EAR

  • ResourceManager and Logging library need to be on the same or a higher ClassLoader than the library and application. If there is an option to bundle then Alexanders approach is cleaner. (we use java.util.logging which is by default on the server level so his approach doesnt work)

like image 20
Stefan Avatar answered Sep 28 '22 09:09

Stefan