Skip to content

Commit 1390311

Browse files
authored
Merge pull request #50 from serilog/dev
3.2.0 Release
2 parents 63fe8de + 9cf0915 commit 1390311

21 files changed

+169
-151
lines changed

Directory.Build.props

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project>
3+
<PropertyGroup>
4+
<Nullable>enable</Nullable>
5+
<LangVersion>latest</LangVersion>
6+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
7+
<AssemblyOriginatorKeyFile>../../assets/Serilog.snk</AssemblyOriginatorKeyFile>
8+
<SignAssembly>true</SignAssembly>
9+
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
10+
</PropertyGroup>
11+
</Project>

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ The built-in properties mirror those available in the CLEF format.
147147
| Array | An array of values, in square brackets | `[1, 'two', null]` |
148148
| 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}` |
149149

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

153153
### Operators and conditionals
@@ -184,7 +184,8 @@ calling a function will be undefined if:
184184

185185
| Function | Description |
186186
| :--- | :--- |
187-
| `Coalesce(p0, p1, ..pN)` | Returns the first defined, non-null argument. |
187+
| `Coalesce(p0, p1, [..pN])` | Returns the first defined, non-null argument. |
188+
| `Concat(s0, s1, [..sN])` | Concatenate two or more strings. |
188189
| `Contains(s, t)` | Tests whether the string `s` contains the substring `t`. |
189190
| `ElementAt(x, i)` | Retrieves a property of `x` by name `i`, or array element of `x` by numeric index `i`. |
190191
| `EndsWith(s, t)` | Tests whether the string `s` ends with substring `t`. |
@@ -195,7 +196,7 @@ calling a function will be undefined if:
195196
| `LastIndexOf(s, t)` | Returns the last index of substring `t` in string `s`, or -1 if the substring does not appear. |
196197
| `Length(x)` | Returns the length of a string or array. |
197198
| `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. |
199+
| `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. |
199200
| `Round(n, m)` | Round the number `n` to `m` decimal places. |
200201
| `StartsWith(s, t)` | Tests whether the string `s` starts with substring `t`. |
201202
| `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. |
@@ -352,7 +353,7 @@ convert the result to plain-old-.NET-types like `string`, `bool`, `Dictionary<K,
352353
User-defined functions can be plugged in by implementing static methods that:
353354

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

serilog-expressions.sln

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.26430.15
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31410.223
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{91E482DE-E1E7-4CE1-9511-C0AF07F3648A}"
77
EndProject
@@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "global", "global", "{24B112
1111
.gitignore = .gitignore
1212
appveyor.yml = appveyor.yml
1313
Build.ps1 = Build.ps1
14+
Directory.Build.props = Directory.Build.props
1415
LICENSE = LICENSE
1516
README.md = README.md
1617
RunPerfTests.ps1 = RunPerfTests.ps1
@@ -60,4 +61,7 @@ Global
6061
{3C2D8E01-5580-426A-BDD9-EC59CD98E618} = {B03B3086-D197-4B32-9AE2-8536C345EA2D}
6162
{D7A37F73-BBA3-4DAE-9648-1A753A86F968} = {B03B3086-D197-4B32-9AE2-8536C345EA2D}
6263
EndGlobalSection
64+
GlobalSection(ExtensibilityGlobals) = postSolution
65+
SolutionGuid = {EB6672D6-318E-493E-8B60-77F5A7A90E66}
66+
EndGlobalSection
6367
EndGlobal

src/Serilog.Expressions/Compatibility/NotNullAttributes.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
using System.Text.RegularExpressions;
2121
using Serilog.Events;
2222
using Serilog.Expressions.Runtime;
23-
using Serilog.Formatting.Display;
2423
using Serilog.Parsing;
2524
using Serilog.Templates.Compilation;
2625

@@ -32,7 +31,7 @@ static class Intrinsics
3231
{
3332
static readonly LogEventPropertyValue NegativeOne = new ScalarValue(-1);
3433
static readonly LogEventPropertyValue Tombstone = new ScalarValue("😬 (if you see this you have found a bug.)");
35-
34+
3635
public static List<LogEventPropertyValue?> CollectSequenceElements(LogEventPropertyValue?[] elements)
3736
{
3837
return elements.ToList();
@@ -53,18 +52,18 @@ static class Intrinsics
5352
if (content is SequenceValue sequence)
5453
foreach (var element in sequence.Elements)
5554
elements.Add(element);
56-
55+
5756
return elements;
5857
}
5958

6059
public static LogEventPropertyValue ConstructSequenceValue(List<LogEventPropertyValue?> elements)
6160
{
6261
if (elements.Any(el => el == null))
6362
return new SequenceValue(elements.Where(el => el != null));
64-
63+
6564
return new SequenceValue(elements);
6665
}
67-
66+
6867
public static List<LogEventProperty> CollectStructureProperties(string[] names, LogEventPropertyValue?[] values)
6968
{
7069
var properties = new List<LogEventProperty>();
@@ -85,7 +84,7 @@ public static LogEventPropertyValue ConstructStructureValue(List<LogEventPropert
8584

8685
return new StructureValue(properties);
8786
}
88-
87+
8988
public static List<LogEventProperty> ExtendStructureValueWithSpread(
9089
List<LogEventProperty> properties,
9190
LogEventPropertyValue? content)
@@ -99,7 +98,7 @@ public static List<LogEventProperty> ExtendStructureValueWithSpread(
9998

10099
return properties;
101100
}
102-
101+
103102
public static List<LogEventProperty> ExtendStructureValueWithProperty(
104103
List<LogEventProperty> properties,
105104
string name,
@@ -129,7 +128,7 @@ public static bool CoerceToScalarBoolean(LogEventPropertyValue value)
129128
return b;
130129
return false;
131130
}
132-
131+
133132
public static LogEventPropertyValue? IndexOfMatch(LogEventPropertyValue value, Regex regex)
134133
{
135134
if (value is ScalarValue scalar &&
@@ -193,9 +192,9 @@ public static string RenderMessage(CompiledMessageToken formatter, EvaluationCon
193192
if (token is PropertyToken {Format: { }} pt)
194193
{
195194
elements ??= new List<LogEventPropertyValue>();
196-
195+
197196
var space = new StringWriter();
198-
197+
199198
pt.Render(logEvent.Properties, space, formatProvider);
200199
elements.Add(new ScalarValue(space.ToString()));
201200
}

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

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
using System.Linq;
1818
using System.Linq.Expressions;
1919
using System.Reflection;
20+
using System.Runtime.InteropServices;
21+
using System.Text;
2022
using Serilog.Events;
2123
using Serilog.Expressions.Ast;
2224
using Serilog.Expressions.Compilation.Transformations;
@@ -27,6 +29,7 @@
2729
using ParameterExpression = System.Linq.Expressions.ParameterExpression;
2830
using LX = System.Linq.Expressions.Expression;
2931
using ExpressionBody = System.Linq.Expressions.Expression;
32+
// ReSharper disable UseIndexFromEndExpression
3033

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

104-
var methodParameters = m.GetParameters();
107+
var methodParameters = m.GetParameters()
108+
.Select(info => (pi: info, optional: info.GetCustomAttribute<OptionalAttribute>() != null))
109+
.ToList();
105110

106-
var parameterCount = methodParameters.Count(pi => pi.ParameterType == typeof(LogEventPropertyValue));
107-
if (parameterCount != call.Operands.Length)
108-
throw new ArgumentException($"The function `{call.OperatorName}` requires {parameterCount} arguments.");
111+
var allowedParameters = methodParameters.Where(info => info.pi.ParameterType == typeof(LogEventPropertyValue)).ToList();
112+
var requiredParameterCount = allowedParameters.Count(info => !info.optional);
113+
114+
if (call.Operands.Length < requiredParameterCount || call.Operands.Length > allowedParameters.Count)
115+
{
116+
var requirements = DescribeRequirements(allowedParameters.Select(info => (info.pi.Name!, info.optional)).ToList());
117+
throw new ArgumentException($"The function `{call.OperatorName}` {requirements}.");
118+
}
109119

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

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

119-
var boundParameters = new List<LX>(methodParameters.Length);
120-
foreach (var pi in methodParameters)
129+
var boundParameters = new List<LX>(methodParameters.Count);
130+
foreach (var (pi, optional) in methodParameters)
121131
{
122132
if (pi.ParameterType == typeof(LogEventPropertyValue))
123-
boundParameters.Add(operands.Dequeue());
133+
{
134+
boundParameters.Add(operands.Count > 0
135+
? operands.Dequeue()
136+
: LX.Constant(null, typeof(LogEventPropertyValue)));
137+
}
124138
else if (pi.ParameterType == typeof(StringComparison))
125139
boundParameters.Add(LX.Constant(call.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal));
126140
else if (pi.ParameterType == typeof(IFormatProvider))
@@ -129,13 +143,38 @@ protected override ExpressionBody Transform(CallExpression call)
129143
boundParameters.Add(LX.Property(Context, EvaluationContextLogEventProperty));
130144
else if (_nameResolver.TryBindFunctionParameter(pi, out var binding))
131145
boundParameters.Add(LX.Constant(binding, pi.ParameterType));
146+
else if (optional)
147+
boundParameters.Add(LX.Constant(
148+
pi.GetCustomAttribute<DefaultParameterValueAttribute>()?.Value, pi.ParameterType));
132149
else
133-
throw new ArgumentException($"The method `{m.Name}` implementing function `{call.OperatorName}` has parameter `{pi.Name}` which could not be bound.");
150+
throw new ArgumentException($"The method `{m.Name}` implementing function `{call.OperatorName}` has argument `{pi.Name}` which could not be bound.");
134151
}
135152

136153
return LX.Call(m, boundParameters);
137154
}
138155

156+
static string DescribeRequirements(IReadOnlyList<(string name, bool optional)> parameters)
157+
{
158+
static string DescribeArgument((string name, bool optional) p) =>
159+
$"`{p.name}`" + (p.optional ? " (optional)" : "");
160+
161+
if (parameters.Count == 0)
162+
return "accepts no arguments";
163+
164+
if (parameters.Count == 1)
165+
return $"accepts one argument, {DescribeArgument(parameters[0])}";
166+
167+
if (parameters.Count == 2)
168+
return $"accepts two arguments, {DescribeArgument(parameters[0])} and {DescribeArgument(parameters[1])}";
169+
170+
var result = new StringBuilder("accepts arguments");
171+
for (var i = 0; i < parameters.Count - 1; ++i)
172+
result.Append($" {DescribeArgument(parameters[i])},");
173+
174+
result.Append($" and {DescribeArgument(parameters[parameters.Count - 1])}");
175+
return result.ToString();
176+
}
177+
139178
static ExpressionBody CompileLogical(Func<ExpressionBody, ExpressionBody, ExpressionBody> apply, ExpressionBody lhs, ExpressionBody rhs)
140179
{
141180
return LX.Convert(

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

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

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

3131
protected override Expression Transform(CallExpression call)
3232
{
33-
if (Operators.SameOperator(call.OperatorName, Operators.OpSubstring) && call.Operands.Length == 2)
34-
{
35-
var operands = call.Operands
36-
.Select(Transform)
37-
.Concat(new[] {CallUndefined()})
38-
.ToArray();
39-
return new CallExpression(call.IgnoreCase, call.OperatorName, operands);
40-
}
41-
42-
if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce))
33+
if (Operators.SameOperator(call.OperatorName, Operators.OpCoalesce) ||
34+
Operators.SameOperator(call.OperatorName, Operators.OpConcat))
4335
{
4436
if (call.Operands.Length == 0)
45-
return CallUndefined();
37+
return new CallExpression(false, Operators.OpUndefined);
4638
if (call.Operands.Length == 1)
4739
return Transform(call.Operands.Single());
4840
if (call.Operands.Length > 2)
@@ -53,18 +45,7 @@ protected override Expression Transform(CallExpression call)
5345
}
5446
}
5547

56-
if (Operators.SameOperator(call.OperatorName, Operators.OpToString) &&
57-
call.Operands.Length == 1)
58-
{
59-
return new CallExpression(call.IgnoreCase, call.OperatorName, call.Operands[0], CallUndefined());
60-
}
61-
6248
return base.Transform(call);
6349
}
64-
65-
static CallExpression CallUndefined()
66-
{
67-
return new CallExpression(false, Operators.OpUndefined);
68-
}
6950
}
7051
}

src/Serilog.Expressions/Expressions/Operators.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ static class Operators
2929
// RuntimeOp* means runtime only.
3030

3131
public const string OpCoalesce = "Coalesce";
32+
public const string OpConcat = "Concat";
3233
public const string OpContains = "Contains";
3334
public const string OpElementAt = "ElementAt";
3435
public const string OpEndsWith = "EndsWith";

0 commit comments

Comments
 (0)