Включая ведение журнала как часть моей доменной модели - PullRequest
2 голосов
/ 14 марта 2019

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

Итак, моя модель домена включает в себя класс 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, который имеет свойство коллекции 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 не установлен правильно или не установлен на ноль, я могу получить сообщения журнала, сохраненные к результатам, к которым они не относятся. Кроме того, поскольку существует только один статический CurrentTargetResult, я не могу поддерживать одновременную обработку нескольких результатов.

Есть ли другой / лучший способ, которым я мог бы подойти к этому? Или то, что я пытаюсь сделать в корне неправильно?

Ответы [ 2 ]

0 голосов
/ 19 марта 2019

Думаю, что static CurrentTargetResult действительно немного хрупок, но общий подход хорош.

Я предлагаю следующие изменения:

  1. нестатически 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);
        }
    }
    
  2. Всегда инициализировать сообщения журнала:

    public class Result
    {
        public bool Succeeded {get; set;}
        public string StatusMessage {get; set;}
        public IList<LogMessage> LogMessages {get; set;} = new List<LogMessage>();
    }
    
  3. Получить сообщения - при необходимости - с помощью следующих вызовов:

    // find target by name
    var logResultTarget1 = LogManager.Configuration.FindTargetByName<LogResultTarget>("target1");
    var results = logResultTarget1.CurrentTargetResult;
    
    // or multiple targets
    var logResultTargets = LogManager.Configuration.AllTargets.OfType<LogResultTarget>();
    var allResults = logResultTargets.Select(t => t.CurrentTargetResult);
    

PS: Вы также можете перезаписать InitializeTarget в LogResultTarget для инициализации цели

0 голосов
/ 18 марта 2019

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

Код вашего домена будет зависеть только от интерфейса ILogger Common Logging. Только когда ваш домен используется средой выполнения, например Web API, затем вы настраиваете, какой поставщик журналов вы собираетесь использовать.

Существует несколько готовых провайдеров, доступных в виде отдельных пакетов nuget:

Common.Logging предоставляет адаптеры, которые поддерживают все следующие популярные цели / каркасы ведения журналов в .NET:

  • Log4Net (v1.2.9 - v1.2.15)
  • NLog (v1.0 - v4.4.1)
  • SeriLog (v1.5.14)
  • Блок приложения Microsoft Enterprise Library Logging (v3.1 - v6.0)
  • Microsoft AppInsights (2.4.0)
  • Отслеживание событий Microsoft для Windows (ETW)
  • Вход в STDOUT
  • Вход в DEBUG OUT

Я использовал это в течение нескольких лет, и было здорово, что ваш домен / библиотечный код был повторно использован в другом контексте, но не должен иметь фиксированную зависимость от каркаса логирования (я перешел из Enterprise Libraries в log4net, чтобы наконец-то NLog ... это был бриз).

...