Чому реєстратори рекомендують використовувати реєстратор для кожного класу?


88

Відповідно до документації NLog:

Більшість програм використовуватимуть по одному реєстратору на клас, де ім'я реєстратора збігається з ім'ям класу.

Це те саме, що працює log4net. Чому це хороша практика?


1
хм здається, тут є дві проблеми - одна має фактичний об'єкт журналу для кожного класу, а друга має ім'я журналу, яке буде таким самим, як і клас.
Peter Recore

Відповіді:


59

У log4net використання одного реєстратора на клас полегшує отримання джерела повідомлення журналу (тобто запис класу в журнал). Якщо у вас немає одного реєстратора на клас, але натомість у вас є один реєстратор для всієї програми, вам потрібно вдатися до додаткових хитрощів, щоб знати, звідки надходять повідомлення журналу.

Порівняйте наступне:

Журнал для класу

using System.Reflection;
private static readonly ILog _logger = 
    LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);    

public void SomeMethod()
{
    _logger.DebugFormat("File not found: {0}", _filename);
}

Один реєстратор на додаток (або подібний)

Logger.DebugFormat("File not found: {0}", _filename); // Logger determines caller

-- or --

Logger.DebugFormat(this, "File not found: {0}", _filename); // Pass in the caller

Використовуючи другий приклад, реєстратору потрібно буде побудувати трасування стека, щоб побачити, хто його викликає, або ваш код завжди повинен передавати абонента. Зі стилем реєстратора за класом ви все одно робите це, але ви можете робити це один раз для класу, а не один раз за дзвінок і усунути серйозну проблему з продуктивністю.


Дякую, це допомагає прояснити речі. Ми просто вводили ім'я класу та метод у повідомлення (тобто "ImageCreator.CreateThumbnail (), що називається"), але краще, якщо реєстратор може це впоратись.
Даніель Т.

1
Тільки FYI, стало "кращою" практикою мати реєстратор для кожного екземпляра, а не для класу (тобто статичного), оскільки це полегшує збір такої інформації, як інформація про потоки. Очевидно, що це справа смаку, ніякого "жорсткого правила", але я хотів просто викинути це.
Will Hartung

7
@will, ти можеш пояснити це трохи більше? Під час реєстрації за допомогою Logger per Class я завжди реєструю ідентифікатор потоку, щоб реєстратор міг отримати поточну інформацію про потоки. Будь-яка інша інформація про потоки також буде доступна реєстратору.
Джеремі Вібе

@ Джеремі Вібе: Це єдина причина? Функціонально не виникає проблем, якщо я використовую єдину глобальну змінну типу реєстратора для цілої програми?
giorgim

1
@Giorgi Ні, я не думаю. Сьогодні ви можете отримати багато цієї інформації за допомогою атрибутів CallerInformation, що робить один реєстратор у класі трохи менш актуальним - msdn.microsoft.com/en-us/library/hh534540.aspx
Джеремі Вібе,

15

Перевага використання "реєстратора на файл" у NLog: у вас є можливість керувати / фільтрувати журнали за простором імен та іменем класу. Приклад:

<logger name="A.NameSpace.MyClass"      minlevel="Debug" writeTo="ImportantLogs" /> 
<logger name="A.NameSpace.MyOtherClass" minlevel="Trace" writeTo="ImportantLogs" /> 
<logger name="StupidLibrary.*"          minlevel="Error" writeTo="StupidLibraryLogs" />

<!-- Hide other messages from StupidLibrary -->
<logger name="StupidLibrary.*" final="true" /> 

<!-- Log all but hidden messages -->
<logger name="*" writeTo="AllLogs" /> 

NLogger має дуже корисний фрагмент коду для цього. nloggerФрагмент коду створює наступний код:

private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

Отже, лише кілька натискань клавіш, і у вас є реєстратор на клас. Він використовуватиме простір імен та ім'я класу як ім'я реєстратора. Щоб встановити інше ім'я для свого реєстратора класів, ви можете використовувати це:

private static NLog.Logger logger = NLog.LogManager.GetLogger("MyLib.MyName");

І, як сказав @JeremyWiebe, вам не потрібно використовувати хитрощі, щоб отримати ім'я класу, який намагається записати повідомлення: Ім'я реєстратора (яке зазвичай є ім'ям класу) можна легко записати у файл (або іншої цілі) за допомогою ${logger}в макеті.


5

Я бачу кілька причин такого вибору.

  • Ви завжди будете знати, звідки взяв певний оператор журналу, якщо включити ім'я реєстратора у свій вихідний формат журналу.
  • Ви можете контролювати, які оператори журналу ви бачите на дрібному рівні, увімкнувши або вимкнувши певні реєстратори, або встановивши їх рівень.

4

У випадку NLog також є переваги щодо продуктивності. Більшість користувачів буде використовувати

Logger logger = LogManager.GetCurrentClassLogger()

Шукаючи поточний клас із трасування стека, потрібно виконати деяку (але не велику) продуктивність.


3

У більшості випадків назва класу забезпечує гарне ім'я для реєстратора. Під час сканування файлів журналу ви можете побачити повідомлення журналу та пов'язати його безпосередньо з рядком коду.

Хорошим прикладом, коли це не найкращий підхід, є журнали SQL Hibernate. Існує спільний реєстратор з назвою "Hibernate.SQL" або щось подібне, де кілька різних класів записують необроблений SQL в одну категорію реєстратора.


2

З точки зору розробки, найпростіше, якщо вам не доведеться створювати об'єкт реєстратора щоразу. З іншого боку, якщо ви цього не зробите, а скоріше динамічно створюєте його за допомогою відображення, це сповільнить продуктивність. Щоб вирішити цю проблему, ви можете використовувати такий код, який створює реєстратор динамічно асинхронно:

using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WinForms
{
    class log
    {

        public static async void Log(int severity, string message)
        {
            await Task.Run(() => LogIt(severity, message));
        }

        private static void LogIt(int severity, string message)
        {
            StackTrace st = new StackTrace();
            StackFrame x = st.GetFrame(2);     //the third one goes back to the original caller
            Type t = x.GetMethod().DeclaringType;
            Logger theLogger = LogManager.GetLogger(t.FullName);

            //https://github.com/NLog/NLog/wiki/Log-levels
            string[] levels = { "Off", "Trace", "Debug", "Info", "Warn", "Error", "Fatal" };
            int level = Math.Min(levels.Length, severity);
            theLogger.Log(LogLevel.FromOrdinal(level), message);

        }
    }
}

1

Відразу з’являються дві причини:

  1. Наявність окремого журналу для кожного класу полегшує групування всіх повідомлень / помилок журналу, що стосуються даного класу.
  2. Наявність журналу в класі дозволяє реєструвати внутрішні деталі, які можуть бути недоступними поза класом (наприклад, приватний стан, інформація, що стосується реалізації класу тощо).

2
У вас є реєстратор у класі, незалежно від того, визначений він на рівні класу або в усьому світі. Глобальні реєстратори не є "поза межами" класу з точки зору видимості. Ви по- , як і раніше посилається на глобальний реєстратор з всередині даного класу , так що ви маєте повний огляд.
Роберт

0

Можливо, тому, що ви хочете мати можливість реєструвати методи, видимі лише для класу, не порушуючи інкапсуляцію, це також полегшує використання класу в іншій програмі, не порушуючи функціонування журналу.


1
Це ускладнює використання цього класу в іншій програмі. Вам потрібно посилатися на бібліотеку журналу, подобається вам це чи ні.
Piotr Perak

0

Полегшує налаштування додатків за простором імен або класом.


0

Якщо ви використовуєте NLOG, ви можете вказати місце виклику в конфігурації, це запише ім'я класу та метод, де знаходився оператор журналювання.

<property name="CallSite" value="${callsite}" />

Потім ви можете використовувати константу для імені вашого журналу або імені збірки.

Застереження: я не знаю, як NLOG збирає цю інформацію, я думаю, це буде відображенням, тому вам, можливо, доведеться розглянути результативність. Якщо ви не використовуєте NLOG v4.4 або новішої версії, є кілька проблем із методами Async.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.