Thursday, October 14, 2021

[.NET 6] Implement custom LogProvider with RabbitMQ into ILogger

In .NET 6, ILogger is very easy to use, but on modern project, we will write log to somewhere else and use log viewer to read it. For example: sent log to RabbitMQ and read on Kibana

Here is a demo to show how to lmplement a custom logging provider with RabbitMQ into ILogger in .NET 6 RC1, basically same as this article but with some modify.

Step 0 : Before Start

In this demo we choose RabbitMQ.Client to communite with RabbitMQ, you can choose other client if you want, just install it from nuget, easy.

Another thing is RabbitMQ server, if you don't have one, you can use docker to create one.

docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management

Step 1 : RabbitMQHelper

We need a helper to publish message to RabbitMQ.


    public class RabbitMQHelper
    {
        private readonly IModel _channel;
        private readonly string _routeKey;
        private readonly string _exchangeId;

        public RabbitMQHelper(IOptions<RabbitMQSetting> options)
        {
            var setting = options.Value;
            _routeKey = setting.RoutingKey;
            _exchangeId = setting.ExchangeId;
            try
            {
                var factory = new ConnectionFactory()
                {
                    HostName = setting.Host,
                    UserName = setting.UserName,
                    Password = setting.Password,
                    Port = options.Value.RabbitPort,
                };
                var connection = factory.CreateConnection();
                _channel = connection.CreateModel();
            }
            catch (Exception ex)
            {
                //TODO
                Console.WriteLine(ex.Message);
            } 
        }

        public IModel GetConnection()
        {
            return _channel;
        }

        public void Publish(object message)
        {
            try
            {
                var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message));
                _channel.BasicPublish(exchange: _exchangeId,
                                        routingKey: _routeKey,
                                        basicProperties: null,
                                        body: body);
            }
            catch (Exception ex)
            {
                //TODO
                Console.WriteLine(ex.Message);
            }
        }
    }

Step 2 : RabbitMQLogger

Create RabbitMQ logger to save log as our format, and choose LogLevel to sent to MQ.


    public class RabbitMQLogger : ILogger
    {
        private readonly string _name;
        private readonly RabbitMQHelper _rabbit;

        public RabbitMQLogger(string name, RabbitMQHelper rabbit) => (_name, _rabbit) = (name, rabbit);

        public IDisposable BeginScope<TState>(TState state) => default;

        public bool IsEnabled(LogLevel level)
        {
            switch (level)
            {
                case LogLevel.Trace:
                case LogLevel.None:
                case LogLevel.Debug:
                    return false;
                default:
                    return true;
            }
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }

            var log = new LogObject
            {
                LogLevel = logLevel,
                EventId = eventId.Id,
                Module = _name,
                LogData = formatter(state, exception)
            };
            if (exception != null)
            {
                log.ErrorCode = exception.HResult;
                log.ErrorMessage = exception.Message;
            }
            _rabbit.Publish(log);
        }
    }
    

Step 3 : RabbitMQLoggerProvider

Create log provider to add into logger later.

    
public sealed class RabbitMQLoggerProvider : ILoggerProvider
    {
        private readonly RabbitMQHelper _rabbit;
        private readonly ConcurrentDictionary<string, RabbitMQLogger> _loggers = new();

        public RabbitMQLoggerProvider(RabbitMQHelper rabbit)
        {
            _rabbit = rabbit;
        }

        public ILogger CreateLogger(string categoryName) => _loggers.GetOrAdd(categoryName, name => new RabbitMQLogger(name, _rabbit));

        public void Dispose()
        {
            _loggers.Clear();
        }
    }
    

Step 4 : AddRabbitMQLogger

Find a place add config extension for AddRabbitMQLogger method.

    
public static ILoggingBuilder AddRabbitMQLogger(this ILoggingBuilder builder)
{
    builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, RabbitMQLoggerProvider>());
    return builder;
}
    

Then add out custom log provider to builder in Program.cs

  
builder.Services.AddLogging(logger =>
{
    logger.AddRabbitMQLogger();
});
    

Then it's done, our custom Rabbit MQ now can work with ILogger, you can check more detail on GitHub

There is some result.


Publish and Consume


LogInformation


LogError


No comments:

Post a Comment