ฉันควรใช้ ILogger, ILogger <T>, ILoggerFactory หรือ ILoggerProvider สำหรับห้องสมุดหรือไม่


113

สิ่งนี้อาจเกี่ยวข้องกับPass ILogger หรือ ILoggerFactory ไปยังตัวสร้างใน AspNet Core หรือไม่ อย่างไรก็ตามนี่เป็นเรื่องเกี่ยวกับการออกแบบไลบรารีโดยเฉพาะไม่เกี่ยวกับวิธีที่แอปพลิเคชันจริงที่ใช้ไลบรารีเหล่านั้นใช้การบันทึก

ฉันกำลังเขียนไลบรารี. net Standard 2.0 ที่จะติดตั้งผ่าน Nuget และเพื่อให้ผู้ที่ใช้ไลบรารีนั้นได้รับข้อมูลการดีบักฉันขึ้นอยู่กับMicrosoft.Extensions.Logging.Abstractionsเพื่อให้สามารถฉีด Logger ที่เป็นมาตรฐานได้

อย่างไรก็ตามฉันเห็นหลายอินเทอร์เฟซและโค้ดตัวอย่างบนเว็บบางครั้งใช้ILoggerFactoryและสร้างตัวบันทึกใน ctor ของคลาส นอกจากนี้ยังILoggerProviderมีลักษณะที่ดูเหมือน Factory เวอร์ชันอ่านอย่างเดียว แต่การใช้งานอาจใช้หรือไม่ใช้ทั้งสองอินเทอร์เฟซดังนั้นฉันจึงต้องเลือก (โรงงานดูเหมือนธรรมดากว่าผู้ให้บริการ)

รหัสบางตัวที่ฉันเคยเห็นใช้ILoggerอินเทอร์เฟซที่ไม่ใช่แบบทั่วไปและอาจแชร์อินสแตนซ์ของคนตัดไม้เดียวกันด้วยซ้ำและบางโค้ดก็ใช้ILogger<T>ctor และคาดว่าคอนเทนเนอร์ DI จะรองรับประเภททั่วไปแบบเปิดหรือการลงทะเบียนอย่างชัดเจนของแต่ละILogger<T>รูปแบบไลบรารีของฉัน ใช้

ตอนนี้ฉันคิดว่านั่นILogger<T>เป็นแนวทางที่ถูกต้องและอาจเป็น ctor ที่ไม่ได้ใช้อาร์กิวเมนต์นั้นและส่ง Null Logger แทน ด้วยวิธีนี้หากไม่จำเป็นต้องมีการบันทึกก็จะไม่มีการใช้ อย่างไรก็ตามตู้คอนเทนเนอร์ DI บางตัวเลือก ctor ที่ใหญ่ที่สุดจึงจะล้มเหลวอยู่ดี

ฉันอยากรู้ว่าฉันควรจะทำอะไรที่นี่เพื่อสร้างความปวดหัวให้กับผู้ใช้น้อยที่สุดในขณะที่ยังให้การสนับสนุนการบันทึกที่เหมาะสมหากต้องการ

คำตอบ:


113

คำจำกัดความ

เรามี 3 อินเทอร์เฟซ: ILogger, ILoggerProviderและILoggerFactory. ลองดูซอร์สโค้ดเพื่อค้นหาความรับผิดชอบของพวกเขา:

ILogger : เป็นผู้รับผิดชอบในการเขียนข้อความบันทึกการกำหนดระดับการเข้าสู่ระบบ

ILoggerProvider : มีหน้าที่สร้างอินสแตนซ์ของILogger(คุณไม่ควรใช้ILoggerProviderเพื่อสร้างคนตัดไม้โดยตรง)

ILoggerFactory : คุณสามารถลงทะเบียนหนึ่งหรือหลายILoggerProviderโรงงานกับโรงงานซึ่งจะใช้ทั้งหมดเพื่อสร้างอินสแตนซ์ของILogger. ILoggerFactoryมีคอลเลกชันของILoggerProviders.

ในตัวอย่างด้านล่างเรากำลังลงทะเบียนผู้ให้บริการ 2 ราย (คอนโซลและไฟล์) กับโรงงาน เมื่อเราสร้างคนตัดไม้โรงงานจะใช้ผู้ให้บริการทั้งสองรายนี้เพื่อสร้างอินสแตนซ์ของคนตัดไม้:

ILoggerFactory factory = new LoggerFactory().AddConsole();    // add console provider
factory.AddProvider(new LoggerFileProvider("c:\\log.txt"));   // add file provider
Logger logger = factory.CreateLogger(); // <-- creates a console logger and a file logger

ดังนั้นคนตัดไม้เองก็ดูแลคอลเลกชันของILoggers และเขียนข้อความบันทึกถึงพวกเขาทั้งหมด เมื่อดูที่ซอร์สโค้ด LoggerเราสามารถยืนยันLoggerได้ว่ามีอาร์เรย์ของILoggers(เช่นLoggerInformation[]) และในขณะเดียวกันก็มีการใช้งานILoggerอินเทอร์เฟซ


การฉีดพึ่งพา

เอกสาร MSมี 2 ​​วิธีในการฉีดคนตัดไม้:

1. ฉีดโรงงาน:

public TodoController(ITodoRepository todoRepository, ILoggerFactory logger)
{
    _todoRepository = todoRepository;
    _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
}

สร้าง Logger กับหมวดหมู่ = TodoApi.Controllers.TodoController

2. การฉีดยาทั่วไปILogger<T>:

public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
{
    _todoRepository = todoRepository;
    _logger = logger;
}

สร้างคนตัดไม้ที่มี Category = ชื่อชนิดแบบเต็มของ TodoController


ILoggerในความคิดของสิ่งที่ทำให้เกิดความสับสนในเอกสารก็คือว่ามันไม่ได้พูดถึงอะไรเกี่ยวกับการฉีดที่ไม่ใช่ทั่วไป ในตัวอย่างเดียวกันข้างต้นเรากำลังฉีดสารที่ไม่ใช่ยาสามัญITodoRepositoryแต่ยังไม่ได้อธิบายว่าทำไมเราถึงไม่ทำเช่นเดียวกันILoggerนี้

อ้างอิงจากMark Seemann :

ตัวสร้างการฉีดไม่ควรทำมากไปกว่าการรับการอ้างอิง

การฉีดโรงงานเข้าไปในคอนโทรลเลอร์ไม่ใช่แนวทางที่ดีเนื่องจากผู้ควบคุมไม่รับผิดชอบในการเริ่มต้น Logger (การละเมิด SRP) ในขณะเดียวกันการฉีดสารทั่วไปILogger<T>จะเพิ่มเสียงรบกวนที่ไม่จำเป็น ดูบล็อกของ Simple Injectorสำหรับรายละเอียดเพิ่มเติม: มีอะไรผิดปกติกับ ASP.NET Core DI ที่เป็นนามธรรม?

สิ่งที่ควรฉีด (อย่างน้อยตามบทความด้านบน) ไม่ใช่แบบทั่วไปILoggerแต่นั่นไม่ใช่สิ่งที่คอนเทนเนอร์ DI ในตัวของ Microsoft สามารถทำได้และคุณต้องใช้ DI Library ของบุคคลที่สาม เอกสารทั้งสองนี้ อธิบายถึงวิธีที่คุณสามารถใช้ไลบรารีของบุคคลที่สามกับ. NET Core


นี่เป็นอีกบทความหนึ่งของ Nikola Malovic ซึ่งเขาอธิบายถึงกฎ 5 ข้อของ IoC

กฎข้อที่ 4 ของ IoC ของ Nikola

ตัวสร้างทุกตัวของคลาสที่ได้รับการแก้ไขไม่ควรมีการนำไปใช้งานใด ๆ นอกจากยอมรับชุดการอ้างอิงของตัวเอง


3
คำตอบของคุณและบทความของสตีเวนนั้นถูกต้องที่สุดและน่าหดหู่ที่สุดในเวลาเดียวกัน
bokibeg

30

ILoggerProviderเหล่านี้จะถูกต้องทั้งหมดยกเว้น ILoggerและILogger<T>เป็นสิ่งที่คุณควรใช้ในการบันทึก ในการรับILoggerคุณใช้ILoggerFactoryไฟล์. ILogger<T>เป็นทางลัดเพื่อรับคนตัดไม้สำหรับหมวดหมู่เฉพาะ (ทางลัดสำหรับประเภทเป็นหมวดหมู่)

เมื่อคุณใช้ILoggerเพื่อดำเนินการบันทึกแต่ละคนที่ลงทะเบียนILoggerProviderจะมีโอกาสจัดการกับข้อความบันทึกนั้น มันไม่ถูกต้องสำหรับการใช้รหัสเพื่อโทรเข้าILoggerProviderโดยตรง


ขอบคุณ. สิ่งนี้ทำให้ฉันคิดว่าวิธีILoggerFactoryที่ดีในการวางสาย DI ให้กับผู้บริโภคเป็นวิธีที่ดีโดยมีการพึ่งพาเพียง 1 ครั้งเท่านั้น ( "ให้โรงงานฉันแล้วฉันจะสร้างคนตัดไม้ของตัวเอง" ) แต่จะป้องกันไม่ให้ใช้คนตัดไม้ที่มีอยู่ ( เว้นแต่ผู้บริโภคจะใช้กระดาษห่อหุ้ม) การใช้ILogger- ทั่วไปหรือไม่ - ช่วยให้ผู้บริโภคสามารถให้เครื่องตัดไม้แก่ฉันที่พวกเขาเตรียมไว้เป็นพิเศษ แต่ทำให้การตั้งค่า DI อาจซับซ้อนมากขึ้น (เว้นแต่จะใช้คอนเทนเนอร์ DI ที่รองรับ Open Generics) ถูกต้องหรือไม่ ในกรณีนั้นฉันคิดว่าฉันจะไปกับโรงงาน
Michael Stum

3
@MichaelStum - ฉันไม่แน่ใจว่าฉันทำตามตรรกะของคุณที่นี่ คุณคาดหวังว่าผู้บริโภคของคุณจะใช้ระบบ DI แต่ต้องการเข้าครอบครองและจัดการการอ้างอิงด้วยตนเองภายในห้องสมุดของคุณหรือไม่? เหตุใดจึงดูเหมือนเป็นแนวทางที่ถูกต้อง
Damien_The_Unbeliever

@Damien_The_Unbeliever นั่นคือจุดที่ดี การเข้าโรงงานดูเหมือนเป็นเรื่องแปลก ฉันคิดว่าแทนที่จะรับILogger<T>ฉันจะใช้ILogger<MyClass>หรือแม้แต่ILoggerแทน - ด้วยวิธีนี้ผู้ใช้สามารถต่อสายได้ด้วยการลงทะเบียนเพียงครั้งเดียวและไม่ต้องใช้ชื่อสามัญแบบเปิดในคอนเทนเนอร์ DI เอนเอียงไปยังสิ่งที่ไม่ใช่ทั่วไปILoggerแต่จะทดลองมากในช่วงสุดสัปดาห์
Michael Stum

10

ILogger<T>เป็นคนจริงที่จะทำสำหรับ DI ILogger เข้ามาเพื่อช่วยให้ใช้รูปแบบโรงงานได้ง่ายขึ้นมากแทนที่จะเขียนตรรกะ DI และ Factory ด้วยตัวเองทั้งหมดนั่นเป็นการตัดสินใจที่ชาญฉลาดที่สุดอย่างหนึ่งในแกนหลักของ asp.net

คุณสามารถเลือกระหว่าง:

ILogger<T>หากคุณจำเป็นต้องใช้รูปแบบโรงงานและ DI ในโค้ดของคุณหรือคุณสามารถใช้ILoggerเพื่อใช้การบันทึกแบบง่ายโดยไม่จำเป็นต้องใช้ DI

ด้วยเหตุILoggerProviderนี้จึงเป็นเพียงสะพานสำหรับจัดการข้อความบันทึกแต่ละรายการที่ลงทะเบียนไว้ ไม่จำเป็นต้องใช้มันเพราะมันไม่มีผลอะไรที่คุณควรจะเข้าไปแทรกแซงโค้ดมันจะฟัง ILoggerProvider ที่ลงทะเบียนและจัดการกับข้อความ เกี่ยวกับมัน.


1
ILogger vs ILogger <T> เกี่ยวข้องกับ DI อย่างไร อาจจะฉีดไม่?
Matthew Hostetler

จริงๆแล้วมันคือ ILogger <TCategoryName> ใน MS Docs มาจาก ILogger และไม่มีการเพิ่มฟังก์ชันการทำงานใหม่ยกเว้นชื่อคลาสที่จะบันทึก นอกจากนี้ยังมีประเภทที่ไม่ซ้ำกันดังนั้น DI จึงฉีดตัวบันทึกชื่อที่ถูกต้อง this ILoggerส่วนขยายของไมโครซอฟท์ขยายไม่ใช่ทั่วไป
Samuel Danielson

8

ตามคำถามฉันเชื่อว่าILogger<T>เป็นตัวเลือกที่ถูกต้องโดยพิจารณาจากข้อเสียของตัวเลือกอื่น ๆ :

  1. การฉีดILoggerFactoryบังคับให้ผู้ใช้ของคุณมอบการควบคุมโรงงานคนตัดไม้ระดับโลกที่ไม่แน่นอนให้กับไลบรารีคลาสของคุณ ยิ่งไปกว่านั้นโดยการยอมรับILoggerFactoryชั้นเรียนของคุณตอนนี้สามารถเขียนบันทึกด้วยชื่อหมวดหมู่ใดก็ได้โดยใช้CreateLoggerวิธีการ แม้ว่าILoggerFactoryโดยปกติจะมีให้ใช้งานในรูปแบบซิงเกิลตันในคอนเทนเนอร์ DI แต่ฉันในฐานะผู้ใช้คงสงสัยว่าทำไมต้องใช้ไลบรารีใด ๆ
  2. ในขณะที่วิธีการILoggerProvider.CreateLoggerดูเหมือนไม่ได้มีไว้สำหรับการฉีด ใช้ILoggerFactory.AddProviderเพื่อให้โรงงานสามารถสร้างการรวมILoggerที่เขียนไปยังหลาย ๆ ที่ILoggerสร้างขึ้นจากผู้ให้บริการที่ลงทะเบียนแต่ละราย สิ่งนี้ชัดเจนเมื่อคุณตรวจสอบการใช้งานLoggerFactory.CreateLogger
  3. การยอมรับILoggerก็ดูเหมือนจะเป็นหนทางไป แต่มันเป็นไปไม่ได้กับ. NET Core DI สิ่งนี้ฟังดูเหมือนเป็นเหตุผลว่าทำไมพวกเขาถึงต้องจัดหาILogger<T>ตั้งแต่แรก

ดังนั้นเราจึงไม่มีทางเลือกที่ดีไปกว่าILogger<T>ถ้าเราจะเลือกจากชั้นเรียนเหล่านั้น

อีกวิธีหนึ่งคือการฉีดอย่างอื่นที่ไม่ใช้ยาทั่วไปILoggerซึ่งในกรณีนี้ควรไม่ใช่แบบทั่วไป แนวคิดก็คือการห่อด้วยชั้นเรียนของคุณเองคุณจะสามารถควบคุมวิธีการกำหนดค่าของผู้ใช้ได้อย่างเต็มที่


6

ILogger<T>วิธีการเริ่มต้นมีขึ้นเพื่อเป็น ซึ่งหมายความว่าในบันทึกจะสามารถมองเห็นบันทึกจากคลาสที่ระบุได้อย่างชัดเจนเนื่องจากจะมีชื่อคลาสเต็มเป็นบริบท ตัวอย่างเช่นถ้าชื่อเต็มของชั้นเรียนของคุณคือMyLibrary.MyClassคุณจะได้รับสิ่งนี้ในรายการบันทึกที่สร้างโดยคลาสนี้ ตัวอย่างเช่น:

MyLibrary.MyClass: Information: บันทึกข้อมูลของฉัน

คุณควรใช้ILoggerFactoryถ้าคุณต้องการระบุบริบทของคุณเอง ตัวอย่างเช่นบันทึกทั้งหมดจากไลบรารีของคุณมีบริบทบันทึกเดียวกันแทนทุกคลาส ตัวอย่างเช่น:

loggerFactory.CreateLogger("MyLibrary");

จากนั้นบันทึกจะมีลักษณะดังนี้:

MyLibrary: Information: บันทึกข้อมูลของฉัน

หากคุณทำเช่นนั้นในทุกชั้นเรียนบริบทจะเป็นเพียง MyLibrary สำหรับทุกชั้นเรียน ฉันคิดว่าคุณคงต้องการทำเช่นนั้นสำหรับไลบรารีถ้าคุณไม่ต้องการเปิดเผยโครงสร้างชั้นในในบันทึก

เกี่ยวกับการบันทึกที่เป็นทางเลือก ฉันคิดว่าคุณควรต้องใช้ ILogger หรือ ILoggerFactory ในตัวสร้างเสมอและปล่อยให้ผู้บริโภคของไลบรารีปิดหรือให้ Logger ที่ไม่ทำอะไรเลยในการแทรกการพึ่งพาหากพวกเขาไม่ต้องการบันทึก มันง่ายมากที่จะเปลี่ยนการบันทึกสำหรับบริบทเฉพาะในการกำหนดค่า ตัวอย่างเช่น:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "MyLibrary": "None"
    }
  }
}

5

สำหรับแนวทางที่ดีในการออกแบบห้องสมุดคือ

1. อย่าบังคับให้ผู้บริโภคฉีดยาคนตัดไม้ในชั้นเรียนของคุณ เพียงสร้าง ctor อื่นผ่าน NullLoggerFactory

class MyClass
{
    private readonly ILoggerFactory _loggerFactory;

    public MyClass():this(NullLoggerFactory.Instance)
    {

    }
    public MyClass(ILoggerFactory loggerFactory)
    {
      this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
    }
}

2. จำกัด จำนวนหมวดหมู่ที่คุณใช้เมื่อสร้างตัวบันทึกเพื่อให้ผู้บริโภคกำหนดค่าการกรองบันทึกได้อย่างง่ายดาย this._loggerFactory.CreateLogger(Consts.CategoryName)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.