- Notifications
You must be signed in to change notification settings - Fork0
An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration.
License
nblumhardt/serilog-expressions
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
An embeddable mini-language for filtering, enriching, and formatting Serilogevents, ideal for use with JSON or XML configuration.
Install the package from NuGet:
dotnet add package Serilog.Expressions
The package adds extension methods to Serilog'sFilter,WriteTo, andEnrich configuration objects, along with anExpressionTemplatetype that's compatible with Serilog sinks accepting anITextFormatter.
Serilog.Expressions addsByExcluding() andByIncludingOnly()overloads to theFilter configuration object that accept filterexpressions:
Log.Logger=newLoggerConfiguration().Filter.ByExcluding("RequestPath like '/health%'").CreateLogger();
Events with aRequestPath property that matches the expressionwill be excluded by the filter.
Note that if the expression syntax is invalid, an
ArgumentExceptionwillbe thrown from theByExcluding()method, and by similar methods elsewherein the package. To check expression syntax without throwing, see theTry*()methods in theSerilogExpressionclass.
InappSettings.json configurationthis is written as:
{"Serilog": {"Using": ["Serilog.Expressions"],"Filter": [ {"Name":"ByExcluding","Args": {"expression":"RequestPath like '/health%'" } } ] }}InXML configuration files,this is written as:
<appSettings> <addkey="serilog:using:Expressions"value="Serilog.Expressions" /> <addkey="serilog:filter:ByExcluding.expression"value="RequestPath like '/health%'" /> </appSettings>
Serilog.Expressions adds a number of expression-based overloads and helper methods to the Serilog configuration syntax:
Filter.ByExcluding(),Filter.ByIncludingOnly()- use an expression to filter events passing through the Serilog pipelineWriteTo.Conditional()- use an expression to select the events passed to a particular sinkEnrich.When()- conditionally enable an enricher when events match an expressionEnrich.WithComputed()- add or modify event properties using an expression
Serilog.Expressions includes theExpressionTemplate class for text formatting.ExpressionTemplate implementsITextFormatter, soit works with any text-based Serilog sink, includingConsole,File,Debug, andEmail:
// using Serilog.Templates;Log.Logger=newLoggerConfiguration().WriteTo.Console(newExpressionTemplate("[{@t:HH:mm:ss} {@l:u3} ({SourceContext})] {@m} (first item is {Cart[0]})\n{@x}")).CreateLogger();// Produces log events like:// [21:21:40 INF (Sample.Program)] Cart contains ["Tea","Coffee"] (first item is Tea)
Templates are based on .NET format strings, and support standard padding, alignment, and format specifiers.
Along with standard properties for the event timestamp (@t), level (@l) and so on, "holes" in expression templates can include complexexpressions over the first-class properties of the event, like{SourceContext} and{Cart[0]} in the example..
Templates support customizable color themes when used with theConsole sink:
.WriteTo.Console(newExpressionTemplate("[{@t:HH:mm:ss} {@l:u3}] {@m}\n{@x}",theme:TemplateTheme.Code))
Newline-delimited JSON (for example, replicating theCLEF format) can be generatedusing object literals:
.WriteTo.Console(newExpressionTemplate("{ {@t, @mt, @r, @l: if @l = 'Information' then undefined() else @l, @x, ..@p} }\n"))
The following properties are available in expressions:
- All first-class properties of the event - no special syntax:
SourceContextandCartare used in the formatting examples above @t- the event's timestamp, as aDateTimeOffset@m- the rendered message@mt- the raw message template@l- the event's level, as aLogEventLevel@x- the exception associated with the event, if any, as anException@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.
| Data type | Description | Examples |
|---|---|---|
| Null | Corresponds to .NET'snull value | null |
| Number | A number in decimal or hexadecimal notation, represented by .NETdecimal | 0,100,-12.34,0xC0FFEE |
| String | A single-quoted Unicode string literal; to escape', double it | 'pie','isn''t','😋' |
| Boolean | A Boolean value | true,false |
| Array | An array of values, in square brackets | [1, 'two', null] |
| Object | A mapping of string keys to values; keys that are valid identifiers do not need to be quoted | {a: 1, 'b c': 2, d} |
Array and object literals support the spread operator:[1, 2, ..others],{a: 1, ..others}. Specifying an undefinedproperty in an object literal will remove it from the result:{..User, Email: Undefined()}
A typical set of operators is supported:
- Equality
=and inequality<>, including for arrays and objects - Boolean
and,or,not - Arithmetic
+,-,*,/,^,% - Numeric comparison
<,<=,>,>= - Existence
is nullandis not null - SQL-style
likeandnot like, with%and_wildcards (double wildcards to escape them) - Array membership with
inandnot in - Accessors
a.b - Indexers
a['b']anda[0] - Wildcard indexing -
a[?]any, anda[*]all - Conditional
if a then b else c(all branches required; see also the section below onconditional blocks)
Comparision operators that act on text all accept an optional postfixci modifier to select case-insensitive comparisons:
User.Name like 'n%' ciFunctions are called using typicalIdentifier(args) syntax.
Except for theIsDefined() function, the result ofcalling a function will be undefined if:
- any argument is undefined, or
- any argument is of an incompatible type.
| Function | Description |
|---|---|
Coalesce(p0, p1, [..pN]) | Returns the first defined, non-null argument. |
Concat(s0, s1, [..sN]) | Concatenate two or more strings. |
Contains(s, t) | Tests whether the strings contains the substringt. |
ElementAt(x, i) | Retrieves a property ofx by namei, or array element ofx by numeric indexi. |
EndsWith(s, t) | Tests whether the strings ends with substringt. |
IndexOf(s, t) | Returns the first index of substringt in strings, or -1 if the substring does not appear. |
IndexOfMatch(s, p) | Returns the index of the first match of regular expressionp in strings, or -1 if the regular expression does not match. |
IsMatch(s, p) | Tests whether the regular expressionp matches within the strings. |
IsDefined(x) | Returnstrue if the expressionx has a value, includingnull, orfalse ifx is undefined. |
LastIndexOf(s, t) | Returns the last index of substringt in strings, or -1 if the substring does not appear. |
Length(x) | Returns the length of a string or array. |
Now() | ReturnsDateTimeOffset.Now. |
Rest([deep]) | In anExpressionTemplate, returns an object containing the first-class event properties not otherwise referenced in the template. Ifdeep istrue, also excludes properties referenced in the event's message template. |
Round(n, m) | Round the numbern tom decimal places. |
StartsWith(s, t) | Tests whether the strings starts with substringt. |
Substring(s, start, [length]) | Return the substring of strings fromstart to the end of the string, or oflength characters, if this argument is supplied. |
TagOf(o) | Returns theTypeTag field of a captured object (i.e. whereTypeOf(x) is'object'). |
ToString(x, [format]) | Convertx to a string, applying the format stringformat ifx isIFormattable. |
TypeOf(x) | Returns a string describing the type of expressionx: a .NET type name ifx is scalar and non-null, or,'array','object','dictionary','null', or'undefined'. |
Undefined() | Explicitly mark an undefined value. |
UtcDateTime(x) | Convert aDateTime orDateTimeOffset into a UTCDateTime. |
Functions that compare text accept an optional postfixci modifier to select case-insensitive comparisons:
StartsWith(User.Name, 'n') ciWithin anExpressionTemplate, a portion of the template can be conditionally evaluated using#if.
Log.Logger=newLoggerConfiguration().WriteTo.Console(newExpressionTemplate("[{@t:HH:mm:ss} {@l:u3}{#if SourceContext is not null} ({SourceContext}){#end}] {@m}\n{@x}")).CreateLogger();// Produces log events like:// [21:21:45 INF] Starting up// [21:21:46 INF (Sample.Program)] Firing engines
The block between the{#if <expr>} and{#end} directives will only appear in the output if<expr> istrue - in the example, events with aSourceContext include this in parentheses, while those without, don't.
It's important to notice that the directive requires a Booleantrue before the conditional block will be evaluated. It wouldn't be sufficient in this case to write{#if SourceContext}, since no values other thantrue are considered "truthy".
The syntax supports{#if <expr>}, chained{#else if <expr>},{#else}, and{#end}, with arbitrary nesting.
If a log event includes structured data in arrays or objects, a template block can be repeated for each element or member using#each/in (newlines, double quotes and construction of theExpressionTemplate omitted for clarity):
{@l:w4}: {SourceContext} {#each s in Scope}=> {s}{#delimit} {#end} {@m}{@x}This example uses the optional#delimit to add a space between each element, producing output like:
info: Sample.Program => Main => TextFormattingExample Hello, world!When using{#each <name> in <expr>} over an object, such as the built-in@p (properties) object,<name> will be bound to thenames of the properties of the object.
To get to thevalues of the properties, use a second binding:
{#each k, v in @p}{k} = {v}{#delimit},{#end}This example, if an event has three properties, will produce output like:
Account = "nblumhardt", Cart = ["Tea", "Coffee"], Powerup = 42The syntax supports{#each <name>[, <name>] in <expr>}, an optional{#delimit} block, and finally an optional{#else} block, which will be evaluated if the array or object is empty.
Trim downSourceContext to a type name only:
Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)This expression takes advantage ofLastIndexOf() returning -1 when no. character appears inSourceContext, to yield astartIndex of 0 in that case.
Write not-referenced context properties (only if there are any):
{#if rest(true) <> {}} <Context: {rest(true)}>{#end}Access a property with a non-identifier name:
@p['some name']Any structured value, including the built-in@p, can be indexed by string key. This means thatUser.Name andUser['Name'] are equivalent, for example.
Access a property with inconsistent casing:
ElementAt(@p, 'someName') ciElementAt() is a function-call version of the[] indexer notation, which means it can accept theci case-insensitivity modifier.
Format events as newline-delimited JSON (template, embedded in C# or JSON):
{ {Timestamp: @t, Username: User.Name} }\nThis output template shows the use of a space between the opening{ of a hole, and the enclosed object literal withTimestamp andUsername fields. The object will be formatted as JSON. The trailing\n is a C# or JSON newline literal (don't escape this any further, asit's not part of the output template syntax).
The package provides the classSerilogExpression in theSerilog.Expressions namespacefor working with expressions.
if(SerilogExpression.TryCompile("RequestPath like '/health%'",outvarcompiled,outvarerror){// `compiled` is a function that can be executed against `LogEvent`s:varresult= compiled(someEvent);// `result` will contain a `LogEventPropertyValue`, or `null` if the result of evaluating the// expression is undefined (for example if the event has no `RequestPath` property).if(resultis ScalarValue value&& value.Valueisboolmatches&& matches){Console.WriteLine("The event matched.");}}else{// `error` describes a syntax error.Console.WriteLine($"Couldn't compile the expression;{error}.");}
Compiled expression delegates returnLogEventPropertyValue because this is the mostconvenient type to work with in many Serilog scenarios (enrichers, sinks, ...). Toconvert the result to plain-old-.NET-types likestring,bool,Dictionary<K,V> andArray, use the functions in theSerilog.Expressions.ExpressionResult class:
varresult=compiled(someEvent);// `true` only if `result` is a scalar Boolean `true`; `false` otherwise:if(ExpressionResult.IsTrue(result)){Console.WriteLine("The event matched.");}
User-defined functions can be plugged in by implementing static methods that:
- Return
LogEventPropertyValue?, - Have arguments of type
LogEventPropertyValue?orLogEvent, - If the
cimodifier is supported, accept aStringComparison, and - If culture-specific formatting or comparisons are used, accepts an
IFormatProvider.
For example:
publicstaticclassMyFunctions{publicstaticLogEventPropertyValue?IsHello(StringComparisoncomparison,LogEventPropertyValue?maybeHello){if(maybeHelloisScalarValuesv&&sv.Valueisstrings)returnnewScalarValue(s.Equals("Hello",comparison));// Undefined - argument was not a string.returnnull;}}
In the example,IsHello('Hello') will evaluate totrue,IsHello('HELLO') will befalse,IsHello('HELLO') ciwill betrue, andIsHello(42) will be undefined.
User-defined functions are supplied through an instance ofNameResolver:
varmyFunctions=newStaticMemberNameResolver(typeof(MyFunctions));varexpr=SerilogExpression.Compile("IsHello(User.Name)",nameResolver:myFunctions);// Filter events based on whether `User.Name` is `'Hello'` :-)
Includes the parser combinator implementation fromSuperpower, copyright Datalust,Superpower Contributors, and Sprache Contributors; licensed under the Apache License, 2.0.
About
An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration.
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Languages
- C#99.5%
- PowerShell0.5%
