diff --git a/kmd-logic-audit-client.sln b/kmd-logic-audit-client.sln
old mode 100755
new mode 100644
index d6f4c48..b8c533a
--- a/kmd-logic-audit-client.sln
+++ b/kmd-logic-audit-client.sln
@@ -40,6 +40,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kmd.Logic.Audit.Client.Seri
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kmd.Logic.Audit.Client.SampleEventHubs", "sample\Kmd.Logic.Audit.Client.SampleEventHubs\Kmd.Logic.Audit.Client.SampleEventHubs.csproj", "{8921E5CF-FC63-4641-B265-EEA4939BACFA}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kmd.Logic.Audit.Client.SerilogLargeAuditEvents", "src\Kmd.Logic.Audit.Client.SerilogLargeAuditEvents\Kmd.Logic.Audit.Client.SerilogLargeAuditEvents.csproj", "{4DD5B679-97AB-42DA-86CF-C999ED9BE797}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink", "src\Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink\Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink.csproj", "{5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -134,6 +138,33 @@ Global
{8921E5CF-FC63-4641-B265-EEA4939BACFA}.Release|x64.Build.0 = Release|Any CPU
{8921E5CF-FC63-4641-B265-EEA4939BACFA}.Release|x86.ActiveCfg = Release|Any CPU
{8921E5CF-FC63-4641-B265-EEA4939BACFA}.Release|x86.Build.0 = Release|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Debug|x64.Build.0 = Debug|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Debug|x86.Build.0 = Debug|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Release|x64.ActiveCfg = Release|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Release|x64.Build.0 = Release|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Release|x86.ActiveCfg = Release|Any CPU
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797}.Release|x86.Build.0 = Release|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Debug|x64.Build.0 = Debug|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Debug|x86.Build.0 = Debug|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Release|x64.ActiveCfg = Release|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Release|x64.Build.0 = Release|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Release|x86.ActiveCfg = Release|Any CPU
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -146,6 +177,11 @@ Global
{B3870AF6-BD84-4943-A0FD-EA79DFE18480} = {C26E8896-8D49-4B97-8A19-556F4EC3A357}
{0159FC58-57BE-435A-B0B8-678D48AFCF8B} = {C928BF1D-6596-4F8F-AB5E-67308DA6D26B}
{8921E5CF-FC63-4641-B265-EEA4939BACFA} = {C26E8896-8D49-4B97-8A19-556F4EC3A357}
+ {4DD5B679-97AB-42DA-86CF-C999ED9BE797} = {C928BF1D-6596-4F8F-AB5E-67308DA6D26B}
+ {5AFCFD4C-F6FA-472C-9AEA-E4F5B4C9DBB4} = {C928BF1D-6596-4F8F-AB5E-67308DA6D26B}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7B6A42A3-6999-43F8-9F44-C64FC107FA47}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9A26425E-4672-4E22-8415-29BE7A953DA1}
diff --git a/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AuditEventPayload.cs b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AuditEventPayload.cs
new file mode 100644
index 0000000..e6312b4
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AuditEventPayload.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using Serilog.Events;
+using Serilog.Formatting;
+
+namespace Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink
+{
+ ///
+ /// This class handles methods related to Audit event payload
+ ///
+ public static class AuditEventPayload
+ {
+ public const string MessageTemplate = "Event details for event id {0} can be found at {1}";
+
+ ///
+ /// This method checks if audit event payload size is more than the limit or not
+ ///
+ /// Formatter
+ /// Log event
+ /// Event size limit
+ /// Returns if message size is greater than the limit
+ public static bool DoesAuditEventPayloadExceedLimit(ITextFormatter textFormatter, LogEvent logEvent, int eventSizeLimit)
+ {
+ string content;
+ using (var render = new StringWriter())
+ {
+ textFormatter.Format(logEvent, render);
+ content = render.ToString();
+ }
+
+ if (Encoding.UTF8.GetByteCount(content) > eventSizeLimit)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Transform the logevent to provide different message with blob url property added
+ ///
+ /// Log event
+ /// Blob url
+ /// New log event after message transformation
+ public static LogEvent AuditEventMessageTransformation(LogEvent logEvent, Uri blobUrl)
+ {
+ var properties = new List();
+ foreach (var property in logEvent.Properties)
+ {
+ var logEventProperty = new LogEventProperty(property.Key, property.Value);
+ properties.Add(logEventProperty);
+ }
+
+ var newLogEvent = new LogEvent(
+ logEvent.Timestamp,
+ logEvent.Level,
+ logEvent.Exception,
+ new MessageTemplate(string.Format(CultureInfo.InvariantCulture, MessageTemplate, logEvent.Properties["_EventId"], blobUrl), logEvent.MessageTemplate.Tokens),
+ properties);
+
+ return newLogEvent;
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobOrEventHubCustomSink.cs b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobOrEventHubCustomSink.cs
new file mode 100644
index 0000000..d3f2771
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobOrEventHubCustomSink.cs
@@ -0,0 +1,64 @@
+using System;
+using Azure.Storage.Blobs;
+using Microsoft.Azure.EventHubs;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting;
+using Serilog.Sinks.AzureEventHub;
+
+namespace Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink
+{
+ public class AzureBlobOrEventHubCustomSink : ILogEventSink
+ {
+ private readonly BlobServiceClient blobServiceClient;
+ private readonly ITextFormatter textFormatter;
+ private readonly int eventSizeLimit;
+ private readonly string storageContainerName;
+ private readonly AzureEventHubSink azureEventHubSink;
+
+ public AzureBlobOrEventHubCustomSink(
+ BlobServiceClient blobServiceClient,
+ EventHubClient eventHubClient,
+ ITextFormatter textFormatter,
+ int eventSizeLimit = 256 * 1024,
+ string storageContainerName = "logs")
+ {
+ this.textFormatter = textFormatter;
+ this.blobServiceClient = blobServiceClient;
+ this.storageContainerName = storageContainerName;
+ this.eventSizeLimit = eventSizeLimit;
+ this.azureEventHubSink = new AzureEventHubSink(eventHubClient, textFormatter);
+ }
+
+ public void Emit(LogEvent logEvent)
+ {
+ if (AuditEventPayload.DoesAuditEventPayloadExceedLimit(this.textFormatter, logEvent, this.eventSizeLimit))
+ {
+ var blobUrl = this.UploadToBlob(logEvent);
+ logEvent = AuditEventPayload.AuditEventMessageTransformation(logEvent, blobUrl);
+ }
+
+ this.azureEventHubSink.Emit(logEvent);
+ }
+
+ ///
+ /// Handles uploading to Azure blob
+ ///
+ /// Log event
+ /// blob url
+ private Uri UploadToBlob(LogEvent logEvent)
+ {
+ var content = AzureBlobServiceHelper.PrepareBlobContentForUpload(this.textFormatter, logEvent);
+ var eventId = string.Empty;
+
+ // Get Event Id value and use it as blob name
+ logEvent.Properties.TryGetValue("_EventId", out var eventIdValue);
+ if (eventIdValue != null)
+ {
+ eventId = eventIdValue.ToString();
+ }
+
+ return AzureBlobServiceProvider.UploadBlob(this.blobServiceClient, this.storageContainerName, eventId, content);
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobServiceHelper.cs b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobServiceHelper.cs
new file mode 100644
index 0000000..41f0483
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobServiceHelper.cs
@@ -0,0 +1,42 @@
+using System;
+using System.IO;
+using Serilog.Events;
+using Serilog.Formatting;
+
+namespace Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink
+{
+ public static class AzureBlobServiceHelper
+ {
+ public static string PrepareBlobContentForUpload(ITextFormatter textFormatter, LogEvent logEvent)
+ {
+ if (textFormatter == null)
+ {
+ throw new ArgumentNullException(nameof(textFormatter));
+ }
+
+ if (logEvent == null)
+ {
+ throw new ArgumentNullException(nameof(logEvent));
+ }
+
+ string content;
+
+ using (var tempStringWriter = new StringWriter())
+ {
+ try
+ {
+ textFormatter.Format(logEvent, tempStringWriter);
+ tempStringWriter.Flush();
+ }
+ catch (Exception ex)
+ {
+ Serilog.Debugging.SelfLog.WriteLine($"Exception {ex} thrown during logEvent formatting. The log event will be dropped.");
+ }
+
+ content = tempStringWriter.ToString();
+ }
+
+ return content;
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobServiceProvider.cs b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobServiceProvider.cs
new file mode 100644
index 0000000..c5f5e4f
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/AzureBlobServiceProvider.cs
@@ -0,0 +1,58 @@
+using System;
+using System.IO;
+using System.Text;
+using Azure.Storage.Blobs;
+
+namespace Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink
+{
+ public static class AzureBlobServiceProvider
+ {
+ public const string PathDivider = "/";
+
+ ///
+ /// Uploads blob and return the blob url
+ ///
+ /// Blob service client
+ /// Container name
+ /// Event id from log context
+ /// Content to be uploaded
+ /// Blob url
+ public static Uri UploadBlob(BlobServiceClient blobServiceClient, string blobContainerName, string eventId, string content)
+ {
+ // Get a reference to a blob
+ var blobClient = GetBlobClient(blobServiceClient, blobContainerName, eventId);
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
+ {
+ try
+ {
+ blobClient.Upload(stream);
+ return blobClient.Uri;
+ }
+ catch (Exception ex)
+ {
+ Serilog.Debugging.SelfLog.WriteLine($"Exception {ex} thrown while trying to upload blob.");
+ return null;
+ }
+ }
+ }
+
+ ///
+ /// Check if container or blob exists or not and return a blob client object accordingly
+ ///
+ /// Blob service client
+ /// Container name
+ /// Event id from log context
+ /// Blob client
+ private static BlobClient GetBlobClient(BlobServiceClient blobServiceClient, string blobContainerName, string eventId)
+ {
+ var dt = DateTimeOffset.UtcNow;
+ var containerClient = blobServiceClient.GetBlobContainerClient(blobContainerName);
+ containerClient.CreateIfNotExists();
+
+ // If event id is empty then create a new guid. This scenario is remotely likely to happen
+ eventId = eventId ?? Guid.NewGuid().ToString();
+ eventId = $"{dt:yyyy}{PathDivider}{dt:MM}{PathDivider}{dt:dd}{PathDivider}{dt:yyyyMMdd_HHMM}_{eventId}.log";
+ return containerClient.GetBlobClient(eventId);
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink.csproj b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink.csproj
new file mode 100644
index 0000000..2bc7ca8
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Library
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/LoggerConfigurationAzureBlobOrEventHubExtensions.cs b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/LoggerConfigurationAzureBlobOrEventHubExtensions.cs
new file mode 100644
index 0000000..0853023
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink/LoggerConfigurationAzureBlobOrEventHubExtensions.cs
@@ -0,0 +1,102 @@
+using System;
+using Azure.Storage.Blobs;
+using Microsoft.Azure.EventHubs;
+using Serilog;
+using Serilog.Configuration;
+using Serilog.Formatting;
+
+namespace Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink
+{
+ public static class LoggerConfigurationAzureBlobOrEventHubExtensions
+ {
+ public static LoggerConfiguration AzureBlobOrEventHub(
+ this LoggerSinkConfiguration loggerConfiguration,
+ string storageConnectionString,
+ string eventHubConnectionString,
+ string eventHubName,
+ int eventSizeLimitInBytes,
+ string storageContainerName = null,
+ ITextFormatter formatter = null)
+ {
+ if (loggerConfiguration == null)
+ {
+ throw new ArgumentNullException(nameof(loggerConfiguration));
+ }
+
+ if (string.IsNullOrEmpty(storageConnectionString))
+ {
+ throw new ArgumentNullException(nameof(storageConnectionString));
+ }
+
+ if (string.IsNullOrWhiteSpace(eventHubConnectionString))
+ {
+ throw new ArgumentNullException(nameof(eventHubConnectionString));
+ }
+
+ if (string.IsNullOrWhiteSpace(eventHubName))
+ {
+ throw new ArgumentNullException(nameof(eventHubName));
+ }
+
+ if (formatter == null)
+ {
+ throw new ArgumentNullException(nameof(formatter));
+ }
+
+ var eventHubConnectionstringBuilder = new EventHubsConnectionStringBuilder(eventHubConnectionString)
+ {
+ EntityPath = eventHubName
+ };
+
+ var eventHubclient = EventHubClient.CreateFromConnectionString(eventHubConnectionstringBuilder.ToString());
+
+ var blobServiceClient = new BlobServiceClient(storageConnectionString);
+
+ return loggerConfiguration.Sink(new AzureBlobOrEventHubCustomSink(blobServiceClient, eventHubclient, formatter, eventSizeLimitInBytes, storageContainerName));
+ }
+
+ public static LoggerConfiguration AzureBlobOrEventHub(
+ this LoggerSinkConfiguration loggerConfiguration,
+ string eventHubConnectionString,
+ string eventHubName,
+ int eventSizeLimitInBytes,
+ BlobServiceClient blobServiceClient,
+ string storageContainerName = null,
+ ITextFormatter formatter = null)
+ {
+ if (loggerConfiguration == null)
+ {
+ throw new ArgumentNullException(nameof(loggerConfiguration));
+ }
+
+ if (string.IsNullOrWhiteSpace(eventHubConnectionString))
+ {
+ throw new ArgumentNullException(nameof(eventHubConnectionString));
+ }
+
+ if (string.IsNullOrWhiteSpace(eventHubName))
+ {
+ throw new ArgumentNullException(nameof(eventHubName));
+ }
+
+ if (blobServiceClient == null)
+ {
+ throw new ArgumentNullException(nameof(blobServiceClient));
+ }
+
+ if (formatter == null)
+ {
+ throw new ArgumentNullException(nameof(formatter));
+ }
+
+ var eventHubConnectionstringBuilder = new EventHubsConnectionStringBuilder(eventHubConnectionString)
+ {
+ EntityPath = eventHubName
+ };
+
+ var eventHubclient = EventHubClient.CreateFromConnectionString(eventHubConnectionstringBuilder.ToString());
+
+ return loggerConfiguration.Sink(new AzureBlobOrEventHubCustomSink(blobServiceClient, eventHubclient, formatter, eventSizeLimitInBytes, storageContainerName));
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/CreatedDateTimeEnricher.cs b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/CreatedDateTimeEnricher.cs
new file mode 100644
index 0000000..22b8b9a
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/CreatedDateTimeEnricher.cs
@@ -0,0 +1,14 @@
+using System;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Kmd.Logic.Audit.Client.SerilogLargeAuditEvents
+{
+ internal class CreatedDateTimeEnricher : ILogEventEnricher
+ {
+ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
+ {
+ logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("_CreatedDateTime", DateTimeOffset.UtcNow));
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/EventIdEnricher.cs b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/EventIdEnricher.cs
new file mode 100644
index 0000000..50b3de4
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/EventIdEnricher.cs
@@ -0,0 +1,14 @@
+using System;
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Kmd.Logic.Audit.Client.SerilogLargeAuditEvents
+{
+ internal class EventIdEnricher : ILogEventEnricher
+ {
+ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
+ {
+ logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("_EventId", Guid.NewGuid()));
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents.csproj b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents.csproj
new file mode 100644
index 0000000..f36a7b1
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netstandard2.0
+ Library
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogCustomSinkLoggerAudit.cs b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogCustomSinkLoggerAudit.cs
new file mode 100644
index 0000000..ff2853f
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogCustomSinkLoggerAudit.cs
@@ -0,0 +1,26 @@
+using System;
+using Serilog;
+
+namespace Kmd.Logic.Audit.Client.SerilogLargeAuditEvents
+{
+ internal class SerilogCustomSinkLoggerAudit : IAudit
+ {
+ private readonly ILogger logger;
+
+ public SerilogCustomSinkLoggerAudit(ILogger logger)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public void Write(string messageTemplate, params object[] propertyValues)
+ {
+ this.logger.Information(messageTemplate, propertyValues);
+ }
+
+ public IAudit ForContext(string propertyName, object value, bool captureObjectStructure = false)
+ {
+ return new SerilogCustomSinkLoggerAudit(
+ this.logger.ForContext(propertyName, value, destructureObjects: captureObjectStructure));
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClient.cs b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClient.cs
new file mode 100644
index 0000000..97d6cf1
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClient.cs
@@ -0,0 +1,63 @@
+using System;
+using Azure.Storage.Blobs;
+using Serilog.Core;
+
+namespace Kmd.Logic.Audit.Client.SerilogLargeAuditEvents
+{
+ public class SerilogLargeAuditEventClient : IAudit, IDisposable
+ {
+ private readonly Logger customSinkLogger;
+ private readonly bool disposeLogger;
+ private readonly SerilogCustomSinkLoggerAudit auditToCustomSink;
+
+ public SerilogLargeAuditEventClient(SerilogLargeAuditEventClientConfiguration config)
+ {
+ this.customSinkLogger = config.CreateDefaultAzureBlobOrEventHubConfiguration().CreateLogger();
+ this.disposeLogger = true;
+ this.auditToCustomSink = new SerilogCustomSinkLoggerAudit(this.customSinkLogger);
+ }
+
+ public SerilogLargeAuditEventClient(SerilogLargeAuditEventClientConfiguration config, BlobServiceClient blobServiceClient)
+ {
+ this.customSinkLogger = config.CreateAzureBlobOrEventHubConfiguration(blobServiceClient).CreateLogger();
+ this.disposeLogger = true;
+ this.auditToCustomSink = new SerilogCustomSinkLoggerAudit(this.customSinkLogger);
+ }
+
+ private SerilogLargeAuditEventClient(Logger logger, bool disposeLogger)
+ {
+ this.customSinkLogger = logger;
+ this.disposeLogger = disposeLogger;
+ this.auditToCustomSink = new SerilogCustomSinkLoggerAudit(this.customSinkLogger);
+ }
+
+ public static SerilogLargeAuditEventClient CreateCustomized(Logger logger, bool disposeLogger = true)
+ {
+ return new SerilogLargeAuditEventClient(logger, disposeLogger);
+ }
+
+ public void Write(string messageTemplate, params object[] propertyValues)
+ {
+ this.auditToCustomSink.Write(messageTemplate, propertyValues);
+ }
+
+ public IAudit ForContext(string propertyName, object value, bool captureObjectStructure = false)
+ {
+ return this.auditToCustomSink.ForContext(propertyName, value, captureObjectStructure);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && this.disposeLogger)
+ {
+ this.customSinkLogger.Dispose();
+ }
+ }
+
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClientConfiguration.cs b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClientConfiguration.cs
new file mode 100644
index 0000000..fe5e527
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClientConfiguration.cs
@@ -0,0 +1,47 @@
+namespace Kmd.Logic.Audit.Client.SerilogLargeAuditEvents
+{
+ public class SerilogLargeAuditEventClientConfiguration
+ {
+ ///
+ /// Gets or sets a value that will be tagged on every event to make
+ /// it clear what the source of the event was.
+ ///
+ public string EventSource { get; set; }
+
+ ///
+ /// Gets or sets the Azure EventHubs connection string.
+ ///
+ public string EventHubConnectionString { get; set; }
+
+ ///
+ /// Gets or sets the Azure Blob ConnectionString.
+ ///
+ public string StorageConnectionString { get; set; }
+
+ ///
+ /// Gets or sets the Azure Blob account name.
+ ///
+ public string StorageAccountName { get; set; }
+
+ ///
+ /// Gets or sets the Azure Blob container name.
+ ///
+ public string StorageContainerName { get; set; } = "logs";
+
+ ///
+ /// Gets or sets the the topic where audit events are ingested on Azure EventHubs.
+ /// Normally just leave this as the default value of "audit".
+ ///
+ public string AuditEventTopic { get; set; } = "audit";
+
+ ///
+ /// Gets or sets if it is going to use enrich from logcontext
+ ///
+ public bool? EnrichFromLogContext { get; set; }
+
+ ///
+ /// Gets or sets the event size limit which decides whether message will be pushed to blob and event hub or only event hub
+ ///
+ public int EventSizeLimitinBytes { get; set; } = 256 * 1024;
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClientConfigurationExtensions.cs b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClientConfigurationExtensions.cs
new file mode 100644
index 0000000..ebe7caa
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLargeAuditEventClientConfigurationExtensions.cs
@@ -0,0 +1,64 @@
+using System;
+using Azure.Storage.Blobs;
+using Kmd.Logic.Audit.Client.AzureBlobOrEventHubSink;
+using Serilog;
+
+namespace Kmd.Logic.Audit.Client.SerilogLargeAuditEvents
+{
+ public static class SerilogLargeAuditEventClientConfigurationExtensions
+ {
+ public static LoggerConfiguration CreateDefaultAzureBlobOrEventHubConfiguration(this SerilogLargeAuditEventClientConfiguration config)
+ {
+ if (config == null)
+ {
+ throw new ArgumentNullException(nameof(config));
+ }
+
+ var configBuilder = new LoggerConfiguration()
+ .Enrich.With(new EventIdEnricher())
+ .Enrich.With(new CreatedDateTimeEnricher())
+ .Enrich.WithProperty("_EventSource", config.EventSource)
+ .WriteTo.AzureBlobOrEventHub(
+ storageConnectionString: config.StorageConnectionString,
+ storageContainerName: config.StorageContainerName,
+ eventHubConnectionString: config.EventHubConnectionString,
+ eventHubName: config.AuditEventTopic,
+ eventSizeLimitInBytes: config.EventSizeLimitinBytes,
+ formatter: new Serilog.Formatting.Compact.CompactJsonFormatter());
+
+ if (config.EnrichFromLogContext == true)
+ {
+ configBuilder = configBuilder.Enrich.FromLogContext();
+ }
+
+ return configBuilder;
+ }
+
+ public static LoggerConfiguration CreateAzureBlobOrEventHubConfiguration(this SerilogLargeAuditEventClientConfiguration config, BlobServiceClient blobServiceClient)
+ {
+ if (config == null)
+ {
+ throw new ArgumentNullException(nameof(config));
+ }
+
+ var configBuilder = new LoggerConfiguration()
+ .Enrich.With(new EventIdEnricher())
+ .Enrich.With(new CreatedDateTimeEnricher())
+ .Enrich.WithProperty("_EventSource", config.EventSource)
+ .WriteTo.AzureBlobOrEventHub(
+ eventHubConnectionString: config.EventHubConnectionString,
+ eventHubName: config.AuditEventTopic,
+ eventSizeLimitInBytes: config.EventSizeLimitinBytes,
+ blobServiceClient: blobServiceClient,
+ storageContainerName: config.StorageContainerName,
+ formatter: new Serilog.Formatting.Compact.CompactJsonFormatter());
+
+ if (config.EnrichFromLogContext == true)
+ {
+ configBuilder = configBuilder.Enrich.FromLogContext();
+ }
+
+ return configBuilder;
+ }
+ }
+}
diff --git a/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLoggerAudit.cs b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLoggerAudit.cs
new file mode 100644
index 0000000..6acdb4b
--- /dev/null
+++ b/src/Kmd.Logic.Audit.Client.SerilogLargeAuditEvents/SerilogLoggerAudit.cs
@@ -0,0 +1,26 @@
+using System;
+using Serilog;
+
+namespace Kmd.Logic.Audit.Client.SerilogLargeAuditEvents
+{
+ internal class SerilogLoggerAudit : IAudit
+ {
+ private readonly ILogger logger;
+
+ public SerilogLoggerAudit(ILogger logger)
+ {
+ this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public void Write(string messageTemplate, params object[] propertyValues)
+ {
+ this.logger.Information(messageTemplate, propertyValues);
+ }
+
+ public IAudit ForContext(string propertyName, object value, bool captureObjectStructure = false)
+ {
+ return new SerilogLoggerAudit(
+ this.logger.ForContext(propertyName, value, destructureObjects: captureObjectStructure));
+ }
+ }
+}