serilog / serilog-expressions

An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration.
Apache License 2.0
193 stars 17 forks source link

Can't use #delimit within an #if block inside a #each loop #107

Closed MichaelIDS closed 1 year ago

MichaelIDS commented 1 year ago

Description I get a syntax error when embedding a #delimit within an #if block inside a #each loop. I already log a "StackTrace" property when its present using an #if block to log it first. So then wanted to log the rest of the unused properties excluding it. I have to add the StackTrace as context with an enricher for a number of 3rd party exceptions that exclude the StackTrace from their ToString(), So SeriLog.Exceptions didn't help on this,

Reproduction My attempted formatter template included: {#if Rest(true) <> {}}{#each name, value in Rest(true)}{#if name <> 'StackTrace'}{name}: {value}{#delimit},{#end}{#end}\n{#end}

Expected behavior I hoped I could use the delimit as per https://github.com/serilog/serilog-expressions#repetition But with my filter on which properties to log.

Relevant package, tooling and runtime versions Using current stable Serilog nuget releases. SeriLog Expressions: 4.0.0

Context I use an enricher to add the StackTrace from these specific exception types as a property. Then use the below full formatter template. Note it does the delimiter manually and so includes a trailing one. Pus it has wrapping brackets around the properties, which makes it a bit messy to read. "template": "{UtcDateTime(@t):yyyy-MM-dd HH:mm:ss.fff} +00:00 [{@l}] {@m}\n{#if @x is not null}{@x}{#end}{#if Length(@p['StackTrace']) > 0}{@p.StackTrace}\n{#end}{#if Rest(true) <> {}}{{{#each name, value in Rest(true)}{#if name <> 'StackTrace'}{name}: {value}, {#end}{#end}}}\n{#end}" If there is a better way to approach filtering the results from Rest(true) that would be most ideal, but I couldn't see any way and appreciate this is a bit of an odd usage situation.

nblumhardt commented 1 year ago

Hi! Thanks for the report.

The delimit block is part of the each expression, it can't be nested under an if block.

But the bug here is that Rest() should be ignoring StackTrace because it's referenced by the template, as @p.StackTrace. The @p prefix is throwing things off. Sending a bug fix now.

The workaround (working version) is:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console(new ExpressionTemplate(
        "{UtcDateTime(@t):yyyy-MM-dd HH:mm:ss.fff} +00:00 [{@l}] {@m}\n" +
        "{#if @x is not null}{@x}{#end}" +
        "{#if Length(StackTrace) > 0}{StackTrace}\n{#end}" +
        "{#if Rest(true) <> {}}" +
            "{{" +
            "{#each name, value in Rest()}" +
                "{name}: {value}" +
            "{#delimit}, " +
            "{#end}" +
            "}}\n" +
        "{#end}"))
    .CreateLogger();
MichaelIDS commented 1 year ago

ah, I had assumed Rest() only applied to properties in the eventLog message, rather than ones used in the template sent to the sink. Or that's how I read the projects documentation at least. So when Rest() is changed updating its documentation would be beneficial. Very nice library btw.

MichaelIDS commented 1 year ago

On just checking your example again I see the direct usage of StackTrace and looking over the github documentation again I see the below that I missed/didn't appreciate when doing this the first time. Although there was a lot of back and forth for me at the time trying to find a working solution, so not too surprising really. This does resolve my #delim issue, thanks 👍 All first-class properties of the event - no special syntax: SourceContext and Cart are used in the formatting examples above