将日志记录作为域模型的一部分

时间:2019-03-14 05:45:53

标签: c# logging nlog error-logging

我正在编写一个应用程序,其中日志记录是我实际域模型的一部分。这是一个自动化和批处理工具,最终用户可以在实际应用程序中查看批处理作业的日志,而不仅仅是文本日志文件。

因此我的域模型包括一个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设置不正确或未设置回null,那么我最终可能会将日志消息存储到它们不适用的结果中。另外,因为只有一个静态CurrentTargetResult,所以我无法支持同时处理多个结果。

我是否可以通过其他/更好的方法来解决这个问题?还是我试图做的事情根本上是错误的?

2 个答案:

答案 0 :(得分:0)

我认为您的方法是正确的,但是您可以使用已经为您完成此抽象的库来节省精力。 Common Logging库就是您所需要的。

您的域代码将仅取决于Common Logging中的ILogger接口。仅当运行时使用您的域时,例如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)
  • 用于Windows(ETW)的Microsoft事件跟踪
  • 登录到STDOUT
  • 登录到调试出

我已经使用了很多年了,很高兴能在另一个上下文中重用您的域/库代码,但是不必一定要依赖于日志记录框架(我已经从Enterprise Libraries移出了到log4net,最后到NLog ...轻而易举。)

答案 1 :(得分:0)

以为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. 始终初始化LogMessages:

    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来初始化目标