#c# #logging #nlog #error-logging
#c# #ведение журнала #nlog #ошибка-ведение журнала
Вопрос:
Я пишу приложение, в котором ведение журнала является частью моей реальной модели домена. Это инструмент автоматизации и пакетной обработки, в котором конечные пользователи смогут просматривать журналы задания пакетной обработки в реальном приложении, а не только текстовые файлы журналов.
Итак, моя модель домена включает LogMessage
класс:
public sealed class LogMessage
{
public string Message { get; }
public DateTime TimestampUtc { get; }
public LogLevel Level { get; }
}
public enum LogLevel
{
Fatal = 5,
Error = 4,
Warn = 3,
Info = 2,
Debug = 1,
Trace = 0
}
У меня также есть Result
класс, который имеет свойство collection LogMessages
. Конечные пользователи могут сохранять результаты в файлах моего приложения и открывать их из них.
public class Result
{
public bool Succeeded {get; set;}
public string StatusMessage {get; set;}
public IList<LogMessage> LogMessages {get; set;}
}
Мое приложение также поддерживает сторонних разработчиков, расширяющих приложение с помощью плагинов, которые также могут записывать сообщения журнала. Итак, я определил общий ILogger
интерфейс для разработчиков подключаемых модулей.
public interface ILogger
{
void Debug(string message);
void Error(string message);
void Fatal(string message);
void Info(string message);
void Log(LogLevel level, string message);
void Trace(string message);
void Warn(string message);
}
Я предоставляю экземпляр ILogger
подключаемым модулям, в которые выполняется запись Result.LogMessages
.
public interface IPlugIn
{
Output DoSomeThing(Input in, ILogger logger);
}
Я, очевидно, также хочу иметь возможность входить в систему из моего собственного внутреннего кода и в конечном итоге хочу Result.LogMessages
содержать смесь моих внутренних сообщений журнала и сообщений журнала из подключаемых модулей. Таким образом, конечный пользователь, у которого возникли проблемы, может отправить мне файл результатов, который будет содержать журналы отладки как из моего внутреннего кода, так и из любых используемых плагинов.
В настоящее время у меня есть решение, работающее с использованием пользовательского целевого объекта NLog.
public class LogResultTarget : NLog.Targets.Target
{
public static Result CurrentTargetResult { get; set; }
protected override void Write(NLog.LogEventInfo logEvent)
{
if (CurrentTargetResult != null)
{
//Convert NLog logEvent to LogMessage
LogLevel level = (LogLevel)Enum.Parse(typeof(LogLevel), logEvent.Level.Name);
LogMessage lm = new LogMessage(logEvent.TimeStamp.ToUniversalTime(), level, logEvent.Message);
CurrentTargetResult.LogMessages.Add(lm);
}
}
protected override void Write(NLog.Common.AsyncLogEventInfo logEvent)
{
Write(logEvent.LogEvent);
}
}
Этот класс пересылает сообщение в Result
присвоенное статическому LogResultTarget.CurrentTargetResult
свойству. Мой внутренний код регистрируется в регистраторах NLog, и у меня есть реализация, ILogger
которая также регистрируется в NLog.Logger
.
Это работает, но кажется действительно хрупким. Если CurrentTargetResult
установлено неправильно или не возвращено значение null, я могу в конечном итоге сохранить сообщения журнала с результатами, к которым они не применимы. Кроме того, поскольку существует только один статический CurrentTargetResult
, я никак не мог поддерживать обработку нескольких результатов одновременно.
Есть ли другой / лучший способ, которым я мог бы подойти к этому? Или то, что я пытаюсь сделать, в корне неверно?
Комментарии:
1. Я не до конца понимаю
CurrentTargetResult
свойство, оно не используется в вашей текущей цели? (Только проверка null?) Как именно выглядитResult
тип?2. Похоже, у меня была опечатка в моем примере кода, где у меня было «Target» вместо «CurrentTargetResult». Я также добавил пример класса «Результат». Помогает ли это?
3. Да, добавлен ответ 🙂
Ответ №1:
Я думаю, что ваш подход правильный, но вы могли бы сэкономить усилия, используя библиотеку, которая уже выполняет эту абстракцию за вас. Общая библиотека ведения журнала — это то, что вам нужно.
Ваш доменный код будет зависеть только от ILogger
интерфейса обычного ведения журнала. Только когда ваш домен используется средой выполнения, например, Web API, вы затем настраиваете, какого поставщика ведения журнала вы собираетесь использовать.
Существует ряд готовых поставщиков, доступных в виде отдельных пакетов nuget:
Обычный.Ведение журнала предоставляет адаптеры, которые поддерживают все следующие популярные цели / платформы ведения журнала в .NET:
- Log4Net (версии v1.2.9 — v1.2.15)
- NLog (v1.0 — v4.4.1)
- SeriLog (версия v1.5.14)
- Блок приложения для ведения журнала корпоративной библиотеки Microsoft (версии v3.1 — v6.0)
- Microsoft AppInsights (2.4.0)
- Отслеживание событий Microsoft для Windows (ETW)
- Войдите в стандартный вывод
- Войдите в систему для ОТЛАДКИ
Я использую это в течение ряда лет, и было здорово, что код вашего домена / библиотеки можно повторно использовать в другом контексте, но при этом не обязательно иметь фиксированную зависимость от платформы ведения журнала (я перешел с корпоративных библиотек на log4net и, наконец, на NLog … это было легко).
Ответ №2:
На мой взгляд, static
CurrentTargetResult действительно немного хрупок, но общий подход хорош.
Я предлагаю следующие изменения:
-
нестатизируйте
CurrentTargetResult
и всегда делайте его инициализированным,Что-то вроде этого:
public class LogResultTarget : NLog.Targets.Target { public Result CurrentTargetResult { get; } = new Result(); protected override void Write(NLog.LogEventInfo logEvent) { //Convert NLog logEvent to LogMessage LogLevel level = (LogLevel)Enum.Parse(typeof(LogLevel), logEvent.Level.Name); LogMessage lm = new LogMessage(logEvent.TimeStamp.ToUniversalTime(), level, logEvent.Message); CurrentTargetResult.LogMessages.Add(lm); } protected override void Write(NLog.Common.AsyncLogEventInfo logEvent) { Write(logEvent.LogEvent); } }
-
Всегда инициализируйте LogMessages:
public class Result { public bool Succeeded {get; set;} public string StatusMessage {get; set;} public IList<LogMessage> LogMessages {get; set;} = new List<LogMessage>(); }
-
Извлекайте сообщения — при необходимости — с помощью следующих вызовов:
// find target by name var logResultTarget1 = LogManager.Configuration.FindTargetByName<LogResultTarget>("target1"); var results = logResultTarget1.CurrentTargetResu< // or multiple targets var logResultTargets = LogManager.Configuration.AllTargets.OfType<LogResultTarget>(); var allResults = logResultTargets.Select(t => t.CurrentTargetResult);
PS: Вы также могли бы перезаписать InitializeTarget
в LogResultTarget
для инициализации цели