Skip to content

Commit

Permalink
Merge pull request #27 from serilog/dev
Browse files Browse the repository at this point in the history
1.1.0 Release
  • Loading branch information
nblumhardt authored Mar 7, 2021
2 parents 8347a7b + 81bcc69 commit 6975368
Show file tree
Hide file tree
Showing 26 changed files with 596 additions and 74 deletions.
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ events, ideal for use with JSON or XML configuration.
Install the package from NuGet:

```shell
dotnet add package Serilog.Expressions -v 1.0.0-*
dotnet add package Serilog.Expressions
```

The package adds extension methods to Serilog's `Filter`, `WriteTo`, and
Expand Down Expand Up @@ -85,6 +85,8 @@ _Serilog.Expressions_ includes the `ExpressionTemplate` class for text formattin
it works with any text-based Serilog sink:

```csharp
// using Serilog.Templates;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(new ExpressionTemplate(
"[{@t:HH:mm:ss} {@l:u3} ({SourceContext})] {@m} (first item is {Items[0]})\n{@x}"))
Expand All @@ -93,27 +95,31 @@ Log.Logger = new LoggerConfiguration()

Note the use of `{Items[0]}`: "holes" in expression templates can include any valid expression.

Newline-delimited JSON (for example, emulating the [CLEF format](https://github.com/serilog/serilog-formatting-compact)) can be generated
Newline-delimited JSON (for example, replicating the [CLEF format](https://github.com/serilog/serilog-formatting-compact)) can be generated
using object literals:

```csharp
.WriteTo.Console(new ExpressionTemplate(
"{ {@t, @mt, @l: if @l = 'Information' then undefined() else @l, @x, ..@p} }\n"))
"{ {@t, @mt, @r, @l: if @l = 'Information' then undefined() else @l, @x, ..@p} }\n"))
```

## Language reference

### Built-in properties
### Properties

The following properties are available in expressions:

* All first-class properties of the event; no special syntax: `SourceContext` and `Items` are used in the formatting example above
* **All first-class properties of the event** — no special syntax: `SourceContext` and `Items` are used in the formatting example 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.

### Literals

Expand All @@ -140,7 +146,8 @@ A typical set of operators is supported:
* Existence `is null` and `is not null`
* SQL-style `like` and `not like`, with `%` and `_` wildcards (double wildcards to escape them)
* Array membership with `in` and `not in`
* Indexers `a[b]` and accessors `a.b`
* Accessors `a.b`
* Indexers `a['b']` and `a[0]`
* Wildcard indexing - `a[?]` any, and `a[*]` all
* Conditional `if a then b else c` (all branches required)

Expand Down Expand Up @@ -172,12 +179,15 @@ calling a function will be undefined if:
| `IsDefined(x)` | Returns `true` if the expression `x` has a value, including `null`, or `false` if `x` is undefined. |
| `LastIndexOf(s, t)` | Returns the last index of substring `t` in string `s`, or -1 if the substring does not appear. |
| `Length(x)` | Returns the length of a string or array. |
| `Now()` | Returns `DateTimeOffset.Now`. |
| `Round(n, m)` | Round the number `n` to `m` decimal places. |
| `StartsWith(s, t)` | Tests whether the string `s` starts with substring `t`. |
| `Substring(s, start, [length])` | Return the substring of string `s` from `start` to the end of the string, or of `length` characters, if this argument is supplied. |
| `TagOf(o)` | Returns the `TypeTag` field of a captured object (i.e. where `TypeOf(x)` is `'object'`). |
| `ToString(x, f)` | Applies the format string `f` to the formattable value `x`. |
| `TypeOf(x)` | Returns a string describing the type of expression `x`: a .NET type name if `x` is scalar and non-null, or, `'array'`, `'object'`, `'dictionary'`, `'null'`, or `'undefined'`. |
| `Undefined()` | Explicitly mark an undefined value. |
| `UtcDateTime(x)` | Convert a `DateTime` or `DateTimeOffset` into a UTC `DateTime`. |

Functions that compare text accept an optional postfix `ci` modifier to select case-insensitive comparisons:

Expand Down
2 changes: 2 additions & 0 deletions src/Serilog.Expressions/Expressions/Ast/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
{
abstract class Expression
{
// Used only as an enabler for testing and debugging.
public abstract override string ToString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,10 @@ public IndexOfMatchExpression(Expression corpus, Regex regex)
Corpus = corpus ?? throw new ArgumentNullException(nameof(corpus));
Regex = regex ?? throw new ArgumentNullException(nameof(regex));
}

public override string ToString()
{
return $"_Internal_IndexOfMatch({Corpus}, '{Regex.ToString().Replace("'", "''")}')";
}
}
}
17 changes: 17 additions & 0 deletions src/Serilog.Expressions/Expressions/BuiltInProperty.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
// Copyright © Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Serilog.Expressions
{
// See https://github.com/serilog/serilog-formatting-compact#reified-properties
static class BuiltInProperty
{
public const string Exception = "x";
Expand All @@ -8,5 +23,7 @@ static class BuiltInProperty
public const string Message = "m";
public const string MessageTemplate = "mt";
public const string Properties = "p";
public const string Renderings = "r";
public const string EventId = "i";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Serilog.Expressions.Compilation
{
static class ExpressionCompiler
{
public static CompiledExpression Compile(Expression expression, NameResolver nameResolver)
public static Expression Translate(Expression expression)
{
var actual = expression;
actual = VariadicCallRewriter.Rewrite(actual);
Expand All @@ -19,7 +19,12 @@ public static CompiledExpression Compile(Expression expression, NameResolver nam
actual = PropertiesObjectAccessorTransformer.Rewrite(actual);
actual = ConstantArrayEvaluator.Evaluate(actual);
actual = WildcardComprehensionTransformer.Expand(actual);

return actual;
}

public static CompiledExpression Compile(Expression expression, NameResolver nameResolver)
{
var actual = Translate(expression);
return LinqExpressionCompiler.Compile(actual, nameResolver);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright © Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;

// ReSharper disable ForCanBeConvertedToForeach

namespace Serilog.Expressions.Compilation.Linq
{
/// <summary>
/// Hash functions for message templates. See <see cref="Compute"/>.
/// </summary>
public static class EventIdHash
{
/// <summary>
/// Compute a 32-bit hash of the provided <paramref name="messageTemplate"/>. The
/// resulting hash value can be uses as an event id in lieu of transmitting the
/// full template string.
/// </summary>
/// <param name="messageTemplate">A message template.</param>
/// <returns>A 32-bit hash of the template.</returns>
[CLSCompliant(false)]
public static uint Compute(string messageTemplate)
{
if (messageTemplate == null) throw new ArgumentNullException(nameof(messageTemplate));

// Jenkins one-at-a-time https://en.wikipedia.org/wiki/Jenkins_hash_function
unchecked
{
uint hash = 0;
for (var i = 0; i < messageTemplate.Length; ++i)
{
hash += messageTemplate[i];
hash += hash << 10;
hash ^= hash >> 6;
}
hash += hash << 3;
hash ^= hash >> 11;
hash += hash << 15;
return hash;
}
}
}
}
23 changes: 23 additions & 0 deletions src/Serilog.Expressions/Expressions/Compilation/Linq/Intrinsics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Text.RegularExpressions;
using Serilog.Events;
using Serilog.Formatting.Display;
using Serilog.Parsing;

// ReSharper disable ParameterTypeCanBeEnumerable.Global

Expand All @@ -15,6 +16,8 @@ static class Intrinsics
{
static readonly LogEventPropertyValue NegativeOne = new ScalarValue(-1);
static readonly LogEventPropertyValue Tombstone = new ScalarValue("😬 (if you see this you have found a bug.)");

// TODO #19: formatting is culture-specific.
static readonly MessageTemplateTextFormatter MessageFormatter = new MessageTemplateTextFormatter("{Message:lj}");

public static List<LogEventPropertyValue?> CollectSequenceElements(LogEventPropertyValue?[] elements)
Expand Down Expand Up @@ -159,5 +162,25 @@ public static string RenderMessage(LogEvent logEvent)
MessageFormatter.Format(logEvent, sw);
return sw.ToString();
}

public static LogEventPropertyValue? GetRenderings(LogEvent logEvent)
{
List<LogEventPropertyValue>? elements = null;
foreach (var token in logEvent.MessageTemplate.Tokens)
{
if (token is PropertyToken pt && pt.Format != null)
{
elements ??= new List<LogEventPropertyValue>();

var space = new StringWriter();

// TODO #19: formatting is culture-specific.
pt.Render(logEvent.Properties, space);
elements.Add(new ScalarValue(space.ToString()));
}
}

return elements == null ? null : new SequenceValue(elements);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
using System;
// Copyright © Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
Expand Down Expand Up @@ -124,25 +138,22 @@ protected override ExpressionBody Transform(AmbientPropertyExpression px)
{
if (px.IsBuiltIn)
{
if (px.PropertyName == BuiltInProperty.Level)
return Splice(context => new ScalarValue(context.Level));

if (px.PropertyName == BuiltInProperty.Message)
return Splice(context => new ScalarValue(Intrinsics.RenderMessage(context)));

if (px.PropertyName == BuiltInProperty.Exception)
return Splice(context => context.Exception == null ? null : new ScalarValue(context.Exception));

if (px.PropertyName == BuiltInProperty.Timestamp)
return Splice(context => new ScalarValue(context.Timestamp));

if (px.PropertyName == BuiltInProperty.MessageTemplate)
return Splice(context => new ScalarValue(context.MessageTemplate.Text));

if (px.PropertyName == BuiltInProperty.Properties)
return Splice(context => new StructureValue(context.Properties.Select(kvp => new LogEventProperty(kvp.Key, kvp.Value)), null));

return LX.Constant(null, typeof(LogEventPropertyValue));
return px.PropertyName switch
{
BuiltInProperty.Level => Splice(context => new ScalarValue(context.Level)),
BuiltInProperty.Message => Splice(context => new ScalarValue(Intrinsics.RenderMessage(context))),
BuiltInProperty.Exception => Splice(context =>
context.Exception == null ? null : new ScalarValue(context.Exception)),
BuiltInProperty.Timestamp => Splice(context => new ScalarValue(context.Timestamp)),
BuiltInProperty.MessageTemplate => Splice(context => new ScalarValue(context.MessageTemplate.Text)),
BuiltInProperty.Properties => Splice(context =>
new StructureValue(context.Properties.Select(kvp => new LogEventProperty(kvp.Key, kvp.Value)),
null)),
BuiltInProperty.Renderings => Splice(context => Intrinsics.GetRenderings(context)),
BuiltInProperty.EventId => Splice(context =>
new ScalarValue(EventIdHash.Compute(context.MessageTemplate.Text))),
_ => LX.Constant(null, typeof(LogEventPropertyValue))
};
}

var propertyName = px.PropertyName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ cx.Constant is ScalarValue scalar &&

static string LikeToRegex(string like)
{
var begin = "^";
var regex = "";
var end = "$";

for (var i = 0; i < like.Length; ++i)
{
var ch = like[i];
Expand All @@ -68,7 +71,17 @@ static string LikeToRegex(string like)
}
else
{
regex += "(?:.|\\r|\\n)*"; // ~= RegexOptions.Singleline
if (i == 0)
begin = "";

if (i == like.Length - 1)
end = "";

if (i == 0 && i == like.Length - 1)
regex += ".*";

if (i != 0 && i != like.Length - 1)
regex += "(?:.|\\r|\\n)*"; // ~= RegexOptions.Singleline
}
}
else if (ch == '_')
Expand All @@ -87,7 +100,7 @@ static string LikeToRegex(string like)
regex += Regex.Escape(ch.ToString());
}

return regex;
return begin + regex + end;
}
}
}
Loading

0 comments on commit 6975368

Please sign in to comment.