2011-12-13 43 views
5

我們有一個普通的舊Java庫,它從許多不同的應用程序實例化。在這種情況下,每個應用程序都是一個Web應用程序,它們都位於同一個Tomcat容器中。針對庫的每個實例的單獨記錄器

每個應用程序使用自己的記錄器記錄到它自己的日誌文件。我們希望由庫生成的與特定應用程序相關的日誌也可以轉到該應用程序單獨的日誌文件。

對於這一點,一個辦法是允許應用程序在其記錄傳送給庫:

library = new library(Logger applicationsVeryOwnLogger); 

,然後使用該記錄器,記錄庫中的所有語句。但是,這意味着記錄器現在是庫中的類變量,並且庫中的每個類都需要引用庫來使用正確的記錄器。

有沒有更好的方法來做到這一點?

回答

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

註冊記錄器在EJB/Web應用程序的初始化階段(需要的任何呼叫到getLogger之前註冊):

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

檢索記錄器在庫(實用方法):

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

這將返回與當前線程關聯的應用程序(EAR)的記錄器。

不限於記錄儀,它也適用於您想要共享的其他資源。

限制:

  • 如果你打包每個部署的EAR

  • 的ResourceManager和日誌記錄庫中的多個應用程序/ EJB的不會工作必須在相同或比庫和應用較高的類加載器。如果有捆綁的選項,那麼Alexanders的方法就會更清晰。 (我們使用java.util.logging,這是默認的服務器級別,所以他的方法不起作用)

+0

作爲一個便箋,這個解決方案也可以用來共享各種資源,而不僅僅是記錄器。 – Stefan

+0

你可能想在你的答案中粘貼相關的代碼段,以防鏈接將來死亡。 – rouble

+0

另一個需要注意的方法是在實例化庫之前必須調用ResourceManager.registerLogger()。否則,班級記錄器將全部使用默認記錄器進行設置。對於某些實現來說,這可能是一個破壞行爲。 – rouble

3

你已經用log4j標記標記了你的問題,所以我假設你正在使用這個標記。

我希望您的圖書館使用獨特的包名稱。

如果是這樣,您實際上可以爲該包安裝一個記錄器。

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

這樣做會從資料庫中記錄消息都libFileAppenderrootFileAppender

如果你不想從資料庫的消息出現在rootFileAppender您可以關閉該記錄器可加性,就像這樣:

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

有了這個,你只會在libFileAppender看到消息

+0

我不認爲這符合要求。有了這個庫的每一個日誌都可以重定向到一個地方。但是,我們需要從特定應用程序生成的庫中的每個日誌到一個地方(這是應用程序的日誌文件)。 – rouble

+0

@prmatta。把這個文件放到WEB-INF/classes中,併爲該應用程序設置appender,並且每個應用程序都會將庫調用記錄到該appender。我不明白它不符合你的要求。 –

+0

Arnt Loggers/Appenders for log4j在JVM級別上管理?如果您爲所有記錄器使用相同的名稱(因爲庫需要名稱,您必須使用相同的名稱),那麼您需要註冊相同的記錄器。 – Stefan

相關問題