martinothamar/Mediator

Possibilties to add Logging for Notifications

meierhoeferjannis opened this issue · 5 comments

With the MediatR library i used a Decorator for the NotificationHandler. The Purpose was to add logging to these Handlers. Usually you would use a Pipeline for this, but as far as i know you can not use Pipelines with Notifications.
In MediatR you could tell him to ignore the Decorator ( because it implements the INotificationHandler Interface and would usually get registered), via the configuration. Sample shown below.

services.AddMediatR(configuration =>
        {
            configuration.TypeEvaluator = type => type != typeof(LoggingDecorator<>);
            configuration.RegisterServicesFromAssembly(ApplicationAssembly.Assembly);
        });



internal sealed class LoggingDecorator<TNotification>:INotificationHandler<TNotification>
    where TNotification : INotification
{
    private readonly INotificationHandler<TNotification> _Inner;
    private readonly ILogger<LoggingDecorator<TNotification>> _Logger;

    public LoggingDecorator(INotificationHandler<TNotification> inner, ILogger<LoggingDecorator<TNotification>> logger)
    {
        _Inner = inner;
        _Logger = logger;
    }

    public async ValueTask Handle(TNotification notification, CancellationToken cancellationToken)
    {
        _Logger.LogInformation("Starting notification {@Notification}, {@DateTimeUtc}", typeof(TNotification),
            DateTime.UtcNow);
        try
        {
            await _Inner.Handle(notification, cancellationToken);
        }
        catch (Exception e)
        {
            _Logger.LogCritical(e, "Exceptional Error: {@Message}", e.Message);
        }

        _Logger.LogInformation("Completed notification {@Notification}, {@DateTimeUtc}", typeof(TNotification),
            DateTime.UtcNow);
    }
}

When you dont do this you get a Circular Dependency. Is there a way to achieve something similar with this Library? Btw if there is a better way to achieve logging in the NotificationHandlers let me know :D

Hi!

I added a sample showing the various ways you can handle notifications. Notably for doing cross cutting concerns such as logging, you can either use a generic handlers like the one you have above, or just handle INotification which is also supported. See sample code:

https://github.com/martinothamar/Mediator/blob/3c6326ef462390d6be51a01dfa7faa078b7269fd/samples/Notifications/Program.cs

public sealed record Notification(Guid Id) : INotification;
public sealed class GenericNotificationHandler<TNotification> : INotificationHandler<TNotification>
where TNotification : INotification
{
public ValueTask Handle(TNotification notification, CancellationToken cancellationToken)
{
if (notification is Notification concrete)
Console.WriteLine($"{GetType().Name} - {concrete.Id}");
return default;
}
}
public sealed class CatchAllNotificationHandler : INotificationHandler<INotification>
{
public ValueTask Handle(INotification notification, CancellationToken cancellationToken)
{
if (notification is Notification concrete)
Console.WriteLine($"{GetType().Name} - {concrete.Id}");
return default;
}
}
public sealed class ConcreteNotificationHandler : INotificationHandler<Notification>
{
public ValueTask Handle(Notification notification, CancellationToken cancellationToken)
{
Console.WriteLine($"{GetType().Name} - {notification.Id}");
return default;
}
}

Code output:

$ dotnet run
Publishing!
-----------------------------------
CatchAllNotificationHandler - e9c1302b-0eab-4193-8445-ab70a93265d6
ConcreteNotificationHandler - e9c1302b-0eab-4193-8445-ab70a93265d6
GenericNotificationHandler`1 - e9c1302b-0eab-4193-8445-ab70a93265d6
-----------------------------------
Finished publishing!

Thank you very much for the reply!
I don't know if I miss something, but the CatchAllNotificationHandler and the GenericNotificationHandler don't handle all the use cases a decorator can handle.
Yes they get always called when a Notification is published but if you want to log when the notification is completed you would need to implement the logging logic in every concrete handler or publish something like a NotificationFinishedNotification, which is logged by a GenericNotificationHandler for example.
Also, if you want to add Exception Handling and Logging you would need to add the Logic in every concrete Handler.
With a decorator you could avoid this. I'm pretty sure you are a more experienced Programmer than I am, and if you say my approach is kind a wrong or violates some kind of programming principle, please let me know.
So would it be possible to enhance the library that Decorators can be used? If so, I would try to implement the logic myself and provide a PR :)

Woops yeah I understand now, I completely misssed your point, sorry 😄
Does this commit help? It lets you remove specific registrations... I think the open generic implementation for notifications is similar to the one in MediatR, but not sure. I'll have a look at this soon

Hi,
yeah, that commit helped a lot! I experimented a bit and found a solution.
var decorator = services.Where(t => t.ImplementationType == typeof(LoggingDecorator<>)); services.Remove(decorator.FirstOrDefault());
With removing the Decorator from the ServiceCollection it works! Thanks a lot and keep up the great work :D

Thats great! Thanks for your patience and kindness 😄