Skip to content

Commit

Permalink
Merge pull request #44 from serilog/dev
Browse files Browse the repository at this point in the history
3.1.0 Release
  • Loading branch information
nblumhardt authored Jun 4, 2021
2 parents c6192d6 + 44e69de commit 63fe8de
Show file tree
Hide file tree
Showing 19 changed files with 486 additions and 65 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ calling a function will be undefined if:
| `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()` | In an `ExpressionTemplate`, returns an object containing the first-class event properties not otherwise referenced in the template or the event's message. |
| `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. |
Expand Down
3 changes: 2 additions & 1 deletion example/Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ public static void Main()
static void TextFormattingExample1()
{
using var log = new LoggerConfiguration()
.Enrich.WithProperty("Application", "Sample")
.WriteTo.Console(new ExpressionTemplate(
"[{@t:HH:mm:ss} {@l:u3}" +
"{#if SourceContext is not null} ({Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)}){#end}] " +
"{@m} (first item is {coalesce(Items[0], '<empty>')})\n{@x}",
"{@m} (first item is {coalesce(Items[0], '<empty>')}) {rest()}\n{@x}",
theme: TemplateTheme.Code))
.CreateLogger();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ class LinqExpressionCompiler : SerilogExpressionTransformer<ExpressionBody>
static readonly MethodInfo TryGetStructurePropertyValueMethod = typeof(Intrinsics)
.GetMethod(nameof(Intrinsics.TryGetStructurePropertyValue), BindingFlags.Static | BindingFlags.Public)!;

static readonly PropertyInfo EvaluationContextLogEventProperty = typeof(EvaluationContext)
.GetProperty(nameof(EvaluationContext.LogEvent), BindingFlags.Instance | BindingFlags.Public)!;

ParameterExpression Context { get; } = LX.Variable(typeof(EvaluationContext), "ctx");

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

protected override ExpressionBody Transform(CallExpression lx)
protected override ExpressionBody Transform(CallExpression call)
{
if (!_nameResolver.TryResolveFunctionName(lx.OperatorName, out var m))
throw new ArgumentException($"The function name `{lx.OperatorName}` was not recognized.");
if (!_nameResolver.TryResolveFunctionName(call.OperatorName, out var m))
throw new ArgumentException($"The function name `{call.OperatorName}` was not recognized.");

var methodParameters = m.GetParameters();

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

var operands = lx.Operands.Select(Transform).ToList();
var operands = new Queue<LX>(call.Operands.Select(Transform));

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

if (Operators.SameOperator(lx.OperatorName, Operators.RuntimeOpOr))
return CompileLogical(LX.OrElse, operands[0], operands[1]);
if (Operators.SameOperator(call.OperatorName, Operators.RuntimeOpOr))
return CompileLogical(LX.OrElse, operands.Dequeue(), operands.Dequeue());

for (var i = 0; i < methodParameters.Length; ++i)
var boundParameters = new List<LX>(methodParameters.Length);
foreach (var pi in methodParameters)
{
var pi = methodParameters[i];
if (pi.ParameterType == typeof(StringComparison))
operands.Insert(i, LX.Constant(lx.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
if (pi.ParameterType == typeof(LogEventPropertyValue))
boundParameters.Add(operands.Dequeue());
else if (pi.ParameterType == typeof(StringComparison))
boundParameters.Add(LX.Constant(call.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
else if (pi.ParameterType == typeof(IFormatProvider))
operands.Insert(i, LX.Constant(_formatProvider, typeof(IFormatProvider)));
boundParameters.Add(LX.Constant(_formatProvider, typeof(IFormatProvider)));
else if (pi.ParameterType == typeof(LogEvent))
boundParameters.Add(LX.Property(Context, EvaluationContextLogEventProperty));
else if (_nameResolver.TryBindFunctionParameter(pi, out var binding))
boundParameters.Add(LX.Constant(binding, pi.ParameterType));
else
throw new ArgumentException($"The method `{m.Name}` implementing function `{call.OperatorName}` has parameter `{pi.Name}` which could not be bound.");
}

return LX.Call(m, operands);
return LX.Call(m, boundParameters);
}

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

protected override ExpressionBody Transform(AccessorExpression spx)
{
var recv = Transform(spx.Receiver);
return LX.Call(TryGetStructurePropertyValueMethod, LX.Constant(StringComparison.OrdinalIgnoreCase), recv, LX.Constant(spx.MemberName, typeof(string)));
var receiver = Transform(spx.Receiver);
return LX.Call(TryGetStructurePropertyValueMethod, LX.Constant(StringComparison.OrdinalIgnoreCase), receiver, LX.Constant(spx.MemberName, typeof(string)));
}

protected override ExpressionBody Transform(ConstantExpression cx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,17 @@ public override bool TryResolveFunctionName(string name, [MaybeNullWhen(false)]
implementation = null;
return false;
}

public override bool TryBindFunctionParameter(ParameterInfo parameter, [MaybeNullWhen(false)] out object boundValue)
{
foreach (var resolver in _orderedResolvers)
{
if (resolver.TryBindFunctionParameter(parameter, out boundValue))
return true;
}

boundValue = null;
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,21 @@ public static Expression Rewrite(Expression expression)
return Instance.Transform(expression);
}

protected override Expression Transform(CallExpression lx)
protected override Expression Transform(CallExpression call)
{
if (lx.Operands.Length != 2)
return base.Transform(lx);
if (call.Operands.Length != 2)
return base.Transform(call);

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

if (Operators.SameOperator(lx.OperatorName, Operators.IntermediateOpNotLike))
if (Operators.SameOperator(call.OperatorName, Operators.IntermediateOpNotLike))
return new CallExpression(
false,
Operators.RuntimeOpStrictNot,
TryCompileLikeExpression(lx.IgnoreCase, lx.Operands[0], lx.Operands[1]));
TryCompileLikeExpression(call.IgnoreCase, call.Operands[0], call.Operands[1]));

return base.Transform(lx);
return base.Transform(call);
}

Expression TryCompileLikeExpression(bool ignoreCase, Expression corpus, Expression like)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ public static Expression Rewrite(Expression expression)
return Instance.Transform(expression);
}

protected override Expression Transform(CallExpression lx)
protected override Expression Transform(CallExpression call)
{
if (lx.Operands.Length != 2)
return base.Transform(lx);
if (call.Operands.Length != 2)
return base.Transform(call);

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

if (Operators.SameOperator(lx.OperatorName, Operators.OpIsMatch))
if (Operators.SameOperator(call.OperatorName, Operators.OpIsMatch))
return new CallExpression(
false,
Operators.RuntimeOpNotEqual,
TryCompileIndexOfMatch(lx.IgnoreCase, lx.Operands[0], lx.Operands[1]),
TryCompileIndexOfMatch(call.IgnoreCase, call.Operands[0], call.Operands[1]),
new ConstantExpression(new ScalarValue(-1)));

return base.Transform(lx);
return base.Transform(call);
}

Expression TryCompileIndexOfMatch(bool ignoreCase, Expression corpus, Expression regex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ bool TryTransform(Expression expr, out Expression result)
return !ReferenceEquals(expr, result);
}

protected override Expression Transform(CallExpression lx)
protected override Expression Transform(CallExpression call)
{
var any = false;
var operands = new List<Expression>();
foreach (var op in lx.Operands)
foreach (var op in call.Operands)
{
if (TryTransform(op, out var result))
any = true;
operands.Add(result);
}

if (!any)
return lx;
return call;

return new CallExpression(lx.IgnoreCase, lx.OperatorName, operands.ToArray());
return new CallExpression(call.IgnoreCase, call.OperatorName, operands.ToArray());
}

protected override Expression Transform(ConstantExpression cx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ protected virtual TResult Transform(Expression expression)
};
}

protected abstract TResult Transform(CallExpression lx);
protected abstract TResult Transform(CallExpression call);
protected abstract TResult Transform(ConstantExpression cx);
protected abstract TResult Transform(AmbientNameExpression px);
protected abstract TResult Transform(LocalNameExpression nlx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,38 @@ public static Expression Rewrite(Expression expression)
return Instance.Transform(expression);
}

protected override Expression Transform(CallExpression lx)
protected override Expression Transform(CallExpression call)
{
if (Operators.SameOperator(lx.OperatorName, Operators.OpSubstring) && lx.Operands.Length == 2)
if (Operators.SameOperator(call.OperatorName, Operators.OpSubstring) && call.Operands.Length == 2)
{
var operands = lx.Operands
var operands = call.Operands
.Select(Transform)
.Concat(new[] {CallUndefined()})
.ToArray();
return new CallExpression(lx.IgnoreCase, lx.OperatorName, operands);
return new CallExpression(call.IgnoreCase, call.OperatorName, operands);
}

if (Operators.SameOperator(lx.OperatorName, Operators.OpCoalesce))
if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce))
{
if (lx.Operands.Length == 0)
if (call.Operands.Length == 0)
return CallUndefined();
if (lx.Operands.Length == 1)
return Transform(lx.Operands.Single());
if (lx.Operands.Length > 2)
if (call.Operands.Length == 1)
return Transform(call.Operands.Single());
if (call.Operands.Length > 2)
{
var first = Transform(lx.Operands.First());
return new CallExpression(lx.IgnoreCase, lx.OperatorName, first,
Transform(new CallExpression(lx.IgnoreCase, lx.OperatorName, lx.Operands.Skip(1).ToArray())));
var first = Transform(call.Operands.First());
return new CallExpression(call.IgnoreCase, call.OperatorName, first,
Transform(new CallExpression(call.IgnoreCase, call.OperatorName, call.Operands.Skip(1).ToArray())));
}
}

if (Operators.SameOperator(lx.OperatorName, Operators.OpToString) &&
lx.Operands.Length == 1)
if (Operators.SameOperator(call.OperatorName, Operators.OpToString) &&
call.Operands.Length == 1)
{
return new CallExpression(lx.IgnoreCase, lx.OperatorName, lx.Operands[0], CallUndefined());
return new CallExpression(call.IgnoreCase, call.OperatorName, call.Operands[0], CallUndefined());
}

return base.Transform(lx);
return base.Transform(call);
}

static CallExpression CallUndefined()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ class WildcardSearch : SerilogExpressionTransformer<IndexerExpression?>
return null;
}

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

return lx.Operands.Select(Transform).FirstOrDefault(e => e != null);
return call.Operands.Select(Transform).FirstOrDefault(e => e != null);
}

protected override IndexerExpression? Transform(IndexOfMatchExpression mx)
Expand Down
22 changes: 20 additions & 2 deletions src/Serilog.Expressions/Expressions/NameResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ public abstract class NameResolver
/// and accept parameters of type <see cref="LogEventPropertyValue"/>. If the <c>ci</c> modifier is supported,
/// a <see cref="StringComparison"/> should be included in the argument list. If the function is culture-specific,
/// an <see cref="IFormatProvider"/> should be included in the argument list.</remarks>
public abstract bool TryResolveFunctionName(string name, [MaybeNullWhen(false)] out MethodInfo implementation);
public virtual bool TryResolveFunctionName(string name, [MaybeNullWhen(false)] out MethodInfo implementation)
{
implementation = null;
return false;
}

/// <summary>
/// Provide a value for a non-<see cref="LogEventPropertyValue"/> parameter. This allows user-defined state to
/// be threaded through user-defined functions.
/// </summary>
/// <param name="parameter">A parameter of a method implementing a user-defined function, which could not be
/// bound to any of the standard runtime-provided values or operands.</param>
/// <param name="boundValue">The value that should be provided when the method is called.</param>
/// <returns><c>True</c> if the parameter could be bound; otherwise, <c>false</c>.</returns>
public virtual bool TryBindFunctionParameter(ParameterInfo parameter, [MaybeNullWhen(false)] out object boundValue)
{
boundValue = null;
return false;
}
}
}
}
2 changes: 1 addition & 1 deletion src/Serilog.Expressions/Serilog.Expressions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>An embeddable mini-language for filtering, enriching, and formatting Serilog
events, ideal for use with JSON or XML configuration.</Description>
<VersionPrefix>3.0.0</VersionPrefix>
<VersionPrefix>3.1.0</VersionPrefix>
<Authors>Serilog Contributors</Authors>
<TargetFrameworks>netstandard2.0;netstandard2.1;net5.0</TargetFrameworks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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.Collections.Generic;
using Serilog.Expressions;
using Serilog.Expressions.Compilation;
using Serilog.Expressions.Runtime;
using Serilog.Templates.Ast;
using Serilog.Templates.Compilation.UnreferencedProperties;

namespace Serilog.Templates.Compilation
{
static class TemplateFunctionNameResolver
{
public static NameResolver Build(NameResolver? additionalNameResolver, Template template)
{
var resolvers = new List<NameResolver>
{
new StaticMemberNameResolver(typeof(RuntimeOperators)),
new UnreferencedPropertiesFunction(template)
};

if (additionalNameResolver != null)
resolvers.Add(additionalNameResolver);

return new OrderedNameResolver(resolvers);
}
}
}
Loading

0 comments on commit 63fe8de

Please sign in to comment.