serilog-expressions
serilog-expressions copied to clipboard
Document on how to support CamelCase
Is your feature request related to a problem? Please describe. I'd like to format my JSON with camelcase properties, since I am using a backend for visualization of logs (Grafana Loki) which adheres to that standard. The possibility to do so is also mentioned in https://nblumhardt.com/2021/06/customize-serilog-json-output/
Describe the solution you'd like Use Serilog Expression to create CamelCase keys in JSON body.
Describe alternatives you've considered I tried creating my own custom resolver to do this, but I didn't manage to get it correctly.
new ExpressionTemplate(
"{{ \"timestamp\": \"{UtcDateTime(@t)}\", \"message\": \"{@m}\", \"level\": \"{@l}\", \"exception\": \"{@x}\",\n" +
" {#each name, value in @p} \"{IsCamelCase(name)}\": " +
"{#if name = 'ExceptionDetail'}" +
"{value:j},"+
"{#else}"+
"\"{value}\"," +
"{#end}{#end} }}\n"
, nameResolver: CamelCaseResolvers))
I think this also quite quickly became too complex from maintainability standpoint.
What I want to achieve
{
"Timestamp": "2022-11-17T07:58:15.6253633Z",
"Message": "An unhandled exception has occurred while executing the request.",
"Level": "Error",
"Exception": "Exception Text"
}
should be
{
"timestamp": "2022-11-17T07:58:15.6253633Z",
"message": "An unhandled exception has occurred while executing the request.",
"level": "Error",
"exception": "Exception Text"
}
Additional context As discussed with @nblumhardt on Twitter, I open my thread here.
Hope to get any good insights for finding a smart solution I might've overlooked!
Here's my first attempt; MakeCamelCase
deals with runs of leading capitals but could still need a few more test cases to shake out bugs :-)
using System.Diagnostics.CodeAnalysis;
using Serilog.Events;
namespace Sample;
public static class CamelCaseFunctions
{
[return: NotNullIfNotNull("value")]
public static LogEventPropertyValue? ToCamelCase(LogEventPropertyValue? value)
{
return value switch
{
null => null,
DictionaryValue dictionaryValue => new DictionaryValue(dictionaryValue.Elements.Select(kvp => KeyValuePair.Create(kvp.Key, ToCamelCase(kvp.Value)))),
ScalarValue scalarValue => scalarValue,
SequenceValue sequenceValue => new SequenceValue(sequenceValue.Elements.Select(ToCamelCase)),
StructureValue structureValue => new StructureValue(
structureValue.Properties.Select(prop => new LogEventProperty(MakeCamelCase(prop.Name), ToCamelCase(prop.Value))),
structureValue.TypeTag),
_ => throw new ArgumentOutOfRangeException(nameof(value))
};
}
static string MakeCamelCase(string s)
{
if (s.Length == 0) return s;
var firstPreserved = s.Length + 1;
for (var i = 1; i < s.Length; ++i)
{
if (char.IsUpper(s[i])) continue;
firstPreserved = i;
break;
}
return s[..(firstPreserved - 1)].ToLowerInvariant() + (firstPreserved <= s.Length ? s[(firstPreserved - 1)..] : "");
}
}
Enable it with nameResolver: new StaticMemberNameResolver(typeof(CamelCaseFunctions))
and call it by wrapping toCamelCase()
around any object literal in the template:
.WriteTo.Console(new ExpressionTemplate(
"{ toCamelCase({@t: UtcDateTime(@t), @mt, @l: if @l = 'Information' then undefined() else @l, @x, UITest: 42, FUN: 8, IPhone: 13, ..@p}) }\n",
nameResolver: new StaticMemberNameResolver(typeof(CamelCaseFunctions))))
Would love to hear how you go!