diff --git a/src/Serilog.Sinks.AzureEventHub/AzureEventHubLogContext.cs b/src/Serilog.Sinks.AzureEventHub/AzureEventHubLogContext.cs
new file mode 100644
index 0000000..8593cc3
--- /dev/null
+++ b/src/Serilog.Sinks.AzureEventHub/AzureEventHubLogContext.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Reflection;
+using Azure.Messaging.EventHubs;
+using Serilog.Context;
+using Serilog.Events;
+
+namespace Serilog
+{
+
+ ///
+ /// Holds ambient properties that can be attached to log events. To configure, use
+ /// the Serilog.Configuration.LoggerEnrichmentConfiguration.FromLogContext method.
+ /// All properties defined using AzureEventHubLogContext will be added as custom
+ /// properties on Azure Event Hub message.
+ ///
+ ///
+ /// The scope of the context is the current logical thread, using AsyncLocal (and
+ /// so is preserved across async/await calls).
+ ///
+ public static class AzureEventHubLogContext
+ {
+ internal static readonly string PropertyPrefix = "_AEHP_";
+
+ ///
+ /// Push a property onto the context, returning an that must later
+ /// be used to remove the property, along with any others that may have been pushed
+ /// on top of it and not yet popped. The property must be popped from the same thread/logical
+ /// call context.
+ ///
+ /// The name of the property.
+ /// The value of the property.
+ /// A handle to later remove the property from the context.
+ public static IDisposable PushProperty(string name, object value) => LogContext.PushProperty($"{PropertyPrefix}{name}", value);
+
+ internal static void PushCustomPropertiesToEventHubData(LogEvent logEvent, EventData eventHubData)
+ {
+ var eventHubProperties = logEvent.Properties.Where(x => x.Key.StartsWith(PropertyPrefix, StringComparison.OrdinalIgnoreCase));
+ foreach (var item in eventHubProperties)
+ {
+ var value = GetEventPropertyValue(item.Value);
+ if (value is IDictionary dic)
+ {
+ FlattenDictionary(dic, eventHubData, item.Key);
+ }
+ else
+ {
+ eventHubData.PushPropertyWithAdjustedKey(item.Key, value);
+ }
+ }
+ }
+
+ private static void FlattenDictionary(IDictionary nestedDictionary, EventData eventHubData, string parentPropertyName)
+ {
+ foreach (var item in nestedDictionary)
+ {
+ var propertyName = $"{parentPropertyName}.{item.Key}";
+ if (item.Value is IDictionary dic)
+ {
+ FlattenDictionary(dic, eventHubData, propertyName);
+ }
+ else
+ {
+ eventHubData.PushPropertyWithAdjustedKey(propertyName, item.Value);
+ }
+ }
+ }
+
+ private static object GetEventPropertyValue(LogEventPropertyValue data)
+ {
+ switch (data)
+ {
+ case ScalarValue value:
+ // Because it can't serialize enums
+ var isEnum = value.Value?.GetType().GetTypeInfo().IsEnum;
+ if (isEnum != null && (bool)isEnum)
+ return value.Value.ToString();
+ return value.Value;
+ case DictionaryValue dictValue:
+ {
+ var expObject = new ExpandoObject() as IDictionary;
+ foreach (var item in dictValue.Elements)
+ {
+ if (item.Key.Value is string key)
+ expObject.Add(key, GetEventPropertyValue(item.Value));
+ }
+ return expObject;
+ }
+
+ case SequenceValue seq:
+ var sequenceItems = seq.Elements.Select(GetEventPropertyValue).ToArray();
+ return string.Join(", ", sequenceItems);
+
+ case StructureValue str:
+ try
+ {
+ if (str.TypeTag == null)
+ return str.Properties.ToDictionary(p => p.Name, p => GetEventPropertyValue(p.Value));
+
+ if (!str.TypeTag.StartsWith("DictionaryEntry") && !str.TypeTag.StartsWith("KeyValuePair"))
+ return str.Properties.ToDictionary(p => p.Name, p => GetEventPropertyValue(p.Value));
+
+ var key = GetEventPropertyValue(str.Properties[0].Value);
+ if (key == null)
+ return null;
+
+ var expObject = new ExpandoObject() as IDictionary;
+ expObject.Add(key.ToString(), GetEventPropertyValue(str.Properties[1].Value));
+ return expObject;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ break;
+ }
+
+ return null;
+ }
+
+ private static void PushPropertyWithAdjustedKey(this EventData eventData, string key, object value)
+ {
+ eventData.Properties.Add(key.Replace(PropertyPrefix, string.Empty), value);
+ }
+ }
+}
diff --git a/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubBatchingSink.cs b/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubBatchingSink.cs
index 5bbbe20..89015f5 100644
--- a/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubBatchingSink.cs
+++ b/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubBatchingSink.cs
@@ -82,6 +82,8 @@ protected override Task EmitBatchAsync(IEnumerable events)
eventHubData.Properties.Add("Type", "SerilogEvent");
eventHubData.Properties.Add("Level", logEvent.Level.ToString());
+ AzureEventHubLogContext.PushCustomPropertiesToEventHubData(logEvent, eventHubData);
+
batchedEvents.Add(eventHubData);
}
return _eventHubClient.SendAsync(batchedEvents, new SendEventOptions() { PartitionKey = batchPartitionKey });
diff --git a/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubSink.cs b/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubSink.cs
index 2db5e28..9ccca0a 100644
--- a/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubSink.cs
+++ b/src/Serilog.Sinks.AzureEventHub/Sinks/AzureEventHub/AzureEventHubSink.cs
@@ -61,6 +61,8 @@ public void Emit(LogEvent logEvent)
eventHubData.Properties.Add("Type", "SerilogEvent");
eventHubData.Properties.Add("Level", logEvent.Level.ToString());
+ AzureEventHubLogContext.PushCustomPropertiesToEventHubData(logEvent, eventHubData);
+
//Unfortunately no support for async in Serilog yet
//https://github.com/serilog/serilog/issues/134
_eventHubClient.SendAsync(new List() { eventHubData } , new SendEventOptions() { PartitionKey = Guid.NewGuid().ToString() }).GetAwaiter().GetResult();