serilog-expressions icon indicating copy to clipboard operation
serilog-expressions copied to clipboard

Clarify documentation around @x

Open IanKemp opened this issue 1 year ago • 6 comments

The README states:

@x - the exception associated with the event, if any, as an Exception

which implies to me that you are able to access the properties of that exception.

In reality however, @x is simply a ToString()'d version of the exception, and to get hold of its properties you need to write a custom ILogEventEnricher. It would be extremely helpful to update the README with this information.

IanKemp avatar Oct 30 '23 18:10 IanKemp

Hi! Thanks for raising this @IanKemp 👍

It's actually a bit of a corner case right now; the documentation is correct - the value of @x is an actual Exception object:

https://github.com/serilog/serilog-expressions/blob/dev/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs#L208

And if you use SerilogExpression.TryCompile("@x", ...) and invoke the resulting delegate with a LogEvent, you'll get a ScalarValue { Value = <some exception> } back.

But, there aren't yet any functions for manipulating exceptions in the rest of the language/library, so there's no way to e.g. retrieve the message, stack trace, or other properties individually, and thus the only way to format it out as text right now is to let it be ToString()ed.

The plan is to add some support functions to the RuntimeOperators that make it possible to turn the opaque Exception object into a data structure and traverse it, along the lines of:

CaptureException(@x).Message

where CaptureException would, stepping only one layer deep through inner exceptions etc., provide a StructureValue with message, stack trace, data, inner exception and innerexceptions (if an AggregateException).

The case of the exception type is already handled by TypeOf(@x).

What does your use case look like, when implemented with CaptureException()?

nblumhardt avatar Oct 31 '23 06:10 nblumhardt

Our use case is emitting logs in a format that Datadog can ingress and use in its metrics, specifically the error.* properties as documented here. It sounds like CaptureException would do exactly what we need, but I do think it is important to update the README to clarify that this feature isn't yet available and until then, it's necessary to use ILogEventEnricher.

IanKemp avatar Nov 01 '23 13:11 IanKemp

I have an implementation of the "capture" function on the way, just a few small details left to figure out, so I'll include that in the README update. Should be done in a day or two.

nblumhardt avatar Nov 16 '23 21:11 nblumhardt

Ah yea I found this confusing as well. As initially I tried to access fields of @x. i.e. @x.StackTrace After a bit of trial and fail I realised it wasn't possible, although wasn't sure entirely why. So moved to using Properties (@p) and adding what I needed via Log.Context() specifically. I then noticed that the documentation never actually says that you can get fields from @x and I had just assumed it was the real exception object. So clarifying exactly what @x is, in the documentation would be beneficial to new users.

MichaelIDS avatar Nov 17 '23 00:11 MichaelIDS

#109 includes some README updates as well as the new function, it'd be great to have some more eyes on it :)

nblumhardt avatar Nov 17 '23 21:11 nblumhardt

#109 includes some README updates as well as the new function, it'd be great to have some more eyes on it :)

Personally, I find the extra details on the variable @x being after the list of variables a bit odd and allows it to be missed when doing searches for @x. Is it there as you wanted to keep the main variable list entries concise or for another reason? The content of the extra details about @x looks clear to me.

  • All first-class properties of the event - no special syntax: SourceContext and Cart are used in the formatting examples above
  • @t - the event's timestamp, as a DateTimeOffset
  • @m - the rendered message
  • @mt - the raw message template
  • @l - the event's level, as a LogEventLevel
  • @x - the exception associated with the event, if any, as an Exception
  • @p - a dictionary containing all first-class properties; this supports properties with non-identifier names, for example @p['snake-case-name']
  • @i - event id; a 32-bit numeric hash of the event's message template
  • @r - renderings; if any tokens in the message template include .NET-specific formatting, an array of rendered values for each such token

The built-in properties mirror those available in the CLEF format.

The exception property @x is treated as a scalar and will appear as a string when formatted into text. The properties of the underlying Exception object can be accessed using Inspect(), for example Inspect(@x).Message, and the type of the exception retrieved using TypeOf(@x).


The description of the Inspect() function looks a little light to me.

  1. The textual description doesn't lead me to think that it returns the object for accessing the properties of. Also, when would that be required given @p can just have the . or [] notations used.
  2. An example of its usage would seem beneficial there, like @x has.
  3. Also, what does the [Deep] argument do for the function.

MichaelIDS avatar Nov 20 '23 20:11 MichaelIDS