Home > database >  Injecting Behaviors Before and After Method Execution to log input and output
Injecting Behaviors Before and After Method Execution to log input and output

Time:11-02

I have many lines of code like:

private SomeOutputModel Method(SomeInputModel model)
    {
        _logger.LogDebug(JsonSerializer.Serialize(model));
        SomeOutputModel result = DoSomething();
        _logger.LogDebug(JsonSerializer.Serialize(result));
        return result;
    }

practically I need to log the input and output of some methods. I know I should do this in respect to AoP and I know there is some non-free library like PostSharp outside, but I need a hint to create a simple decorator pattern for my methods or delegates to give me this ability to invoke or execute these methods. Hence I would be able to inject any behavior among the functions, but I don't want to use reflection as much as possible because I assume it will make the code meaningless and comparatively slow to original one.

CodePudding user response:

One approach for accomplishing this would be to use something like MediatR, which, to quote the author, is "a low-ambition library trying to solve a simple problem — decoupling the in-process sending of messages from handling messages."

That decoupling introduces opportunities to create handlers for specific requests types that can be wrapped with a pipeline behavior at a more generic level to cover cross-cutting concerns like logging.

You might create a handler like follows:

public class SomeOutputModelHandler : IRequestHandler<SomeInputModel, SomeOutputModel>
{
    private ISomeInterfaceDependency _somethingDoer;

    public ModelHandler(ISomeInterfaceDependency somethingDoer)
    {
        _somethingDoer = somethingDoer;
    }

    public async Task<SomeOutputModel> Handle(SomeInputModel request, CancellationToken cancellationToken)
    {
        var response = await _somethingDoer.DoSomething(request);
        return response;
    }
}

This would be invoked by passing the model to Mediatr like: var outputModel = await mediator.Send(myInputModel);

At this point, lifting directly from the MediatR wiki, you can register a generic pipeline behavior that covers all of your handler cases that logs everything before and after execution:

https://github.com/jbogard/MediatR/wiki/Behaviors#registering-pipeline-behaviors

public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

    public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        _logger.LogInformation($"Handling {typeof(TRequest).Name}");
        var response = await next();
        _logger.LogInformation($"Handled {typeof(TResponse).Name}");

        return response;
    }
}

You can see examples of this request/response behavior including pipelines in the MediatR samples at https://github.com/jbogard/MediatR/tree/master/samples/MediatR.Examples

CodePudding user response:

Without using reflection, and with the caveat that the "decoration" has to be done on method call instead of on method definition, a simple and basic solution could be:

For each generic Func or Action signature you need, create a corresponding method that would perform the Logging.

Using Serilog (or equivalent), you wouldn't have to do serialization by yourself to log the input and output objects, by leveraging the Serilog @ operator.

Serilog homepage explain briefly the @ operator use, see: https://serilog.net/

Here is how it could be done:

public static class LogUtils
{
    public static TResult LogDecorate<TSource, TResult>(Func<TSource, TResult> method, TSource input)
    {
        Log.Information("{@input1}", input1);
        var result = method(input);
        Log.Information("{@result}", result);
        return result;
    }


    public static TResult LogDecorate<TSource1, TSource2, TResult>(Func<TSource1, TSource2, TResult> Method, TSource1 input1, TSource2 input2)
    {
        Log.Information("{@input1} - {@input2}", input1, input2);
        var result = Method(input);
        Log.Information("{@result}", result);
        return result;
    }
    // Etc...
}

Use would be:

var result = LogUtils.LogDecorate(MyMethod, input);

  • Related