Skip to content

Commit 608d925

Browse files
authored
AOT Annotations and CI smoke tests for AOT publishes (#529)
Adds AOT annotations for all published projects and ensures they are advertised as AOT compatible. Create a smoke test binary that writes to an in memory elasticsearch endpoint for our NLog and Extensions loggers. Serilog is currently failing with: ``` ecs-aot-smoketest failed with 2 error(s) (58.2s) → examples/ecs-aot-smoketest/bin/release/net9.0/osx-arm64/ecs-aot-smoketest.dll ILC : Trim analysis error IL2067: Serilog.Capturing.PropertyValueConverter.TryConvertStructure(Object,Type,Destructuring,StructureValue&): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'Serilog.Capturing.PropertyValueConverter.CreateStructureValue(Object,Type,Boolean)'. The parameter 'type' of method 'Serilog.Capturing.PropertyValueConverter.TryConvertStructure(Object,Type,Destructuring,StructureValue&)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. /Users/mpdreamz/.nuget/packages/microsoft.dotnet.ilcompiler/9.0.7/build/Microsoft.NETCore.Native.targets(317,5): error MSB3073: The command ""/Users/mpdreamz/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/9.0.7/tools/ilc" @"obj/release/net9.0/osx-arm64/native/ecs-aot-smoketest.ilc.rsp"" exited with code -1. ``` Will create an issue with Serilog to see if it can be resolved. This removes https://github.com/benaadams/Ben.Demystifier as its not AOT compatible. AOT compatibility beats nicer stacktrace strings as a feature to keep supporting IMO. This bumps NLog to 6.0.0 to take benefit of its work to be AOT compatible (cc @snakefoot)
1 parent 4a96d68 commit 608d925

File tree

39 files changed

+404
-54
lines changed

39 files changed

+404
-54
lines changed

.github/workflows/test.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,31 @@ jobs:
9292
with:
9393
name: test-results-linux
9494
path: build/output/junit-*.xml
95+
96+
aot-validate:
97+
runs-on: ${{ matrix.os.runner }}
98+
strategy:
99+
fail-fast: false
100+
matrix:
101+
os:
102+
- runner: ubuntu-latest
103+
folder: linux-x64
104+
binary: ecs-aot-smoketest
105+
- runner: macos-latest
106+
folder: osx-arm64
107+
binary: ecs-aot-smoketest
108+
- runner: windows-latest
109+
folder: win-x64
110+
binary: ecs-aot-smoketest.exe
111+
steps:
112+
- uses: actions/checkout@v4
113+
114+
- uses: actions/setup-dotnet@v4
115+
with:
116+
global-json-file: ./global.json
117+
118+
- name: Publish AOT
119+
run: dotnet publish examples/ecs-aot-smoketest
120+
121+
- name: Invoke AOT
122+
run: ./examples/ecs-aot-smoketest/bin/Release/net9.0/${{ matrix.os.folder }}/publish/${{ matrix.os.binary }}

dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"rollForward": false
2525
},
2626
"nupkg-validator": {
27-
"version": "0.6.0",
27+
"version": "0.7.0",
2828
"commands": [
2929
"nupkg-validator"
3030
],

ecs-dotnet.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Serilog.Sinks.Tests
141141
EndProject
142142
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elastic.Serilog.Enrichers.Web", "src\Elastic.Serilog.Enrichers.Web\Elastic.Serilog.Enrichers.Web.csproj", "{B6DCC4C4-1287-41BE-A19D-8F311C6E39F7}"
143143
EndProject
144+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ecs-aot-smoketest", "examples\ecs-aot-smoketest\ecs-aot-smoketest.csproj", "{46706BAE-BBCD-4DD9-ADBD-AC099C770854}"
145+
EndProject
144146
Global
145147
GlobalSection(SolutionConfigurationPlatforms) = preSolution
146148
Debug|Any CPU = Debug|Any CPU
@@ -311,6 +313,10 @@ Global
311313
{B6DCC4C4-1287-41BE-A19D-8F311C6E39F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
312314
{B6DCC4C4-1287-41BE-A19D-8F311C6E39F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
313315
{B6DCC4C4-1287-41BE-A19D-8F311C6E39F7}.Release|Any CPU.Build.0 = Release|Any CPU
316+
{46706BAE-BBCD-4DD9-ADBD-AC099C770854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
317+
{46706BAE-BBCD-4DD9-ADBD-AC099C770854}.Debug|Any CPU.Build.0 = Debug|Any CPU
318+
{46706BAE-BBCD-4DD9-ADBD-AC099C770854}.Release|Any CPU.ActiveCfg = Release|Any CPU
319+
{46706BAE-BBCD-4DD9-ADBD-AC099C770854}.Release|Any CPU.Build.0 = Release|Any CPU
314320
EndGlobalSection
315321
GlobalSection(SolutionProperties) = preSolution
316322
HideSolutionNode = FALSE
@@ -358,6 +364,7 @@ Global
358364
{5EDF109F-9DFF-4957-8864-BA2702FB78F6} = {7610B796-BB3E-4CB2-8296-79BBFF6D23FC}
359365
{933FD923-A2DC-49E3-B21E-8BA888DB5924} = {3582B07D-C2B0-49CC-B676-EAF806EB010E}
360366
{B6DCC4C4-1287-41BE-A19D-8F311C6E39F7} = {7610B796-BB3E-4CB2-8296-79BBFF6D23FC}
367+
{46706BAE-BBCD-4DD9-ADBD-AC099C770854} = {05075402-8669-45BD-913A-BD40A29BBEAB}
361368
EndGlobalSection
362369
GlobalSection(ExtensibilityGlobals) = postSolution
363370
SolutionGuid = {7F60C4BB-6216-4E50-B1E4-9C38EB484843}

examples/Elastic.Extensions.Logging.Console.Example/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Microsoft.Extensions.Configuration;
77
using Microsoft.Extensions.DependencyInjection;
88
using Microsoft.Extensions.Hosting;
9+
using Microsoft.Extensions.Logging;
910
using Host = Microsoft.Extensions.Hosting.Host;
1011

1112
await Host.CreateDefaultBuilder(args)
@@ -24,4 +25,3 @@ await Host.CreateDefaultBuilder(args)
2425
})
2526
.Build()
2627
.RunAsync();
27-

examples/Elastic.Serilog.Sinks.Example/Elastic.Serilog.Sinks.Example.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Elastic.Apm" Version="1.22.0" />
12-
<PackageReference Include="Serilog" Version="4.1.0" />
12+
<PackageReference Include="Serilog" Version="4.3.0" />
1313
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
1414
<PackageReference Include="Elastic.Elasticsearch.Ephemeral" Version="0.6.0" />
1515
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.0.4" />

examples/aspnetcore-with-serilog/AspnetCoreExample.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<ItemGroup>
88
<PackageReference Include="Elastic.Apm" Version="1.22.0" />
99
<PackageReference Include="Elastic.Apm.AspNetCore" Version="1.22.0" />
10-
<PackageReference Include="Serilog" Version="4.1.0" />
10+
<PackageReference Include="Serilog" Version="4.3.0" />
1111
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
1212
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
1313
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Elastic.Channels.Diagnostics;
2+
using Elastic.Extensions.Logging;
3+
using Elastic.Extensions.Logging.Options;
4+
using Elastic.Transport;
5+
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Options;
7+
8+
public class ExtensionsLogger(ITransport<ITransportConfiguration> transport)
9+
{
10+
public IDisposable CreateExtensionsLogger(
11+
out ILogger logger,
12+
out ElasticsearchLoggerProvider provider,
13+
out string @namespace,
14+
out WaitHandle waitHandle,
15+
out IChannelDiagnosticsListener listener
16+
)
17+
{
18+
@namespace = Guid.NewGuid().ToString("N").ToLowerInvariant().Substring(0, 6);
19+
var slim = new CountdownEvent(1);
20+
waitHandle = slim.WaitHandle;
21+
var s = @namespace;
22+
var options = new ConfigureOptions<ElasticsearchLoggerOptions>(o =>
23+
{
24+
o.Transport = transport;
25+
o.DataStream = new DataStreamNameOptions { Type = "x", Namespace = s, DataSet = "dotnet" };
26+
var nodes = transport.Configuration.NodePool.Nodes.Select(n => n.Uri).ToArray();
27+
o.ShipTo = new ShipToOptions { NodeUris = nodes, NodePoolType = NodePoolType.Static };
28+
});
29+
30+
var channelSetup = new IChannelSetup[]
31+
{
32+
new ChannelSetup(c =>
33+
{
34+
c.BufferOptions.WaitHandle = slim;
35+
c.BufferOptions.OutboundBufferMaxSize = 1;
36+
c.BufferOptions.OutboundBufferMaxLifetime = TimeSpan.FromSeconds(1);
37+
c.BufferOptions.ExportMaxRetries = 0;
38+
c.BufferOptions.ExportMaxConcurrency = 1;
39+
})
40+
};
41+
42+
var optionsFactory = new OptionsFactory<ElasticsearchLoggerOptions>([options], []);
43+
var optionsMonitor = new OptionsMonitor<ElasticsearchLoggerOptions>(optionsFactory, [], new OptionsCache<ElasticsearchLoggerOptions>());
44+
provider = new ElasticsearchLoggerProvider(optionsMonitor, channelSetup);
45+
var loggerFactory = new LoggerFactory( [provider], new LoggerFilterOptions { MinLevel = LogLevel.Information });
46+
logger = loggerFactory.CreateLogger<ElasticsearchLogger>();
47+
listener = provider.DiagnosticsListener!;
48+
return loggerFactory;
49+
}
50+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Elastic.Channels.Diagnostics;
2+
using Elastic.Transport;
3+
using NLog.Targets;
4+
5+
public class NLogExporter(ITransport<ITransportConfiguration> transport)
6+
{
7+
public IDisposable CreateNLogLogger(
8+
out NLog.Logger logger,
9+
out NLog.LogFactory logFactory,
10+
out string @namespace,
11+
out WaitHandle waitHandle,
12+
out IChannelDiagnosticsListener listener
13+
)
14+
{
15+
var slim = new CountdownEvent(1);
16+
waitHandle = slim.WaitHandle;
17+
@namespace = Guid.NewGuid().ToString("N").ToLowerInvariant().Substring(0, 6);
18+
19+
logFactory = new NLog.LogFactory();
20+
var logConfig = new NLog.Config.LoggingConfiguration(logFactory);
21+
var logTarget = new ElasticsearchTarget { Name = "elastic" };
22+
logTarget.RequestInvoker = transport.Configuration.RequestInvoker;
23+
logTarget.DataStreamNamespace = @namespace;
24+
logTarget.OutboundBufferMaxSize = 1;
25+
logTarget.OutboundBufferMaxLifetimeSeconds = 1;
26+
logTarget.ExportMaxRetries = 0;
27+
logTarget.ExportMaxConcurrency = 1;
28+
logTarget.ConfigureChannel = (cfg) => cfg.BufferOptions.WaitHandle = slim;
29+
30+
logTarget.DataStreamType = "x";
31+
logTarget.DataStreamSet = "dotnet";
32+
var nodesUris = string.Join(",", transport.Configuration.NodePool.Nodes.Select(n => n.Uri.ToString()).ToArray());
33+
logTarget.NodeUris = nodesUris;
34+
logTarget.NodePoolType = ElasticPoolType.Static;
35+
logConfig.AddRuleForAllLevels(logTarget);
36+
logFactory.Configuration = logConfig;
37+
listener = logTarget.DiagnosticsListener!;
38+
logger = logFactory.GetLogger("TestLogger");
39+
return logFactory;
40+
}
41+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// See https://aka.ms/new-console-template for more information
2+
3+
using Elastic.CommonSchema;
4+
using Elastic.Transport;
5+
using Microsoft.Extensions.Logging;
6+
using Log = Elastic.CommonSchema.Log;
7+
using LogLevel = NLog.LogLevel;
8+
9+
Console.WriteLine("Hello, World!");
10+
11+
var serialized = @$"{{}}";
12+
var deserialized = EcsDocument.Deserialize(serialized);
13+
if (deserialized == null) throw new Exception("deserialized is null");
14+
15+
serialized = @$"{{ ""agent"": {{ ""unknown"": ""value"" }} }}";
16+
deserialized = EcsDocument.Deserialize(serialized);
17+
if (deserialized == null) throw new Exception("deserialized is null");
18+
if (deserialized.Agent == null) throw new Exception("deserialized agent is null");
19+
20+
var d = new EcsDocument { Agent = new Agent { Name = "some-agent" }, Log = new Log { Level = "debug" } };
21+
22+
serialized = d.Serialize();
23+
if (string.IsNullOrEmpty(serialized)) throw new Exception("serialized is null");
24+
Console.WriteLine(serialized);
25+
26+
var invoker = new InMemoryRequestInvoker();
27+
var pool = new StaticNodePool([new Node(new Uri("http://localhost:9200"))]);
28+
var configuration = new TransportConfiguration(pool, invoker);
29+
var transport = new DistributedTransport(configuration);
30+
31+
var extension = new ExtensionsLogger(transport);
32+
LogInMemoryExtensionsLogger(extension);
33+
34+
var nlog = new NLogExporter(transport);
35+
LogInMemoryNLog(nlog);
36+
37+
/*
38+
var serilog = new SerilogExporter(transport);
39+
LogInMemorySerilog(serilog);
40+
41+
void LogInMemorySerilog(SerilogExporter serilogExporter)
42+
{
43+
using var logger = serilogExporter.CreateSerilogLogger(out var waitHandle, out var listener);
44+
logger.Information("an error occurred {Status}", "failure");
45+
46+
if (!waitHandle.WaitOne(TimeSpan.FromSeconds(10)))
47+
throw new Exception($"No flush occurred in 10 seconds: {listener}", listener.ObservedException);
48+
49+
if (!listener.PublishSuccess)
50+
throw new Exception("Serilog Logger did not export correctly");
51+
if (listener.ObservedException != null)
52+
throw new Exception("Serilog Logger received exception", listener.ObservedException);
53+
Console.WriteLine("Serilog Logger export success");
54+
}
55+
*/
56+
57+
void LogInMemoryExtensionsLogger(ExtensionsLogger extensionsLogger)
58+
{
59+
using var _ = extensionsLogger.CreateExtensionsLogger(out var logger, out var provider, out var @namespace, out var waitHandle, out var listener);
60+
logger.LogError("an error occurred {Status}", "failure");
61+
62+
if (!waitHandle.WaitOne(TimeSpan.FromSeconds(10)))
63+
throw new Exception($"No flush occurred in 10 seconds: {listener}", listener.ObservedException);
64+
65+
if (!listener.PublishSuccess)
66+
throw new Exception("Extensions Logger did not export correctly");
67+
if (listener.ObservedException != null)
68+
throw new Exception("Extensions Logger received exception", listener.ObservedException);
69+
Console.WriteLine("Extensions Logger export success");
70+
}
71+
72+
void LogInMemoryNLog(NLogExporter serilogExporter)
73+
{
74+
using var _ = serilogExporter.CreateNLogLogger(out var logger, out var _, out var _, out var waitHandle, out var listener);
75+
logger.Log(LogLevel.Error, "an error occurred {Status}", "failure");
76+
77+
if (!waitHandle.WaitOne(TimeSpan.FromSeconds(10)))
78+
throw new Exception($"No flush occurred in 10 seconds: {listener}", listener.ObservedException);
79+
80+
if (!listener.PublishSuccess)
81+
throw new Exception("NLog did not export correctly");
82+
if (listener.ObservedException != null)
83+
throw new Exception("NLog export received exception", listener.ObservedException);
84+
Console.WriteLine("NLog export success");
85+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Elastic.Channels;
2+
using Elastic.Channels.Diagnostics;
3+
using Elastic.Ingest.Elasticsearch.DataStreams;
4+
using Elastic.Serilog.Sinks;
5+
using Elastic.Transport;
6+
using Serilog;
7+
using Serilog.Core;
8+
9+
public class SerilogExporter(ITransport<ITransportConfiguration> transport)
10+
{
11+
public Logger CreateSerilogLogger(out WaitHandle waitHandle, out IChannelDiagnosticsListener listener)
12+
{
13+
var countdown = new CountdownEvent(1);
14+
waitHandle = countdown.WaitHandle;
15+
16+
IChannelDiagnosticsListener? listen = null;
17+
var options = new ElasticsearchSinkOptions(transport)
18+
{
19+
DataStream = new DataStreamName("logs", "serilog", "tests"),
20+
ConfigureChannel = c =>
21+
{
22+
c.BufferOptions = new BufferOptions
23+
{
24+
WaitHandle = countdown,
25+
OutboundBufferMaxSize = 1
26+
};
27+
},
28+
ChannelDiagnosticsCallback = l => listen = l
29+
};
30+
listener = listen ?? throw new Exception("No listener");
31+
32+
var loggerConfig = new LoggerConfiguration()
33+
.MinimumLevel.Information()
34+
.WriteTo.Elasticsearch(options);
35+
36+
var logger = loggerConfig.CreateLogger();
37+
return logger;
38+
}
39+
}

0 commit comments

Comments
 (0)