ฉันต้องการใช้ nlogger ในแอปพลิเคชันของฉันบางทีในอนาคตฉันจะต้องเปลี่ยนระบบการบันทึก เลยอยากใช้ซุ้มไม้ซุง
คุณทราบคำแนะนำสำหรับตัวอย่างที่มีอยู่ว่าจะเขียนอย่างไร? หรือเพียงแค่ให้ลิงก์ไปยังแนวทางปฏิบัติที่ดีที่สุดในด้านนี้
ฉันต้องการใช้ nlogger ในแอปพลิเคชันของฉันบางทีในอนาคตฉันจะต้องเปลี่ยนระบบการบันทึก เลยอยากใช้ซุ้มไม้ซุง
คุณทราบคำแนะนำสำหรับตัวอย่างที่มีอยู่ว่าจะเขียนอย่างไร? หรือเพียงแค่ให้ลิงก์ไปยังแนวทางปฏิบัติที่ดีที่สุดในด้านนี้
คำตอบ:
ฉันเคยใช้หน้าตัดไม้เช่น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
ตัวสร้างจะถูกเรียกใช้งานเสมอเมื่อมีการใช้นามธรรมของคนตัดไม้แม้ว่าคนตัดไม้นั้นจะถูกตัดออก / ล้อเลียนก็ตาม สิ่งนี้ให้ความมั่นใจมากขึ้นเกี่ยวกับความถูกต้องของการโทรไปยังคนตัดไม้เมื่อทำงานในชุดทดสอบ อินเทอร์เฟซแบบแผ่นเดียวทำให้การทดสอบง่ายขึ้นมากเช่นกัน การมีสิ่งที่เป็นนามธรรมร่วมกับสมาชิกจำนวนมากทำให้ยากที่จะสร้างการนำไปใช้งาน (เช่นล้อเลียนอะแดปเตอร์และมัณฑนากร)
เมื่อคุณทำสิ่งนี้แทบจะไม่จำเป็นต้องมีสิ่งที่เป็นนามธรรมแบบคงที่ซึ่งการบันทึกด้านหน้า (หรือไลบรารีอื่น ๆ ) อาจมีให้
ฉันใช้อะแดปเตอร์ wrapper + อินเทอร์เฟซขนาดเล็กจากhttps://github.com/uhaciogullari/NLogอินเทอร์เฟซที่มีให้ผ่าน NuGet :
PM> Install-Package NLog.Interface
ณ ตอนนี้ทางออกที่ดีที่สุดคือใช้แพ็คเกจMicrosoft.Extensions.Logging ( ตามที่ Julian ชี้ให้เห็น ) กรอบการบันทึกส่วนใหญ่สามารถใช้กับสิ่งนี้ได้
การกำหนดอินเทอร์เฟซของคุณเองตามที่อธิบายไว้ในคำตอบของสตีเวนนั้นใช้ได้สำหรับกรณีง่ายๆ แต่พลาดบางสิ่งที่ฉันคิดว่าสำคัญ:
IsEnabled(LogLevel)
ที่คุณต้องการเพื่อเหตุผลด้านประสิทธิภาพอีกครั้งคุณสามารถนำสิ่งเหล่านี้ไปใช้ได้ทั้งหมดในนามธรรมของคุณเอง แต่เมื่อถึงจุดนั้นคุณจะต้องสร้างวงล้อขึ้นมาใหม่
โดยทั่วไปฉันชอบสร้างอินเทอร์เฟซเช่น
public interface ILogger
{
void LogInformation(string msg);
void LogError(string error);
}
และในรันไทม์ฉันฉีดคลาสคอนกรีตที่ใช้งานจากอินเทอร์เฟซนี้
LogWarning
และLogCritical
วิธีการและทับถมของพวกเขาทั้งหมด เมื่อทำเช่นนี้คุณจะละเมิดการเชื่อมต่อแยกหลักการ ชอบกำหนดILogger
อินเทอร์เฟซด้วยLog
วิธีการเดียว
LogEntry
การดำเนินการต้องรับมือกับสิ่งเหล่านี้อาจจะแม้ว่าซึ่งเป็นกลิ่นรหัส ซ่อนการพึ่งพาทำไม? การนำไปใช้งานจะต้องจัดการกับระดับการบันทึกอยู่แล้วดังนั้นจึงเป็นการดีกว่าที่จะระบุอย่างชัดเจนเกี่ยวกับสิ่งที่การนำไปใช้งานควรทำแทนที่จะซ่อนไว้หลังวิธีการเดียวที่มีอาร์กิวเมนต์ทั่วไป LoggingEventType
ILogger
LoggingEventTypes
case/switch
LoggingEventTypes
ICommand
ซึ่งมีซึ่งจะนำHandle
object
การนำไปใช้งานต้องcase/switch
อยู่ในประเภทที่เป็นไปได้เพื่อให้เป็นไปตามสัญญาของอินเทอร์เฟซ สิ่งนี้ไม่เหมาะ อย่ามีสิ่งที่เป็นนามธรรมซึ่งซ่อนการพึ่งพาที่ต้องจัดการอยู่ดี มีอินเทอร์เฟซที่ระบุสิ่งที่คาดไว้อย่างชัดเจนแทน: "ฉันคาดว่าคนตัดไม้ทุกคนจะจัดการคำเตือนข้อผิดพลาดร้ายแรง ฯลฯ " สิ่งนี้ดีกว่าสำหรับ "ฉันคาดหวังว่าคนตัดไม้ทุกคนจะจัดการกับข้อความซึ่งรวมถึงคำเตือนข้อผิดพลาดการเสียชีวิต ฯลฯ "
LoggingEventType
ควรเรียกว่าLoggingEventLevel
ประเภทเป็นคลาสและควรเข้ารหัสใน OOP สำหรับฉันไม่มีความแตกต่างระหว่างการไม่ใช้วิธีการเชื่อมต่อกับการไม่ใช้enum
ค่าที่สอดคล้องกัน แทนที่จะใช้ErrorLoggger : ILogger
โดยInformationLogger : ILogger
ที่คนตัดไม้ทุกคนกำหนดระดับของตัวเอง จากนั้น DI จำเป็นต้องฉีดเครื่องมือตัดไม้ที่จำเป็นซึ่งอาจผ่านทางคีย์ (enum) แต่คีย์นี้ไม่ได้เป็นส่วนหนึ่งของอินเทอร์เฟซอีกต่อไป (ตอนนี้คุณเป็นของแข็งแล้ว)
วิธีแก้ปัญหาที่ยอดเยี่ยมสำหรับปัญหานี้เกิดขึ้นในรูปแบบของโครงการLibLog
LibLog เป็นนามธรรมการบันทึกที่มีการสนับสนุนในตัวสำหรับคนตัดไม้รายใหญ่เช่น Serilog, NLog, Log4net และ Enterprise logger มีการติดตั้งผ่านตัวจัดการแพ็คเกจ NuGet ลงในไลบรารีเป้าหมายเป็นไฟล์ซอร์ส (.cs) แทนการอ้างอิง. dll วิธีการดังกล่าวช่วยให้สามารถรวมสิ่งที่เป็นนามธรรมของการบันทึกได้โดยไม่ต้องบังคับให้ไลบรารีต้องพึ่งพาการพึ่งพาภายนอก นอกจากนี้ยังช่วยให้ผู้เขียนไลบรารีสามารถรวมการบันทึกโดยไม่ต้องบังคับให้แอปพลิเคชันที่ใช้งานอยู่ต้องจัดเตรียมตัวบันทึกข้อมูลให้กับไลบรารีอย่างชัดเจน LibLog ใช้การไตร่ตรองเพื่อดูว่ามีการใช้เครื่องตัดไม้คอนกรีตแบบใดและเชื่อมต่อกับเครื่องตัดไม้โดยไม่ต้องมีรหัสการเดินสายที่ชัดเจนในโครงการไลบรารี
ดังนั้น LibLog จึงเป็นทางออกที่ยอดเยี่ยมสำหรับการบันทึกภายในโครงการห้องสมุด เพียงอ้างอิงและกำหนดค่าเครื่องตัดไม้ที่เป็นรูปธรรม (Serilog for the win) ในแอปพลิเคชันหรือบริการหลักของคุณแล้วเพิ่ม LibLog ในห้องสมุดของคุณ!
แทนการเขียนซุ้มของคุณเองคุณสามารถใช้ทั้งปราสาทบริการเข้าสู่ระบบหรือเข้าสู่ระบบง่ายFaçade
ทั้งสองมีอะแดปเตอร์สำหรับ NLog และ Log4net
ตั้งแต่ปี 2015 คุณยังสามารถใช้. NET Core Loggingหากคุณกำลังสร้างแอปพลิเคชันหลักของ. NET
แพ็คเกจสำหรับ NLog ที่จะเชื่อมต่อคือ: