Skip to content

Commit 09baab9

Browse files
authored
Merge pull request #281 from nblumhardt/switch-to-serilog-expressions
Switch seqcli ingest filtering to use Serilog.Expressions; fix filters over non-Serilog level names
2 parents cbcab86 + 8735dd0 commit 09baab9

File tree

10 files changed

+86
-23
lines changed

10 files changed

+86
-23
lines changed

src/SeqCli/Api/ApiConstants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace SeqCli.Api;
1616

1717
static class ApiConstants
1818
{
19-
public const string ClefMediatType = "application/vnd.serilog.clef";
19+
public const string ClefMediaType = "application/vnd.serilog.clef";
2020
public const string IngestionEndpoint = "api/events/raw";
2121
public const string ApiKeyHeaderName = "X-Seq-ApiKey";
2222
}

src/SeqCli/Cli/Commands/IngestCommand.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
using Serilog;
2424
using Serilog.Core;
2525
using Serilog.Events;
26-
using Serilog.Filters.Expressions;
26+
using Serilog.Expressions;
2727

2828
namespace SeqCli.Cli.Commands;
2929

@@ -57,7 +57,7 @@ public IngestCommand(SeqConnectionFactory connectionFactory)
5757

5858
Options.Add("json",
5959
"Read the events as JSON (the default assumes plain text)",
60-
v => _json = true);
60+
_ => _json = true);
6161

6262
Options.Add("f=|filter=",
6363
"Filter expression to select a subset of events",
@@ -92,9 +92,12 @@ protected override async Task<int> Run()
9292
Func<LogEvent, bool>? filter = null;
9393
if (_filter != null)
9494
{
95-
var expr = _filter.Replace("@Level", SurrogateLevelProperty.PropertyName);
96-
var eval = FilterLanguage.CreateFilter(expr);
97-
filter = evt => true.Equals(eval(evt));
95+
// Support non-Serilog level names (`@l` can't be overridden by the name resolver). At a later date,
96+
// we hope to be able to use the Serilog.Expressions AST to do this reliably (at the moment, all occurrences
97+
// of @l, whether referring to the property or not, will be replaced).
98+
var expr = _filter.Replace("@l", "@Level").Replace("@Level", $"coalesce(@p['{SurrogateLevelProperty.PropertyName}'],@l)");
99+
var eval = SerilogExpression.Compile(expr, nameResolver: new SeqBuiltInNameResolver());
100+
filter = evt => ExpressionResult.IsTrue(eval(evt));
98101
}
99102

100103
var connection = _connectionFactory.Connect(_connection);

src/SeqCli/Cli/Commands/LogCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ protected override async Task<int> Run()
9999
payload.WriteTo(jsonWriter);
100100
jsonWriter.Flush();
101101
builder.WriteLine();
102-
content = new StringContent(builder.ToString(), Encoding.UTF8, ApiConstants.ClefMediatType);
102+
content = new StringContent(builder.ToString(), Encoding.UTF8, ApiConstants.ClefMediaType);
103103
}
104104

105105
var connection = _connectionFactory.Connect(_connection);

src/SeqCli/Cli/Features/OutputFormatFeature.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using Newtonsoft.Json.Converters;
2020
using Newtonsoft.Json.Linq;
2121
using Seq.Api.Model;
22-
using Seq.Api.Model.AppInstances;
2322
using SeqCli.Config;
2423
using SeqCli.Csv;
2524
using SeqCli.Levels;
@@ -49,7 +48,7 @@ public OutputFormatFeature(SeqCliOutputConfig outputConfig)
4948

5049
public bool Json => _json;
5150

52-
bool ApplyThemeToRedirectedOutput => _noColor == false && _forceColor == true;
51+
bool ApplyThemeToRedirectedOutput => _noColor == false && _forceColor;
5352

5453
ConsoleTheme Theme
5554
=> _noColor ? ConsoleTheme.None
@@ -61,13 +60,13 @@ public override void Enable(OptionSet options)
6160
options.Add(
6261
"json",
6362
"Print output in newline-delimited JSON (the default is plain text)",
64-
v => _json = true);
63+
_ => _json = true);
6564

66-
options.Add("no-color", "Don't colorize text output", v => _noColor = true);
65+
options.Add("no-color", "Don't colorize text output", _ => _noColor = true);
6766

6867
options.Add("force-color",
6968
"Force redirected output to have ANSI color (unless `--no-color` is also specified)",
70-
v => _forceColor = true);
69+
_ => _forceColor = true);
7170
}
7271

7372
public Logger CreateOutputLogger()

src/SeqCli/Ingestion/LogShipper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ static async Task<bool> SendBatchAsync(
159159
foreach (var evt in batch)
160160
Formatter.Format(evt, builder);
161161

162-
content = new StringContent(builder.ToString(), Encoding.UTF8, ApiConstants.ClefMediatType);
162+
content = new StringContent(builder.ToString(), Encoding.UTF8, ApiConstants.ClefMediaType);
163163
}
164164

165165
var request = new HttpRequestMessage(HttpMethod.Post, ApiConstants.IngestionEndpoint) { Content = content };
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright © Datalust Pty Ltd
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.Diagnostics.CodeAnalysis;
16+
using Serilog.Expressions;
17+
18+
#nullable enable
19+
20+
namespace SeqCli.Ingestion;
21+
22+
/// <summary>
23+
/// Extends Serilog.Expressions with support for commonly-used Seq property names.
24+
/// </summary>
25+
/// <remarks>SeqCli ingestion filters previously used Serilog.Filters.Expressions, so this type ensures most
26+
/// older filters will continue to work.</remarks>
27+
public class SeqBuiltInNameResolver: NameResolver
28+
{
29+
public override bool TryResolveBuiltInPropertyName(string alias, [NotNullWhen(true)] out string? target)
30+
{
31+
switch (alias)
32+
{
33+
case "@Properties":
34+
target = "@p";
35+
return true;
36+
case "@Timestamp":
37+
target = "@t";
38+
return true;
39+
case "@Level":
40+
target = "@l";
41+
return true;
42+
case "@Message":
43+
target = "@m";
44+
return true;
45+
case "@MessageTemplate":
46+
target = "@mt";
47+
return true;
48+
case "@EventType":
49+
target = "@i";
50+
return true;
51+
case "@Exception":
52+
target = "@x";
53+
return true;
54+
default:
55+
target = null;
56+
return false;
57+
}
58+
}
59+
}

src/SeqCli/PlainText/Extraction/ExtractionPatternInterpreter.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Linq;
43
using SeqCli.PlainText.Patterns;
54

src/SeqCli/PlainText/LogEvents/LogEventBuilder.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ public static LogEvent FromProperties(IDictionary<string, object?> properties, s
3434
var messageTemplate = GetMessageTemplate(properties);
3535
var props = GetLogEventProperties(properties, remainder, level);
3636

37+
var fallbackMappedLevel = level != null ? LevelMapping.ToSerilogLevel(level) : LogEventLevel.Information;
38+
3739
return new LogEvent(
3840
timestamp,
39-
LogEventLevel.Information,
41+
fallbackMappedLevel,
4042
exception,
4143
messageTemplate,
4244
props);
@@ -56,12 +58,13 @@ static MessageTemplate GetMessageTemplate(IDictionary<string, object?> propertie
5658
return NoMessage;
5759
}
5860

59-
static string GetLevel(IDictionary<string, object?> properties)
61+
static string? GetLevel(IDictionary<string, object?> properties)
6062
{
6163
if (properties.TryGetValue(ReifiedProperties.Level, out var l) &&
6264
l is TextSpan ts)
6365
return ts.ToStringValue();
64-
return LogEventLevel.Information.ToString();
66+
67+
return null;
6568
}
6669

6770
static Exception? TryGetException(IDictionary<string, object?> properties)
@@ -72,7 +75,7 @@ static string GetLevel(IDictionary<string, object?> properties)
7275
return null;
7376
}
7477

75-
static IEnumerable<LogEventProperty> GetLogEventProperties(IDictionary<string, object?> properties, string? remainder, string level)
78+
static IEnumerable<LogEventProperty> GetLogEventProperties(IDictionary<string, object?> properties, string? remainder, string? level)
7679
{
7780
var payload = properties
7881
.Where(p => !ReifiedProperties.IsReifiedProperty(p.Key))

src/SeqCli/SeqCli.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<PackageReference Include="Destructurama.JsonNet" Version="2.0.0" />
3030
<PackageReference Include="newtonsoft.json" Version="13.0.2" />
3131
<PackageReference Include="Serilog" Version="2.12.0" />
32-
<PackageReference Include="serilog.filters.expressions" Version="2.1.0" />
32+
<PackageReference Include="serilog.expressions" Version="3.4.1" />
3333
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
3434
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="2.0.0" />
3535
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />

test/SeqCli.Tests/PlainText/LogEventBuilderTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ public void SuppliedValuesAreUsed()
2929

3030
Assert.Equal("2018-02-01T13:00:00.1230000+00:00", evt.Timestamp.ToString("o"));
3131
Assert.Equal("Hello, world", evt.RenderMessage());
32-
Assert.Equal(LogEventLevel.Information, evt.Level);
32+
Assert.Equal(LogEventLevel.Warning, evt.Level);
3333
Assert.Equal("WRN", ((ScalarValue)evt.Properties[SurrogateLevelProperty.PropertyName]).Value);
34-
Assert.Equal("EverythingFailedException", evt.Exception.ToString());
34+
Assert.Equal("EverythingFailedException", evt.Exception?.ToString());
3535
Assert.Equal(42, ((ScalarValue)evt.Properties["Count"]).Value);
36-
Assert.Equal("TP", ((ScalarValue)evt.Properties["MachineName"]).Value.ToString());
37-
Assert.Equal("rem", ((ScalarValue)evt.Properties["@unmatched"]).Value.ToString());
36+
Assert.Equal("TP", ((ScalarValue)evt.Properties["MachineName"]).Value!.ToString());
37+
Assert.Equal("rem", ((ScalarValue)evt.Properties["@unmatched"]).Value!.ToString());
3838
}
3939

4040
[Fact]

0 commit comments

Comments
 (0)