this post was submitted on 06 Feb 2024
14 points (100.0% liked)

C Sharp

1511 readers
8 users here now

A community about the C# programming language

Getting started

Useful resources

IDEs and code editors

Tools

Rules

Related communities

founded 1 year ago
MODERATORS
 

Hello! I'm starting a personal project in .NET.

For a logging solution, I want to achieve something similar to what I have in a Python REST API I wrote a while back using the decorator pattern (example in the image).

In the example, the outter "log" function receives the logger I want to use, the decorator function receives a function and returns the decorated function ready to be called (this would be the decorator), and the wrapper function receives the same arguments as the function to be decorated (which is a generic (*args, **kwargs) ofc, because it's meant to decorate any function) and returns whatever the return type of the function is (so, Any). In lines 17 - 24 I just call the passed in "func" with the passed in arguments, but in between I wrap it in a try except block and log that the function with name func.__name__ started or finished executing. In practice, using this decorator in Python looks like this:

import logging
from my.decorator.module import log

_logger = logging.getLogger(__name__)

@log(_logger)
def my_func(arg1: Arg1Type, arg2: Arg2Type) -> ReturnType:
    ...

Ofc it's a lot simpler in Python, however I was wondering if it would be possible or even recommended to attempt something similar in C#. I wouldn't mind having to call the function and wrap it manually, something like this:

return RunWithLogs(MyFunction, arg1, arg2);

What I do want to avoid is manually writing the log statements inside the service's business logic, or having to write separate wrappers for each method I want to log. Would be nice to have one generic function or class that I can somehow plug-in to any method and have it log when the call starts and finishes.

Any suggestions? Thanks in advance.

you are viewing a single comment's thread
view the rest of the comments
[–] Lmaydev@programming.dev 4 points 7 months ago* (last edited 7 months ago) (2 children)

It's certainly possible. You could use the Action and Func delegates.

TReturn RunWithLog<T, TResult>(ILogger logger, Func<T, TResult> function, T parameter1)
{
    logger.LogInformation(....);
    return function(parameter1);
}

void RunWithLog<T>(ILogger logger, Action<T> function, T parameter1)
{
    logger.LogInformation(....);
    function(parameter1);
}

You'd need to implement one for each number of parameters you want to accept.

You could also return a new func or action that contains the code in those methods to be called repeatedly. But I don't think there'd be any advantage to doing it that way. I guess you could then pass it as an argument.

Action<T> BuildRunWithLog<T>(ILogger logger, Action<T> function)
{
    return (T parameter1) => 
    {
        logger.LogInformation(....);
        function(parameter1);
    }
 }

Wrote that from memory so syntax may be wrong.

[–] pips34@programming.dev 2 points 7 months ago (1 children)

Love it! Works great for me, since most of my services receive only 2 params, and even if I didn't, having 2 or 3 wrappers is no big deal. Tysm! :)

[–] Lmaydev@programming.dev 1 points 7 months ago

No worries mate 🙂

[–] theit8514@lemmy.world 1 points 7 months ago (1 children)

The primary issue with this approach is that you'll need a RunWithLog for each number of parameters. Which is why Func and all the ones in between exists. https://learn.microsoft.com/en-us/dotnet/api/system?view=net-8.0#delegates

[–] Lmaydev@programming.dev 1 points 7 months ago

Yeah I did say that. It's a pain but chances are you only need 5 or 6.

You could do it with reflection and params but then you lose type safety etc.