2010-08-10 54 views
67

我有興趣瞭解更多關於人們如何使用依賴注入平臺注入日誌的知識。雖然下面的鏈接和我的示例都提到了log4net和Unity,但我不一定會使用其中的任何一個。對於依賴注入/ IOC,我可能會使用MEF,因爲這是項目其他部分(大)正在解決的標準。我很新的依賴注入/ ioc,並且對於C#和.NET來說很新(在VC6和VB6的過去10年左右,C#/ .NET中已經寫了很少的生產代碼)。我已經對各種各樣的日誌解決方案進行了很多調查,所以我認爲我對他們的功能集有很好的把握。我只是不太熟悉用實際的機制來獲得一個依賴注入(或者,也許更「正確地」,得到一個注入的依賴注入的抽象版本)。依賴注入和命名記錄器

我已經看到了相關的記錄和/或依賴注入其他職位,如: dependency injection and logging interfaces

Logging best practices

What would a Log4Net Wrapper class look like?

again about log4net and Unity IOC config

我的問題沒有專門做「如何使用ioc工具yyy注入日誌記錄平臺xxx?「相反,我感興趣的是人們如何處理包裝日誌平臺(通常,但並不總是推薦)和配置(即app.config)。例如,使用log4net的作爲一個例子,我可以配置(在app.config)中的一些記錄器中,然後在使用這樣的代碼的標準方法得到的那些記錄器(不依賴注入):

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 

替代地如果我的記錄是未命名的一類,而是一個功能區域,我可以這樣做:

private static readonly ILog logger = LogManager.GetLogger("Login"); 
private static readonly ILog logger = LogManager.GetLogger("Query"); 
private static readonly ILog logger = LogManager.GetLogger("Report"); 

所以,我想,我的「要求」將是這樣的:

  1. 我想將我的產品的來源與日誌平臺的直接依賴關係隔離開來。

  2. 我希望能夠通過某種依賴注入(可能是MEF)直接或間接地解析特定的命名記錄器實例(可能在同一命名實例的所有請求者之間共享相同的實例)。

  3. 我不知道我是否會稱這是一個困難的要求,但我希望能夠根據需要獲得一個命名記錄器(不同於類記錄器)。例如,我可能會根據班級名稱爲我的班級創建一個記錄器,但是一種方法需要特別嚴格的診斷,我想單獨控制這些診斷。換句話說,我可能希望單個類「依賴」兩個獨立的記錄器實例。

讓我們從數字1開始。我已經閱讀了很多文章,主要是關於stackoverflow,關於它是否是一個好主意換行。請參閱上面的「最佳實踐」鏈接,並轉至jeffrey hantin的評論,以獲取有關爲什麼打包log4net很糟糕的觀點。如果你真的包裝了(如果你能有效地包裝)你會嚴格包裝注入/消除直接歧視的目的?或者你還會嘗試抽象掉一些或全部log4net app.config信息?假設我想要使用System.Diagnostics,我可能想要實現一個基於接口的記錄器(甚至可能使用「常見的」ILogger/ILog接口),可能基於TraceSource,這樣我就可以注入它。您是否會通過TraceSource實現接口,並按原樣使用System.Diagnostics app.config信息?

事情是這樣的:

public class MyLogger : ILogger 
{ 
    private TraceSource ts; 
    public MyLogger(string name) 
    { 
    ts = new TraceSource(name); 
    } 

    public void ILogger.Log(string msg) 
    { 
    ts.TraceEvent(msg); 
    } 
} 

而且使用這樣的:

private static readonly ILogger logger = new MyLogger("stackoverflow"); 
logger.Info("Hello world!") 

移動到2號......如何解決一個特定的命名記錄器實例?我是否應該利用我選擇的日誌平臺的app.config信息(即根據app.config中的命名方案解析記錄器)?所以,在log4net的情況下,我可能更喜歡「注入」LogManager(請注意,我知道這是不可能的,因爲它是一個靜態對象)?我可以打包LogManager(稱爲MyLogManager),給它一個ILogManager接口,然後解析MyLogManager.ILogManager接口。我的其他對象可能在ILogManager上有一個依賴關係(用MEF的說法導入)(從它實現的程序集導出)。現在我可以有這樣的對象:

public class MyClass 
{ 
    private ILogger logger; 
    public MyClass([Import(typeof(ILogManager))] logManager) 
    { 
    logger = logManager.GetLogger("MyClass"); 
    } 
} 

任何時候調用ILogManager,它都會直接委託給log4net的LogManager。或者,包裝的LogManager能否根據app.config獲取ILogger實例,並將它們按名稱添加到(a?)MEF容器中。稍後,如果請求具有相同名稱的記錄器,則會爲該名稱查詢包裝的LogManager。如果ILogger在那裏,就這樣解決。如果MEF可以做到這一點,那麼這樣做有沒有什麼好處?

在這種情況下,實際上只有ILogManager是「注入」的,它可以按照log4net的正常方式發出ILogger實例。這種類型的注入(本質上是工廠)與注入指定的記錄器實例相比如何?這確實可以更輕鬆地利用log4net(或其他日誌平臺)app.config文件。

我知道,我能得到命名實例出這樣的MEF容器:

var container = new CompositionContainer(<catalogs and other stuff>); 
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger"); 

但如何獲取命名實例放入容器?我知道基於屬性的模型,我可以有不同的ILogger實現,每個實例都被命名(通過MEF屬性),但這對我沒有任何幫助。有沒有辦法像app.config(或其中的一部分)那樣創建類似於記錄器(所有相同的實現)的名稱,並且MEF可以讀取?可以/應該有一箇中央「管理器」(如MyLogManager),通過底層app.config解析命名的記錄器,然後將解析的記錄器插入到MEF容器中?這樣,對於有權訪問相同MEF容器的其他人可以使用它(儘管沒有MyLogManager瞭解如何使用log4net的app.config信息,似乎該容器將無法直接解析任何指定的記錄器)。

這已經變得很長了。我希望它是連貫的。請隨意分享關於您如何依賴注入日誌平臺的任何特定信息(我們很可能將log4net,NLog或基於System.Diagnostics構建的某些內容(希望很細)考慮到您的應用程序中。

你注入了「管理器」並讓它返回記錄器實例嗎?

您是否在自己的配置部分或DI平臺的配置部分中添加了一些自己的配置信息,以便於/可以直接插入記錄器實例(即使您的依賴項位於ILogger而不是ILogManager上)。

如何讓一個靜態或全局容器具有ILogManager接口或其中的一組命名的ILogger實例。因此,不是按照常規的意義(通過構造函數,屬性或成員數據)進行注入,而是根據需要明確解決日誌依賴性問題。這是依賴注入的好方法還是壞方法?

我把這個標記爲一個社區wiki,因爲它看起來不像一個有明確答案的問題。如果任何人有其他感覺,請隨時更改。

感謝您的幫助!

回答

13

這是爲任何想要了解如何注入記錄器依賴項的人提供的幫助,當您要爲其注入的記錄器提供日誌記錄平臺(如log4net或NLog)時。我的問題是,當我知道特定ILogger的解析將取決於知道依賴於ILogger的類的類型時,我無法理解如何創建一個類(例如MyClass)依賴於ILogger類型的接口(例如MyClass)。 DI/IoC平臺/容器如何獲得正確的ILogger?

那麼,我已經看過Castle和NInject的源代碼,並且看過它們是如何工作的。我也看了AutoFac和StructureMap。

Castle和NInject都提供了日誌記錄的實現。兩者都支持log4net和NLog。 Castle還支持System.Diagnostics。在這兩種情況下,當平臺解析給定對象的依賴關係時(例如,當平臺創建MyClass並且MyClass依賴於ILogger時),它將創建依賴項(ILogger)委託給ILogger「provider」(解析器可能更多通用術語)。然後,ILogger提供程序的實現負責實際實例化一個ILogger實例並將其返回,然後將其注入到相關類(例如MyClass)中。在這兩種情況下,提供者/解析器都知道相關類的類型(例如MyClass)。所以,當MyClass被創建並且它的依賴被解析時,ILogger「解析器」知道這個類是MyClass。在使用Castle或NInject提供的日誌記錄解決方案的情況下,這意味着日誌解決方案(作爲log4net或NLog的封裝實現)獲取類型(MyClass),因此它可以委託給log4net.LogManager.GetLogger()或NLog.LogManager.GetLogger()。 (對於log4net和NLog不是100%確定的語法,但你明白了)。

儘管AutoFac和StructureMap沒有提供日誌工具(至少我可以通過查看來看),但他們確實提供了實現自定義解析器的功能。所以,如果你想編寫你自己的日誌抽象層,你也可以編寫一個相應的自定義解析器。這樣,當容器想要解析ILogger時,解析器將被用來獲得正確的ILogger,並且它可以訪問當前上下文(即當前正在滿足什麼對象的依賴關係 - 哪個對象依賴於ILogger)。獲取對象的類型,並準備將ILogger的創建委託給當前配置的日誌平臺(您可能已經抽象出了一個接口併爲其編寫了解析器)。

所以,要求一對夫婦,我懷疑關鍵點,但我沒有完全掌握之前:

  1. 最終的DI容器必須 知道,不知何故,什麼記錄 平臺的使用。通常,這是 通過指定「ILogger」是 通過了「分解」是 是具體到一個日誌平臺 (解決完成,因此,城堡有log4net的,NLOG, 和System.Diagnostics程序「解析器」 (中其他))。可通過配置文件或以編程方式完成 的規格 ,其中可使用解析器 。

  2. 解析器需要知道 上下文的依賴關係 (ILogger)正在被解析。這 ,如果MyClass的已創建和 它依賴於ILogger,然後 當解析器正試圖 創建正確的ILogger,它( 解析器)必須知道當前類型 (MyClass的)。這樣,解析器 可以使用底層日誌記錄 實現(log4net,NLog等) 來獲取正確的記錄器。

這些點可能是明顯的對於DI/IoC的用戶在那裏,但我剛纔進入它,所以它採取了我一段時間讓我的頭周圍。

我還沒弄清楚的一件事是如何或如果這樣的事情可能與MEF。我可以擁有一個依賴於接口的對象,然後在MEF創建對象並解析接口/依賴關係之後執行我的代碼?因此,假設我有一個這樣的類:

public class MyClass 
{ 
    [Import(ILogger)] 
    public ILogger logger; 

    public MyClass() 
    { 
    } 

    public void DoSomething() 
    { 
    logger.Info("Hello World!"); 
    } 
} 

當MEF被解析爲MyClass的進口,我可以有我自己的一些代碼(通過一個屬性,通過對ILogger實施額外的接口,在別處???)基於它是MyClass的事實來執行和解析ILogger導入,它現在處於上下文中,並返回一個(可能)不同的ILogger實例,而不是爲YourClass檢索的實例?我是否實施某種MEF提供商?

至此,我還不瞭解MEF。

+0

看起來像MEF和Unity(來自MS的Patterns&Practices組的IoC)就像比較蘋果和橘子。也許一個真正的IoC容器是這個特定問題所需要的,它將具有可擴展性來添加自定義依賴性解決方案。 http://stackoverflow.com/questions/293051/is-mef-a-dependency-injection-framework – 2012-12-28 18:42:31

5

我看到你想出了你自己的答案:)但是,對於未來的人們來說,如何避免將自己綁定到某個特定的日誌框架上,這個庫:Common.Logging正好可以幫助解決這個問題。

+0

與qstarin相同的評論。感謝您指向Common.Logging。 – wageoghe 2010-09-15 16:58:06

15

也可以使用Common.Logging立面或Simple Logging Facade

這兩個都使用服務定位器樣式模式來檢索ILogger。

坦率地說,日誌記錄是我在自動注入中看不到任何價值的那些依賴之一。

我的大多數類需要記錄服務的是這樣的:

public class MyClassThatLogs { 
    private readonly ILogger log = Slf.LoggerService.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName); 

} 

通過利用簡單的日誌門面我轉一個項目從log4net的到NLOG,我已經從第三方庫添加日誌記錄除了使用NLog的應用程序日誌記錄之外,還使用了log4net。也就是說,立面給了我們很好的幫助。

難以避免的一個警告是特定於一個日誌框架或另一個日誌框架的功能丟失,或許是最常見的例子是自定義日誌記錄級別。

+0

感謝有關SLF和Common.Logging的提示。我最近對這些庫進行了實驗,它們都很酷。最後,我認爲我們的項目可能不會使用DI來獲取日誌,所以我們最終可能會使用SLF或Common.Logging。 – wageoghe 2010-09-15 16:57:42

36

我使用Ninject爲記錄器實例這樣解決當前的類名:

kernel.Bind<ILogger>().To<NLogLogger>() 
    .WithConstructorArgument("currentClassName", x => x.Request.ParentContext.Request.Service.FullName); 

一個NLOG實施的構造看起來是這樣的:

public NLogLogger(string currentClassName) 
{ 
    _logger = LogManager.GetLogger(currentClassName); 
} 

這一辦法我猜也可以和其他國際奧委會容器一起工作。

+5

我用'x.Request.ParentContext.Plan.Type.FullName'來獲取具體的類名而不是接口名。 – Chadwick 2013-09-07 20:28:13

+0

我總是將ParentContext作爲null。我有從實施ILogger的Loggerbase繼承的NLogLogger。 – Marshal 2015-10-05 13:29:08

+0

如果基類在不同的項目中,那麼確保再次初始化記錄器,如: [程序集:log4net.Config.XmlConfigurator(Watch = true)] – Orhan 2016-08-04 11:40:40

0

我讓我的自定義ServiceExportProvider,由供應商註冊Log4Net記錄器爲MEF依賴注入。因此,您可以使用記錄器進行不同類型的注射。注射

實施例:

class Program 
{ 
    static CompositionContainer CreateContainer() 
    { 
     var logFactoryProvider = new ServiceExportProvider<ILog>(LogManager.GetLogger); 
     var catalog = new AssemblyCatalog(typeof(Program).Assembly); 
     return new CompositionContainer(catalog, logFactoryProvider); 
    } 

    static void Main(string[] args) 
    { 
     log4net.Config.XmlConfigurator.Configure(); 
     var container = CreateContainer(); 
     var part = container.GetExport<Part>().Value; 
     part.Log.Info("Hello, world! - 1"); 
     var anotherPart = container.GetExport<AnotherPart>().Value; 
     anotherPart.Log.Fatal("Hello, world! - 2"); 
    } 
} 

結果在控制檯::

[Export] 
public class Part 
{ 
    [ImportingConstructor] 
    public Part(ILog log) 
    { 
     Log = log; 
    } 

    public ILog Log { get; } 
} 

[Export(typeof(AnotherPart))] 
public class AnotherPart 
{ 
    [Import] 
    public ILog Log { get; set; } 
} 

使用示例

2016-11-21 13:55:16,152 INFO Log4Mef.Part - Hello, world! - 1 
2016-11-21 13:55:16,572 FATAL Log4Mef.AnotherPart - Hello, world! - 2 

ServiceExportProvider實現:

public class ServiceExportProvider<TContract> : ExportProvider 
{ 
    private readonly Func<string, TContract> _factoryMethod; 

    public ServiceExportProvider(Func<string, TContract> factoryMethod) 
    { 
     _factoryMethod = factoryMethod; 
    } 

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition) 
    { 
     var cb = definition as ContractBasedImportDefinition; 
     if (cb?.RequiredTypeIdentity == typeof(TContract).FullName) 
     { 
      var ce = definition as ICompositionElement; 
      var displayName = ce?.Origin?.DisplayName; 
      yield return new Export(definition.ContractName,() => _factoryMethod(displayName)); 
     } 
    } 
}