monty
monty

Reputation: 8755

Custom implementation of ILogger

In my project I often add prefixes to my log messages.

Currently I am doing this with

      logger.LogDebug(prefix + " some message");

I thought it would be a good way to implement a custom logger where I set the prefix and the logger itself attaches it every time it logs something.

So I created my custom logger class and implemented the ILogger interface. But I do not understand how to use the

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)

method to add the prefix (which is a member of the custom logger class).

My full code is:

      public class CustomLogger : ILogger
      {

        private readonly ILogger _logger;
        private string _logPrefix;

        public CustomLogger(ILogger logger)
        {
          _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _logPrefix = null;
        }

        public ILogger SetLogPrefix(string logPrefix)
        {
          _logPrefix = logPrefix;
          return this;
        }

        public IDisposable BeginScope<TState>(TState state)
        {
          return _logger.BeginScope(state);
        }

        public bool IsEnabled(LogLevel logLevel)
        {
          return _logger.IsEnabled(logLevel);
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
          _logger.Log(logLevel, eventId, state, exception, formatter);
        }

      }

Upvotes: 16

Views: 28527

Answers (3)

Khai Nguyen
Khai Nguyen

Reputation: 945

I think you should not call a _logger in a custom logger.

It would be a circular call on runtime and the result would be "prefix: prefix: prefix: prefix: prefix: prefix: prefix: prefix: ..."

Simply, you can create a simple logger and implement a log writter such as Console, database writter, log4net, ...

Now first, you should change your custom logger like below:

    public class CustomLogger : ILogger
    {
        private readonly string CategoryName;
        private readonly string _logPrefix;

        public CustomLogger(string categoryName, string logPrefix)
        {
            CategoryName = categoryName;
            _logPrefix = logPrefix;
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return new NoopDisposable();
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            string message = _logPrefix;
            if (formatter != null)
            {
                message += formatter(state, exception);
            }
            // Implement log writter as you want. I am using Console
            Console.WriteLine($"{logLevel.ToString()} - {eventId.Id} - {CategoryName} - {message}");
        }

        private class NoopDisposable : IDisposable
        {
            public void Dispose()
            {
            }
        }
    }

The second step, create a logger provider:

     public class LoggerProvider : ILoggerProvider
        {     
            public ILogger CreateLogger(string categoryName)
            {                
                return new CustomLogger(categoryName, "This is prefix: ");
            }

            public void Dispose()
            {
            }
        }

The third step, in Configure from Startup.cs:

    loggerFactory.AddProvider(new MicroserviceLoggerProvider());

Upvotes: 15

Hung Quach
Hung Quach

Reputation: 2187

Personally, I think It's not a good way to do that, "prefix" will be duplicated a lot. Why don't you use Log Scopes instead?

public IActionResult GetById(string id)
{
    TodoItem item;
    using (_logger.BeginScope("Message attached to logs created in the using block"))
    {
        _logger.LogInformation(LoggingEvents.GetItem, "Getting item {ID}", id);
        item = _todoRepository.Find(id);
        if (item == null)
        {
            _logger.LogWarning(LoggingEvents.GetItemNotFound, "GetById({ID}) NOT FOUND", id);
            return NotFound();
        }
    }
    return new ObjectResult(item);
}

Output

info: TodoApi.Controllers.TodoController[1002]
      => RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
      Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
      => RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
      GetById(0) NOT FOUND

Currently, you can't change the logging template, it's the limitation of built-in basic logging in Asp.net Core. For more powerful one, you can try Serilog, keep using ILogger interface and change some line of code in program.cs class

You should also look at this Benefits of Structured Logging vs basic logging

Upvotes: 11

Edward
Edward

Reputation: 30046

Implement an extensions for adding prefix to log records.

    public static class LogExtensions
    {
        public static void PrefixLogDebug(this ILogger logger, string message, string prefix = "Edward", params object[] args)
        {
            logger.LogDebug($"{prefix} {message}");
        }
    }

Useage:

        _log.PrefixLogDebug("Log From Prefix extension");
        _log.PrefixLogDebug("Log From Prefix extension", "New Prefix");

Upvotes: 5

Related Questions