Skip to content

Commit 1560831

Browse files
authored
Merge pull request #30 from nblumhardt/tracing-functions
Improve `Seq.Syntax` templating compatibility with trace spans
2 parents fa1d353 + c3e6ba9 commit 1560831

File tree

14 files changed

+117
-16
lines changed

14 files changed

+117
-16
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<VersionPrefix>1.0.1</VersionPrefix>
3+
<VersionPrefix>1.1.0</VersionPrefix>
44
</PropertyGroup>
55
</Project>

src/Seq.Syntax/BuiltIns/SeqBuiltInPropertyNameResolver.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ public override bool TryResolveBuiltInPropertyName(string alias, [NotNullWhen(tr
2424
"TraceId" or "tr" => "@p['@tr']",
2525
"SpanId" or "sp" => "@p['@sp']",
2626
"Resource" or "ra" => "@p['@ra']",
27-
"Start" or "st" => "@p['@st']",
27+
"Start" or "st" => "_AsDateTimeOffset(@p['@st'])",
2828
"ParentId" or "ps" => "@p['@ps']",
29+
"SpanKind" or "sk" => "@p['@sk']",
2930
"Scope" or "sa" => "@p['@sa']",
3031
"Elapsed" => "_Elapsed(@st, @t)",
3132
"Arrived" or "Document" or "Data" => "undefined()",
@@ -37,7 +38,7 @@ public override bool TryResolveBuiltInPropertyName(string alias, [NotNullWhen(tr
3738

3839
public override bool TryResolveFunctionName(string name, [NotNullWhen(true)] out MethodInfo? implementation)
3940
{
40-
if (name == nameof(_Elapsed))
41+
if (name == nameof(_Elapsed) || name == nameof(_AsDateTimeOffset))
4142
{
4243
implementation = typeof(SeqBuiltInPropertyNameResolver).GetMethod(name)!;
4344
return true;
@@ -55,6 +56,14 @@ public override bool TryResolveFunctionName(string name, [NotNullWhen(true)] out
5556
return null;
5657
}
5758

59+
public static LogEventPropertyValue? _AsDateTimeOffset(LogEventPropertyValue? value)
60+
{
61+
if (AsDateTimeOffset(value) is { } dto)
62+
return new ScalarValue(dto);
63+
64+
return null;
65+
}
66+
5867
static DateTimeOffset? AsDateTimeOffset(LogEventPropertyValue? value)
5968
{
6069
if (value is ScalarValue { Value: DateTime dt })

src/Seq.Syntax/Expressions/Compilation/Linq/LinqExpressionCompiler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ protected override ExpressionBody Transform(CallExpression call)
104104
if (!_nameResolver.TryResolveFunctionName(call.OperatorName, out var m))
105105
throw new ArgumentException($"The function name `{call.OperatorName}` was not recognized.");
106106

107+
if (m == null!)
108+
throw new InvalidOperationException(
109+
$"The name resolver {_nameResolver} failed to return a valid `MethodInfo` for function `{call.OperatorName}`.");
110+
107111
var methodParameters = m.GetParameters()
108112
.Select(info => (pi: info, optional: info.GetCustomAttribute<OptionalAttribute>() != null))
109113
.ToList();

src/Seq.Syntax/Expressions/Operators.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616
using System.Collections.Generic;
1717
using Seq.Syntax.Expressions.Ast;
1818

19-
// ReSharper disable UnusedMember.Global
20-
21-
// ReSharper disable InconsistentNaming, MemberCanBePrivate.Global
19+
// ReSharper disable UnusedMember.Global, InconsistentNaming, MemberCanBePrivate.Global
2220

2321
namespace Seq.Syntax.Expressions;
2422

@@ -37,8 +35,10 @@ static class Operators
3735
public const string OpEndsWith = "EndsWith";
3836
public const string OpIndexOf = "IndexOf";
3937
public const string OpIndexOfMatch = "IndexOfMatch";
40-
public const string OpIsMatch = "IsMatch";
4138
public const string OpIsDefined = "IsDefined";
39+
public const string OpIsMatch = "IsMatch";
40+
public const string OpIsRootSpan = "IsRootSpan";
41+
public const string OpIsSpan = "IsSpan";
4242
public const string OpLastIndexOf = "LastIndexOf";
4343
public const string OpLength = "Length";
4444
public const string OpNow = "Now";
@@ -49,6 +49,7 @@ static class Operators
4949
public const string OpToLower = "ToLower";
5050
public const string OpToUpper = "ToUpper";
5151
public const string OpToString = "ToString";
52+
public const string OpTotalMilliseconds = "TotalMilliseconds";
5253
public const string OpTypeOf = "TypeOf";
5354
public const string OpUndefined = "Undefined";
5455
public const string OpUriEncode = "UriEncode";

src/Seq.Syntax/Expressions/Runtime/RuntimeOperators.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ static class RuntimeOperators
2929
static readonly LogEventPropertyValue ConstantTrue = new ScalarValue(true),
3030
ConstantFalse = new ScalarValue(false);
3131

32+
const string SpanStartTimestampPropertyName = "@st", ParentSpanIdPropertyName = "@ps";
33+
3234
internal static LogEventPropertyValue ScalarBoolean(bool value)
3335
{
3436
return value ? ConstantTrue : ConstantFalse;
@@ -505,6 +507,10 @@ public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? v
505507

506508
var toString = sv.Value switch
507509
{
510+
bool boolean => boolean ? "true" : "false",
511+
DateTimeOffset dto when dto.Offset == TimeSpan.Zero && fmt == null => dto.UtcDateTime.ToString("O"),
512+
DateTimeOffset dto when fmt == null => dto.ToString("O"),
513+
DateTime dt when fmt == null => dt.ToString("O"),
508514
LogEventLevel level => LevelRenderer.GetLevelMoniker(level, fmt),
509515
IFormattable formattable => formattable.ToString(fmt, formatProvider),
510516
_ => sv.Value.ToString()
@@ -557,4 +563,39 @@ public static LogEventPropertyValue _Internal_IsNotNull(LogEventPropertyValue? v
557563

558564
return null;
559565
}
566+
567+
public static LogEventPropertyValue? IsSpan(LogEvent logEvent)
568+
{
569+
return new ScalarValue(logEvent is { TraceId: not null, SpanId: not null } &&
570+
logEvent.Properties.ContainsKey(SpanStartTimestampPropertyName));
571+
}
572+
573+
public static LogEventPropertyValue? IsRootSpan(LogEvent logEvent)
574+
{
575+
return new ScalarValue(logEvent is { TraceId: not null, SpanId: not null } &&
576+
logEvent.Properties.ContainsKey(SpanStartTimestampPropertyName) &&
577+
!logEvent.Properties.ContainsKey(ParentSpanIdPropertyName));
578+
}
579+
580+
public static LogEventPropertyValue? FromUnixEpoch(LogEventPropertyValue? dateTime)
581+
{
582+
if (dateTime is ScalarValue sv)
583+
{
584+
if (sv.Value is DateTimeOffset dto)
585+
return new ScalarValue(dto.UtcDateTime - DateTime.UnixEpoch);
586+
587+
if (sv.Value is DateTime dt)
588+
return new ScalarValue(dt.ToUniversalTime() - DateTime.UnixEpoch);
589+
}
590+
591+
return null;
592+
}
593+
594+
public static LogEventPropertyValue? TotalMilliseconds(LogEventPropertyValue? timeSpan)
595+
{
596+
if (timeSpan is ScalarValue { Value: TimeSpan ts })
597+
return new ScalarValue(ts.Ticks / (decimal)TimeSpan.TicksPerMillisecond);
598+
599+
return null;
600+
}
560601
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"@t":"2025-09-30T00:56:42.4868884Z","@mt":"Run demo app","@m":"Run demo app","@i":"6047df22","@tr":"f42777ef7fcfa457aa7126d57da507fe","@sp":"a6c09c6e0fa0d607","@st":"2025-09-30T00:56:41.4493934Z","@sk":"Internal","Application":"Demo"}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.IO;
2+
using System.Linq;
3+
using Seq.Syntax.Templates;
4+
using Seq.Syntax.Tests.Support;
5+
using Serilog.Formatting.Compact.Reader;
6+
using Xunit;
7+
8+
namespace Seq.Syntax.Tests.Expressions;
9+
10+
public class AppHostCompatibilityTests
11+
{
12+
[Theory]
13+
[InlineData("TotalMilliseconds(@Elapsed)", "1037.495")]
14+
[InlineData("Round(TotalMilliseconds(@Elapsed), 0)", "1037")]
15+
[InlineData("IsSpan()", "true")]
16+
[InlineData("IsRootSpan()", "true")]
17+
[InlineData("@SpanKind", "Internal")]
18+
[InlineData("@Start", "2025-09-30T00:56:41.4493934Z")]
19+
[InlineData("FromUnixEpoch(@Start)", "20361.00:56:41.4493934")]
20+
[InlineData("TotalMilliseconds(FromUnixEpoch(@Start))", "1759193801449.3934")]
21+
[InlineData("FromUnixEpoch(@Timestamp)", "20361.00:56:42.4868884")]
22+
[InlineData("@EventType", "1615322914")]
23+
public void AppHostJsonProcessingIsReasonable(string expression, string expected)
24+
{
25+
var json = TestCases.ReadNDJsonCases("app-host-compatibility-case.json").Single();
26+
var evt = LogEventReader.ReadFromString(json);
27+
var template = $"{{ {expression} }}";
28+
var output = new StringWriter();
29+
new ExpressionTemplate(template).Format(evt, output);
30+
Assert.Equal(expected, output.ToString());
31+
}
32+
}

test/Seq.Syntax.Tests/Expressions/ExpressionEvaluationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Seq.Syntax.Tests.Expressions;
1313
public class ExpressionEvaluationTests
1414
{
1515
public static IEnumerable<object[]> ExpressionEvaluationCases =>
16-
AsvCases.ReadCases("expression-evaluation-cases.asv");
16+
TestCases.ReadAsvCases("expression-evaluation-cases.asv");
1717

1818
[Theory]
1919
[MemberData(nameof(ExpressionEvaluationCases))]

test/Seq.Syntax.Tests/Expressions/ExpressionTranslationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Seq.Syntax.Tests.Expressions;
99
public class ExpressionTranslationTests
1010
{
1111
public static IEnumerable<object[]> ExpressionEvaluationCases =>
12-
AsvCases.ReadCases("translation-cases.asv");
12+
TestCases.ReadAsvCases("translation-cases.asv");
1313

1414
[Theory]
1515
[MemberData(nameof(ExpressionEvaluationCases))]

test/Seq.Syntax.Tests/Seq.Syntax.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
</PackageReference>
1313
<PackageReference Include="xunit" Version="2.8.1" />
1414
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
15+
<PackageReference Include="Serilog.Formatting.Compact.Reader" Version="4.0.0" />
1516
</ItemGroup>
1617
<ItemGroup>
1718
<ProjectReference Include="..\..\src\Seq.Syntax\Seq.Syntax.csproj" />
@@ -20,5 +21,8 @@
2021
<Content Include="Cases/*.asv">
2122
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2223
</Content>
24+
<Content Include="Cases/*.json">
25+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
26+
</Content>
2327
</ItemGroup>
2428
</Project>

0 commit comments

Comments
 (0)