Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="Elastic.Apm" Version="1.22.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Elastic.Elasticsearch.Ephemeral" Version="0.4.3" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.16.2" />
Expand Down
2 changes: 1 addition & 1 deletion examples/Elastic.Serilog.Sinks.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
{
BootstrapMethod = BootstrapMethod.Failure,
DataStream = new DataStreamName("logs", "console-example"),
TextFormatting = new EcsTextFormatterConfiguration
TextFormatting = new EcsTextFormatterConfiguration<LogEventEcsDocument>
{
MapCustom = (e, _) => e
},
Expand Down
2 changes: 1 addition & 1 deletion examples/aspnetcore-with-serilog/AspnetCoreExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Elastic.Apm" Version="1.22.0" />
<PackageReference Include="Elastic.Apm.AspNetCore" Version="1.22.0" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<IsPackable>True</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Elastic.Apm" Version="1.22.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,5 @@ public class EcsTextFormatterConfiguration<TEcsDocument> : IEcsTextFormatterConf

// ReSharper disable once ClassNeverInstantiated.Global
/// <inheritdoc cref="IEcsTextFormatterConfiguration{TEcsDocument}"/>
public class EcsTextFormatterConfiguration : EcsTextFormatterConfiguration<EcsDocument>
{

}
public class EcsTextFormatterConfiguration : EcsTextFormatterConfiguration<EcsDocument>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<ProjectReference Include="..\Elastic.CommonSchema\Elastic.CommonSchema.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.9.0.0" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="PolySharp" Version="1.13.2" PrivateAssets="all">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
14 changes: 12 additions & 2 deletions src/Elastic.CommonSchema.Serilog/LogEventConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json.Serialization;
using Serilog.Events;
using static Elastic.CommonSchema.Serilog.SpecialProperties;

namespace Elastic.CommonSchema.Serilog
{
/// A specialized instance of <see cref="EcsDocument"/> that holds on to the original <see cref="LogEvent"/>
/// <para> This property won't be emitted to JSON but is used to report back to serilog failure pipelines</para>
public class LogEventEcsDocument : EcsDocument
{
/// The original <see cref="LogEvent"/> for bookkeeping, not send over to Elasticsearch
[JsonIgnore]
public LogEvent LogEvent { get; set; } = null!;
}

/// <summary>
/// Elastic Common Schema converter for LogEvent
/// </summary>
Expand Down Expand Up @@ -178,7 +188,7 @@ private static bool PropertyAlreadyMapped(string property)
}
}

private static object PropertyValueToObject(LogEventPropertyValue propertyValue)
private static object? PropertyValueToObject(LogEventPropertyValue propertyValue)
{
switch (propertyValue)
{
Expand All @@ -187,7 +197,7 @@ private static object PropertyValueToObject(LogEventPropertyValue propertyValue)
case ScalarValue sv:
return sv.Value;
case DictionaryValue dv:
return dv.Elements.ToDictionary(keySelector: kvp => kvp.Key.Value.ToString() ?? string.Empty,
return dv.Elements.ToDictionary(keySelector: kvp => kvp.Key?.Value?.ToString() ?? string.Empty,
elementSelector: (kvp) => PropertyValueToObject(kvp.Value));
case StructureValue ov:
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Serilog" Version="2.9.0.0"/>
<PackageReference Include="Serilog" Version="4.1.0" />
</ItemGroup>

<ItemGroup>
Expand Down
48 changes: 40 additions & 8 deletions src/Elastic.Serilog.Sinks/ElasticsearchSink.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Elastic.Channels;
using Elastic.Channels.Buffers;
using Elastic.Channels.Diagnostics;
using Elastic.CommonSchema;
Expand Down Expand Up @@ -44,7 +45,7 @@ public interface IElasticsearchSinkOptions
/// <summary>
/// Provides configuration options to <see cref="ElasticsearchSink"/> to control how and where data gets written
/// </summary>
public class ElasticsearchSinkOptions : ElasticsearchSinkOptions<EcsDocument>
public class ElasticsearchSinkOptions : ElasticsearchSinkOptions<LogEventEcsDocument>
{
/// <inheritdoc cref="ElasticsearchSinkOptions"/>
public ElasticsearchSinkOptions() { }
Expand All @@ -56,7 +57,7 @@ public ElasticsearchSinkOptions(ITransport transport) : base(transport) { }
/// <inheritdoc cref="ElasticsearchSinkOptions{TEcsDocument}"/>
public class ElasticsearchSinkOptions<TEcsDocument>
: IElasticsearchSinkOptions
where TEcsDocument : EcsDocument, new()
where TEcsDocument : LogEventEcsDocument, new()
{
/// <inheritdoc cref="ElasticsearchSinkOptions"/>
public ElasticsearchSinkOptions() : this(new DistributedTransport(TransportHelper.Default())) { }
Expand Down Expand Up @@ -110,18 +111,19 @@ public ElasticsearchSinkOptions() : this(new DistributedTransport(TransportHelpe
/// <summary>
/// This sink allows you to write serilog logs directly to Elasticsearch or Elastic Cloud
/// </summary>
public class ElasticsearchSink : ElasticsearchSink<EcsDocument>
public class ElasticsearchSink : ElasticsearchSink<LogEventEcsDocument>
{
/// <inheritdoc cref="ElasticsearchSink"/>>
public ElasticsearchSink(ElasticsearchSinkOptions options) : base(options) {}
}

/// <inheritdoc cref="ElasticsearchSink"/>>
public class ElasticsearchSink<TEcsDocument> : ILogEventSink, IDisposable
where TEcsDocument : EcsDocument, new()
public class ElasticsearchSink<TEcsDocument> : ILogEventSink, IDisposable, ISetLoggingFailureListener
where TEcsDocument : LogEventEcsDocument, new()
{
private readonly EcsTextFormatterConfiguration<TEcsDocument> _formatterConfiguration;
private readonly EcsDataStreamChannel<TEcsDocument> _channel;
private ILoggingFailureListener _failureListener = SelfLog.FailureListener;

/// <inheritdoc cref="IElasticsearchSinkOptions"/>
public IElasticsearchSinkOptions Options { get; }
Expand All @@ -133,7 +135,9 @@ public ElasticsearchSink(ElasticsearchSinkOptions<TEcsDocument> options)
_formatterConfiguration = options.TextFormatting;
var channelOptions = new DataStreamChannelOptions<TEcsDocument>(options.Transport)
{
DataStream = options.DataStream
DataStream = options.DataStream,
ExportMaxRetriesCallback = EmitExportFailures

};
options.ConfigureChannel?.Invoke(channelOptions);
_channel = new EcsDataStreamChannel<TEcsDocument>(channelOptions, new [] { new SelfLogCallbackListener<TEcsDocument>(options)});
Expand All @@ -142,18 +146,46 @@ public ElasticsearchSink(ElasticsearchSinkOptions<TEcsDocument> options)
_channel.BootstrapElasticsearch(options.BootstrapMethod, options.IlmPolicy);
}

private void EmitExportFailures(IReadOnlyCollection<TEcsDocument> documents)
{
var logs = documents
.Select(d => d.LogEvent)
.ToArray();
_failureListener.OnLoggingFailed(
this,
LoggingFailureKind.Temporary,
"Failure to export events over to Elasticsearch.",
logs,
exception: null
);
}

/// <inheritdoc cref="ILogEventSink.Emit"/>
public void Emit(LogEvent logEvent)
{
var ecsDoc = LogEventConverter.ConvertToEcs(logEvent, _formatterConfiguration);
_channel.TryWrite(ecsDoc);
ecsDoc.LogEvent = logEvent;
if (!_channel.TryWrite(ecsDoc))
{
_failureListener.OnLoggingFailed(
this,
LoggingFailureKind.Temporary,
"Failure to push event over the channel.",
[logEvent],
exception: null
);
}
}

/// <summary> Disposes and flushed <see cref="EcsDataStreamChannel{TEcsDocument}"/> </summary>
public void Dispose() => _channel.Dispose();

void ISetLoggingFailureListener.SetFailureListener(ILoggingFailureListener failureListener) =>
_failureListener = failureListener ?? throw new ArgumentNullException(nameof(failureListener));
}

internal class SelfLogCallbackListener<TEcsDocument> : IChannelCallbacks<TEcsDocument, BulkResponse> where TEcsDocument : EcsDocument, new()
internal class SelfLogCallbackListener<TEcsDocument> : IChannelCallbacks<TEcsDocument, BulkResponse>
where TEcsDocument : LogEventEcsDocument, new()
{
public Action<Exception>? ExportExceptionCallback { get; }
public Action<BulkResponse, IWriteTrackingBuffer>? ExportResponseCallback { get; }
Expand Down
9 changes: 5 additions & 4 deletions src/Elastic.Serilog.Sinks/ElasticsearchSinkExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Elastic.CommonSchema;
using Elastic.CommonSchema.Serilog;
using Elastic.Transport;
using Serilog;
using Serilog.Configuration;
Expand Down Expand Up @@ -31,7 +32,7 @@ public static LoggerConfiguration Elasticsearch(this LoggerSinkConfiguration log
/// <para>This generic overload using <typeparamref name="TEcsDocument"/> allows you to use your own <see cref="EcsDocument"/> subclasses</para>
/// </summary>
public static LoggerConfiguration Elasticsearch<TEcsDocument>(this LoggerSinkConfiguration loggerConfiguration, ElasticsearchSinkOptions<TEcsDocument>? options = null)
where TEcsDocument : EcsDocument, new() =>
where TEcsDocument : LogEventEcsDocument, new() =>
loggerConfiguration.Sink(
new ElasticsearchSink<TEcsDocument>(options ?? new ElasticsearchSinkOptions<TEcsDocument>())
, restrictedToMinimumLevel: options?.MinimumLevel ?? LevelAlias.Minimum
Expand Down Expand Up @@ -76,7 +77,7 @@ public static LoggerConfiguration Elasticsearch<TEcsDocument>(
bool useSniffing = false,
LoggingLevelSwitch? levelSwitch = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
) where TEcsDocument : EcsDocument, new()
) where TEcsDocument : LogEventEcsDocument, new()
{
var transportConfig = useSniffing ? TransportHelper.Sniffing(nodes) : TransportHelper.Static(nodes);
configureTransport?.Invoke(transportConfig);
Expand Down Expand Up @@ -125,7 +126,7 @@ public static LoggerConfiguration ElasticCloud<TEcsDocument>(
Action<TransportConfigurationDescriptor>? configureTransport = null,
LoggingLevelSwitch? levelSwitch = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
) where TEcsDocument : EcsDocument, new()
) where TEcsDocument : LogEventEcsDocument, new()
{
var transportConfig = TransportHelper.Cloud(cloudId, apiKey);
configureTransport?.Invoke(transportConfig);
Expand Down Expand Up @@ -176,7 +177,7 @@ public static LoggerConfiguration ElasticCloud<TEcsDocument>(
Action<TransportConfigurationDescriptor>? configureTransport = null,
LoggingLevelSwitch? levelSwitch = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
) where TEcsDocument : EcsDocument, new()
) where TEcsDocument : LogEventEcsDocument, new()
{
var transportConfig = TransportHelper.Cloud(cloudId, username, password);
configureTransport?.Invoke(transportConfig);
Expand Down
12 changes: 6 additions & 6 deletions tests/Elastic.Serilog.Sinks.Tests/JsonConfigTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ namespace Elastic.Serilog.Sinks.Tests;
public class JsonConfigTestBase
{
protected static void GetBits(string json,
out ElasticsearchSink<EcsDocument> sink,
out EcsTextFormatterConfiguration<EcsDocument> formatterConfig,
out EcsDataStreamChannel<EcsDocument> channel,
out ElasticsearchSink<LogEventEcsDocument> sink,
out EcsTextFormatterConfiguration<LogEventEcsDocument> formatterConfig,
out EcsDataStreamChannel<LogEventEcsDocument> channel,
out ITransportConfiguration transportConfig)
{
var config = new ConfigurationBuilder()
Expand All @@ -28,9 +28,9 @@ protected static void GetBits(string json,
var field = loggerConfig.GetType().GetField("_logEventSinks", BindingFlags.Instance | BindingFlags.NonPublic);
var sinks = field?.GetValue(loggerConfig) as IList<ILogEventSink>;
sinks.Should().HaveCount(1);
sink = sinks?.FirstOrDefault() as ElasticsearchSink<EcsDocument> ?? throw new NullReferenceException();
formatterConfig = Reflect<EcsTextFormatterConfiguration<EcsDocument>>(sink, "_formatterConfiguration");
channel = Reflect<EcsDataStreamChannel<EcsDocument>>(sink, "_channel");
sink = sinks?.FirstOrDefault() as ElasticsearchSink<LogEventEcsDocument> ?? throw new NullReferenceException();
formatterConfig = Reflect<EcsTextFormatterConfiguration<LogEventEcsDocument>>(sink, "_formatterConfiguration");
channel = Reflect<EcsDataStreamChannel<LogEventEcsDocument>>(sink, "_channel");

var transport = channel.Options.Transport as ITransport<ITransportConfiguration> ?? throw new NullReferenceException();
transportConfig = transport.Configuration;
Expand Down
Loading