2010-08-10 31 views
67

Tôi quan tâm đến việc tìm hiểu thêm về cách mọi người tiêm đăng nhập với nền tảng tiêm phụ thuộc. Mặc dù các liên kết bên dưới và các ví dụ của tôi đề cập đến log4net và Unity, nhưng tôi không nhất thiết phải sử dụng một trong hai ví dụ đó. Đối với tiêm phụ thuộc/IOC, tôi có thể sẽ sử dụng MEF vì đó là tiêu chuẩn mà phần còn lại của dự án (lớn) đang được giải quyết.Tiêm phụ thuộc và ghi tên được đặt tên

Tôi rất mới để tiêm phụ thuộc/ioc và khá mới đối với C# và .NET (đã viết rất ít mã sản xuất trong C# /. NET sau 10 năm qua hoặc VC6 và VB6). Tôi đã thực hiện rất nhiều cuộc điều tra về các giải pháp ghi nhật ký khác nhau đang tồn tại ở đó, vì vậy tôi nghĩ rằng tôi có một xử lý tốt trên các bộ tính năng của họ. Tôi chỉ không đủ quen thuộc với cơ chế thực tế của việc tiêm một phụ thuộc (hoặc, có thể nhiều hơn "chính xác", nhận được một phiên bản trừu tượng của một phụ thuộc tiêm).

Tôi đã thấy bài viết khác liên quan đến khai thác gỗ và/hoặc dependency injection như: dependency injection and logging interfaces

Logging best practices

What would a Log4Net Wrapper class look like?

again about log4net and Unity IOC config

Câu hỏi của tôi không có đặc biệt để làm với " Làm thế nào để tôi tiêm nền tảng đăng nhập xxx bằng cách sử dụng công cụ ioc yyy? " Thay vào đó, tôi quan tâm đến cách mọi người xử lý gói nền tảng ghi nhật ký (thường là, nhưng không phải lúc nào cũng được đề xuất) và cấu hình (ví dụ: app.config). Ví dụ, sử dụng log4net là một ví dụ, tôi có thể cấu hình (trong app.config) một số người khai thác gỗ và sau đó nhận được những người khai thác gỗ (không phụ thuộc tiêm) trong cách tiêu chuẩn của việc sử dụng mã như thế này:

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

Ngoài , nếu logger của tôi không được đặt tên cho một lớp, nhưng đúng hơn, cho một khu vực chức năng, tôi có thể làm điều này:

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

vì vậy, tôi đoán rằng tôi "yêu cầu" sẽ là một cái gì đó như thế này:

  1. Tôi muốn cách ly nguồn sản phẩm của mình từ sự phụ thuộc trực tiếp trên nền tảng ghi nhật ký.

  2. Tôi muốn có thể giải quyết một cá thể được đặt tên cụ thể (có thể chia sẻ cùng một cá thể trong số tất cả người yêu cầu cùng một cá thể) trực tiếp hoặc gián tiếp bởi một số loại tiêm phụ thuộc, có thể là MEF.

  3. Tôi không biết nếu tôi gọi đây là một yêu cầu khó khăn, nhưng tôi muốn có khả năng để có được một trình ghi tên có tên (khác với trình ghi lớp) theo yêu cầu. Ví dụ, tôi có thể tạo một logger cho lớp của tôi dựa trên tên lớp, nhưng một phương pháp cần chẩn đoán nặng đặc biệt mà tôi muốn kiểm soát riêng. Nói cách khác, tôi có thể muốn một lớp duy nhất "phụ thuộc" vào hai cá thể logger riêng biệt.

Hãy bắt đầu với số 1. Tôi đã đọc một số bài viết, chủ yếu ở đây trên stackoverflow, về việc có nên quấn hay không. Xem liên kết "thực hành tốt nhất" ở trên và truy cập vào nhận xét của jeffrey hantin về một chế độ xem về lý do tại sao việc tải log4net lại là xấu. Nếu bạn đã bọc (và nếu bạn có thể bọc hiệu quả) bạn sẽ bọc chặt chẽ cho mục đích tiêm/loại bỏ trực tiếp depdency? Hoặc bạn cũng sẽ cố gắng để tóm tắt đi một số hoặc tất cả các thông tin app4config log4net?Giả sử tôi muốn sử dụng System.Diagnostics, tôi có thể muốn triển khai một logger dựa trên giao diện (thậm chí có thể sử dụng giao diện "phổ biến" ILogger/ILog), có lẽ dựa trên TraceSource, để tôi có thể tiêm nó . Bạn sẽ thực hiện giao diện, nói trên TraceSource, và chỉ sử dụng thông tin app.Diagnostics app.config như là?

Something như thế này:

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

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

Và sử dụng nó như thế này:

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

Chuyển sang số 2 ... Làm thế nào để giải quyết một trường hợp logger tên cụ thể không? Tôi có nên tận dụng thông tin app.config của nền tảng ghi nhật ký mà tôi chọn (tức là giải quyết logger dựa trên lược đồ đặt tên trong app.config) không? Vì vậy, trong trường hợp của log4net, tôi có thể thích "tiêm" LogManager (lưu ý rằng tôi biết điều này là không thể vì nó là một đối tượng tĩnh)? Tôi có thể bọc LogManager (gọi nó là MyLogManager), cung cấp cho nó một giao diện ILogManager, và sau đó giải quyết giao diện MyLogManager.ILogManager. Các đối tượng khác của tôi có thể có một sự thiếu hụt (nhập khẩu trong MEF parlance) trên ILogManager (Xuất khẩu từ hội đồng nơi nó được thực hiện). Bây giờ tôi có thể có các đối tượng như thế này:

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

Bất kỳ lúc nào ILogManager được gọi, nó sẽ trực tiếp ủy quyền cho log4net của LogManager. Ngoài ra, có thể gói LogManager lấy các cá thể ILogger mà nó dựa trên app.config và thêm chúng vào thùng chứa (a?) MEF theo tên. Sau đó, khi một logger cùng tên được yêu cầu, LogManager được bao bọc sẽ được truy vấn cho tên đó. Nếu ILogger ở đó, nó được giải quyết theo cách đó. Nếu điều này có thể xảy ra với MEF, có lợi ích gì không?

Trong trường hợp này, thực sự, chỉ ILogManager mới được "tiêm" và nó có thể phân phát các cá thể ILogger theo cách mà log4net thường làm. Làm thế nào để loại tiêm (về cơ bản của một nhà máy) so sánh với tiêm các trường hợp logger được đặt tên? Điều này cho phép dễ dàng tận dụng các tập tin app.config của log4net (hoặc nền tảng đăng nhập khác).

Tôi biết rằng tôi có thể đặt tên trường ra khỏi container MEF như thế này:

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

Nhưng làm thế nào để tôi nhận được các trường hợp được đặt tên vào container? Tôi biết về mô hình dựa trên thuộc tính mà tôi có thể có các triển khai khác nhau của ILogger, mỗi cái được đặt tên (thông qua một thuộc tính MEF), nhưng điều đó không thực sự giúp tôi. Có cách nào để tạo ra một cái gì đó giống như một app.config (hoặc một phần trong đó) sẽ liệt kê các logger (tất cả cùng một thực hiện) theo tên và MEF có thể đọc? Có thể/nên có một "quản lý" trung tâm (như MyLogManager) mà giải quyết được đặt tên logger thông qua app.config cơ bản và sau đó chèn logger giải quyết vào container MEF? Bằng cách này nó sẽ có sẵn cho người khác có quyền truy cập vào cùng một container MEF (mặc dù không có kiến ​​thức của MyLogManager về cách sử dụng thông tin app.config của log4net, có vẻ như container sẽ không thể giải quyết bất kỳ logger có tên trực tiếp).

Điều này đã nhận được khá lâu. Tôi hy vọng nó là mạch lạc. Xin vui lòng chia sẻ bất kỳ thông tin cụ thể về cách bạn phụ thuộc tiêm một nền tảng khai thác gỗ (chúng tôi rất có thể xem xét log4net, NLog, hoặc một cái gì đó (hy vọng mỏng) được xây dựng trên System.Diagnostics) vào ứng dụng của bạn.

Bạn đã tiêm "người quản lý" và yêu cầu trở lại các bản ghi nhật ký chưa?

Bạn đã thêm một số thông tin cấu hình của riêng mình vào phần cấu hình của riêng bạn hoặc trong phần cấu hình của nền tảng DI của bạn để dễ dàng hơn/có thể tiêm trực tiếp các bản ghi nhật ký (tức là phụ thuộc vào ILogger chứ không phải ILogManager).

Điều gì về việc có vùng chứa tĩnh hoặc toàn cầu có giao diện ILogManager trong đó hoặc tập hợp các phiên bản ILogger được đặt tên trong đó. Vì vậy, thay vì tiêm theo nghĩa thông thường (thông qua hàm tạo, thuộc tính hoặc dữ liệu thành viên), phụ thuộc ghi nhật ký được giải quyết rõ ràng theo yêu cầu. Đây có phải là cách tốt hay xấu để tiêm phụ thuộc.

Tôi đánh dấu trang này là một wiki cộng đồng vì nó dường như không phải là câu hỏi có câu trả lời rõ ràng. Nếu bất cứ ai cảm thấy khác, hãy thay đổi nó.

Cảm ơn bạn đã trợ giúp!

Trả lời

13

Điều này là vì lợi ích của bất kỳ ai đang cố gắng tìm ra cách để phụ thuộc vào trình ghi nhật ký khi trình ghi nhật ký mà bạn muốn chèn được cung cấp nền tảng ghi nhật ký như log4net hoặc NLog. Vấn đề của tôi là tôi không thể hiểu làm thế nào tôi có thể làm cho một lớp (ví dụ MyClass) phụ thuộc vào một giao diện kiểu ILogger khi tôi biết rằng độ phân giải của ILogger cụ thể sẽ phụ thuộc vào việc biết loại của lớp phụ thuộc vào ILogger (ví dụ MyClass). Làm thế nào để nền tảng/container DI/IoC có được ILogger đúng?

Vâng, tôi đã xem xét nguồn của Castle và NInject và đã thấy cách chúng hoạt động. Tôi cũng đã xem AutoFac và StructureMap.

Lâu đài và NInject đều cung cấp triển khai ghi nhật ký. Cả hai hỗ trợ log4net và NLog. Castle cũng hỗ trợ System.Diagnostics. Trong cả hai trường hợp, khi nền tảng giải quyết các phụ thuộc cho một đối tượng cụ thể (ví dụ khi nền tảng đang tạo MyClass và MyClass phụ thuộc vào ILogger), nó ủy nhiệm việc tạo phụ thuộc (ILogger) cho nhà cung cấp ILogger (trình phân giải có thể là một thuật ngữ chung). Việc thực hiện nhà cung cấp ILogger sau đó chịu trách nhiệm thực sự khởi tạo một cá thể của ILogger và đưa nó trở lại, sau đó được tiêm vào lớp phụ thuộc (ví dụ: MyClass). Trong cả hai trường hợp, nhà cung cấp/người giải quyết biết loại của lớp phụ thuộc (ví dụ: MyClass). Vì vậy, khi MyClass đã được tạo và các phụ thuộc của nó đang được giải quyết, trình giải quyết "ILogger" biết rằng lớp đó là MyClass. Trong trường hợp sử dụng Castle hoặc NInject cung cấp các giải pháp ghi nhật ký, điều đó có nghĩa là giải pháp ghi nhật ký (được thực thi như một trình bao bọc trên log4net hoặc NLog) có kiểu (MyClass), vì vậy nó có thể ủy nhiệm xuống log4net.LogManager.GetLogger() hoặc NLog.LogManager.GetLogger(). (Không chắc chắn 100% cú pháp cho log4net và NLog, nhưng bạn có ý tưởng).

Mặc dù AutoFac và StructureMap không cung cấp cơ sở ghi nhật ký (ít nhất là tôi có thể biết bằng cách tìm kiếm), chúng dường như cung cấp khả năng triển khai các giải pháp tùy chỉnh. Vì vậy, nếu bạn muốn viết lớp trừu tượng ghi nhật ký của riêng bạn, bạn cũng có thể viết một trình giải quyết tùy chỉnh tương ứng. Bằng cách đó, khi container muốn giải quyết ILogger, trình phân giải của bạn sẽ được sử dụng để lấy đúng ILogger VÀ nó sẽ có quyền truy cập vào ngữ cảnh hiện tại (nghĩa là phụ thuộc của đối tượng hiện đang được thỏa mãn - đối tượng nào phụ thuộc vào ILogger). Nhận loại đối tượng và bạn đã sẵn sàng ủy quyền việc tạo ILogger cho nền tảng ghi nhật ký được định cấu hình hiện tại (có thể bạn đã trừu tượng hóa sau một giao diện và bạn đã viết một trình giải quyết).

Vì vậy, một vài điểm quan trọng mà tôi nghi ngờ đã được yêu cầu nhưng mà tôi không nắm bắt đầy đủ trước là:

  1. Cuối cùng container DI phải biết, bằng cách nào đó, những gì khai thác gỗ nền tảng để sử dụng.Thông thường điều này được thực hiện bằng cách xác định rằng "ILogger" là để được giải quyết bằng một "giải quyết" rằng là cụ thể cho một nền tảng khai thác gỗ (vì thế, Lâu đài có log4net, NLog, và System.Diagnostics "phân giải" (trong số khác)). Các đặc điểm kỹ thuật trong đó giải quyết để sử dụng có thể được thực hiện thông qua tập tin cấu hình hoặc lập trình.

  2. Trình giải quyết cần biết ngữ cảnh mà phụ thuộc (ILogger) đang được giải quyết. Đó là , nếu MyClass đã được tạo và , nó phụ thuộc vào ILogger, sau đó khi trình phân giải đang cố gắng tạo đúng ILogger, trình phân giải ) phải biết loại hiện tại (MyClass). Bằng cách đó, người giải quyết có thể sử dụng thực hiện đăng nhập cơ bản (log4net, NLog, v.v.) để nhận trình ghi nhật ký chính xác.

Những điểm này có thể hiển nhiên đối với những người dùng DI/IoC ở đó, nhưng tôi mới bắt đầu làm việc đó, vì vậy tôi phải mất một thời gian để quay đầu lại.

Một điều mà tôi chưa tìm ra là làm thế nào hoặc nếu một cái gì đó như thế này là có thể với MEF. Tôi có thể có một đối tượng phụ thuộc vào một giao diện và sau đó có mã của tôi thực hiện sau khi MEF đã tạo ra đối tượng và trong khi giao diện/phụ thuộc đang được giải quyết? Vì vậy, giả sử tôi có một lớp học như thế này:

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

    public MyClass() 
    { 
    } 

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

Khi MEF được giải quyết nhập khẩu cho MyClass, tôi có thể có một số mã của riêng tôi (thông qua một thuộc tính, thông qua một giao diện thêm về việc thực hiện ILogger, ở nơi khác ???) thực hiện và giải quyết việc nhập khẩu ILogger dựa trên thực tế là nó là MyClass hiện đang trong ngữ cảnh và trả về một cá thể ILogger (tiềm năng) khác với sẽ được truy lục cho YourClass? Tôi có thực hiện một số loại Nhà cung cấp MEF không?

Tại thời điểm này, tôi vẫn không biết về MEF.

+0

Dường như MEF và Unity (IoC từ Nhóm mẫu & Thực hành của MS) giống như so sánh táo và cam. Có thể một container IoC thực sự là những gì cần thiết cho vấn đề cụ thể này mà sẽ có các điểm mở rộng để thêm độ phân giải phụ thuộc tùy chỉnh. http://stackoverflow.com/questions/293051/is-mef-a-dependency-injection-framework –

5

Tôi thấy bạn đã tìm ra câu trả lời của riêng bạn :) Nhưng, đối với những người trong tương lai có câu hỏi này về cách KHÔNG tự buộc mình vào một khung đăng nhập cụ thể, thư viện này: Common.Logging trợ giúp chính xác kịch bản đó.

+0

Nhận xét tương tự như đối với qstarin. Cảm ơn bạn đã trỏ đến Common.Logging. – wageoghe

15

Bạn cũng có thể sử dụng mặt tiền Common.Logging hoặc Simple Logging Facade.

Cả hai đều sử dụng mẫu kiểu định vị dịch vụ để truy xuất ILogger.

Thành thật mà nói, đăng nhập là một trong những phụ thuộc đó tôi thấy ít hoặc không có giá trị khi tự động tiêm.

Hầu hết các lớp học của tôi đòi hỏi phải có các dịch vụ khai thác gỗ trông như thế này:

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

} 

Bằng cách sử dụng các Facade Logging đơn giản tôi đã chuyển sang một dự án từ log4net để NLog, và tôi đã thêm khai thác gỗ từ một thư viện của bên thứ ba mà sử dụng log4net ngoài việc ghi nhật ký của ứng dụng của tôi bằng NLog. Đó là để nói, mặt tiền đã phục vụ chúng ta tốt.

Một cảnh báo khó tránh là mất các tính năng cụ thể cho một khung đăng nhập hoặc khung công tác khác, có lẽ ví dụ thường gặp nhất là mức ghi nhật ký tùy chỉnh.

+0

Cảm ơn lời khuyên về SLF và Common.Logging. Tôi đã thực sự thử nghiệm với các thư viện này gần đây và chúng đều khá tuyệt. Cuối cùng, tôi nghĩ rằng dự án của chúng tôi có thể sẽ không sử dụng DI để đăng nhập, vì vậy chúng tôi có thể sẽ sử dụng SLF hoặc Common.Logging. – wageoghe

36

Tôi đang sử dụng Ninject để giải quyết tên lớp hiện hành đối với các trường hợp logger như thế này:

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

Các nhà xây dựng của một thi NLog có thể trông như thế này:

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

Cách tiếp cận này nên làm việc với các thùng chứa IOC khác, tôi đoán vậy.

+5

Tôi đã sử dụng 'x.Request.ParentContext.Plan.Type.FullName' để lấy tên lớp bê tông thay vì tên giao diện. – Chadwick

+0

Tôi luôn nhận ParentContext dưới dạng null. Tôi có NLogLogger kế thừa từ Loggerbase đang triển khai ILogger. – Marshal

+0

Nếu lớp cơ sở nằm trong một dự án khác thì hãy đảm bảo khởi tạo lại trình ghi nhật ký như sau: [assembly: log4net.Config.XmlConfigurator (Watch = true)] – Orhan

0

Tôi đã tùy chỉnh ServiceExportProvider của mình, bởi nhà cung cấp, tôi đăng ký nhật ký Log4Net để tiêm phụ thuộc bởi MEF. Kết quả là bạn có thể sử dụng logger cho các loại tiêm khác nhau.

Ví dụ về tiêm:

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

Ví dụ về sử dụng:

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

quả trong giao diện điều khiển:

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 thực hiện:

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)); 
     } 
    } 
} 
Các vấn đề liên quan