Created
August 4, 2022 22:13
-
-
Save johnjuuljensen/66c069c895e0dd1baf9fe6c4cf84f0f5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Executing:
Will result in
handlerExpr
in StructuredLogger::Log having the value$"POD test, number {number}, and some text {text}"