Почему регистраторы рекомендуют использовать регистратор для каждого класса?

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 () called»), но лучше, если регистратор сможет это обработать.
Daniel T.
1
Просто к вашему сведению, стало «лучшей» практикой иметь регистратор для каждого экземпляра, а не для каждого класса (т.е. статического), потому что это упрощает сбор информации, такой как информация о потоках. Очевидно, это дело вкуса, а не «жесткого правила», но я хотел просто отбросить его.
Уилл Хартунг,
7
@will, ты можешь еще немного объяснить это? При ведении журнала с использованием средства ведения журнала для каждого класса я всегда записываю идентификатор потока, чтобы средство ведения журнала могло получить информацию о текущем потоке. Любая другая информация о потоке также будет доступна регистратору.
Джереми Вибе,
@ Джереми Вибе: Это единственная причина? Функционально нет проблем, если я использую единую глобальную переменную типа logger для всего приложения?
giorgim
1
@Giorgi Нет, я так не думаю. В наши дни вы можете получить большую часть этой информации с помощью атрибутов CallerInformation, которые делают один регистратор для каждого класса немного менее актуальным - msdn.microsoft.com/en-us/library/hh534540.aspx
Джереми Вибе,
15

Преимущество использования «logger per file» в 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}макета.

CoperNick
источник
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);

        }
    }
}
as9876
источник
1

На ум сразу приходят две причины:

  1. Наличие отдельного журнала для каждого класса позволяет легко сгруппировать все сообщения журнала / ошибки, относящиеся к данному классу.
  2. Наличие журнала внутри класса позволяет вам регистрировать внутренние детали, которые могут быть недоступны за пределами класса (например, частное состояние, информация, касающаяся реализации класса и т. Д.).
Дэн Тао
источник
2
У вас есть регистратор в классе независимо от того, определен ли он на уровне класса или глобально. Глобальные регистраторы не находятся «вне» класса с точки зрения видимости. Вы по- прежнему ссылается на глобальный регистратор из внутри данного класса , так что вы имеете полный обзор.
Роберт
0

Вероятно, потому что вы хотите иметь возможность регистрировать методы, которые видны только классу, без нарушения инкапсуляции, это также упрощает использование класса в другом приложении без нарушения функциональности ведения журнала.

Родрик Чепмен
источник
1
Это затрудняет использование этого класса в другом приложении. Вы должны ссылаться на библиотеку журналов, нравится вам это или нет.
Петр Перак
0

Упрощает настройку приложений по пространству имен или классу.

дотджо
источник
0

Если вы используете NLOG, вы можете указать callite в конфигурации, это будет записывать имя класса и метод, где находился оператор регистрации.

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

Затем вы можете использовать константу для имени вашего регистратора или имени сборки.

Отказ от ответственности: я не знаю, как NLOG собирает эту информацию, я предполагаю, что это отражение, поэтому вам, возможно, придется учитывать производительность. Есть несколько проблем с методами Async, если вы не используете NLOG v4.4 или новее.

user6271979
источник