Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Support properties to be included in event data #32

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
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 @@ -51,30 +51,34 @@ public static LoggerConfiguration AzureEventHub(
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
)
{
if (loggerConfiguration == null)
if (loggerConfiguration == null)
throw new ArgumentNullException("loggerConfiguration");
if (eventHubClient == null)
throw new ArgumentNullException("eventHubClient");
if (outputTemplate == null)
if (outputTemplate == null)
throw new ArgumentNullException("outputTemplate");

var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);

return AzureEventHub(loggerConfiguration, formatter, eventHubClient, restrictedToMinimumLevel);
return AzureEventHub(loggerConfiguration, formatter, "text/plain", false, eventHubClient, restrictedToMinimumLevel);
}

/// <summary>
/// A sink that puts log events into a provided Azure Event Hub.
/// </summary>
/// <param name="loggerConfiguration">The logger configuration.</param>
/// <param name="formatter">Formatter used to convert log events to text.</param>
/// <param name="contentType">Content type that the <paramref name="formatter"/> produces.</param>
/// <param name="shouldIncludeProperties">Should the properties be included in the event data. You probably do not want this when using a JSON formatted that includes properties already.</param>
/// <param name="eventHubClient">The Event Hub to use to insert the log entries to.</param>
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink.</param>
/// <returns>Logger configuration, allowing configuration to continue.</returns>
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
public static LoggerConfiguration AzureEventHub(
this LoggerAuditSinkConfiguration loggerConfiguration,
ITextFormatter formatter,
string contentType,
bool shouldIncludeProperties,
EventHubProducerClient eventHubClient,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum)
{
Expand All @@ -83,7 +87,7 @@ public static LoggerConfiguration AzureEventHub(
if (eventHubClient == null)
throw new ArgumentNullException("eventHubClient");

var sink = new AzureEventHubSink(eventHubClient, formatter);
var sink = new AzureEventHubSink(eventHubClient, formatter, contentType, shouldIncludeProperties);
return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
}

Expand Down Expand Up @@ -125,6 +129,8 @@ public static LoggerConfiguration AzureEventHub(
/// </summary>
/// <param name="loggerConfiguration">The logger configuration.</param>
/// <param name="formatter">Formatter used to convert log events to text.</param>
/// <param name="contentType">Content type that the <paramref name="formatter"/> produces.</param>
/// <param name="shouldIncludeProperties">Should the properties be included in the event data. You probably do not want this when using a JSON formatted that includes properties already.</param>
/// <param name="connectionString">The Event Hub connection string.</param>
/// <param name="eventHubName">The Event Hub name.</param>
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink.</param>
Expand All @@ -133,6 +139,8 @@ public static LoggerConfiguration AzureEventHub(
public static LoggerConfiguration AzureEventHub(
this LoggerAuditSinkConfiguration loggerConfiguration,
ITextFormatter formatter,
string contentType,
bool shouldIncludeProperties,
string connectionString,
string eventHubName,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum
Expand All @@ -146,7 +154,7 @@ public static LoggerConfiguration AzureEventHub(
throw new ArgumentNullException("eventHubName");

var client = new EventHubProducerClient(connectionString, eventHubName);
return AzureEventHub(loggerConfiguration, formatter, client, restrictedToMinimumLevel);
return AzureEventHub(loggerConfiguration, formatter, contentType, shouldIncludeProperties, client, restrictedToMinimumLevel);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Serilog.Formatting;
using Serilog.Formatting.Display;
using Serilog.Sinks.AzureEventHub;
using Serilog.Sinks.PeriodicBatching;

namespace Serilog
{
Expand Down Expand Up @@ -68,23 +69,25 @@ public static LoggerConfiguration AzureEventHub(
int? batchPostingLimit = null
)
{
if (loggerConfiguration == null)
if (loggerConfiguration == null)
throw new ArgumentNullException("loggerConfiguration");
if (eventHubClient == null)
throw new ArgumentNullException("eventHubClient");
if (outputTemplate == null)
if (outputTemplate == null)
throw new ArgumentNullException("outputTemplate");

var formatter = new MessageTemplateTextFormatter(outputTemplate, formatProvider);

return AzureEventHub(loggerConfiguration, formatter, eventHubClient, restrictedToMinimumLevel,writeInBatches, period, batchPostingLimit);
return AzureEventHub(loggerConfiguration, formatter, "text/plain", false, eventHubClient, restrictedToMinimumLevel, writeInBatches, period, batchPostingLimit);
}

/// <summary>
/// A sink that puts log events into a provided Azure Event Hub.
/// </summary>
/// <param name="loggerConfiguration">The logger configuration.</param>
/// <param name="formatter">Formatter used to convert log events to text.</param>
/// <param name="contentType">Content type that the <paramref name="formatter"/> produces.</param>
/// <param name="shouldIncludeProperties">Should the properties be included in the event data. You probably do not want this when using a JSON formatted that includes properties already.</param>
/// <param name="eventHubClient">The Event Hub to use to insert the log entries to.</param>
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink.</param>
/// <param name="writeInBatches">Use a periodic batching sink, as opposed to a synchronous one-at-a-time sink; this alters the partition
Expand All @@ -96,6 +99,8 @@ public static LoggerConfiguration AzureEventHub(
public static LoggerConfiguration AzureEventHub(
this LoggerSinkConfiguration loggerConfiguration,
ITextFormatter formatter,
string contentType,
bool shouldIncludeProperties,
EventHubProducerClient eventHubClient,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
bool writeInBatches = false,
Expand All @@ -107,13 +112,35 @@ public static LoggerConfiguration AzureEventHub(
if (eventHubClient == null)
throw new ArgumentNullException("eventHubClient");

var sink = writeInBatches ?
(ILogEventSink)new AzureEventHubBatchingSink(
var batchSizeLimit = batchPostingLimit ?? DefaultBatchPostingLimit;
if (batchSizeLimit < 1 || batchSizeLimit > 100)
{
throw new ArgumentException(
"batchSizeLimit must be between 1 and 100.", nameof(batchPostingLimit));
}

ILogEventSink sink;
if (writeInBatches)
{
var eventHubSink = new AzureEventHubBatchingSink(
eventHubClient,
formatter,
batchPostingLimit ?? DefaultBatchPostingLimit,
period ?? DefaultPeriod) :
new AzureEventHubSink(eventHubClient, formatter);
contentType,
shouldIncludeProperties);
var batchingOptions = new PeriodicBatchingSinkOptions
{
BatchSizeLimit = batchSizeLimit,
Period = period ?? DefaultPeriod,
EagerlyEmitFirstEvent = true,
QueueLimit = 10000
};

sink = new PeriodicBatchingSink(eventHubSink, batchingOptions);
}
else
{
sink = new AzureEventHubSink(eventHubClient, formatter, contentType, shouldIncludeProperties);
}

return loggerConfiguration.Sink(sink, restrictedToMinimumLevel);
}
Expand Down Expand Up @@ -163,6 +190,8 @@ public static LoggerConfiguration AzureEventHub(
/// </summary>
/// <param name="loggerConfiguration">The logger configuration.</param>
/// <param name="formatter">Formatter used to convert log events to text.</param>
/// <param name="contentType">Content type that the <paramref name="formatter"/> produces.</param>
/// <param name="shouldIncludeProperties">Should the properties be included in the event data. You probably do not want this when using a JSON formatted that includes properties already.</param>
/// <param name="connectionString">The Event Hub connection string.</param>
/// <param name="eventHubName">The Event Hub name.</param>
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink.</param>
Expand All @@ -175,6 +204,8 @@ public static LoggerConfiguration AzureEventHub(
public static LoggerConfiguration AzureEventHub(
this LoggerSinkConfiguration loggerConfiguration,
ITextFormatter formatter,
string contentType,
bool shouldIncludeProperties,
string connectionString,
string eventHubName,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
Expand All @@ -192,7 +223,7 @@ public static LoggerConfiguration AzureEventHub(

var client = new EventHubProducerClient(connectionString, eventHubName);

return AzureEventHub(loggerConfiguration, formatter, client, restrictedToMinimumLevel, writeInBatches, period, batchPostingLimit);
return AzureEventHub(loggerConfiguration, formatter, contentType, shouldIncludeProperties, client, restrictedToMinimumLevel, writeInBatches, period, batchPostingLimit);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
</PropertyGroup>
<ItemGroup>
<!-- <PackageReference Include="Microsoft.Azure.EventHubs" Version="4.*"/> -->
<PackageReference Include="Serilog" Version="2.5.0"/>
<PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="2.1.1"/>
<PackageReference Include="Azure.Messaging.EventHubs" Version="5.4.1"/>
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="3.1.0" />
<PackageReference Include="Azure.Messaging.EventHubs" Version="5.10.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -28,40 +28,34 @@ namespace Serilog.Sinks.AzureEventHub
/// <summary>
/// Writes log events to an Azure Event Hub in batches.
/// </summary>
public class AzureEventHubBatchingSink : PeriodicBatchingSink
public class AzureEventHubBatchingSink : IBatchedLogEventSink
{
private readonly EventHubProducerClient _eventHubClient;
private readonly ITextFormatter _formatter;
private readonly string _contentType;
private readonly bool _shouldIncludeProperties;

/// <summary>
/// Construct a sink that saves log events to the specified EventHubClient.
/// </summary>
/// <param name="eventHubClient">The EventHubClient to use in this sink.</param>
/// <param name="formatter">Provides formatting for outputting log data</param>
/// <param name="batchSizeLimit"></param>
/// <param name="period"></param>
/// <param name="contentType">Content type that the <paramref name="formatter"/> produces.</param>
/// <param name="shouldIncludeProperties">Should the properties be included in the event data.</param>
public AzureEventHubBatchingSink(
EventHubProducerClient eventHubClient,
ITextFormatter formatter,
int batchSizeLimit,
TimeSpan period)
: base(batchSizeLimit, period)
string contentType,
bool shouldIncludeProperties)
{
if (batchSizeLimit < 1 || batchSizeLimit > 100)
{
throw new ArgumentException(
"batchSizeLimit must be between 1 and 100.");
}

_eventHubClient = eventHubClient;
_formatter = formatter;
_contentType = contentType;
_shouldIncludeProperties = shouldIncludeProperties;
}

/// <summary>
/// Emit a batch of log events, running to completion synchronously.
/// </summary>
/// <param name="events">The events to emit.</param>
protected override Task EmitBatchAsync(IEnumerable<LogEvent> events)
/// <inheritdoc />
public Task EmitBatchAsync(IEnumerable<LogEvent> events)
{
var batchedEvents = new List<EventData>();
var batchPartitionKey = Guid.NewGuid().ToString();
Expand All @@ -77,14 +71,44 @@ protected override Task EmitBatchAsync(IEnumerable<LogEvent> events)
_formatter.Format(logEvent, render);
body = Encoding.UTF8.GetBytes(render.ToString());
}
var eventHubData = new EventData(body);


var eventHubData = EventHubsModelFactory.EventData(new BinaryData(body));
if (!string.IsNullOrWhiteSpace(_contentType))
{
eventHubData.ContentType = _contentType;
}

eventHubData.Properties.Add("Timestamp", logEvent.Timestamp);
eventHubData.Properties.Add("Type", "SerilogEvent");
eventHubData.Properties.Add("Level", logEvent.Level.ToString());

if (logEvent.TraceId != null)
{
eventHubData.Properties.Add(nameof(logEvent.TraceId), logEvent.TraceId?.ToString());
}

if (logEvent.SpanId != null)
{
eventHubData.Properties.Add(nameof(logEvent.SpanId), logEvent.SpanId?.ToString());
}

if (logEvent.Exception != null)
{
eventHubData.Properties.Add(nameof(logEvent.Exception), logEvent.Exception);
}

if (_shouldIncludeProperties)
{
eventHubData.AddFlattenedProperties(logEvent);
}

batchedEvents.Add(eventHubData);
}

return _eventHubClient.SendAsync(batchedEvents, new SendEventOptions() { PartitionKey = batchPartitionKey });
}

/// <inheritdoc />
public Task OnEmptyBatchAsync() => Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,28 @@ namespace Serilog.Sinks.AzureEventHub
/// </summary>
public class AzureEventHubSink : ILogEventSink
{
readonly EventHubProducerClient _eventHubClient;
readonly ITextFormatter _formatter;
private readonly EventHubProducerClient _eventHubClient;
private readonly ITextFormatter _formatter;
private readonly string _contentType;
private readonly bool _shouldIncludeProperties;

/// <summary>
/// Construct a sink that saves log events to the specified EventHubClient.
/// </summary>
/// <param name="eventHubClient">The EventHubClient to use in this sink.</param>
/// <param name="formatter">Provides formatting for outputting log data</param>
/// <param name="contentType">Content type that the <paramref name="formatter"/> produces.</param>
/// <param name="shouldIncludeProperties">Should the properties be included in the event data.</param>
public AzureEventHubSink(
EventHubProducerClient eventHubClient,
ITextFormatter formatter)
ITextFormatter formatter,
string contentType,
bool shouldIncludeProperties)
{
_eventHubClient = eventHubClient;
_formatter = formatter;
_contentType = contentType;
_shouldIncludeProperties = shouldIncludeProperties;
}

/// <summary>
Expand All @@ -57,13 +65,40 @@ public void Emit(LogEvent logEvent)
_formatter.Format(logEvent, render);
body = Encoding.UTF8.GetBytes(render.ToString());
}
var eventHubData = new EventData(body);

var eventHubData = EventHubsModelFactory.EventData(new BinaryData(body));
if (!string.IsNullOrWhiteSpace(_contentType))
{
eventHubData.ContentType = _contentType;
}

eventHubData.Properties.Add("Timestamp", logEvent.Timestamp);
eventHubData.Properties.Add("Type", "SerilogEvent");
eventHubData.Properties.Add("Level", logEvent.Level.ToString());

if (logEvent.TraceId != null)
{
eventHubData.Properties.Add(nameof(logEvent.TraceId), logEvent.TraceId?.ToString());
}

if (logEvent.SpanId != null)
{
eventHubData.Properties.Add(nameof(logEvent.SpanId), logEvent.SpanId?.ToString());
}

if (logEvent.Exception != null)
{
eventHubData.Properties.Add(nameof(logEvent.Exception), logEvent.Exception);
}

if (_shouldIncludeProperties)
{
eventHubData.AddFlattenedProperties(logEvent);
}

//Unfortunately no support for async in Serilog yet
//https://github.com/serilog/serilog/issues/134
_eventHubClient.SendAsync(new List<EventData>() { eventHubData } , new SendEventOptions() { PartitionKey = Guid.NewGuid().ToString() }).GetAwaiter().GetResult();
}
}
}
}
Loading