แนวทางปฏิบัติที่ดีที่สุดของ Logger Wrapper


91

ฉันต้องการใช้ nlogger ในแอปพลิเคชันของฉันบางทีในอนาคตฉันจะต้องเปลี่ยนระบบการบันทึก เลยอยากใช้ซุ้มไม้ซุง

คุณทราบคำแนะนำสำหรับตัวอย่างที่มีอยู่ว่าจะเขียนอย่างไร? หรือเพียงแค่ให้ลิงก์ไปยังแนวทางปฏิบัติที่ดีที่สุดในด้านนี้


4
มีอยู่แล้ว: netcommon.sourceforge.net
R. Martinho Fernandes

คุณลองดูโครงการ The Simple Logging Facade บน Codeplexหรือยัง?
JefClaes

ที่เกี่ยวข้อง: stackoverflow.com/questions/9892137/…
Steven

คำตอบ:


207

ฉันเคยใช้หน้าตัดไม้เช่นCommon.Logging (แม้จะซ่อนไลบรารีCuttingEdge.Loggingของตัวเอง) แต่ปัจจุบันฉันใช้รูปแบบ Dependency Injectionและสิ่งนี้ช่วยให้ฉันซ่อนคนตัดไม้ไว้ด้านหลังนามธรรม (ง่าย ๆ ) ของตัวเองที่ยึดตามทั้งDependency หลักการผกผันและหลักการแยกส่วนต่อประสาน(ISP) เนื่องจากมีสมาชิกหนึ่งคนและเนื่องจากอินเทอร์เฟซถูกกำหนดโดยแอปพลิเคชันของฉัน ไม่ใช่ไลบรารีภายนอก การลดความรู้ที่ส่วนหลักของแอปพลิเคชันของคุณมีเกี่ยวกับการมีอยู่ของไลบรารีภายนอกให้น้อยที่สุดยิ่งดี แม้ว่าคุณจะไม่มีความตั้งใจที่จะเปลี่ยนไลบรารีการบันทึกของคุณก็ตาม การพึ่งพาไลบรารีภายนอกอย่างหนักทำให้การทดสอบโค้ดของคุณยากขึ้นและทำให้แอปพลิเคชันของคุณซับซ้อนขึ้นด้วย API ที่ไม่เคยออกแบบมาเฉพาะสำหรับแอปพลิเคชัน

นี่คือสิ่งที่นามธรรมมักจะดูเหมือนในแอปพลิเคชันของฉัน:

public interface ILogger
{
    void Log(LogEntry entry);
}

public enum LoggingEventType { Debug, Information, Warning, Error, Fatal };

// Immutable DTO that contains the log information.
public class LogEntry 
{
    public readonly LoggingEventType Severity;
    public readonly string Message;
    public readonly Exception Exception;

    public LogEntry(LoggingEventType severity, string message, Exception exception = null)
    {
        if (message == null) throw new ArgumentNullException("message");
        if (message == string.Empty) throw new ArgumentException("empty", "message");

        this.Severity = severity;
        this.Message = message;
        this.Exception = exception;
    }
}

คุณสามารถเลือกที่จะขยายสิ่งที่เป็นนามธรรมนี้ด้วยวิธีการขยายแบบง่าย ๆ (ทำให้อินเทอร์เฟซแคบและยึดติดกับ ISP) ทำให้โค้ดสำหรับผู้บริโภคของอินเทอร์เฟซนี้ง่ายขึ้นมาก:

public static class LoggerExtensions
{
    public static void Log(this ILogger logger, string message) {
        logger.Log(new LogEntry(LoggingEventType.Information, message));
    }

    public static void Log(this ILogger logger, Exception exception) {
        logger.Log(new LogEntry(LoggingEventType.Error, exception.Message, exception));
    }

    // More methods here.
}

ตั้งแต่อินเตอร์เฟซที่มีเพียงวิธีเดียวที่คุณสามารถสร้างILoggerการใช้งานที่ผู้รับมอบฉันทะ log4net , การ Serilog , Microsoft.Extensions.Logging , NLog หรือไลบรารีการเข้าสู่ระบบอื่น ๆ และกำหนดค่าภาชนะ DI ของคุณที่จะฉีดในชั้นเรียนที่มีILoggerอยู่ในพวกเขา ผู้สร้าง

โปรดทราบว่าการมีวิธีการขยายแบบคงที่ที่ด้านบนของอินเทอร์เฟซด้วยวิธีการเดียวนั้นค่อนข้างแตกต่างจากการมีส่วนต่อประสานกับสมาชิกจำนวนมาก วิธีการขยายเป็นเพียงวิธีการช่วยเหลือที่สร้างLogEntryข้อความและส่งผ่านวิธีการเดียวบนILoggerอินเทอร์เฟซ วิธีการขยายกลายเป็นส่วนหนึ่งของรหัสของผู้บริโภค ไม่ใช่ส่วนหนึ่งของนามธรรม สิ่งนี้ไม่เพียงช่วยให้วิธีการขยายสามารถพัฒนาได้โดยไม่จำเป็นต้องเปลี่ยนสิ่งที่เป็นนามธรรมวิธีการขยายและLogEntryตัวสร้างจะถูกเรียกใช้งานเสมอเมื่อมีการใช้นามธรรมของคนตัดไม้แม้ว่าคนตัดไม้นั้นจะถูกตัดออก / ล้อเลียนก็ตาม สิ่งนี้ให้ความมั่นใจมากขึ้นเกี่ยวกับความถูกต้องของการโทรไปยังคนตัดไม้เมื่อทำงานในชุดทดสอบ อินเทอร์เฟซแบบแผ่นเดียวทำให้การทดสอบง่ายขึ้นมากเช่นกัน การมีสิ่งที่เป็นนามธรรมร่วมกับสมาชิกจำนวนมากทำให้ยากที่จะสร้างการนำไปใช้งาน (เช่นล้อเลียนอะแดปเตอร์และมัณฑนากร)

เมื่อคุณทำสิ่งนี้แทบจะไม่จำเป็นต้องมีสิ่งที่เป็นนามธรรมแบบคงที่ซึ่งการบันทึกด้านหน้า (หรือไลบรารีอื่น ๆ ) อาจมีให้


4
@GabrielEspinoza: ทั้งหมดนี้ขึ้นอยู่กับเนมสเปซที่คุณวางเมธอดส่วนขยายไว้ถ้าคุณวางไว้ในเนมสเปซเดียวกับอินเทอร์เฟซหรือในเนมสเปซรูทของโปรเจ็กต์ของคุณปัญหาจะไม่มีอยู่จริง
Steven

2
@ user1829319 เป็นเพียงตัวอย่างเท่านั้น ฉันแน่ใจว่าคุณสามารถนำไปใช้ตามคำตอบนี้ที่เหมาะกับความต้องการเฉพาะของคุณ
Steven

2
ฉันยังไม่เข้าใจ ... ข้อดีของการมี 5 วิธี Logger เป็นส่วนขยายของ ILogger และไม่ได้เป็นสมาชิกของ ILogger อยู่ตรงไหน?
Elisabeth

3
@Elisabeth ข้อดีคือคุณสามารถปรับแต่งส่วนต่อประสานด้านหน้ากับเฟรมเวิร์กการบันทึกใดก็ได้เพียงแค่ใช้ฟังก์ชันเดียว: "ILogger :: Log" วิธีการส่วนขยายช่วยให้มั่นใจได้ว่าเราสามารถเข้าถึง API 'ความสะดวกสบาย' (เช่น "LogError" "LogWarning" ฯลฯ ) ไม่ว่าคุณจะตัดสินใจใช้กรอบงานใด เป็นวิธีการอ้อมในการเพิ่มฟังก์ชัน 'คลาสพื้นฐาน' ทั่วไปแม้ว่าจะทำงานร่วมกับอินเทอร์เฟซ C #
BTownTKD

2
ฉันต้องขอย้ำอีกครั้งว่าทำไมสิ่งนี้ถึงดีมาก การแปลงรหัสของคุณจาก DotNetFramework เป็น DotNetCore โครงการที่ฉันทำนี้ฉันต้องเขียนคอนกรีตใหม่เพียงชิ้นเดียว คนที่ฉันไม่ได้ .... gaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa! ฉันดีใจที่พบ "ทางกลับ" นี้
granadaCoder

10

ฉันใช้อะแดปเตอร์ wrapper + อินเทอร์เฟซขนาดเล็กจากhttps://github.com/uhaciogullari/NLogอินเทอร์เฟซที่มีให้ผ่าน NuGet :

PM> Install-Package NLog.Interface 

10
มีอินเทอร์เฟซ ILogger ในไลบรารี NLog โดย v4.0 คุณไม่ต้องการห้องสมุดนี้อีกต่อไป
Jowen

8

ณ ตอนนี้ทางออกที่ดีที่สุดคือใช้แพ็คเกจMicrosoft.Extensions.Logging ( ตามที่ Julian ชี้ให้เห็น ) กรอบการบันทึกส่วนใหญ่สามารถใช้กับสิ่งนี้ได้

การกำหนดอินเทอร์เฟซของคุณเองตามที่อธิบายไว้ในคำตอบของสตีเวนนั้นใช้ได้สำหรับกรณีง่ายๆ แต่พลาดบางสิ่งที่ฉันคิดว่าสำคัญ:

  • การบันทึกที่มีโครงสร้างและการยกเลิกโครงสร้างวัตถุ (สัญกรณ์ @ ใน Serilog และ NLog)
  • การสร้าง / การจัดรูปแบบสตริงล่าช้า: เนื่องจากต้องใช้สตริงจึงต้องประเมิน / จัดรูปแบบทุกอย่างเมื่อถูกเรียกแม้ว่าในท้ายที่สุดเหตุการณ์จะไม่ถูกบันทึกเนื่องจากต่ำกว่าเกณฑ์ (ต้นทุนประสิทธิภาพดูจุดก่อนหน้า)
  • การตรวจสอบเงื่อนไขตามIsEnabled(LogLevel)ที่คุณต้องการเพื่อเหตุผลด้านประสิทธิภาพอีกครั้ง

คุณสามารถนำสิ่งเหล่านี้ไปใช้ได้ทั้งหมดในนามธรรมของคุณเอง แต่เมื่อถึงจุดนั้นคุณจะต้องสร้างวงล้อขึ้นมาใหม่


4

โดยทั่วไปฉันชอบสร้างอินเทอร์เฟซเช่น

public interface ILogger
{
 void LogInformation(string msg);
 void LogError(string error);
}

และในรันไทม์ฉันฉีดคลาสคอนกรีตที่ใช้งานจากอินเทอร์เฟซนี้


11
และไม่ลืมLogWarningและLogCriticalวิธีการและทับถมของพวกเขาทั้งหมด เมื่อทำเช่นนี้คุณจะละเมิดการเชื่อมต่อแยกหลักการ ชอบกำหนดILoggerอินเทอร์เฟซด้วยLogวิธีการเดียว
Steven

2
ฉันขอโทษจริงๆนั่นไม่ใช่ความตั้งใจของฉัน ไม่จำเป็นต้องละอายใจ ฉันเห็นการออกแบบนี้บ่อยมากเพราะนักพัฒนาหลายคนใช้ log4net ยอดนิยม (ซึ่งใช้การออกแบบที่แน่นอนนี้) เป็นตัวอย่าง น่าเสียดายที่การออกแบบไม่ดีจริงๆ
Steven

2
ฉันชอบสิ่งนี้มากกว่าคำตอบของ @ Steven เขาแนะนำให้พึ่งพาและทำให้การพึ่งพาLogEntry การดำเนินการต้องรับมือกับสิ่งเหล่านี้อาจจะแม้ว่าซึ่งเป็นกลิ่นรหัส ซ่อนการพึ่งพาทำไม? การนำไปใช้งานจะต้องจัดการกับระดับการบันทึกอยู่แล้วดังนั้นจึงเป็นการดีกว่าที่จะระบุอย่างชัดเจนเกี่ยวกับสิ่งที่การนำไปใช้งานควรทำแทนที่จะซ่อนไว้หลังวิธีการเดียวที่มีอาร์กิวเมนต์ทั่วไป LoggingEventTypeILoggerLoggingEventTypescase/switchLoggingEventTypes
DharmaTurtle

1
ในฐานะที่เป็นตัวอย่างมากจินตนาการICommandซึ่งมีซึ่งจะนำHandle objectการนำไปใช้งานต้องcase/switchอยู่ในประเภทที่เป็นไปได้เพื่อให้เป็นไปตามสัญญาของอินเทอร์เฟซ สิ่งนี้ไม่เหมาะ อย่ามีสิ่งที่เป็นนามธรรมซึ่งซ่อนการพึ่งพาที่ต้องจัดการอยู่ดี มีอินเทอร์เฟซที่ระบุสิ่งที่คาดไว้อย่างชัดเจนแทน: "ฉันคาดว่าคนตัดไม้ทุกคนจะจัดการคำเตือนข้อผิดพลาดร้ายแรง ฯลฯ " สิ่งนี้ดีกว่าสำหรับ "ฉันคาดหวังว่าคนตัดไม้ทุกคนจะจัดการกับข้อความซึ่งรวมถึงคำเตือนข้อผิดพลาดการเสียชีวิต ฯลฯ "
DharmaTurtle

ฉันเห็นด้วยกับทั้ง @Steven และ @DharmaTurtle นอกจากนี้LoggingEventTypeควรเรียกว่าLoggingEventLevelประเภทเป็นคลาสและควรเข้ารหัสใน OOP สำหรับฉันไม่มีความแตกต่างระหว่างการไม่ใช้วิธีการเชื่อมต่อกับการไม่ใช้enumค่าที่สอดคล้องกัน แทนที่จะใช้ErrorLoggger : ILoggerโดยInformationLogger : ILoggerที่คนตัดไม้ทุกคนกำหนดระดับของตัวเอง จากนั้น DI จำเป็นต้องฉีดเครื่องมือตัดไม้ที่จำเป็นซึ่งอาจผ่านทางคีย์ (enum) แต่คีย์นี้ไม่ได้เป็นส่วนหนึ่งของอินเทอร์เฟซอีกต่อไป (ตอนนี้คุณเป็นของแข็งแล้ว)
Wouter

4

วิธีแก้ปัญหาที่ยอดเยี่ยมสำหรับปัญหานี้เกิดขึ้นในรูปแบบของโครงการLibLog

LibLog เป็นนามธรรมการบันทึกที่มีการสนับสนุนในตัวสำหรับคนตัดไม้รายใหญ่เช่น Serilog, NLog, Log4net และ Enterprise logger มีการติดตั้งผ่านตัวจัดการแพ็คเกจ NuGet ลงในไลบรารีเป้าหมายเป็นไฟล์ซอร์ส (.cs) แทนการอ้างอิง. dll วิธีการดังกล่าวช่วยให้สามารถรวมสิ่งที่เป็นนามธรรมของการบันทึกได้โดยไม่ต้องบังคับให้ไลบรารีต้องพึ่งพาการพึ่งพาภายนอก นอกจากนี้ยังช่วยให้ผู้เขียนไลบรารีสามารถรวมการบันทึกโดยไม่ต้องบังคับให้แอปพลิเคชันที่ใช้งานอยู่ต้องจัดเตรียมตัวบันทึกข้อมูลให้กับไลบรารีอย่างชัดเจน LibLog ใช้การไตร่ตรองเพื่อดูว่ามีการใช้เครื่องตัดไม้คอนกรีตแบบใดและเชื่อมต่อกับเครื่องตัดไม้โดยไม่ต้องมีรหัสการเดินสายที่ชัดเจนในโครงการไลบรารี

ดังนั้น LibLog จึงเป็นทางออกที่ยอดเยี่ยมสำหรับการบันทึกภายในโครงการห้องสมุด เพียงอ้างอิงและกำหนดค่าเครื่องตัดไม้ที่เป็นรูปธรรม (Serilog for the win) ในแอปพลิเคชันหรือบริการหลักของคุณแล้วเพิ่ม LibLog ในห้องสมุดของคุณ!


ฉันเคยใช้สิ่งนี้เพื่อแก้ไขปัญหาการเปลี่ยนแปลงที่ทำลาย log4net (yuck) ( wiktorzychla.com/2012/03/pathetic-breaking-change-between.html ) หากคุณได้รับสิ่งนี้จาก nuget มันจะสร้างไฟล์. cs ในโค้ดของคุณแทนที่จะเพิ่มการอ้างอิงไปยัง dll ที่คอมไพล์ไว้ล่วงหน้า ไฟล์. cs ถูกเนมสเปซไปยังโปรเจ็กต์ของคุณ ดังนั้นหากคุณมีเลเยอร์ที่แตกต่างกัน (csprojs) คุณจะมีหลายเวอร์ชันหรือคุณต้องรวมเข้ากับ csproj ที่แชร์ คุณจะเข้าใจสิ่งนี้เมื่อคุณพยายามใช้ แต่อย่างที่ฉันพูดนี่คือตัวช่วยชีวิตที่มีปัญหาการเปลี่ยนแปลงของ log4net
granadaCoder


2

ตั้งแต่ปี 2015 คุณยังสามารถใช้. NET Core Loggingหากคุณกำลังสร้างแอปพลิเคชันหลักของ. NET

แพ็คเกจสำหรับ NLog ที่จะเชื่อมต่อคือ:

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