最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - How to Avoid Repeating ILogger Message and Reuse Message - Stack Overflow

programmeradmin3浏览0评论

When logging, I often end up with very similar repeated code to create log, notification, and progress messages, like this:

var productContracts = await context.ProductContracts.ToListAsync();

string contractListSummary = productContracts.ValuesToStringNewLines(c =>
    c.GetDescription()
);

// Would like to avoid creating the same message twice

logger.LogInformation(
    "All contracts are up to date:\r\n{ContractListSummary}",
    contractListSummary
);

await App.Show.AlertSuccess(
    $"All contracts are up to date:\r\n{contractListSummary}"
);

This litters methods that log, show alerts, report progress, etc. with essentially the same code to build the message string.

Is there an way to avoid this?

For example,

var message = logger.LogInformation(...);
ShowAlert(message);

or

logger.LogInformation(...).Then(message => ShowAlert(message))

It's possible to do something like:

    var message = "My message";
    logger.LogInformation("{Message}", message);
    ShowAlert(message);

... but that loses the benefit of structure logging.

When logging, I often end up with very similar repeated code to create log, notification, and progress messages, like this:

var productContracts = await context.ProductContracts.ToListAsync();

string contractListSummary = productContracts.ValuesToStringNewLines(c =>
    c.GetDescription()
);

// Would like to avoid creating the same message twice

logger.LogInformation(
    "All contracts are up to date:\r\n{ContractListSummary}",
    contractListSummary
);

await App.Show.AlertSuccess(
    $"All contracts are up to date:\r\n{contractListSummary}"
);

This litters methods that log, show alerts, report progress, etc. with essentially the same code to build the message string.

Is there an way to avoid this?

For example,

var message = logger.LogInformation(...);
ShowAlert(message);

or

logger.LogInformation(...).Then(message => ShowAlert(message))

It's possible to do something like:

    var message = "My message";
    logger.LogInformation("{Message}", message);
    ShowAlert(message);

... but that loses the benefit of structure logging.

Share Improve this question asked Mar 10 at 11:40 SeanSean 7006 silver badges13 bronze badges 7
  • 1 If you are using the Microsoft logging framework, you could add a custom logging provider that logs to the application. However I'm not sure how you'd handle the async method. Perhaps something like this – Matthew Watson Commented Mar 10 at 11:59
  • 1 There is unlikely to be a simple and clean solution here. One option would be a custom logging source generator. Whether this is worth it would very much depend on how common this use case (and information to two places is). Another option would be a custom provider for the alerts. – Richard Commented Mar 10 at 11:59
  • Create a Dictionary of messages and then get message from dictionary. You can put the code into a method and call the method with a key name in the dictionary. – jdweng Commented Mar 10 at 12:18
  • 1 ^^ Is this going to stay english-only? If you were to go multi-lang, should log messages also be localized? – Fildor Commented Mar 10 at 12:24
  • @Fildor That's an interesting point. In my case, I'm fine with staying English, but interested to see if other have solutions for the case you mentioned. – Sean Commented Mar 11 at 7:28
 |  Show 2 more comments

1 Answer 1

Reset to default 2

You can make use of custom interpolated string handlers to achieve this. In the code below there are two options - one where you supply the names for the structured logging explicitly and another where CallerArgumentExpression helps us get the template names from the actual expression:

public sealed class InterpolatedStringHolder {
    public string Value => _handler.Value;
    public string Template => _handler.Template;
    public object[] Arguments => _handler.Arguments;

    TemplateInterpolatedStringHandler _handler;

    public InterpolatedStringHolder(TemplateInterpolatedStringHandler handler) {
        _handler = handler;
    }

    public InterpolatedStringHolder(string[] templateNames,
    [InterpolatedStringHandlerArgument(nameof(templateNames))]
    TemplateInterpolatedStringHandler handler) {
        _handler = handler;
    }
}

[InterpolatedStringHandler]
public struct TemplateInterpolatedStringHandler {
    StringBuilder _builder;
    StringBuilder _templateBuilder;

    int _counter;
    string[] _templateNames;

    object[] _arguments;
    string _value;
    string _template;

    public object[] Arguments => _arguments;
    public string Value => _value ??= _builder.ToString();
    public string Template => _template ??= _templateBuilder.ToString();

    public TemplateInterpolatedStringHandler(int literalLength, int formattedCount) {
        _builder = new StringBuilder(literalLength);
        _templateBuilder = new StringBuilder(literalLength);
        _arguments = new object[formattedCount];
    }

    public TemplateInterpolatedStringHandler(int literalLength, int formattedCount,
    string[] templateNames) {
        if (templateNames.Length != formattedCount)
            throw new ArgumentException();

        _templateNames = templateNames;
        _builder = new StringBuilder(literalLength);
        _templateBuilder = new StringBuilder(literalLength);
        _arguments = new object[formattedCount];
    }

    public void AppendLiteral(string s) {
        _builder.Append(s);
        _templateBuilder.Append(s);
    }
    
    public void AppendFormatted<T>(T t,
    [CallerArgumentExpression(nameof(t))] string messageExpression = "") {
        _builder.Append(t);
        if (_templateNames == null) {
            _templateBuilder.Append($"{{{messageExpression}}}");
        } else {
            _templateBuilder.Append($"{{{_templateNames[_counter]}}}");
        }
        _arguments[_counter] = t;
        _counter++;
    }

    public string GetFormattedText() => Value;
}

General use:

var someInt = 42;
var datetime = DateTime.Now;

// with explicit template names for structured logging
var foo = new InterpolatedStringHolder(
new string[] { "CurrentTime", "Sales" },
$"Current time is {datetime} and sales are {someInt}");

Console.WriteLine(foo.Value); // Current time is 11-Mar-25 3:50:21 AM and sales are 42
Console.WriteLine(foo.Template); // Current time is {CurrentTime} and sales are {Sales}
Console.WriteLine(foo.Arguments.Length); // 2

// with implicit template names, i.e. datetime and someint
var foo2 = new InterpolatedStringHolder(
$"Current time is {datetime} and sales are {someInt}");

Console.WriteLine(foo2.Value); // Current time is 11-Mar-25 3:50:21 AM and sales are 42
Console.WriteLine(foo2.Template); // Current time is {datetime} and sales are {someInt}
Console.WriteLine(foo2.Arguments.Length); // 2

For your use case:

var productContracts = await context.ProductContracts.ToListAsync();

string contractListSummary = productContracts.ValuesToStringNewLines(c =>
    c.GetDescription()
);

var stringHolder = new InterpolatedStringHolder(
new string[] { "ContractListSummary" },
$"All contracts are up to date:\r\n{contractListSummary}");

logger.LogInformation(
    stringHolder.Template,
    stringHolder.Arguments
);

await App.Show.AlertSuccess(stringHolder.Value);
发布评论

评论列表(0)

  1. 暂无评论