Skip to content

Commit ec786d2

Browse files
authored
Add support for ISetLoggingFailureListener (Serilog fallback logging) (#502)
1 parent bbdf958 commit ec786d2

File tree

11 files changed

+70
-30
lines changed

11 files changed

+70
-30
lines changed

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="2.10.0" />
12+
<PackageReference Include="Serilog" Version="4.1.0" />
1313
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
1414
<PackageReference Include="Elastic.Elasticsearch.Ephemeral" Version="0.4.3" />
1515
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.16.2" />

examples/Elastic.Serilog.Sinks.Example/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
{
5454
BootstrapMethod = BootstrapMethod.Failure,
5555
DataStream = new DataStreamName("logs", "console-example"),
56-
TextFormatting = new EcsTextFormatterConfiguration
56+
TextFormatting = new EcsTextFormatterConfiguration<LogEventEcsDocument>
5757
{
5858
MapCustom = (e, _) => e
5959
},

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="2.9.0" />
10+
<PackageReference Include="Serilog" Version="4.1.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" />

src/Elastic.Apm.SerilogEnricher/Elastic.Apm.SerilogEnricher.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<IsPackable>True</IsPackable>
77
</PropertyGroup>
88
<ItemGroup>
9-
<PackageReference Include="Serilog" Version="2.9.0" />
9+
<PackageReference Include="Serilog" Version="4.1.0" />
1010
<PackageReference Include="Elastic.Apm" Version="1.22.0" />
1111
</ItemGroup>
1212
</Project>

src/Elastic.CommonSchema.Serilog/EcsTextFormatterConfiguration.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,5 @@ public class EcsTextFormatterConfiguration<TEcsDocument> : IEcsTextFormatterConf
8181

8282
// ReSharper disable once ClassNeverInstantiated.Global
8383
/// <inheritdoc cref="IEcsTextFormatterConfiguration{TEcsDocument}"/>
84-
public class EcsTextFormatterConfiguration : EcsTextFormatterConfiguration<EcsDocument>
85-
{
86-
87-
}
84+
public class EcsTextFormatterConfiguration : EcsTextFormatterConfiguration<EcsDocument>;
8885
}

src/Elastic.CommonSchema.Serilog/Elastic.CommonSchema.Serilog.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ProjectReference Include="..\Elastic.CommonSchema\Elastic.CommonSchema.csproj" />
1414
</ItemGroup>
1515
<ItemGroup>
16-
<PackageReference Include="Serilog" Version="2.9.0.0" />
16+
<PackageReference Include="Serilog" Version="4.1.0" />
1717
<PackageReference Include="PolySharp" Version="1.13.2" PrivateAssets="all">
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
</PackageReference>

src/Elastic.CommonSchema.Serilog/LogEventConverter.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,21 @@
66
using System.Collections.Generic;
77
using System.Diagnostics.CodeAnalysis;
88
using System.Linq;
9+
using System.Text.Json.Serialization;
910
using Serilog.Events;
1011
using static Elastic.CommonSchema.Serilog.SpecialProperties;
1112

1213
namespace Elastic.CommonSchema.Serilog
1314
{
15+
/// A specialized instance of <see cref="EcsDocument"/> that holds on to the original <see cref="LogEvent"/>
16+
/// <para> This property won't be emitted to JSON but is used to report back to serilog failure pipelines</para>
17+
public class LogEventEcsDocument : EcsDocument
18+
{
19+
/// The original <see cref="LogEvent"/> for bookkeeping, not send over to Elasticsearch
20+
[JsonIgnore]
21+
public LogEvent LogEvent { get; set; } = null!;
22+
}
23+
1424
/// <summary>
1525
/// Elastic Common Schema converter for LogEvent
1626
/// </summary>
@@ -178,7 +188,7 @@ private static bool PropertyAlreadyMapped(string property)
178188
}
179189
}
180190

181-
private static object PropertyValueToObject(LogEventPropertyValue propertyValue)
191+
private static object? PropertyValueToObject(LogEventPropertyValue propertyValue)
182192
{
183193
switch (propertyValue)
184194
{
@@ -187,7 +197,7 @@ private static object PropertyValueToObject(LogEventPropertyValue propertyValue)
187197
case ScalarValue sv:
188198
return sv.Value;
189199
case DictionaryValue dv:
190-
return dv.Elements.ToDictionary(keySelector: kvp => kvp.Key.Value.ToString() ?? string.Empty,
200+
return dv.Elements.ToDictionary(keySelector: kvp => kvp.Key?.Value?.ToString() ?? string.Empty,
191201
elementSelector: (kvp) => PropertyValueToObject(kvp.Value));
192202
case StructureValue ov:
193203
{

src/Elastic.Serilog.Enrichers.Web/Elastic.Serilog.Enrichers.Web.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</ItemGroup>
2121

2222
<ItemGroup>
23-
<PackageReference Include="Serilog" Version="2.9.0.0"/>
23+
<PackageReference Include="Serilog" Version="4.1.0" />
2424
</ItemGroup>
2525

2626
<ItemGroup>

src/Elastic.Serilog.Sinks/ElasticsearchSink.cs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using Elastic.Channels;
45
using Elastic.Channels.Buffers;
56
using Elastic.Channels.Diagnostics;
67
using Elastic.CommonSchema;
@@ -44,7 +45,7 @@ public interface IElasticsearchSinkOptions
4445
/// <summary>
4546
/// Provides configuration options to <see cref="ElasticsearchSink"/> to control how and where data gets written
4647
/// </summary>
47-
public class ElasticsearchSinkOptions : ElasticsearchSinkOptions<EcsDocument>
48+
public class ElasticsearchSinkOptions : ElasticsearchSinkOptions<LogEventEcsDocument>
4849
{
4950
/// <inheritdoc cref="ElasticsearchSinkOptions"/>
5051
public ElasticsearchSinkOptions() { }
@@ -56,7 +57,7 @@ public ElasticsearchSinkOptions(ITransport transport) : base(transport) { }
5657
/// <inheritdoc cref="ElasticsearchSinkOptions{TEcsDocument}"/>
5758
public class ElasticsearchSinkOptions<TEcsDocument>
5859
: IElasticsearchSinkOptions
59-
where TEcsDocument : EcsDocument, new()
60+
where TEcsDocument : LogEventEcsDocument, new()
6061
{
6162
/// <inheritdoc cref="ElasticsearchSinkOptions"/>
6263
public ElasticsearchSinkOptions() : this(new DistributedTransport(TransportHelper.Default())) { }
@@ -110,18 +111,19 @@ public ElasticsearchSinkOptions() : this(new DistributedTransport(TransportHelpe
110111
/// <summary>
111112
/// This sink allows you to write serilog logs directly to Elasticsearch or Elastic Cloud
112113
/// </summary>
113-
public class ElasticsearchSink : ElasticsearchSink<EcsDocument>
114+
public class ElasticsearchSink : ElasticsearchSink<LogEventEcsDocument>
114115
{
115116
/// <inheritdoc cref="ElasticsearchSink"/>>
116117
public ElasticsearchSink(ElasticsearchSinkOptions options) : base(options) {}
117118
}
118119

119120
/// <inheritdoc cref="ElasticsearchSink"/>>
120-
public class ElasticsearchSink<TEcsDocument> : ILogEventSink, IDisposable
121-
where TEcsDocument : EcsDocument, new()
121+
public class ElasticsearchSink<TEcsDocument> : ILogEventSink, IDisposable, ISetLoggingFailureListener
122+
where TEcsDocument : LogEventEcsDocument, new()
122123
{
123124
private readonly EcsTextFormatterConfiguration<TEcsDocument> _formatterConfiguration;
124125
private readonly EcsDataStreamChannel<TEcsDocument> _channel;
126+
private ILoggingFailureListener _failureListener = SelfLog.FailureListener;
125127

126128
/// <inheritdoc cref="IElasticsearchSinkOptions"/>
127129
public IElasticsearchSinkOptions Options { get; }
@@ -133,7 +135,9 @@ public ElasticsearchSink(ElasticsearchSinkOptions<TEcsDocument> options)
133135
_formatterConfiguration = options.TextFormatting;
134136
var channelOptions = new DataStreamChannelOptions<TEcsDocument>(options.Transport)
135137
{
136-
DataStream = options.DataStream
138+
DataStream = options.DataStream,
139+
ExportMaxRetriesCallback = EmitExportFailures
140+
137141
};
138142
options.ConfigureChannel?.Invoke(channelOptions);
139143
_channel = new EcsDataStreamChannel<TEcsDocument>(channelOptions, new [] { new SelfLogCallbackListener<TEcsDocument>(options)});
@@ -142,18 +146,46 @@ public ElasticsearchSink(ElasticsearchSinkOptions<TEcsDocument> options)
142146
_channel.BootstrapElasticsearch(options.BootstrapMethod, options.IlmPolicy);
143147
}
144148

149+
private void EmitExportFailures(IReadOnlyCollection<TEcsDocument> documents)
150+
{
151+
var logs = documents
152+
.Select(d => d.LogEvent)
153+
.ToArray();
154+
_failureListener.OnLoggingFailed(
155+
this,
156+
LoggingFailureKind.Temporary,
157+
"Failure to export events over to Elasticsearch.",
158+
logs,
159+
exception: null
160+
);
161+
}
162+
145163
/// <inheritdoc cref="ILogEventSink.Emit"/>
146164
public void Emit(LogEvent logEvent)
147165
{
148166
var ecsDoc = LogEventConverter.ConvertToEcs(logEvent, _formatterConfiguration);
149-
_channel.TryWrite(ecsDoc);
167+
ecsDoc.LogEvent = logEvent;
168+
if (!_channel.TryWrite(ecsDoc))
169+
{
170+
_failureListener.OnLoggingFailed(
171+
this,
172+
LoggingFailureKind.Temporary,
173+
"Failure to push event over the channel.",
174+
[logEvent],
175+
exception: null
176+
);
177+
}
150178
}
151179

152180
/// <summary> Disposes and flushed <see cref="EcsDataStreamChannel{TEcsDocument}"/> </summary>
153181
public void Dispose() => _channel.Dispose();
182+
183+
void ISetLoggingFailureListener.SetFailureListener(ILoggingFailureListener failureListener) =>
184+
_failureListener = failureListener ?? throw new ArgumentNullException(nameof(failureListener));
154185
}
155186

156-
internal class SelfLogCallbackListener<TEcsDocument> : IChannelCallbacks<TEcsDocument, BulkResponse> where TEcsDocument : EcsDocument, new()
187+
internal class SelfLogCallbackListener<TEcsDocument> : IChannelCallbacks<TEcsDocument, BulkResponse>
188+
where TEcsDocument : LogEventEcsDocument, new()
157189
{
158190
public Action<Exception>? ExportExceptionCallback { get; }
159191
public Action<BulkResponse, IWriteTrackingBuffer>? ExportResponseCallback { get; }

src/Elastic.Serilog.Sinks/ElasticsearchSinkExtensions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using Elastic.CommonSchema;
4+
using Elastic.CommonSchema.Serilog;
45
using Elastic.Transport;
56
using Serilog;
67
using Serilog.Configuration;
@@ -31,7 +32,7 @@ public static LoggerConfiguration Elasticsearch(this LoggerSinkConfiguration log
3132
/// <para>This generic overload using <typeparamref name="TEcsDocument"/> allows you to use your own <see cref="EcsDocument"/> subclasses</para>
3233
/// </summary>
3334
public static LoggerConfiguration Elasticsearch<TEcsDocument>(this LoggerSinkConfiguration loggerConfiguration, ElasticsearchSinkOptions<TEcsDocument>? options = null)
34-
where TEcsDocument : EcsDocument, new() =>
35+
where TEcsDocument : LogEventEcsDocument, new() =>
3536
loggerConfiguration.Sink(
3637
new ElasticsearchSink<TEcsDocument>(options ?? new ElasticsearchSinkOptions<TEcsDocument>())
3738
, restrictedToMinimumLevel: options?.MinimumLevel ?? LevelAlias.Minimum
@@ -76,7 +77,7 @@ public static LoggerConfiguration Elasticsearch<TEcsDocument>(
7677
bool useSniffing = false,
7778
LoggingLevelSwitch? levelSwitch = null,
7879
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
79-
) where TEcsDocument : EcsDocument, new()
80+
) where TEcsDocument : LogEventEcsDocument, new()
8081
{
8182
var transportConfig = useSniffing ? TransportHelper.Sniffing(nodes) : TransportHelper.Static(nodes);
8283
configureTransport?.Invoke(transportConfig);
@@ -125,7 +126,7 @@ public static LoggerConfiguration ElasticCloud<TEcsDocument>(
125126
Action<TransportConfigurationDescriptor>? configureTransport = null,
126127
LoggingLevelSwitch? levelSwitch = null,
127128
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
128-
) where TEcsDocument : EcsDocument, new()
129+
) where TEcsDocument : LogEventEcsDocument, new()
129130
{
130131
var transportConfig = TransportHelper.Cloud(cloudId, apiKey);
131132
configureTransport?.Invoke(transportConfig);
@@ -176,7 +177,7 @@ public static LoggerConfiguration ElasticCloud<TEcsDocument>(
176177
Action<TransportConfigurationDescriptor>? configureTransport = null,
177178
LoggingLevelSwitch? levelSwitch = null,
178179
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
179-
) where TEcsDocument : EcsDocument, new()
180+
) where TEcsDocument : LogEventEcsDocument, new()
180181
{
181182
var transportConfig = TransportHelper.Cloud(cloudId, username, password);
182183
configureTransport?.Invoke(transportConfig);

0 commit comments

Comments
 (0)