Skip to content

Instantly share code, notes, and snippets.

@johnjuuljensen
Created August 4, 2022 22:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnjuuljensen/66c069c895e0dd1baf9fe6c4cf84f0f5 to your computer and use it in GitHub Desktop.
Save johnjuuljensen/66c069c895e0dd1baf9fe6c4cf84f0f5 to your computer and use it in GitHub Desktop.
#if NET6_0_OR_GREATER
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using Serilog;
namespace Example {
public interface IStructuredLogger {
bool IsEnabled { get; }
void Log([InterpolatedStringHandlerArgument("")] ref LogInterpolatedStringHandler handler, [CallerArgumentExpression("handler")] string handlerExpr = "");
}
[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler {
public struct StringPart {
public string Literal;
public string Name;
}
public bool IsEnabled => StringParts != null;
// Could maybe be thread local and cached??
public List<StringPart> StringParts { get; } = null;
// must be array, otherwise we'll not hit the params overload in Serilog
public object[] Arguments { get; } = null;
public int LiteralLength { get; } = 0;
int argCount = 0;
public LogInterpolatedStringHandler(int literalLength, int formattedCount, IStructuredLogger logger, out bool isEnabled) {
isEnabled = logger.IsEnabled;
if (!isEnabled) return;
LiteralLength = literalLength;
StringParts = new(formattedCount + 1);
Arguments = new object[formattedCount];
}
public void AppendLiteral(string s) =>
StringParts.Add(new StringPart { Literal = s });
public void AppendFormatted<T>(T value, [CallerArgumentExpression("value")] string name = "") {
StringParts.Add(new StringPart { Name = name });
Arguments[argCount++] = value;
}
}
public class StructuredLogger: IStructuredLogger {
public StructuredLogger(Serilog.ILogger seriLogger) {
_SeriLogger=seriLogger;
}
public ILogger _SeriLogger { get; }
private static readonly ConcurrentDictionary<string, string> TemplateCache = new();
public bool IsEnabled => true;
public void Log(ref LogInterpolatedStringHandler handler, [CallerArgumentExpression("handler")] string handlerExpr = null) {
if (!handler.IsEnabled) return;
var stringParts = handler.StringParts;
int literalLength = handler.LiteralLength;
int formattedCount = handler.Arguments.Length;
var template = TemplateCache.GetOrAdd(handlerExpr, _ => BuildTemplate(stringParts, literalLength, formattedCount));
if (_SeriLogger.BindMessageTemplate(template, handler.Arguments, out Serilog.Events.MessageTemplate parsedTemplate, out IEnumerable<Serilog.Events.LogEventProperty> boundProperties)) {
_SeriLogger.Write(new Serilog.Events.LogEvent(DateTimeOffset.Now, Serilog.Events.LogEventLevel.Information, null, parsedTemplate, boundProperties));
}
}
private static string BuildTemplate(in IReadOnlyCollection<LogInterpolatedStringHandler.StringPart> stringParts, int literalLength, int formattedCount) {
StringBuilder sb = new(literalLength + formattedCount*20);
foreach (var p in stringParts) {
if (p.Literal != null) {
sb?.Append(p.Literal);
} else {
sb?.Append($"{{@{p.Name}}}");
}
}
return sb.ToString();
}
}
}
#endif
@johnjuuljensen
Copy link
Author

Executing:

var text = "ABCD";
var number = 5;
logger.Log($"POD test, number {number}, and some text {text}");

Will result in handlerExpr in StructuredLogger::Log having the value $"POD test, number {number}, and some text {text}"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment