Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Including logging as part of my domain model

I'm writing an application where logging is part of my actual domain model. It's an automation and batch processing tool where end users will be able to view the logs of a batch processing job in the actual application and not just text log files.

So my domain model includes a LogMessage class:

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
}

I also have a Result class which has a collection property of LogMessages. Results can be saved to and opened from files with my application by end users.

public class Result
{
    public bool Succeeded {get; set;}
    public string StatusMessage {get; set;}
    public IList<LogMessage> LogMessages {get; set;}
}

My application also supports third party developers extending the application with plug-ins that can also write log messages. So I've defined a generic ILogger interface for the plug-in developers.

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);
}

I provide an instance of an ILogger to the plug-ins which writes to Result.LogMessages.

public interface IPlugIn
{
    Output DoSomeThing(Input in, ILogger logger);
}

I obviously also want to be able to log from my own internal code and ultimately want Result.LogMessages to contain a mixture of my internal log messages and log messages from plug-ins. So an end user having trouble could send me a Result file that would contain debug logs both from my internal code, and any plug-ins used.

Currently, I have a solution working using a custom NLog target.

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);
    }
}

This class forwards message to the Result assigned to the static LogResultTarget.CurrentTargetResult property. My internal code logs to NLog loggers, and and I have a implementation of ILogger that logs to an NLog.Logger as well.

This is working, but feels really fragile. If CurrentTargetResult is not set correctly or not set back to null I can end up with log messages being stored to results that they do not apply to. Also because there is only one static CurrentTargetResult there's no way I could support processing multiple results simultaneously.

Is there a different/better way I could approach this? Or is what I'm trying to do fundamentally wrong?

like image 858
Eric Anastas Avatar asked Dec 28 '25 11:12

Eric Anastas


1 Answers

I think your approach is the right one, but you could save effort by using a library which already does this abstraction for you. The Common Logging library is what you're after.

Your domain code will depend only on the ILogger interface from Common Logging. Only when your domain is used by a runtime e.g. Web API, do you then configure what logging provider you're going to use.

There are a number of pre-built providers available as separate nuget packages:

Common.Logging provides adapters that support all of the following popular logging targets/frameworks in .NET:

  • Log4Net (v1.2.9 - v1.2.15)
  • NLog (v1.0 - v4.4.1)
  • SeriLog (v1.5.14)
  • Microsoft Enterprise Library Logging Application Block (v3.1 - v6.0)
  • Microsoft AppInsights (2.4.0)
  • Microsoft Event Tracing for Windows (ETW)
  • Log to STDOUT
  • Log to DEBUG OUT

I've used this for a number of years and it's been great to have your domain/library code be reused in another context, but not have to have a fixed dependency on a logging framework (I've moved from Enterprise Libraries to log4net, to finally NLog ... it was a breeze).

like image 84
Matt Tester Avatar answered Dec 31 '25 00:12

Matt Tester



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!