dotnet / extensions

This repository contains a suite of libraries that provide facilities commonly needed when creating production-ready applications.
MIT License
2.59k stars 743 forks source link

[API Proposal]: Logging source generator: support template customization #5223

Closed Micke3rd closed 3 months ago

Micke3rd commented 3 months ago

Background and motivation

  1. There are parameters that you want to determine in many log functions. Instead of manually maintaining repetitive code in each method marked with [LoggerMessage], these parts should be moveable to the generator. Example:

    [LoggerMessage(LogLevel.Information)]
    internal static partial void ExampleA(ILogger logger, Person person
        , [CallerMemberName] string SourceMemberName = ""                    --> repetitive
        , [CallerFilePath] string SourceFilePath = ""                         --> repetitive
        , [CallerLineNumber] int SourceLineNumber = 0                          --> repetitive
        );
  2. Customizable template would allow TState to be filled with customized logic. This includes the use of serializers that are not limited to [LogProperties]. This is already possible by overriding ToString() in the respective classes, but since these data structures are usually created for the respective log method, this overload must be created accordingly often. Example:

    readonly record struct Person(uint Id, string Name, Person[]? Parents = null)
    {
    public override string ToString() => JsonSerializer.Serialize(this);
    }

API Proposal

no instructions on how to access, the main thing is somehow

Risks

no known risks

thank you for reading

julealgon commented 3 months ago

3. If there are specific requirements how to serialize logged objects, each of these classes/structs must override ToString. Example:

readonly record struct Person(uint Id, string Name, Person[]? Parents = null)
{
  public override string ToString() => JsonSerializer.Serialize(this);
}

@Micke3rd use Microsoft.Extensions.Telemetry set of NuGet packages to log entire objects if that's what you need:

It also adds the LogProperties attribute which can be applied to an object parameter of a LoggerMessage method. It introspects the passed-in object and automatically adds tags for all its properties. This leads to more informative logs without the need for manual tagging of each property.

[LoggerMessage(LogLevel.Information)]
internal static partial void ExampleA(ILogger logger, [LogProperties] Person person

Serializing an object as json to put it in the log like your code does is a really bad way to do it, IMHO.

Micke3rd commented 3 months ago

Serializing an object as json to put it in the log like your code does is a really bad way to do it

Why ? The JsonSerialization in the example symbolizes logging for the purpose of later machine processing. [LogProperties] only serializes the first level, which is not always sufficient.

julealgon commented 3 months ago

@Micke3rd

Serializing an object as json to put it in the log like your code does is a really bad way to do it

Why ? The JsonSerialization in the example symbolizes logging for the purpose of later machine processing.

By outputting a json you miss the per-attribute indexing capabilities of observability platforms, making it much harder to correlate and search fields across different logs and traces.

[LogProperties] only serializes the first level, which is not always sufficient.

Interesting, I did not know that. Looking at the documentation for the parameter, it apparently not only applies to arguments, but also to properties.

Can you try adding this attribute to one of your complex type properties in your model, and see if it respects it and starts outputting nested properties from that as well?

tarekgh commented 3 months ago

Moving it to extensions repo as more replated to advanced features like LogProperties.

CC @geeknoid

geeknoid commented 3 months ago

Serializing an object as json to put it in the log like your code does is a really bad way to do it

Why ? The JsonSerialization in the example symbolizes logging for the purpose of later machine processing. [LogProperties] only serializes the first level, which is not always sufficient.

[LogProperties] can be applied to properties in your objects. With this, you can log tranaitively full graphs of objects.

geeknoid commented 3 months ago

@Micke3rd Hey there, I'm sorry but I don't actually understand what you're suggesting/asking. Could you please provide an example of what you'd like to see?

Micke3rd commented 3 months ago

Hi @geeknoid and thank you for asking.

The LoggerMessageGenerator inside of Microsoft.Extensions.Logging.Generators.Roslyn contains a class Emitter (LoggerMessageGenerator.Emitter.cs) This class contains what I mean by template - the construction instructions for the method to be generated. My request would be whether you can create a possibility for an alternative emitter to be used in:

public partial class LoggerMessageGenerator : IIncrementalGenerator
{
 private static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes, SourceProductionContext context)
 {
    ...
    var e = new Emitter();
    ...
 }
}

In my opinion, the only possible alternative emitter is one that inherits the current emitter, not an interface. Because you only want to make adjustments, not rewrite everything. A possible option to promote this emitter might be inside of LoggerFactory.Create(builder => .... )

I should have described it like this right at the beginning, therefore ty again for asking

geeknoid commented 3 months ago

Unfortunately, it's not that simple. Basically what you're asking for is to mostly write a new logging generator, since the emitter is most of what a generator is about. If you look around in the code base, you'll see that there is a lot of logic to support the generation of the source code.

Stepping away from the implementation of emitters, is the behavior you're looking for as a user is to be able to tell the generator to inject a specific set of parameters on all the logging methods? Can you come up with an example that shows what you would like to write in your source code, and what you would like to have the generator produce as output?

Micke3rd commented 3 months ago

Can you come up with an example that shows

omg, you caught me with the default question for change requests ... because I just coded the solution to share it here, to see that the real compiler, compared to the one in my head, is much less enthusiastic about my idea.

What I want must be solved with a separate source generator. "Eureka" ^^

ty for your patience !