From ccb6b01f9cc2bff5f8fc0a8ea6fc0f7e9792618f Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Thu, 9 Nov 2023 16:54:13 +1000 Subject: [PATCH 01/11] Dev version bump [skip ci] --- src/Serilog.Expressions/Serilog.Expressions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index bc30a65..647cc6a 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -3,7 +3,7 @@ An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration. - 4.0.0 + 4.0.1 Serilog Contributors netstandard2.1;netstandard2.0;net5.0;net6.0;net7.0 true From 9e1343c30e99564a504cfc9244e74430d8cfcc75 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Fri, 17 Nov 2023 08:16:29 +1000 Subject: [PATCH 02/11] Make the rest() function aware of @p.X and @p['X'] (breaking change) --- .../Serilog.Expressions.csproj | 2 +- .../ExpressionReferencedPropertiesFinder.cs | 21 +++++++++++++++++-- .../UnreferencedPropertiesFunctionTests.cs | 14 +++++++------ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index 647cc6a..aedfcbf 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -3,7 +3,7 @@ An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration. - 4.0.1 + 5.0.0 Serilog Contributors netstandard2.1;netstandard2.0;net5.0;net6.0;net7.0 true diff --git a/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/ExpressionReferencedPropertiesFinder.cs b/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/ExpressionReferencedPropertiesFinder.cs index 25ceb6b..8334f2c 100644 --- a/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/ExpressionReferencedPropertiesFinder.cs +++ b/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/ExpressionReferencedPropertiesFinder.cs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Serilog.Expressions; using Serilog.Expressions.Ast; +using Serilog.Expressions.Compilation; using Serilog.Expressions.Compilation.Transformations; namespace Serilog.Templates.Compilation.UnreferencedProperties; @@ -47,7 +49,11 @@ protected override IEnumerable Transform(LocalNameExpression nlx) protected override IEnumerable Transform(AccessorExpression spx) { - return Transform(spx.Receiver); + if (Pattern.IsAmbientProperty(spx.Receiver, BuiltInProperty.Properties, true)) + yield return spx.MemberName; + + foreach (var nested in Transform(spx.Receiver)) + yield return nested; } protected override IEnumerable Transform(LambdaExpression lmx) @@ -79,7 +85,18 @@ protected override IEnumerable Transform(ObjectExpression ox) protected override IEnumerable Transform(IndexerExpression ix) { - return Transform(ix.Index).Concat(Transform(ix.Receiver)); + if (Pattern.IsAmbientProperty(ix.Receiver, BuiltInProperty.Properties, true) && + Pattern.IsStringConstant(ix.Index, out var name)) + { + yield return name; + } + else + { + foreach (var nested in Transform(ix.Index).Concat(Transform(ix.Receiver))) + { + yield return nested; + } + } } protected override IEnumerable Transform(IndexOfMatchExpression mx) diff --git a/test/Serilog.Expressions.Tests/Templates/UnreferencedPropertiesFunctionTests.cs b/test/Serilog.Expressions.Tests/Templates/UnreferencedPropertiesFunctionTests.cs index 479dae5..1a3a8f0 100644 --- a/test/Serilog.Expressions.Tests/Templates/UnreferencedPropertiesFunctionTests.cs +++ b/test/Serilog.Expressions.Tests/Templates/UnreferencedPropertiesFunctionTests.cs @@ -19,32 +19,34 @@ public void UnreferencedPropertiesFunctionIsNamedRest() [Fact] public void UnreferencedPropertiesExcludeThoseInMessageAndTemplate() { - Assert.True(new TemplateParser().TryParse("{@m}{A + 1}{#if true}{B}{#end}", out var template, out _)); + Assert.True(new TemplateParser().TryParse("{@m}{A + 1}{#if true}{B}{@p.C}{@p['D']}{#end}", out var template, out _)); - var function = new UnreferencedPropertiesFunction(template!); + var function = new UnreferencedPropertiesFunction(template); var evt = new LogEvent( DateTimeOffset.Now, LogEventLevel.Debug, null, - new(new[] {new PropertyToken("C", "{C}")}), + new(new[] {new PropertyToken("E", "{E}")}), new[] { new LogEventProperty("A", new ScalarValue(null)), new LogEventProperty("B", new ScalarValue(null)), new LogEventProperty("C", new ScalarValue(null)), new LogEventProperty("D", new ScalarValue(null)), + new LogEventProperty("E", new ScalarValue(null)), + new LogEventProperty("F", new ScalarValue(null)), }); var deep = UnreferencedPropertiesFunction.Implementation(function, evt, new ScalarValue(true)); var sv = Assert.IsType(deep); var included = Assert.Single(sv.Properties); - Assert.Equal("D", included!.Name); + Assert.Equal("F", included.Name); var shallow = UnreferencedPropertiesFunction.Implementation(function, evt); sv = Assert.IsType(shallow); - Assert.Contains(sv.Properties, p => p.Name == "C"); - Assert.Contains(sv.Properties, p => p.Name == "D"); + Assert.Contains(sv.Properties, p => p.Name == "E"); + Assert.Contains(sv.Properties, p => p.Name == "F"); } } \ No newline at end of file From 47d8bee72eec0b9d849e14bb9a3f0383bb927152 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Fri, 17 Nov 2023 15:09:26 +1000 Subject: [PATCH 03/11] Add the Inspect() function; fixes #103 --- README.md | 51 ++++++++++--------- .../Expressions/Operators.cs | 1 + .../Expressions/Runtime/RuntimeOperators.cs | 41 ++++++++++++++- .../Cases/expression-evaluation-cases.asv | 4 ++ .../Runtime/RuntimeOperatorsTests.cs | 39 ++++++++++++++ .../Serilog.Expressions.Tests/Support/Some.cs | 16 +++++- 6 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 test/Serilog.Expressions.Tests/Expressions/Runtime/RuntimeOperatorsTests.cs diff --git a/README.md b/README.md index 87aed36..0eafc9f 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,10 @@ The following properties are available in expressions: 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)`. + ### Literals | Data type | Description | Examples | @@ -181,29 +185,30 @@ calling 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 string `s` contains the substring `t`. | -| `ElementAt(x, i)` | Retrieves a property of `x` by name `i`, or array element of `x` by numeric index `i`. | -| `EndsWith(s, t)` | Tests whether the string `s` ends with substring `t`. | -| `IndexOf(s, t)` | Returns the first index of substring `t` in string `s`, or -1 if the substring does not appear. | -| `IndexOfMatch(s, p)` | Returns the index of the first match of regular expression `p` in string `s`, or -1 if the regular expression does not match. | -| `IsMatch(s, p)` | Tests whether the regular expression `p` matches within the string `s`. | -| `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`. | -| `Rest([deep])` | In an `ExpressionTemplate`, returns an object containing the first-class event properties not otherwise referenced in the template. If `deep` is `true`, also excludes properties referenced in the event's message template. | -| `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, [format])` | Convert `x` to a string, applying the format string `format` if `x` is `IFormattable`. | -| `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`. | +| 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 string `s` contains the substring `t`. | +| `ElementAt(x, i)` | Retrieves a property of `x` by name `i`, or array element of `x` by numeric index `i`. | +| `EndsWith(s, t)` | Tests whether the string `s` ends with substring `t`. | +| `IndexOf(s, t)` | Returns the first index of substring `t` in string `s`, or -1 if the substring does not appear. | +| `IndexOfMatch(s, p)` | Returns the index of the first match of regular expression `p` in string `s`, or -1 if the regular expression does not match. | +| `Inspect(o, [deep])` | Read properties from an object captured as the scalar value `o`. | +| `IsMatch(s, p)` | Tests whether the regular expression `p` matches within the string `s`. | +| `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`. | +| `Rest([deep])` | In an `ExpressionTemplate`, returns an object containing the first-class event properties not otherwise referenced in the template. If `deep` is `true`, also excludes properties referenced in the event's message template. | +| `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, [format])` | Convert `x` to a string, applying the format string `format` if `x` is `IFormattable`. | +| `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: diff --git a/src/Serilog.Expressions/Expressions/Operators.cs b/src/Serilog.Expressions/Expressions/Operators.cs index 2059250..b6549cd 100644 --- a/src/Serilog.Expressions/Expressions/Operators.cs +++ b/src/Serilog.Expressions/Expressions/Operators.cs @@ -33,6 +33,7 @@ static class Operators public const string OpEndsWith = "EndsWith"; public const string OpIndexOf = "IndexOf"; public const string OpIndexOfMatch = "IndexOfMatch"; + public const string OpInspect = "Inspect"; public const string OpIsMatch = "IsMatch"; public const string OpIsDefined = "IsDefined"; public const string OpLastIndexOf = "LastIndexOf"; diff --git a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs index 38183ca..00058ce 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/RuntimeOperators.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Reflection; +using Serilog.Debugging; using Serilog.Events; using Serilog.Expressions.Compilation.Linq; using Serilog.Templates.Rendering; @@ -538,4 +540,41 @@ public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? v // DateTimeOffset.Now is the generator for LogEvent.Timestamp. return new ScalarValue(DateTimeOffset.Now); } -} \ No newline at end of file + + public static LogEventPropertyValue? Inspect(LogEventPropertyValue? value, LogEventPropertyValue? deep = null) + { + if (value is not ScalarValue { Value: {} toCapture }) + return value; + + var result = new List(); + var logger = new LoggerConfiguration().CreateLogger(); + var properties = toCapture.GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty); + + foreach (var property in properties) + { + object? p; + try + { + p = property.GetValue(toCapture); + } + catch (Exception ex) + { + SelfLog.WriteLine("Serilog.Expressions Inspect() target property threw exception: {0}", ex); + continue; + } + + if (deep is ScalarValue { Value: true }) + { + if (logger.BindProperty(property.Name, p, destructureObjects: true, out var bound)) + result.Add(bound); + } + else + { + result.Add(new LogEventProperty(property.Name, new ScalarValue(p))); + } + } + + return new StructureValue(result); + } +} diff --git a/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv b/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv index 7c0a43b..102b748 100644 --- a/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv +++ b/test/Serilog.Expressions.Tests/Cases/expression-evaluation-cases.asv @@ -264,6 +264,7 @@ typeof(true) ⇶ 'System.Boolean' typeof(null) ⇶ 'null' typeof([]) ⇶ 'array' typeof({}) ⇶ 'object' +typeof(@x) ⇶ 'System.DivideByZeroException' // UtcDateTime tostring(utcdatetime(now()), 'o') like '20%' ⇶ true @@ -313,3 +314,6 @@ tostring(@x) like 'System.DivideByZeroException%' ⇶ true @l ⇶ 'Warning' @sp ⇶ 'bb1111820570b80e' @tr ⇶ '1befc31e94b01d1a473f63a7905f6c9b' + +// Inspect +inspect(@x).Message ⇶ 'Attempted to divide by zero.' diff --git a/test/Serilog.Expressions.Tests/Expressions/Runtime/RuntimeOperatorsTests.cs b/test/Serilog.Expressions.Tests/Expressions/Runtime/RuntimeOperatorsTests.cs new file mode 100644 index 0000000..a644fd9 --- /dev/null +++ b/test/Serilog.Expressions.Tests/Expressions/Runtime/RuntimeOperatorsTests.cs @@ -0,0 +1,39 @@ +using System.Reflection; +using Serilog.Events; +using Serilog.Expressions.Runtime; +using Serilog.Expressions.Tests.Support; +using Xunit; + +namespace Serilog.Expressions.Tests.Expressions.Runtime; + +public class RuntimeOperatorsTests +{ + [Fact] + public void InspectReadsPublicPropertiesFromScalarValue() + { + var message = Some.String(); + var ex = new DivideByZeroException(message); + var scalar = new ScalarValue(ex); + var inspected = RuntimeOperators.Inspect(scalar); + var structure = Assert.IsType(inspected); + var asProperties = structure.Properties.ToDictionary(p => p.Name, p => p.Value); + Assert.Contains("Message", asProperties); + Assert.Contains("StackTrace", asProperties); + var messageResult = Assert.IsType(asProperties["Message"]); + Assert.Equal(message, messageResult.Value); + } + + [Fact] + public void DeepInspectionReadsSubproperties() + { + var innerMessage = Some.String(); + var inner = new DivideByZeroException(innerMessage); + var ex = new TargetInvocationException(inner); + var scalar = new ScalarValue(ex); + var inspected = RuntimeOperators.Inspect(scalar, deep: new ScalarValue(true)); + var structure = Assert.IsType(inspected); + var innerStructure = Assert.IsType(structure.Properties.Single(p => p.Name == "InnerException").Value); + var innerMessageValue = Assert.IsType(innerStructure.Properties.Single(p => p.Name == "Message").Value); + Assert.Equal(innerMessage, innerMessageValue.Value); + } +} \ No newline at end of file diff --git a/test/Serilog.Expressions.Tests/Support/Some.cs b/test/Serilog.Expressions.Tests/Support/Some.cs index 2022f00..aa98d64 100644 --- a/test/Serilog.Expressions.Tests/Support/Some.cs +++ b/test/Serilog.Expressions.Tests/Support/Some.cs @@ -5,6 +5,8 @@ namespace Serilog.Expressions.Tests.Support; static class Some { + static int _next; + public static LogEvent InformationEvent(string messageTemplate = "Hello, world!", params object?[] propertyValues) { return LogEvent(LogEventLevel.Information, messageTemplate, propertyValues); @@ -29,11 +31,21 @@ public static LogEvent LogEvent(LogEventLevel level, string messageTemplate = "H public static object AnonymousObject() { - return new {A = 42}; + return new {A = Int()}; } public static LogEventPropertyValue LogEventPropertyValue() { return new ScalarValue(AnonymousObject()); } -} \ No newline at end of file + + static int Int() + { + return Interlocked.Increment(ref _next); + } + + public static string String() + { + return $"+S_{Int()}"; + } +} From a09472955223e500489b86a6c51cb8c74057bf4b Mon Sep 17 00:00:00 2001 From: Marcin Nowak Date: Fri, 5 Jan 2024 18:29:22 +0100 Subject: [PATCH 04/11] Fix crash on apps built with PublishTrimmed=True --- .../Expressions/StaticMemberNameResolver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs b/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs index 37866ee..f76c56c 100644 --- a/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs +++ b/src/Serilog.Expressions/Expressions/StaticMemberNameResolver.cs @@ -28,7 +28,11 @@ public class StaticMemberNameResolver : NameResolver /// Create a that returns members of the specified . /// /// A with public static members implementing runtime functions. - public StaticMemberNameResolver(Type type) + public StaticMemberNameResolver( +#if NET6_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] +#endif + Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); From 5612f0b05e3e0597b7d0c64a746e5b9af442aaa9 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Mon, 8 Jan 2024 09:18:53 +1000 Subject: [PATCH 05/11] Publishing key update --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 396eae9..15d7eac 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,7 +8,7 @@ artifacts: deploy: - provider: NuGet api_key: - secure: AcMGMnsJdQe1+SQwf+9VpRqcKNw93zr96OlxAEmPob52vqxDNH844SmdYidGX0cL + secure: ZCEcKeB0btSRWVPgGPqQKphQeTcljBBsA4GKGW0Gmjw+UfXvS0LCcWzYdPXUWo5N skip_symbols: true on: branch: /^(main|dev)$/ From 79899466db072ecd3e4b3cd67ab45e1e97135ca1 Mon Sep 17 00:00:00 2001 From: RandomBuffer <160499735+RandomBuffer@users.noreply.github.com> Date: Wed, 6 Mar 2024 00:37:09 +1100 Subject: [PATCH 06/11] Add trace id and span id properties Add trace id and span id properties missing from the documentation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 87aed36..b2017ab 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,8 @@ The following properties are available in expressions: * `@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 + * `@tr` - trace id; The id of the trace that was active when the event was created, if any + * `@sp` - span id; The id of the span that was active when the event was created, if any The built-in properties mirror those available in the CLEF format. From 64ea43be04ce517de84e8a47ce48b9b6e6366bde Mon Sep 17 00:00:00 2001 From: Andrii Bratanin Date: Tue, 14 May 2024 01:51:13 +0300 Subject: [PATCH 07/11] Update README.md Add a link to clarification of missing ":lj" format specifiers for "@m" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b2017ab..2e9782d 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ The following properties are available in expressions: * **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 + * `@m` - the rendered message (Note: do not add format specifiers like `:lj` or you'll lose theme color rendering. These format specifiers are not supported as they've become the default and only option - see their discussion [here](https://github.com/serilog/serilog-expressions/issues/56#issuecomment-1146472988)) * `@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` From b6cc8b51b5565790ca5f73f2a620b52a1f852500 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 14 May 2024 09:22:50 +1000 Subject: [PATCH 08/11] Update README.md Co-authored-by: Ruben Bartelink --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e9782d..9dd5ecd 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ The following properties are available in expressions: * **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 (Note: do not add format specifiers like `:lj` or you'll lose theme color rendering. These format specifiers are not supported as they've become the default and only option - see their discussion [here](https://github.com/serilog/serilog-expressions/issues/56#issuecomment-1146472988)) + * `@m` - the rendered message (Note: do not add format specifiers like `:lj` or you'll lose theme color rendering. These format specifiers are not supported as they've become the default and only option - [see the discussion here](https://github.com/serilog/serilog-expressions/issues/56#issuecomment-1146472988) * `@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` From 1b53520aa5d28dbe13923aee0cb92c7dd18f6b77 Mon Sep 17 00:00:00 2001 From: Olgagaa Date: Fri, 31 May 2024 14:08:47 +0400 Subject: [PATCH 09/11] Upgraded Serilog version Serilog version 3.1.0 causes stackoverflowException. https://github.com/serilog/serilog/pull/1977 --- src/Serilog.Expressions/Serilog.Expressions.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index 647cc6a..6779bc4 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -18,7 +18,7 @@ - + From a918f6a5574d0442f574fe35547aded2f68cea10 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 4 Jun 2024 18:34:12 +1000 Subject: [PATCH 10/11] TFM and dependency update --- src/Serilog.Expressions/Serilog.Expressions.csproj | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Serilog.Expressions/Serilog.Expressions.csproj b/src/Serilog.Expressions/Serilog.Expressions.csproj index 6779bc4..b498935 100644 --- a/src/Serilog.Expressions/Serilog.Expressions.csproj +++ b/src/Serilog.Expressions/Serilog.Expressions.csproj @@ -3,9 +3,14 @@ An embeddable mini-language for filtering, enriching, and formatting Serilog events, ideal for use with JSON or XML configuration. - 4.0.1 + 5.0.0 Serilog Contributors - netstandard2.1;netstandard2.0;net5.0;net6.0;net7.0 + + net471;net462 + + $(TargetFrameworks);net8.0;net6.0;netstandard2.0 true Serilog serilog @@ -18,7 +23,7 @@ - + From fde5bfbcddba9c157490005fcc72a5c02b7894a5 Mon Sep 17 00:00:00 2001 From: Nicholas Blumhardt Date: Tue, 4 Jun 2024 19:31:29 +1000 Subject: [PATCH 11/11] Review, cleanup, push forward the XDOCs --- serilog-expressions.sln.DotSettings | 1 + .../Expressions/Ast/AccessorExpression.cs | 8 ++++ .../Expressions/Ast/AmbientNameExpression.cs | 5 ++ .../Expressions/Ast/ArrayExpression.cs | 7 +++ .../Expressions/Ast/CallExpression.cs | 4 ++ .../Expressions/Ast/ConstantExpression.cs | 22 ++++----- .../Expressions/Ast/Element.cs | 7 +-- .../Expressions/Ast/Expression.cs | 9 +++- .../Expressions/Ast/IndexOfMatchExpression.cs | 4 ++ .../Expressions/Ast/IndexerExpression.cs | 5 ++ .../Expressions/Ast/IndexerWildcard.cs | 3 ++ .../Ast/IndexerWildcardExpression.cs | 5 ++ .../Expressions/Ast/ItemElement.cs | 3 ++ .../Expressions/Ast/LambdaExpression.cs | 4 ++ .../Expressions/Ast/LocalNameExpression.cs | 3 ++ .../Expressions/Ast/Member.cs | 7 +-- .../Expressions/Ast/ObjectExpression.cs | 5 ++ .../Expressions/Ast/ParameterExpression.cs | 3 ++ .../Expressions/Ast/PropertyMember.cs | 5 ++ .../Expressions/Ast/SpreadElement.cs | 7 +++ .../Expressions/Ast/SpreadMember.cs | 5 ++ .../DefaultFunctionNameResolver.cs | 2 +- .../Linq/LinqExpressionCompiler.cs | 4 +- .../Linq/ParameterReplacementVisitor.cs | 2 +- .../Compilation/Text/LikeSyntaxTransformer.cs | 4 +- .../Text/TextMatchingTransformer.cs | 4 +- .../WildcardComprehensionTransformer.cs | 4 +- .../Expressions/Helpers.cs | 4 +- .../Parsing/ExpressionTextParsers.cs | 2 +- .../Parsing/ExpressionTokenizer.cs | 10 ++-- .../Expressions/Runtime/Coerce.cs | 7 ++- .../ParserConstruction/Combinators.cs | 14 +++--- .../ParserConstruction/Model/Result.cs | 2 +- .../ParserConstruction/Parsers/Character.cs | 2 +- .../ParserConstruction/Parsers/Numerics.cs | 4 +- .../ParserConstruction/Parsers/Span.cs | 2 +- .../Util/ArrayEnumerable.cs | 42 ---------------- .../ParserConstruction/Util/CharInfo.cs | 2 +- .../Templates/Ast/Template.cs | 4 +- .../Compilation/CompiledLevelToken.cs | 8 ++-- .../UnreferencedPropertiesFunction.cs | 2 +- .../Templates/Parsing/TemplateTokenizer.cs | 2 +- .../Templates/Rendering/LevelRenderer.cs | 48 +++++++++---------- .../ExpressionCompilerTests.cs | 10 ++-- .../TemplateTokenizerTests.cs | 25 ++++------ 45 files changed, 183 insertions(+), 149 deletions(-) delete mode 100644 src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs diff --git a/serilog-expressions.sln.DotSettings b/serilog-expressions.sln.DotSettings index b1832e8..93030a6 100644 --- a/serilog-expressions.sln.DotSettings +++ b/serilog-expressions.sln.DotSettings @@ -1,4 +1,5 @@  + CI True True True diff --git a/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs b/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs index 0ccfea8..c481f69 100644 --- a/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/AccessorExpression.cs @@ -14,6 +14,14 @@ namespace Serilog.Expressions.Ast; +/// +/// An accessor retrieves a property from a (structured) object. For example, in the expression +/// Headers.ContentType the . operator forms an accessor expression that +/// retrieves the ContentType property from the Headers object. +/// +/// Note that the AST type can represent accessors that cannot be validly written using +/// . notation. In these cases, if the accessor is formatted back out as an expression, +/// notation will be used. class AccessorExpression : Expression { public AccessorExpression(Expression receiver, string memberName) diff --git a/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs b/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs index 5390a86..4c40a10 100644 --- a/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/AmbientNameExpression.cs @@ -14,6 +14,11 @@ namespace Serilog.Expressions.Ast; +/// +/// An ambient name is generally a property name or built-in that appears standalone in an expression. For example, +/// in Headers.ContentType, Headers is an ambient name that produces an +/// . Built-ins like @Level are also parsed as ambient names. +/// class AmbientNameExpression : Expression { readonly bool _requiresEscape; diff --git a/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs index cce26e9..4a5256a 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ArrayExpression.cs @@ -14,6 +14,13 @@ namespace Serilog.Expressions.Ast; +/// +/// An array expression constructs an array from a list of elements. For example, [1, 2, 3] is an +/// array expression. The items in an array expression can be literal values or expressions, like in the +/// above example, or they can be spread expressions that describe zero or more elements to include in the +/// list. Whether included via regular elements or spread expressions, undefined values are ignored and won't +/// appear in the resulting array value. +/// class ArrayExpression : Expression { public ArrayExpression(Element[] elements) diff --git a/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs b/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs index a7f9bde..c622187 100644 --- a/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/CallExpression.cs @@ -14,6 +14,10 @@ namespace Serilog.Expressions.Ast; +/// +/// A is a function call made up of the function name, parenthesised argument +/// list, and optional postfix ci modifier. For example, Substring(RequestPath, 0, 5). +/// class CallExpression : Expression { public CallExpression(bool ignoreCase, string operatorName, params Expression[] operands) diff --git a/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs index 8795c47..7f9e42f 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ConstantExpression.cs @@ -17,6 +17,9 @@ namespace Serilog.Expressions.Ast; +/// +/// A constant such as 'hello', true, null, or 123.45. +/// class ConstantExpression : Expression { public ConstantExpression(LogEventPropertyValue constant) @@ -30,19 +33,14 @@ public override string ToString() { if (Constant is ScalarValue sv) { - switch (sv.Value) + return sv.Value switch { - case string s: - return "'" + s.Replace("'", "''") + "'"; - case true: - return "true"; - case false: - return "false"; - case IFormattable formattable: - return formattable.ToString(null, CultureInfo.InvariantCulture); - default: - return (sv.Value ?? "null").ToString() ?? ""; - } + string s => "'" + s.Replace("'", "''") + "'", + true => "true", + false => "false", + IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture), + _ => (sv.Value ?? "null").ToString() ?? "" + }; } return Constant.ToString(); diff --git a/src/Serilog.Expressions/Expressions/Ast/Element.cs b/src/Serilog.Expressions/Expressions/Ast/Element.cs index ca44e73..c0f5932 100644 --- a/src/Serilog.Expressions/Expressions/Ast/Element.cs +++ b/src/Serilog.Expressions/Expressions/Ast/Element.cs @@ -14,6 +14,7 @@ namespace Serilog.Expressions.Ast; -abstract class Element -{ -} \ No newline at end of file +/// +/// An element in an . +/// +abstract class Element; \ No newline at end of file diff --git a/src/Serilog.Expressions/Expressions/Ast/Expression.cs b/src/Serilog.Expressions/Expressions/Ast/Expression.cs index ed493c9..c43fbe8 100644 --- a/src/Serilog.Expressions/Expressions/Ast/Expression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/Expression.cs @@ -14,8 +14,15 @@ namespace Serilog.Expressions.Ast; +/// +/// An AST node. +/// abstract class Expression { - // Used only as an enabler for testing and debugging. + /// + /// The representation of an is not + /// guaranteed to be syntactically valid: this is provided for debugging purposes only. + /// + /// A textual representation of the expression. public abstract override string ToString(); } \ No newline at end of file diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs b/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs index a46445a..820f859 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexOfMatchExpression.cs @@ -16,6 +16,10 @@ namespace Serilog.Expressions.Ast; +/// +/// A non-syntax expression tree node used when compiling the , +/// , and SQL-style like expressions. +/// class IndexOfMatchExpression : Expression { public Expression Corpus { get; } diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs b/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs index 92a9b0e..18ee1e0 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexerExpression.cs @@ -14,6 +14,11 @@ namespace Serilog.Expressions.Ast; +/// +/// An retrieves a property from an object, by name, or an item from an array +/// by zero-based numeric index. For example, Headers['Content-Type'] and Items[2] are +/// parsed as indexer expressions. +/// class IndexerExpression : Expression { public Expression Receiver { get; } diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs index 2cab2cc..e2c286e 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcard.cs @@ -14,4 +14,7 @@ namespace Serilog.Expressions.Ast; +/// +/// Describes the wildcard in a . +/// enum IndexerWildcard { Undefined, Any, All } \ No newline at end of file diff --git a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs index d687dff..2cfa095 100644 --- a/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/IndexerWildcardExpression.cs @@ -14,6 +14,11 @@ namespace Serilog.Expressions.Ast; +/// +/// An indexer wildcard is a placeholder in a property path expression. For example, +/// in Headers[?] = 'test', the question-mark token is a wildcard that means "any property of +/// the Headers object". The other wildcard indexer is the asterisk, meaning "all". +/// class IndexerWildcardExpression : Expression { public IndexerWildcardExpression(IndexerWildcard wildcard) diff --git a/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs b/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs index 45d9fe5..13d2c7e 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ItemElement.cs @@ -14,6 +14,9 @@ namespace Serilog.Expressions.Ast; +/// +/// A single item in an . +/// class ItemElement : Element { public Expression Value { get; } diff --git a/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs b/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs index c4da3b0..c2e82e6 100644 --- a/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/LambdaExpression.cs @@ -14,6 +14,10 @@ namespace Serilog.Expressions.Ast; +/// +/// A non-syntax expression tree node used in the compilation of . Only +/// very limited support for lambda expressions is currently present. +/// class LambdaExpression : Expression { public LambdaExpression(ParameterExpression[] parameters, Expression body) diff --git a/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs b/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs index bf10d63..0f24251 100644 --- a/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/LocalNameExpression.cs @@ -14,6 +14,9 @@ namespace Serilog.Expressions.Ast; +/// +/// Represents the iteration variable in template #each directives. +/// class LocalNameExpression : Expression { public LocalNameExpression(string name) diff --git a/src/Serilog.Expressions/Expressions/Ast/Member.cs b/src/Serilog.Expressions/Expressions/Ast/Member.cs index 6c9bbe1..8d8c2ea 100644 --- a/src/Serilog.Expressions/Expressions/Ast/Member.cs +++ b/src/Serilog.Expressions/Expressions/Ast/Member.cs @@ -14,6 +14,7 @@ namespace Serilog.Expressions.Ast; -abstract class Member -{ -} \ No newline at end of file +/// +/// A member in an . +/// +abstract class Member; \ No newline at end of file diff --git a/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs index 601f16c..2b9b5db 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ObjectExpression.cs @@ -14,6 +14,11 @@ namespace Serilog.Expressions.Ast; +/// +/// Constructs an object given a list of members. Members can be name: value pairs, or spread +/// expressions that include members from another object. Where names conflict, the rightmost appearance of +/// a name wins. Members that evaluate to an undefined value do not appear in the resulting object. +/// class ObjectExpression : Expression { public ObjectExpression(Member[] members) diff --git a/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs b/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs index 256bc92..bdc4599 100644 --- a/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs +++ b/src/Serilog.Expressions/Expressions/Ast/ParameterExpression.cs @@ -14,6 +14,9 @@ namespace Serilog.Expressions.Ast; +/// +/// Non-syntax expression type used to represent parameters in bodies. +/// class ParameterExpression : Expression { public ParameterExpression(string parameterName) diff --git a/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs b/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs index f2a8279..6b25dbb 100644 --- a/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs +++ b/src/Serilog.Expressions/Expressions/Ast/PropertyMember.cs @@ -16,6 +16,11 @@ namespace Serilog.Expressions.Ast; +/// +/// An member comprising an optionally-quoted name and a value, for example +/// the object {Username: 'alice'} includes a single with name +/// Username and value 'alice'. +/// class PropertyMember : Member { public string Name { get; } diff --git a/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs b/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs index 41933a1..756be3e 100644 --- a/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs +++ b/src/Serilog.Expressions/Expressions/Ast/SpreadElement.cs @@ -14,6 +14,13 @@ namespace Serilog.Expressions.Ast; +/// +/// An element in an that describes zero or more items to include in the array. +/// Spread elements are written with two dots preceding an expression that evaluates to an array of elements to +/// insert into the result array at the position of the spread element, for example, in [1, 2, ..Others], +/// the ..Others expression is a spread element. If the value of the array in the spread is +/// undefined, no items will be added to the list. +/// class SpreadElement : Element { public Expression Content { get; } diff --git a/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs b/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs index 0ecd6ac..240c4b5 100644 --- a/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs +++ b/src/Serilog.Expressions/Expressions/Ast/SpreadMember.cs @@ -14,6 +14,11 @@ namespace Serilog.Expressions.Ast; +/// +/// An member that designates another object from which to copy members into the +/// current object. Spread member expressions comprise two dots preceding an expression that is expected to +/// evaluate to an object. +/// class SpreadMember : Member { public Expression Content { get; } diff --git a/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs b/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs index 3868a69..1a1ae83 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/DefaultFunctionNameResolver.cs @@ -22,7 +22,7 @@ public static NameResolver Build(NameResolver? additionalNameResolver) { var defaultResolver = new StaticMemberNameResolver(typeof(RuntimeOperators)); return additionalNameResolver == null - ? (NameResolver) defaultResolver + ? defaultResolver : new OrderedNameResolver(new[] {defaultResolver, additionalNameResolver }); } } \ No newline at end of file diff --git a/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs b/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs index 389aaa1..2c6c99c 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs @@ -176,7 +176,7 @@ static ExpressionBody CompileLogical(Func px.Item2).ToArray()); // Unfortunately, right now, functions need to be threaded through in constant scalar values :-D - return LX.New(typeof(ScalarValue).GetConstructor(new[] {typeof(object)})!, + return LX.New(typeof(ScalarValue).GetConstructor([typeof(object)])!, LX.Convert(lambda, typeof(object))); } diff --git a/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs b/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs index 249a8ba..c4e6f3d 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Linq/ParameterReplacementVisitor.cs @@ -23,7 +23,7 @@ class ParameterReplacementVisitor : ExpressionVisitor public static Expression ReplaceParameters(LambdaExpression lambda, params ParameterExpression[] newParameters) { var v = new ParameterReplacementVisitor(lambda.Parameters.ToArray(), newParameters); - return v.Visit(lambda.Body); + return v.Visit(lambda.Body)!; } ParameterReplacementVisitor(ParameterExpression[] from, ParameterExpression[] to) diff --git a/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs index a987799..4206d88 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs @@ -48,9 +48,7 @@ protected override Expression Transform(CallExpression call) Expression TryCompileLikeExpression(bool ignoreCase, Expression corpus, Expression like) { - if (like is ConstantExpression cx && - cx.Constant is ScalarValue scalar && - scalar.Value is string s) + if (like is ConstantExpression { Constant: ScalarValue { Value: string s } }) { var regex = LikeToRegex(s); var opts = RegexOptions.Compiled | RegexOptions.ExplicitCapture; diff --git a/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs index 6e7c4ee..9fe3ecb 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs @@ -49,9 +49,7 @@ protected override Expression Transform(CallExpression call) Expression TryCompileIndexOfMatch(bool ignoreCase, Expression corpus, Expression regex) { - if (regex is ConstantExpression cx && - cx.Constant is ScalarValue scalar && - scalar.Value is string s) + if (regex is ConstantExpression { Constant: ScalarValue { Value: string s } }) { var opts = RegexOptions.Compiled | RegexOptions.ExplicitCapture; if (ignoreCase) diff --git a/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs b/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs index 6d0bbb2..361c95b 100644 --- a/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs +++ b/src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardComprehensionTransformer.cs @@ -68,7 +68,7 @@ protected override Expression Transform(CallExpression lx) comparisonArgs[indexerOperand] = nestedComparand; var body = new CallExpression(lx.IgnoreCase, lx.OperatorName, comparisonArgs); - var lambda = new LambdaExpression(new[] { px }, body); + var lambda = new LambdaExpression([px], body); var op = Operators.ToRuntimeWildcardOperator(wc); var call = new CallExpression(false, op, coll, lambda); @@ -84,7 +84,7 @@ protected override Expression Transform(IndexerExpression ix) var px = new ParameterExpression("p" + _nextParameter++); var coll = Transform(ix.Receiver); - var lambda = new LambdaExpression(new[] { px }, px); + var lambda = new LambdaExpression([px], px); var op = Operators.ToRuntimeWildcardOperator(wx.Wildcard); return new CallExpression(false, op, coll, lambda); } diff --git a/src/Serilog.Expressions/Expressions/Helpers.cs b/src/Serilog.Expressions/Expressions/Helpers.cs index da0d6d3..20f57af 100644 --- a/src/Serilog.Expressions/Expressions/Helpers.cs +++ b/src/Serilog.Expressions/Expressions/Helpers.cs @@ -19,7 +19,7 @@ namespace Serilog.Expressions /// /// Helper methods. /// - internal static class Helpers + static class Helpers { /// /// Backport .NET Standard 2.1 additions to maintain .NET Standard 2.0 compatibility. @@ -35,7 +35,7 @@ internal static class Helpers /// public static bool Contains(this string source, string value, StringComparison comparisonType) { - return source?.IndexOf(value, comparisonType) >= 0; + return source.IndexOf(value, comparisonType) >= 0; } } } diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs index 0c2feec..4968909 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTextParsers.cs @@ -29,7 +29,7 @@ static class ExpressionTextParsers public static readonly TextParser HexInteger = Span.EqualTo("0x") - .IgnoreThen(Character.Digit.Or(Character.Matching(ch => ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F', "a-f")) + .IgnoreThen(Character.Digit.Or(Character.Matching(ch => ch is >= 'a' and <= 'f' or >= 'A' and <= 'F', "a-f")) .Named("hex digit") .AtLeastOnce()) .Select(chars => new string(chars)); diff --git a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs index 3d21cea..2ac71dd 100644 --- a/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs +++ b/src/Serilog.Expressions/Expressions/Parsing/ExpressionTokenizer.cs @@ -22,7 +22,7 @@ class ExpressionTokenizer : Tokenizer readonly ExpressionToken[] _singleCharOps = new ExpressionToken[128]; readonly ExpressionKeyword[] _keywords = - { + [ new("and", ExpressionToken.And), new("in", ExpressionToken.In), new("is", ExpressionToken.Is), @@ -38,8 +38,8 @@ class ExpressionTokenizer : Tokenizer new("end", ExpressionToken.End), new("ci", ExpressionToken.CI), new("each", ExpressionToken.Each), - new("delimit", ExpressionToken.Delimit), - }; + new("delimit", ExpressionToken.Delimit) + ]; public ExpressionTokenizer() { @@ -109,7 +109,7 @@ protected override IEnumerable> Tokenize(TextSpan string if (!IsDelimiter(next)) { - yield return Result.Empty(next.Location, new[] { "digit" }); + yield return Result.Empty(next.Location, ["digit"]); } } else if (next.Value == '\'') @@ -134,7 +134,7 @@ protected override IEnumerable> Tokenize(TextSpan string if (next.Remainder == startOfName) { - yield return Result.Empty(startOfName, new[] { "built-in identifier name" }); + yield return Result.Empty(startOfName, ["built-in identifier name"]); } else { diff --git a/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs b/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs index b6a3af7..dd42ba0 100644 --- a/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs +++ b/src/Serilog.Expressions/Expressions/Runtime/Coerce.cs @@ -20,10 +20,13 @@ namespace Serilog.Expressions.Runtime; static class Coerce { - static readonly Type[] NumericTypes = { typeof(decimal), + static readonly Type[] NumericTypes = + [ + typeof(decimal), typeof(int), typeof(long), typeof(double), typeof(float), typeof(uint), typeof(sbyte), - typeof(byte), typeof(short), typeof(ushort), typeof(ulong) }; + typeof(byte), typeof(short), typeof(ushort), typeof(ulong) + ]; public static bool Numeric(LogEventPropertyValue? value, out decimal numeric) { diff --git a/src/Serilog.Expressions/ParserConstruction/Combinators.cs b/src/Serilog.Expressions/ParserConstruction/Combinators.cs index 4c1f4a0..6126915 100644 --- a/src/Serilog.Expressions/ParserConstruction/Combinators.cs +++ b/src/Serilog.Expressions/ParserConstruction/Combinators.cs @@ -112,7 +112,7 @@ public static TokenListParser AtEnd(this TokenListParser AtLeastOnce(this TextParser parser) { if (parser == null) throw new ArgumentNullException(nameof(parser)); - return parser.Then(first => parser.Many().Select(rest => ArrayEnumerable.Cons(first, rest))); + return parser.Then(first => parser.Many().Select(rest => (T[])[first, ..rest])); } /// @@ -129,7 +129,7 @@ public static TokenListParser AtLeastOnceDelimitedBy(th if (parser == null) throw new ArgumentNullException(nameof(parser)); if (delimiter == null) throw new ArgumentNullException(nameof(delimiter)); - return parser.Then(first => delimiter.IgnoreThen(parser).Many().Select(rest => ArrayEnumerable.Cons(first, rest))); + return parser.Then(first => delimiter.IgnoreThen(parser).Many().Select(rest => (T[])[first, ..rest])); } /// @@ -280,11 +280,11 @@ public static TokenListParser ManyDelimitedBy( return parser .AtLeastOnceDelimitedBy(delimiter) .Then(p => end.Value(p)) - .Or(end.Value(new T[0])); + .Or(end.Value(Array.Empty())); return parser - .Then(first => delimiter.IgnoreThen(parser).Many().Select(rest => ArrayEnumerable.Cons(first, rest))) - .OptionalOrDefault(new T[0]); + .Then(first => delimiter.IgnoreThen(parser).Many().Select(rest => (T[])[first, ..rest])) + .OptionalOrDefault([]); } /// @@ -306,7 +306,7 @@ public static TokenListParser Named(this TokenListParser(result.Remainder, new[] { name }); + return TokenListParserResult.Empty(result.Remainder, [name]); }; } @@ -328,7 +328,7 @@ public static TextParser Named(this TextParser parser, string name) if (result.HasValue || result.IsPartial(input)) return result; - return Result.Empty(result.Remainder, new[] { name }); + return Result.Empty(result.Remainder, [name]); }; } diff --git a/src/Serilog.Expressions/ParserConstruction/Model/Result.cs b/src/Serilog.Expressions/ParserConstruction/Model/Result.cs index d8fc231..490fed8 100644 --- a/src/Serilog.Expressions/ParserConstruction/Model/Result.cs +++ b/src/Serilog.Expressions/ParserConstruction/Model/Result.cs @@ -85,7 +85,7 @@ public static Result CombineEmpty(Result first, Result second) if (expectations == null) expectations = second.Expectations; else if (second.Expectations != null) - expectations = ArrayEnumerable.Concat(first.Expectations!, second.Expectations); + expectations = [..first.Expectations!, ..second.Expectations]; return new(second.Remainder, second.ErrorMessage, expectations, second.Backtrack); } diff --git a/src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs index 4a71309..06b5aee 100644 --- a/src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parsers/Character.cs @@ -45,7 +45,7 @@ public static TextParser Matching(Func predicate, string name) if (predicate == null) throw new ArgumentNullException(nameof(predicate)); if (name == null) throw new ArgumentNullException(nameof(name)); - return Matching(predicate, new[] { name }); + return Matching(predicate, [name]); } /// diff --git a/src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs index 2a8fbd5..7dd6ef9 100644 --- a/src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parsers/Numerics.cs @@ -24,8 +24,8 @@ namespace Serilog.ParserConstruction.Parsers; //* of generics over numbers in C#. static class Numerics { - static readonly string[] ExpectedDigit = { "digit" }; - static readonly string[] ExpectedSignOrDigit = { "sign", "digit" }; + static readonly string[] ExpectedDigit = ["digit"]; + static readonly string[] ExpectedSignOrDigit = ["sign", "digit"]; /// /// A string of digits, converted into a . diff --git a/src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs b/src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs index 0b268a7..3c05892 100644 --- a/src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs +++ b/src/Serilog.Expressions/ParserConstruction/Parsers/Span.cs @@ -44,7 +44,7 @@ public static TextParser EqualTo(string text) if (ch.Location == input) return Result.Empty(ch.Location, expectations); - return Result.Empty(ch.Location, new[] { Presentation.FormatLiteral(text[i]) }); + return Result.Empty(ch.Location, [Presentation.FormatLiteral(text[i])]); } remainder = ch.Remainder; } diff --git a/src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs b/src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs deleted file mode 100644 index 3dafe36..0000000 --- a/src/Serilog.Expressions/ParserConstruction/Util/ArrayEnumerable.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2016 Datalust, Superpower Contributors, Sprache 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.Runtime.CompilerServices; - -namespace Serilog.ParserConstruction.Util; - -static class ArrayEnumerable -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] Cons(T first, T[] rest) - { - var all = new T[rest.Length + 1]; - all[0] = first; - for (var i = 0; i < rest.Length; ++i) - all[i + 1] = rest[i]; - return all; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] Concat(T[] first, T[] rest) - { - var all = new T[first.Length + rest.Length]; - var i = 0; - for (; i < first.Length; ++i) - all[i] = first[i]; - for (var j = 0; j < rest.Length; ++i, ++j) - all[i] = rest[j]; - return all; - } -} \ No newline at end of file diff --git a/src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs b/src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs index 22e1472..500fe11 100644 --- a/src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs +++ b/src/Serilog.Expressions/ParserConstruction/Util/CharInfo.cs @@ -18,6 +18,6 @@ static class CharInfo { public static bool IsLatinDigit(char ch) { - return ch >= '0' && ch <= '9'; + return ch is >= '0' and <= '9'; } } \ No newline at end of file diff --git a/src/Serilog.Expressions/Templates/Ast/Template.cs b/src/Serilog.Expressions/Templates/Ast/Template.cs index c7a2be5..b81617b 100644 --- a/src/Serilog.Expressions/Templates/Ast/Template.cs +++ b/src/Serilog.Expressions/Templates/Ast/Template.cs @@ -14,6 +14,4 @@ namespace Serilog.Templates.Ast; -abstract class Template -{ -} \ No newline at end of file +abstract class Template; \ No newline at end of file diff --git a/src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs b/src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs index c86c2c2..1f80589 100644 --- a/src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs +++ b/src/Serilog.Expressions/Templates/Compilation/CompiledLevelToken.cs @@ -29,15 +29,15 @@ public CompiledLevelToken(string? format, Alignment? alignment, TemplateTheme th { _format = format; _alignment = alignment; - _levelStyles = new[] - { + _levelStyles = + [ theme.GetStyle(TemplateThemeStyle.LevelVerbose), theme.GetStyle(TemplateThemeStyle.LevelDebug), theme.GetStyle(TemplateThemeStyle.LevelInformation), theme.GetStyle(TemplateThemeStyle.LevelWarning), theme.GetStyle(TemplateThemeStyle.LevelError), - theme.GetStyle(TemplateThemeStyle.LevelFatal), - }; + theme.GetStyle(TemplateThemeStyle.LevelFatal) + ]; } public override void Evaluate(EvaluationContext ctx, TextWriter output) diff --git a/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/UnreferencedPropertiesFunction.cs b/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/UnreferencedPropertiesFunction.cs index 416e3c4..8ad593e 100644 --- a/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/UnreferencedPropertiesFunction.cs +++ b/src/Serilog.Expressions/Templates/Compilation/UnreferencedProperties/UnreferencedPropertiesFunction.cs @@ -42,7 +42,7 @@ class UnreferencedPropertiesFunction : NameResolver public UnreferencedPropertiesFunction(Template template) { var finder = new TemplateReferencedPropertiesFinder(); - _referencedInTemplate = new(finder.FindReferencedProperties(template)); + _referencedInTemplate = [..finder.FindReferencedProperties(template)]; } public override bool TryBindFunctionParameter(ParameterInfo parameter, [MaybeNullWhen(false)] out object boundValue) diff --git a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs index 084f16c..d3e39f3 100644 --- a/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs +++ b/src/Serilog.Expressions/Templates/Parsing/TemplateTokenizer.cs @@ -81,7 +81,7 @@ protected override IEnumerable> Tokenize(TextSpan span) } else { - yield return Result.Empty(next.Remainder, new[] {"escaped `}`"}); + yield return Result.Empty(next.Remainder, ["escaped `}`"]); yield break; } } diff --git a/src/Serilog.Expressions/Templates/Rendering/LevelRenderer.cs b/src/Serilog.Expressions/Templates/Rendering/LevelRenderer.cs index ebeeebc..66e0a97 100644 --- a/src/Serilog.Expressions/Templates/Rendering/LevelRenderer.cs +++ b/src/Serilog.Expressions/Templates/Rendering/LevelRenderer.cs @@ -27,34 +27,34 @@ namespace Serilog.Templates.Rendering; static class LevelRenderer { static readonly string[][] TitleCaseLevelMap = - { - new[] { "V", "Vb", "Vrb", "Verb" }, - new[] { "D", "De", "Dbg", "Dbug" }, - new[] { "I", "In", "Inf", "Info" }, - new[] { "W", "Wn", "Wrn", "Warn" }, - new[] { "E", "Er", "Err", "Eror" }, - new[] { "F", "Fa", "Ftl", "Fatl" }, - }; + [ + ["V", "Vb", "Vrb", "Verb"], + ["D", "De", "Dbg", "Dbug"], + ["I", "In", "Inf", "Info"], + ["W", "Wn", "Wrn", "Warn"], + ["E", "Er", "Err", "Eror"], + ["F", "Fa", "Ftl", "Fatl"] + ]; static readonly string[][] LowercaseLevelMap = - { - new[] { "v", "vb", "vrb", "verb" }, - new[] { "d", "de", "dbg", "dbug" }, - new[] { "i", "in", "inf", "info" }, - new[] { "w", "wn", "wrn", "warn" }, - new[] { "e", "er", "err", "eror" }, - new[] { "f", "fa", "ftl", "fatl" }, - }; + [ + ["v", "vb", "vrb", "verb"], + ["d", "de", "dbg", "dbug"], + ["i", "in", "inf", "info"], + ["w", "wn", "wrn", "warn"], + ["e", "er", "err", "eror"], + ["f", "fa", "ftl", "fatl"] + ]; static readonly string[][] UppercaseLevelMap = - { - new[] { "V", "VB", "VRB", "VERB" }, - new[] { "D", "DE", "DBG", "DBUG" }, - new[] { "I", "IN", "INF", "INFO" }, - new[] { "W", "WN", "WRN", "WARN" }, - new[] { "E", "ER", "ERR", "EROR" }, - new[] { "F", "FA", "FTL", "FATL" }, - }; + [ + ["V", "VB", "VRB", "VERB"], + ["D", "DE", "DBG", "DBUG"], + ["I", "IN", "INF", "INFO"], + ["W", "WN", "WRN", "WARN"], + ["E", "ER", "ERR", "EROR"], + ["F", "FA", "FTL", "FATL"] + ]; public static string GetLevelMoniker(LogEventLevel value, string? format) { diff --git a/test/Serilog.Expressions.Tests/ExpressionCompilerTests.cs b/test/Serilog.Expressions.Tests/ExpressionCompilerTests.cs index 631d717..b4ba5a1 100644 --- a/test/Serilog.Expressions.Tests/ExpressionCompilerTests.cs +++ b/test/Serilog.Expressions.Tests/ExpressionCompilerTests.cs @@ -69,8 +69,8 @@ public void ExpressionsEvaluateNumericComparisons() public void ExpressionsEvaluateWildcardsOnCollectionItems() { AssertEvaluation("Items[?] like 'C%'", - Some.InformationEvent("Cart contains {@Items}", new[] { new[] { "Tea", "Coffee" } }), // Test helper doesn't correct this case - Some.InformationEvent("Cart contains {@Items}", new[] { new[] { "Apricots" } })); + Some.InformationEvent("Cart contains {@Items}", [new[] { "Tea", "Coffee" }]), // Test helper doesn't correct this case + Some.InformationEvent("Cart contains {@Items}", [new[] { "Apricots" }])); } [Fact] @@ -86,7 +86,7 @@ public void ExpressionsEvaluateExistentials() { AssertEvaluation("AppId is not null", Some.InformationEvent("{AppId}", 10), - Some.InformationEvent("{AppId}", new object?[] {null}), + Some.InformationEvent("{AppId}", [null]), Some.InformationEvent()); } @@ -112,8 +112,8 @@ public void ExpressionsEvaluateSubproperties() public void SequenceLengthCanBeDetermined() { AssertEvaluation("length(Items) > 1", - Some.InformationEvent("Checking out {Items}", new object[] { new[] { "pears", "apples" }}), - Some.InformationEvent("Checking out {Items}", new object[] { new[] { "pears" }})); + Some.InformationEvent("Checking out {Items}", [new[] { "pears", "apples" }]), + Some.InformationEvent("Checking out {Items}", [new[] { "pears" }])); } [Fact] diff --git a/test/Serilog.Expressions.Tests/TemplateTokenizerTests.cs b/test/Serilog.Expressions.Tests/TemplateTokenizerTests.cs index aaf2993..5c1af87 100644 --- a/test/Serilog.Expressions.Tests/TemplateTokenizerTests.cs +++ b/test/Serilog.Expressions.Tests/TemplateTokenizerTests.cs @@ -14,31 +14,26 @@ public static IEnumerable ValidCases { return new[] { - new object[] - { + [ "aa", new[] {Text} - }, - new object[] - { + ], + [ "{bb}", new[] {LBrace, Identifier, RBrace} - }, - new object[] - { + ], + [ "aa{bb}", new[] {Text, LBrace, Identifier, RBrace} - }, - new object[] - { + ], + [ "aa{{bb}}", new[] {Text, DoubleLBrace, Text, DoubleRBrace} - }, - new object[] - { + ], + [ "{ {b: b} }c", new[] {LBrace, LBrace, Identifier, Colon, Identifier, RBrace, RBrace, Text} - }, + ], new object[] { "{bb,-10:cc}",