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 | Show 2 more comments1 Answer
Reset to default 2You 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);
async
method. Perhaps something like this – Matthew Watson Commented Mar 10 at 11:59