Skip to content

Commit 63fe8de

Browse files
authored
Merge pull request #44 from serilog/dev
3.1.0 Release
2 parents c6192d6 + 44e69de commit 63fe8de

19 files changed

+486
-65
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ calling a function will be undefined if:
195195
| `LastIndexOf(s, t)` | Returns the last index of substring `t` in string `s`, or -1 if the substring does not appear. |
196196
| `Length(x)` | Returns the length of a string or array. |
197197
| `Now()` | Returns `DateTimeOffset.Now`. |
198+
| `Rest()` | In an `ExpressionTemplate`, returns an object containing the first-class event properties not otherwise referenced in the template or the event's message. |
198199
| `Round(n, m)` | Round the number `n` to `m` decimal places. |
199200
| `StartsWith(s, t)` | Tests whether the string `s` starts with substring `t`. |
200201
| `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. |

example/Sample/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ public static void Main()
2323
static void TextFormattingExample1()
2424
{
2525
using var log = new LoggerConfiguration()
26+
.Enrich.WithProperty("Application", "Sample")
2627
.WriteTo.Console(new ExpressionTemplate(
2728
"[{@t:HH:mm:ss} {@l:u3}" +
2829
"{#if SourceContext is not null} ({Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)}){#end}] " +
29-
"{@m} (first item is {coalesce(Items[0], '<empty>')})\n{@x}",
30+
"{@m} (first item is {coalesce(Items[0], '<empty>')}) {rest()}\n{@x}",
3031
theme: TemplateTheme.Code))
3132
.CreateLogger();
3233

src/Serilog.Expressions/Expressions/Compilation/Linq/LinqExpressionCompiler.cs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ class LinqExpressionCompiler : SerilogExpressionTransformer<ExpressionBody>
7171
static readonly MethodInfo TryGetStructurePropertyValueMethod = typeof(Intrinsics)
7272
.GetMethod(nameof(Intrinsics.TryGetStructurePropertyValue), BindingFlags.Static | BindingFlags.Public)!;
7373

74+
static readonly PropertyInfo EvaluationContextLogEventProperty = typeof(EvaluationContext)
75+
.GetProperty(nameof(EvaluationContext.LogEvent), BindingFlags.Instance | BindingFlags.Public)!;
76+
7477
ParameterExpression Context { get; } = LX.Variable(typeof(EvaluationContext), "ctx");
7578

7679
LinqExpressionCompiler(IFormatProvider? formatProvider, NameResolver nameResolver)
@@ -93,36 +96,44 @@ ExpressionBody Splice(Expression<Evaluatable> lambda)
9396
return ParameterReplacementVisitor.ReplaceParameters(lambda, Context);
9497
}
9598

96-
protected override ExpressionBody Transform(CallExpression lx)
99+
protected override ExpressionBody Transform(CallExpression call)
97100
{
98-
if (!_nameResolver.TryResolveFunctionName(lx.OperatorName, out var m))
99-
throw new ArgumentException($"The function name `{lx.OperatorName}` was not recognized.");
101+
if (!_nameResolver.TryResolveFunctionName(call.OperatorName, out var m))
102+
throw new ArgumentException($"The function name `{call.OperatorName}` was not recognized.");
100103

101104
var methodParameters = m.GetParameters();
102105

103106
var parameterCount = methodParameters.Count(pi => pi.ParameterType == typeof(LogEventPropertyValue));
104-
if (parameterCount != lx.Operands.Length)
105-
throw new ArgumentException($"The function `{lx.OperatorName}` requires {parameterCount} arguments.");
107+
if (parameterCount != call.Operands.Length)
108+
throw new ArgumentException($"The function `{call.OperatorName}` requires {parameterCount} arguments.");
106109

107-
var operands = lx.Operands.Select(Transform).ToList();
110+
var operands = new Queue<LX>(call.Operands.Select(Transform));
108111

109112
// `and` and `or` short-circuit to save execution time; unlike the earlier Serilog.Filters.Expressions, nothing else does.
110-
if (Operators.SameOperator(lx.OperatorName, Operators.RuntimeOpAnd))
111-
return CompileLogical(LX.AndAlso, operands[0], operands[1]);
113+
if (Operators.SameOperator(call.OperatorName, Operators.RuntimeOpAnd))
114+
return CompileLogical(LX.AndAlso, operands.Dequeue(), operands.Dequeue());
112115

113-
if (Operators.SameOperator(lx.OperatorName, Operators.RuntimeOpOr))
114-
return CompileLogical(LX.OrElse, operands[0], operands[1]);
116+
if (Operators.SameOperator(call.OperatorName, Operators.RuntimeOpOr))
117+
return CompileLogical(LX.OrElse, operands.Dequeue(), operands.Dequeue());
115118

116-
for (var i = 0; i < methodParameters.Length; ++i)
119+
var boundParameters = new List<LX>(methodParameters.Length);
120+
foreach (var pi in methodParameters)
117121
{
118-
var pi = methodParameters[i];
119-
if (pi.ParameterType == typeof(StringComparison))
120-
operands.Insert(i, LX.Constant(lx.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
122+
if (pi.ParameterType == typeof(LogEventPropertyValue))
123+
boundParameters.Add(operands.Dequeue());
124+
else if (pi.ParameterType == typeof(StringComparison))
125+
boundParameters.Add(LX.Constant(call.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
121126
else if (pi.ParameterType == typeof(IFormatProvider))
122-
operands.Insert(i, LX.Constant(_formatProvider, typeof(IFormatProvider)));
127+
boundParameters.Add(LX.Constant(_formatProvider, typeof(IFormatProvider)));
128+
else if (pi.ParameterType == typeof(LogEvent))
129+
boundParameters.Add(LX.Property(Context, EvaluationContextLogEventProperty));
130+
else if (_nameResolver.TryBindFunctionParameter(pi, out var binding))
131+
boundParameters.Add(LX.Constant(binding, pi.ParameterType));
132+
else
133+
throw new ArgumentException($"The method `{m.Name}` implementing function `{call.OperatorName}` has parameter `{pi.Name}` which could not be bound.");
123134
}
124135

125-
return LX.Call(m, operands);
136+
return LX.Call(m, boundParameters);
126137
}
127138

128139
static ExpressionBody CompileLogical(Func<ExpressionBody, ExpressionBody, ExpressionBody> apply, ExpressionBody lhs, ExpressionBody rhs)
@@ -138,8 +149,8 @@ static ExpressionBody CompileLogical(Func<ExpressionBody, ExpressionBody, Expres
138149

139150
protected override ExpressionBody Transform(AccessorExpression spx)
140151
{
141-
var recv = Transform(spx.Receiver);
142-
return LX.Call(TryGetStructurePropertyValueMethod, LX.Constant(StringComparison.OrdinalIgnoreCase), recv, LX.Constant(spx.MemberName, typeof(string)));
152+
var receiver = Transform(spx.Receiver);
153+
return LX.Call(TryGetStructurePropertyValueMethod, LX.Constant(StringComparison.OrdinalIgnoreCase), receiver, LX.Constant(spx.MemberName, typeof(string)));
143154
}
144155

145156
protected override ExpressionBody Transform(ConstantExpression cx)

src/Serilog.Expressions/Expressions/Compilation/OrderedNameResolver.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,17 @@ public override bool TryResolveFunctionName(string name, [MaybeNullWhen(false)]
3939
implementation = null;
4040
return false;
4141
}
42+
43+
public override bool TryBindFunctionParameter(ParameterInfo parameter, [MaybeNullWhen(false)] out object boundValue)
44+
{
45+
foreach (var resolver in _orderedResolvers)
46+
{
47+
if (resolver.TryBindFunctionParameter(parameter, out boundValue))
48+
return true;
49+
}
50+
51+
boundValue = null;
52+
return false;
53+
}
4254
}
4355
}

src/Serilog.Expressions/Expressions/Compilation/Text/LikeSyntaxTransformer.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,21 @@ public static Expression Rewrite(Expression expression)
3030
return Instance.Transform(expression);
3131
}
3232

33-
protected override Expression Transform(CallExpression lx)
33+
protected override Expression Transform(CallExpression call)
3434
{
35-
if (lx.Operands.Length != 2)
36-
return base.Transform(lx);
35+
if (call.Operands.Length != 2)
36+
return base.Transform(call);
3737

38-
if (Operators.SameOperator(lx.OperatorName, Operators.IntermediateOpLike))
39-
return TryCompileLikeExpression(lx.IgnoreCase, lx.Operands[0], lx.Operands[1]);
38+
if (Operators.SameOperator(call.OperatorName, Operators.IntermediateOpLike))
39+
return TryCompileLikeExpression(call.IgnoreCase, call.Operands[0], call.Operands[1]);
4040

41-
if (Operators.SameOperator(lx.OperatorName, Operators.IntermediateOpNotLike))
41+
if (Operators.SameOperator(call.OperatorName, Operators.IntermediateOpNotLike))
4242
return new CallExpression(
4343
false,
4444
Operators.RuntimeOpStrictNot,
45-
TryCompileLikeExpression(lx.IgnoreCase, lx.Operands[0], lx.Operands[1]));
45+
TryCompileLikeExpression(call.IgnoreCase, call.Operands[0], call.Operands[1]));
4646

47-
return base.Transform(lx);
47+
return base.Transform(call);
4848
}
4949

5050
Expression TryCompileLikeExpression(bool ignoreCase, Expression corpus, Expression like)

src/Serilog.Expressions/Expressions/Compilation/Text/TextMatchingTransformer.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,22 @@ public static Expression Rewrite(Expression expression)
3030
return Instance.Transform(expression);
3131
}
3232

33-
protected override Expression Transform(CallExpression lx)
33+
protected override Expression Transform(CallExpression call)
3434
{
35-
if (lx.Operands.Length != 2)
36-
return base.Transform(lx);
35+
if (call.Operands.Length != 2)
36+
return base.Transform(call);
3737

38-
if (Operators.SameOperator(lx.OperatorName, Operators.OpIndexOfMatch))
39-
return TryCompileIndexOfMatch(lx.IgnoreCase, lx.Operands[0], lx.Operands[1]);
38+
if (Operators.SameOperator(call.OperatorName, Operators.OpIndexOfMatch))
39+
return TryCompileIndexOfMatch(call.IgnoreCase, call.Operands[0], call.Operands[1]);
4040

41-
if (Operators.SameOperator(lx.OperatorName, Operators.OpIsMatch))
41+
if (Operators.SameOperator(call.OperatorName, Operators.OpIsMatch))
4242
return new CallExpression(
4343
false,
4444
Operators.RuntimeOpNotEqual,
45-
TryCompileIndexOfMatch(lx.IgnoreCase, lx.Operands[0], lx.Operands[1]),
45+
TryCompileIndexOfMatch(call.IgnoreCase, call.Operands[0], call.Operands[1]),
4646
new ConstantExpression(new ScalarValue(-1)));
4747

48-
return base.Transform(lx);
48+
return base.Transform(call);
4949
}
5050

5151
Expression TryCompileIndexOfMatch(bool ignoreCase, Expression corpus, Expression regex)

src/Serilog.Expressions/Expressions/Compilation/Transformations/IdentityTransformer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@ bool TryTransform(Expression expr, out Expression result)
2525
return !ReferenceEquals(expr, result);
2626
}
2727

28-
protected override Expression Transform(CallExpression lx)
28+
protected override Expression Transform(CallExpression call)
2929
{
3030
var any = false;
3131
var operands = new List<Expression>();
32-
foreach (var op in lx.Operands)
32+
foreach (var op in call.Operands)
3333
{
3434
if (TryTransform(op, out var result))
3535
any = true;
3636
operands.Add(result);
3737
}
3838

3939
if (!any)
40-
return lx;
40+
return call;
4141

42-
return new CallExpression(lx.IgnoreCase, lx.OperatorName, operands.ToArray());
42+
return new CallExpression(call.IgnoreCase, call.OperatorName, operands.ToArray());
4343
}
4444

4545
protected override Expression Transform(ConstantExpression cx)

src/Serilog.Expressions/Expressions/Compilation/Transformations/FilterExpressionTransformer`1.cs renamed to src/Serilog.Expressions/Expressions/Compilation/Transformations/SerilogExpressionTransformer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected virtual TResult Transform(Expression expression)
4141
};
4242
}
4343

44-
protected abstract TResult Transform(CallExpression lx);
44+
protected abstract TResult Transform(CallExpression call);
4545
protected abstract TResult Transform(ConstantExpression cx);
4646
protected abstract TResult Transform(AmbientNameExpression px);
4747
protected abstract TResult Transform(LocalNameExpression nlx);

src/Serilog.Expressions/Expressions/Compilation/Variadics/VariadicCallRewriter.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,38 @@ public static Expression Rewrite(Expression expression)
2828
return Instance.Transform(expression);
2929
}
3030

31-
protected override Expression Transform(CallExpression lx)
31+
protected override Expression Transform(CallExpression call)
3232
{
33-
if (Operators.SameOperator(lx.OperatorName, Operators.OpSubstring) && lx.Operands.Length == 2)
33+
if (Operators.SameOperator(call.OperatorName, Operators.OpSubstring) && call.Operands.Length == 2)
3434
{
35-
var operands = lx.Operands
35+
var operands = call.Operands
3636
.Select(Transform)
3737
.Concat(new[] {CallUndefined()})
3838
.ToArray();
39-
return new CallExpression(lx.IgnoreCase, lx.OperatorName, operands);
39+
return new CallExpression(call.IgnoreCase, call.OperatorName, operands);
4040
}
4141

42-
if (Operators.SameOperator(lx.OperatorName, Operators.OpCoalesce))
42+
if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce))
4343
{
44-
if (lx.Operands.Length == 0)
44+
if (call.Operands.Length == 0)
4545
return CallUndefined();
46-
if (lx.Operands.Length == 1)
47-
return Transform(lx.Operands.Single());
48-
if (lx.Operands.Length > 2)
46+
if (call.Operands.Length == 1)
47+
return Transform(call.Operands.Single());
48+
if (call.Operands.Length > 2)
4949
{
50-
var first = Transform(lx.Operands.First());
51-
return new CallExpression(lx.IgnoreCase, lx.OperatorName, first,
52-
Transform(new CallExpression(lx.IgnoreCase, lx.OperatorName, lx.Operands.Skip(1).ToArray())));
50+
var first = Transform(call.Operands.First());
51+
return new CallExpression(call.IgnoreCase, call.OperatorName, first,
52+
Transform(new CallExpression(call.IgnoreCase, call.OperatorName, call.Operands.Skip(1).ToArray())));
5353
}
5454
}
5555

56-
if (Operators.SameOperator(lx.OperatorName, Operators.OpToString) &&
57-
lx.Operands.Length == 1)
56+
if (Operators.SameOperator(call.OperatorName, Operators.OpToString) &&
57+
call.Operands.Length == 1)
5858
{
59-
return new CallExpression(lx.IgnoreCase, lx.OperatorName, lx.Operands[0], CallUndefined());
59+
return new CallExpression(call.IgnoreCase, call.OperatorName, call.Operands[0], CallUndefined());
6060
}
6161

62-
return base.Transform(lx);
62+
return base.Transform(call);
6363
}
6464

6565
static CallExpression CallUndefined()

src/Serilog.Expressions/Expressions/Compilation/Wildcards/WildcardSearch.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ class WildcardSearch : SerilogExpressionTransformer<IndexerExpression?>
7676
return null;
7777
}
7878

79-
protected override IndexerExpression? Transform(CallExpression lx)
79+
protected override IndexerExpression? Transform(CallExpression call)
8080
{
8181
// If we hit a wildcard-compatible operation, then any wildcards within its operands "belong" to
8282
// it and can't be the result of this search.
83-
if (Operators.WildcardComparators.Contains(lx.OperatorName))
83+
if (Operators.WildcardComparators.Contains(call.OperatorName))
8484
return null;
8585

86-
return lx.Operands.Select(Transform).FirstOrDefault(e => e != null);
86+
return call.Operands.Select(Transform).FirstOrDefault(e => e != null);
8787
}
8888

8989
protected override IndexerExpression? Transform(IndexOfMatchExpression mx)

src/Serilog.Expressions/Expressions/NameResolver.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ public abstract class NameResolver
3434
/// and accept parameters of type <see cref="LogEventPropertyValue"/>. If the <c>ci</c> modifier is supported,
3535
/// a <see cref="StringComparison"/> should be included in the argument list. If the function is culture-specific,
3636
/// an <see cref="IFormatProvider"/> should be included in the argument list.</remarks>
37-
public abstract bool TryResolveFunctionName(string name, [MaybeNullWhen(false)] out MethodInfo implementation);
37+
public virtual bool TryResolveFunctionName(string name, [MaybeNullWhen(false)] out MethodInfo implementation)
38+
{
39+
implementation = null;
40+
return false;
41+
}
42+
43+
/// <summary>
44+
/// Provide a value for a non-<see cref="LogEventPropertyValue"/> parameter. This allows user-defined state to
45+
/// be threaded through user-defined functions.
46+
/// </summary>
47+
/// <param name="parameter">A parameter of a method implementing a user-defined function, which could not be
48+
/// bound to any of the standard runtime-provided values or operands.</param>
49+
/// <param name="boundValue">The value that should be provided when the method is called.</param>
50+
/// <returns><c>True</c> if the parameter could be bound; otherwise, <c>false</c>.</returns>
51+
public virtual bool TryBindFunctionParameter(ParameterInfo parameter, [MaybeNullWhen(false)] out object boundValue)
52+
{
53+
boundValue = null;
54+
return false;
55+
}
3856
}
39-
}
57+
}

src/Serilog.Expressions/Serilog.Expressions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Description>An embeddable mini-language for filtering, enriching, and formatting Serilog
55
events, ideal for use with JSON or XML configuration.</Description>
6-
<VersionPrefix>3.0.0</VersionPrefix>
6+
<VersionPrefix>3.1.0</VersionPrefix>
77
<Authors>Serilog Contributors</Authors>
88
<TargetFrameworks>netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
99
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright © Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Collections.Generic;
16+
using Serilog.Expressions;
17+
using Serilog.Expressions.Compilation;
18+
using Serilog.Expressions.Runtime;
19+
using Serilog.Templates.Ast;
20+
using Serilog.Templates.Compilation.UnreferencedProperties;
21+
22+
namespace Serilog.Templates.Compilation
23+
{
24+
static class TemplateFunctionNameResolver
25+
{
26+
public static NameResolver Build(NameResolver? additionalNameResolver, Template template)
27+
{
28+
var resolvers = new List<NameResolver>
29+
{
30+
new StaticMemberNameResolver(typeof(RuntimeOperators)),
31+
new UnreferencedPropertiesFunction(template)
32+
};
33+
34+
if (additionalNameResolver != null)
35+
resolvers.Add(additionalNameResolver);
36+
37+
return new OrderedNameResolver(resolvers);
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)