Skip to content

3.2.0 Release #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AssemblyOriginatorKeyFile>../../assets/Serilog.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
</Project>
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ The built-in properties mirror those available in the CLEF format.
| Array | An array of values, in square brackets | `[1, 'two', null]` |
| Object | A mapping of string keys to values; keys that are valid identifiers do not need to be quoted | `{a: 1, 'b c': 2, d}` |

Array and object literals support the spread operator: `[1, 2, ..rest]`, `{a: 1, ..other}`. Specifying an undefined
Array and object literals support the spread operator: `[1, 2, ..others]`, `{a: 1, ..others}`. Specifying an undefined
property in an object literal will remove it from the result: `{..User, Email: Undefined()}`

### Operators and conditionals
Expand Down Expand Up @@ -184,7 +184,8 @@ calling a function will be undefined if:

| Function | Description |
| :--- | :--- |
| `Coalesce(p0, p1, ..pN)` | Returns the first defined, non-null argument. |
| `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`. |
Expand All @@ -195,7 +196,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. |
| `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. |
Expand Down Expand Up @@ -352,7 +353,7 @@ convert the result to plain-old-.NET-types like `string`, `bool`, `Dictionary<K,
User-defined functions can be plugged in by implementing static methods that:

* Return `LogEventPropertyValue?`,
* Have arguments of type `LogEventPropertyValue?`,
* Have arguments of type `LogEventPropertyValue?` or `LogEvent`,
* If the `ci` modifier is supported, accept a `StringComparison`, and
* If culture-specific formatting or comparisons are used, accepts an `IFormatProvider`.

Expand Down
8 changes: 6 additions & 2 deletions serilog-expressions.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.15
# Visual Studio Version 16
VisualStudioVersion = 16.0.31410.223
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{91E482DE-E1E7-4CE1-9511-C0AF07F3648A}"
EndProject
Expand All @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{24B112
.gitignore = .gitignore
appveyor.yml = appveyor.yml
Build.ps1 = Build.ps1
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE
README.md = README.md
RunPerfTests.ps1 = RunPerfTests.ps1
Expand Down Expand Up @@ -60,4 +61,7 @@ Global
{3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {B03B3086-D197-4B32-9AE2-8536C345EA2D}
{D7A37F73-BBA3-4DAE-9648-1A753A86F968} = {B03B3086-D197-4B32-9AE2-8536C345EA2D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EB6672D6-318E-493E-8B60-77F5A7A90E66}
EndGlobalSection
EndGlobal
26 changes: 0 additions & 26 deletions src/Serilog.Expressions/Compatibility/NotNullAttributes.cs

This file was deleted.

19 changes: 9 additions & 10 deletions src/Serilog.Expressions/Expressions/Compilation/Linq/Intrinsics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
using System.Text.RegularExpressions;
using Serilog.Events;
using Serilog.Expressions.Runtime;
using Serilog.Formatting.Display;
using Serilog.Parsing;
using Serilog.Templates.Compilation;

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

public static List<LogEventPropertyValue?> CollectSequenceElements(LogEventPropertyValue?[] elements)
{
return elements.ToList();
Expand All @@ -53,18 +52,18 @@ static class Intrinsics
if (content is SequenceValue sequence)
foreach (var element in sequence.Elements)
elements.Add(element);

return elements;
}

public static LogEventPropertyValue ConstructSequenceValue(List<LogEventPropertyValue?> elements)
{
if (elements.Any(el => el == null))
return new SequenceValue(elements.Where(el => el != null));

return new SequenceValue(elements);
}

public static List<LogEventProperty> CollectStructureProperties(string[] names, LogEventPropertyValue?[] values)
{
var properties = new List<LogEventProperty>();
Expand All @@ -85,7 +84,7 @@ public static LogEventPropertyValue ConstructStructureValue(List<LogEventPropert

return new StructureValue(properties);
}

public static List<LogEventProperty> ExtendStructureValueWithSpread(
List<LogEventProperty> properties,
LogEventPropertyValue? content)
Expand All @@ -99,7 +98,7 @@ public static List<LogEventProperty> ExtendStructureValueWithSpread(

return properties;
}

public static List<LogEventProperty> ExtendStructureValueWithProperty(
List<LogEventProperty> properties,
string name,
Expand Down Expand Up @@ -129,7 +128,7 @@ public static bool CoerceToScalarBoolean(LogEventPropertyValue value)
return b;
return false;
}

public static LogEventPropertyValue? IndexOfMatch(LogEventPropertyValue value, Regex regex)
{
if (value is ScalarValue scalar &&
Expand Down Expand Up @@ -193,9 +192,9 @@ public static string RenderMessage(CompiledMessageToken formatter, EvaluationCon
if (token is PropertyToken {Format: { }} pt)
{
elements ??= new List<LogEventPropertyValue>();

var space = new StringWriter();

pt.Render(logEvent.Properties, space, formatProvider);
elements.Add(new ScalarValue(space.ToString()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using Serilog.Events;
using Serilog.Expressions.Ast;
using Serilog.Expressions.Compilation.Transformations;
Expand All @@ -27,6 +29,7 @@
using ParameterExpression = System.Linq.Expressions.ParameterExpression;
using LX = System.Linq.Expressions.Expression;
using ExpressionBody = System.Linq.Expressions.Expression;
// ReSharper disable UseIndexFromEndExpression

namespace Serilog.Expressions.Compilation.Linq
{
Expand Down Expand Up @@ -101,11 +104,18 @@ protected override ExpressionBody Transform(CallExpression call)
if (!_nameResolver.TryResolveFunctionName(call.OperatorName, out var m))
throw new ArgumentException($"The function name `{call.OperatorName}` was not recognized.");

var methodParameters = m.GetParameters();
var methodParameters = m.GetParameters()
.Select(info => (pi: info, optional: info.GetCustomAttribute<OptionalAttribute>() != null))
.ToList();

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

if (call.Operands.Length < requiredParameterCount || call.Operands.Length > allowedParameters.Count)
{
var requirements = DescribeRequirements(allowedParameters.Select(info => (info.pi.Name!, info.optional)).ToList());
throw new ArgumentException($"The function `{call.OperatorName}` {requirements}.");
}

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

Expand All @@ -116,11 +126,15 @@ protected override ExpressionBody Transform(CallExpression call)
if (Operators.SameOperator(call.OperatorName, Operators.RuntimeOpOr))
return CompileLogical(LX.OrElse, operands.Dequeue(), operands.Dequeue());

var boundParameters = new List<LX>(methodParameters.Length);
foreach (var pi in methodParameters)
var boundParameters = new List<LX>(methodParameters.Count);
foreach (var (pi, optional) in methodParameters)
{
if (pi.ParameterType == typeof(LogEventPropertyValue))
boundParameters.Add(operands.Dequeue());
{
boundParameters.Add(operands.Count > 0
? operands.Dequeue()
: LX.Constant(null, typeof(LogEventPropertyValue)));
}
else if (pi.ParameterType == typeof(StringComparison))
boundParameters.Add(LX.Constant(call.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
else if (pi.ParameterType == typeof(IFormatProvider))
Expand All @@ -129,13 +143,38 @@ protected override ExpressionBody Transform(CallExpression call)
boundParameters.Add(LX.Property(Context, EvaluationContextLogEventProperty));
else if (_nameResolver.TryBindFunctionParameter(pi, out var binding))
boundParameters.Add(LX.Constant(binding, pi.ParameterType));
else if (optional)
boundParameters.Add(LX.Constant(
pi.GetCustomAttribute<DefaultParameterValueAttribute>()?.Value, pi.ParameterType));
else
throw new ArgumentException($"The method `{m.Name}` implementing function `{call.OperatorName}` has parameter `{pi.Name}` which could not be bound.");
throw new ArgumentException($"The method `{m.Name}` implementing function `{call.OperatorName}` has argument `{pi.Name}` which could not be bound.");
}

return LX.Call(m, boundParameters);
}

static string DescribeRequirements(IReadOnlyList<(string name, bool optional)> parameters)
{
static string DescribeArgument((string name, bool optional) p) =>
$"`{p.name}`" + (p.optional ? " (optional)" : "");

if (parameters.Count == 0)
return "accepts no arguments";

if (parameters.Count == 1)
return $"accepts one argument, {DescribeArgument(parameters[0])}";

if (parameters.Count == 2)
return $"accepts two arguments, {DescribeArgument(parameters[0])} and {DescribeArgument(parameters[1])}";

var result = new StringBuilder("accepts arguments");
for (var i = 0; i < parameters.Count - 1; ++i)
result.Append($" {DescribeArgument(parameters[i])},");

result.Append($" and {DescribeArgument(parameters[parameters.Count - 1])}");
return result.ToString();
}

static ExpressionBody CompileLogical(Func<ExpressionBody, ExpressionBody, ExpressionBody> apply, ExpressionBody lhs, ExpressionBody rhs)
{
return LX.Convert(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

namespace Serilog.Expressions.Compilation.Variadics
{
// Now a bit of a misnomer - handles variadic `coalesce()`, as well as optional arguments for other functions.
// Handles variadic `coalesce()` and `concat()`, as well as optional arguments for other functions.
class VariadicCallRewriter : IdentityTransformer
{
static readonly VariadicCallRewriter Instance = new VariadicCallRewriter();
Expand All @@ -30,19 +30,11 @@ public static Expression Rewrite(Expression expression)

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

if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce))
if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce) ||
Operators.SameOperator(call.OperatorName, Operators.OpConcat))
{
if (call.Operands.Length == 0)
return CallUndefined();
return new CallExpression(false, Operators.OpUndefined);
if (call.Operands.Length == 1)
return Transform(call.Operands.Single());
if (call.Operands.Length > 2)
Expand All @@ -53,18 +45,7 @@ protected override Expression Transform(CallExpression call)
}
}

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

return base.Transform(call);
}

static CallExpression CallUndefined()
{
return new CallExpression(false, Operators.OpUndefined);
}
}
}
1 change: 1 addition & 0 deletions src/Serilog.Expressions/Expressions/Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ static class Operators
// RuntimeOp* means runtime only.

public const string OpCoalesce = "Coalesce";
public const string OpConcat = "Concat";
public const string OpContains = "Contains";
public const string OpElementAt = "ElementAt";
public const string OpEndsWith = "EndsWith";
Expand Down
Loading