Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document on how to support CamelCase #86

Open
luddskunk opened this issue Nov 18, 2022 · 1 comment
Open

Document on how to support CamelCase #86

luddskunk opened this issue Nov 18, 2022 · 1 comment
Labels
enhancement New feature or request

Comments

@luddskunk
Copy link

luddskunk commented Nov 18, 2022

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!

@luddskunk luddskunk added the enhancement New feature or request label Nov 18, 2022
@nblumhardt
Copy link
Member

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants