简体   繁体   中英

How to use serilog in a referenced class library and use with CallerMember, CallerFilePath (not asp.net core)

I am working on a WPF application and can sucessfully setup serilog and access it's static method by placing it inside App.xaml.cs,

However I am wanting to also log inside the various referenced class libraries in the project.

As I can't reference the main application from the class library itself, due to a circular dependency, what is the best way to perform logging in these class libraries?

public class LogManager : IMLogManager
    {
        #region Constructor

        public LogManager()
        {
            Log.Logger = new LoggerConfiguration()
                         .MinimumLevel.Debug()
                         .Enrich.FromLogContext()
                         .WriteTo.Console(LogEventLevel.Debug, OutputTemplate, theme: AnsiConsoleTheme.Code)
                         .WriteTo.Async(a => a.File("./log/log.log",
                                                    LogEventLevel.Debug,
                                                    OutputTemplate,
                                                    rollingInterval: RollingInterval.Day,
                                                    encoding: Encoding.UTF8,
                                                    buffered: true,
                                                    rollOnFileSizeLimit: true))
                         .CreateLogger();
        }

        #endregion

        #region Private properties

        /// <summary>
        ///     Template ghi log.
        /// </summary>
        public static string OutputTemplate =>
            "[{Timestamp:dd-MM-yyyy HH:mm:ss.fff} {Level:u3}] source {SourceContext} ::: {Message:lj}{NewLine}{Exception}{NewLine}";

        #endregion

        #region Private methods

        private static string FormatMessage(string message,
                                            string memberName = "",
                                            string filePath = "",
                                            int lineNumber = 0)
            => $"in method [{Path.GetFileNameWithoutExtension(filePath)} > {memberName}]\r\n\tat {filePath}:{lineNumber}\r\n{message}";

        #endregion

        #region Private fields

        #endregion

        #region Implementation of IMLogManager

        /// <summary>
        ///     Write app log.
        /// </summary>
        /// <param name="level"></param>
        /// <param name="message"></param>
        /// <param name="exception"></param>
        /// <param name="memberName"></param>
        /// <param name="filePath"></param>
        /// <param name="lineNumber"></param>
        public void WriteLog(LogEventLevel level,
                             string message,
                             Exception exception = null,
                             [CallerMemberName] string memberName = "",
                             [CallerFilePath] string filePath = "",
                             [CallerLineNumber] int lineNumber = 0)
        {
            switch (level)
            {
                case LogEventLevel.Verbose:
                    Log.Verbose(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Debug:
                    Log.Debug(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Information:
                    Log.Information(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Warning:
                    Log.Warning(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Error:
                    Log.Error(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Fatal:
                    Log.Fatal(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(level), level, null);
            }
        }

        public void WriteLog<T>(LogEventLevel level,
                                string message,
                                Exception exception = null,
                                [CallerMemberName] string memberName = "",
                                [CallerFilePath] string filePath = "",
                                [CallerLineNumber] int lineNumber = 0) where T : class
        {
            var log = Log.ForContext<T>()
                         .ForContext("MemberName", memberName)
                         .ForContext("FilePath", filePath)
                         .ForContext("FileName", Path.GetFileNameWithoutExtension(filePath))
                         .ForContext("LineNumber", lineNumber);
            switch (level)
            {
                case LogEventLevel.Verbose:
                    log.Verbose(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Debug:
                    log.Debug(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Information:
                    log.Information(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Warning:
                    log.Warning(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Error:
                    log.Error(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                case LogEventLevel.Fatal:
                    log.Fatal(exception, FormatMessage(message, memberName, filePath, lineNumber));

                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(level), level, null);
            }
        }

        public void CloseAndFlush()
        {
            Log.CloseAndFlush();
        }

        #endregion
    }

Typically, a library that needs to expose logging via Serilog would depend on the Serilog library only, and any class wishing to perform logging would either take a dependency on a ILogger through a constructor parameter or would use the static Log , eg

// Constructor
public MyClass(ILogger logger)
{
    _logger = logger.ForContext(GetType());
}
// Constructor
public MyClass()
{
    _logger = Log.ForContext(GetType());
}

Since it looks like you want to add caller information to every log message, I would suggest extracting this functionality into a shared library that all of your "referenced class libraries" can use, eg as an extension method that looks something like the following:

public static ILogger WithCallerContext(
    this ILogger logger,
    [CallerMemberName] string memberName = "",   
    [CallerFilePath] string filePath = "",    
    [CallerLineNumber] int lineNumber = 0)
{
    return logger.ForContext("MemberName", memberName)
        .ForContext("FilePath", filePath)
        .ForContext("FileName", Path.GetFileNameWithoutExtension(filePath))
        .ForContext("LineNumber", lineNumber);
}

Then in your libraries you can log as follows:

_logger.WithCallerContext().Information("Hello, world!");

The other great advantage of this approach is that it separates the actions of adding caller context and logging the message, so you can use the ILogger.Information etc. methods with their full message templating support rather than always having to go through a custom WriteLog method. Then you can do things like:

_logger.WithCallerContext().Information("Hello from {Name}!", name);

Note that even though the caller context won't be included in the Message property with this approach, you can still include it in the output by referencing the property names directly in the output template eg {FilePath} , {LineNumber} .

Final tip, is that in your WriteLog methods (if you end up using them), instead of switching between the various log event levels and calling the appropriate Log.<Level> method, you can simply use the following Log.Write overload:

Log.Write(level, exception, FormatMessage(message, memberName, filePath, lineNumber));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM