为库的每个实例分别记录器

rou*_*ble 6 java oop logging design-patterns log4j

我们有一个普通的旧java库,它可以从许多不同的应用程序中实例化.在这种情况下,每个应用程序都是一个Web应用程序,它们都位于同一个tomcat容器中.

每个应用程序使用自己的记录器记录到自己的日志文件.我们希望库生成的与特定应用程序相关的日志也会转到该应用程序单独的日志文件.

为此,一种方法是允许应用程序将其记录器传递给库:

library = new library(Logger applicationsVeryOwnLogger);
Run Code Online (Sandbox Code Playgroud)

然后使用该记录器记录库中的所有语句.但是,这意味着记录器现在是库中的类变量,并且库中的每个类都需要对库的引用才能使用正确的记录器.

有没有更好的方法来做到这一点?

Ste*_*fan 2

我们在一个较旧的应用程序中也有类似的需求。我们提出的解决方案是一个 ResourceManager,它将通过(上下文)ClassLoader 检索资源(记录器、配置文件等)。

通常,部署为 EAR 的每个应用程序都会获得自己的 ClassLoader,然后库只需调用 ResourceManager.getLogger() 即可获取与当前线程/应用程序关联的 Logger。这样您就不需要在库中的每个方法调用中传递它(它要求您可以更改库)。

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);
    }       
}
Run Code Online (Sandbox Code Playgroud)

在EJB/WebApp的init阶段注册Logger(需要在调用getLogger之前注册):

Logger logger = Logger.getLogger([Application Logger Name]);
ResourceManager.registerLogger(logger);
Run Code Online (Sandbox Code Playgroud)

检索库中的记录器(实用方法):

private Logger getLogger()
    {
        return ResourceManager.getLogger();     
    }
Run Code Online (Sandbox Code Playgroud)

这将返回与当前线程关联的应用程序 (EAR) 的记录器。

不仅限于记录器,它还适用于您想要共享的其他资源。

限制:

  • 如果您为每个部署的 EAR 打包多个应用程序/EJB,则无法工作

  • ResourceManager 和日志库需要位于与库和应用程序相同或更高的 ClassLoader 上。如果有捆绑选项,那么亚历山大的方法就更干净。(我们使用 java.util.logging,默认情况下在服务器级别,所以他的方法不起作用)