thepirat000

Audit.NET

An extensible framework to audit executing operations in .NET and .NET Core.
Under MIT License
By thepirat000

wcf webapi netcore entity-framework mvc audit audit-log audit-logs

Audit.NET

Quick navigation
USAGE | OUTPUT | CUSTOMIZATION | DATA PROVIDERS | CREATION POLICY | CONFIGURATION | EXTENSIONS


issues | chat / support | donations
------------ | ---------------- | --------------
| |


An extensible framework to audit executing operations in .NET and .NET Core.

Generate audit logs with evidence for reconstruction and examination of activities that have affected specific operations or procedures.


With Audit.NET you can generate tracking information about operations being executed. It gathers environmental information such as the caller user id, machine name, method name, exceptions, including execution time and exposing an extensible mechanism to enrich the logs and handle the audit output.


Output extensions are provided to log to JSON Files,
Event Log, SQL,
MySQL,
PostgreSQL,
MongoDB,
AzureBlob,
AzureCosmos,
Redis,
Elasticsearch,
DynamoDB,
UDP datagrams and more.


Interaction extensions to audit different systems are provided, such as Entity Framework,
MVC,
WebAPI,
WCF,
File System,
SignalR
and HttpClient.


NuGet



To install the package run the following command on the Package Manager Console:


PM> Install-Package Audit.NET


Changelog

Check the CHANGELOG.md file.


Breaking change in version 18

Starting on version 18, Audit.NET will default to diferent serialization mechanism depending on the target framework of the client application,
as shown on the following table:


| Target | Serialization |
| ------------ | ---------------- |
| ≥ .NET 5.0 | System.Text.Json |
| ≤ .NETSTANDARD2.1 / .NETCOREAPP3.1 | Newtonsoft.Json |
| ≤ .NET 4.8 | Newtonsoft.Json |



If you want to change the default behavior, refer to Custom serialization mechanism.


Usage

The Audit Scope is the central object of this framework. It encapsulates an audit event, controlling its life cycle.
The Audit Event is an extensible information container of an audited operation.
See the audit scope statechart.


There are several ways to create an Audit Scope:



AuditScope options

Option | Type | Description
------------ | ---------------- | ----------------
EventType | string | A string representing the type of the event
TargetGetter | Func<object> | Target object getter (a func that returns the object to track)
ExtraFields | object | Anonymous object that contains additional fields to be merged into the audit event
DataProvider | AuditDataProvider | The data provider to use. Defaults to the DataProvider configured on Audit.Core.Configuration.DataProvider
CreationPolicy | EventCreationPolicy | The creation policy to use. Default is InsertOnEnd
IsCreateAndSave | bool | Value indicating whether this scope should be immediately ended and saved after creation. Default is false
AuditEvent | AuditEvent | Custom initial audit event to use. By default it will create a new instance of basic AuditEvent
SkipExtraFrames | int | Value used to indicate how many frames in the stack should be skipped to determine the calling method. Default is 0
CallingMethod | MethodBase | Specific calling method to store on the event. Default is to use the calling stack to determine the calling method.


Suppose you have the following code to cancel an order that you want to audit:


c#
Order order = Db.GetOrder(orderId);
order.Status = -1;
order.OrderItems = null;
order = Db.OrderUpdate(order);


To audit this operation, you can surround the code with a using block that creates an AuditScope, indicating a target object to track:


c#
Order order = Db.GetOrder(orderId);
using (AuditScope.Create("Order:Update", () => order))
{
order.Status = -1;
order.OrderItems = null;
order = Db.OrderUpdate(order);
}



It is not mandatory to use a using block, but it simplifies the syntax when the code to audit is on a single block, allowing the detection of exceptions and calculating the duration by implicitly saving the event on disposal.


When using the extensions that logs interactions with different systems, like Audit.EntityFramework, Audit.WebApi, etc. you don't need to explicitly create the AuditScope or AuditEvent, they are created internally by the extension.



Simple logging

If you are not tracking an object, nor the duration of an event, you can use the Log shortcut method that logs an event immediately.
For example:
c#
AuditScope.Log("Event Type", new { ExtraField = "extra value" });


Manual Saving

You can control the creation and saving logic, by creating a manual AuditScope. For example to log a pair of Start/End method calls as a single event:


```c#
public class SomethingThatStartsAndEnds
{
private AuditScope auditScope;


public int Status { get; set; }

public void Start()
{
// Create a manual scope
auditScope = AuditScope.Create(new AuditScopeOptions()
{
EventType = "MyEvent",
TargetGetter = () => this.Status,
CreationPolicy = EventCreationPolicy.Manual
});
}

public void End()
{
// Save the event
auditScope.Save();
// Discard to avoid further saving
auditScope.Discard();
}


}
```


For more information about the EventCreationPolicy please see Event Creation Policy section.


Asynchronous operations

Asynchronous versions of the operations that saves audit logs are also provided. For example:


c#
public async Task SaveOrderAsync(Order order)
{
AuditScope auditScope = null;
try
{
// async scope creation
auditScope = await AuditScope.CreateAsync("order", () => order);
}
finally
{
// async disposal
await auditScope.DisposeAsync();
}
}



Note: On older .NET framework versions the Dispose method was always synchronous, so if your audit code is on async methods and you created the scope within a using statement, you should explicitly call the DisposeAsync() method. For projects targeting .NET Standard starting on version 2.0 and C# 8, you can simply use the await using statement, since the AuditScope implements the IAsyncDisposable interface.



Output

The library will generate an output (AuditEvent) for each operation, including:
- Tracked object's state before and after the operation.
- Execution time and duration.
- Environment information such as user, machine, domain, locale, etc.
- Comments and Custom Fields provided.


An example of the output in JSON:


javascript
{
"EventType": "Order:Update",
"Environment": {
"UserName": "Federico",
"MachineName": "HP",
"DomainName": "HP",
"CallingMethodName": "Audit.UnitTest.AuditTests.TestUpdate()",
"Exception": null,
"Culture": "en-GB"
},
"StartDate": "2016-08-23T11:33:14.653191-05:00",
"EndDate": "2016-08-23T11:33:23.1820786-05:00",
"Duration": 8529,
"Target": {
"Type": "Order",
"Old": {
"OrderId": "39dc0d86-d5fc-4d2e-b918-fb1a97710c99",
"Status": 2,
"OrderItems": [{
"Sku": "1002",
"Quantity": 3.0
}]
},
"New": {
"OrderId": "39dc0d86-d5fc-4d2e-b918-fb1a97710c99",
"Status": -1,
"OrderItems": null
}
}
}


Output details

The following tables describes the output fields:



Custom Fields and Comments

The AuditScope object provides two methods to extend the event output.



For example:


c#
Order order = Db.GetOrder(orderId);
using (var audit = AuditScope.Create("Order:Update", () => order))
{
audit.SetCustomField("ReferenceId", orderId);
order.Status = -1;
order = Db.OrderUpdate(order);
audit.Comment("Status Updated to Cancelled");
}


You can also set Custom Fields when creating the AuditScope, by passing an anonymous object with the properties you want as extra fields. For example:


c#
using (var audit = AuditScope.Create("Order:Update", () => order, new { ReferenceId = orderId }))
{
order.Status = -1;
order = Db.OrderUpdate(order);
audit.Comment("Status Updated to Cancelled");
}


You can also access the Custom Fields directly from Event.CustomFields property of the scope. For example:
c#
using (var audit = AuditScope.Create("Order:Update", () => order, new { ReferenceId = orderId }))
{
audit.Event.CustomFields["ReferenceId"] = orderId;
}



Custom fields are not limited to single properties, you can store any object as well, by default they will be JSON serialized.



Extending AuditEvent

Another way to enrich the event output is to create a class inheriting from the AuditEvent class, then you can pass an instance of your class to the AuditScope.Create method. For example:


```c#
public class YourAuditEvent : AuditEvent
{
public Guid ReferenceId { get; set; } = Guid.NewGuid();
}


using (var scope = AuditScope.Create(new AuditScopeOptions { AuditEvent = new YourAuditEvent() }))
{
//...
}
```


The output of the previous examples would be:


```javascript
{
"EventType": "Order:Update",
"Environment": {
"UserName": "Federico",
"MachineName": "HP",
"DomainName": "HP",
"CallingMethodName": "Audit.UnitTest.AuditTests.TestUpdate()",
"Exception": null,
"Culture": "en-GB"
},
"Target": {
"Type": "Order",
"Old": {
"OrderId": "39dc0d86-d5fc-4d2e-b918-fb1a97710c99",
"Status": 2,


    },
"New": {
"OrderId": "39dc0d86-d5fc-4d2e-b918-fb1a97710c99",
"Status": -1,

}
},
"ReferenceId": "39dc0d86-d5fc-4d2e-b918-fb1a97710c99", // <-- Custom Field
"Comments": ["Status Updated to Cancelled"], // <-- Comments
"StartDate": "2016-08-23T11:34:44.656101-05:00",
"EndDate": "2016-08-23T11:34:55.1810821-05:00",
"Duration": 8531


}
```


Discard option

The AuditScope object has a Discard() method to allow the user to discard an event. Discarding an event means it won't be saved.


For example, if you want to avoid saving the audit event under certain condition:


c#
using (var scope = AuditScope.Create(new AuditScopeOptions("SomeEvent", () => someTarget)))
{
try
{
//some operation
Critical.Operation();
}
catch (Exception ex)
{
//If an exception is thrown, discard the audit event
scope.Discard();
}
}


Data providers

A data provider (or storage sink) contains the logic to handle the audit event output, where you define what to do with the audit logs.


You can use one of the data providers included or inject your own mechanism
by creating a class that inherits from AuditDataProvider and overrides its methods:



If your data provider will support asynchronous operations, you must also implement the following methods:



Also, if your data provider will support event retrieval, you should implement the methods:



For example:
c#
public class MyCustomDataProvider : AuditDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var fileName = $"Log{Guid.NewGuid()}.json";
File.WriteAllText(fileName, auditEvent.ToJson());
return fileName;
}
public override void ReplaceEvent(object eventId, AuditEvent auditEvent)
{
var fileName = eventId.ToString();
File.WriteAllText(fileName, auditEvent.ToJson());
}
public override T GetEvent<T>(object eventId)
{
var fileName = eventId.ToString();
return JsonConvert.DeserializeObject<T>(File.ReadAllText(fileName));
}
// async implementation:
public override async Task<object> InsertEventAsync(AuditEvent auditEvent)
{
var fileName = $"Log{Guid.NewGuid()}.json";
await File.WriteAllTextAsync(fileName, auditEvent.ToJson());
return fileName;
}
public override async Task ReplaceEventAsync(object eventId, AuditEvent auditEvent)
{
var fileName = eventId.ToString();
await File.WriteAllTextAsync(fileName, auditEvent.ToJson());
}
public override async Task<T> GetEventAsync<T>(object eventId)
{
var fileName = eventId.ToString();
return await GetFromFileAsync<T>(fileName);
}
}


Data provider selection

The data provider can be set globally for the entire application or per audit scope.


To set the global data provider assign the DataProvider property on the static Audit.Core.Configuration object. For example:


c#
Audit.Core.Configuration.DataProvider = new MyCustomDataProvider();


Or using the fluent API UseCustomProvider method:


c#
Audit.Core.Configuration.Setup()
.UseCustomProvider(new MyCustomDataProvider());


You can also set the global data provider with a factory method that is called when an Audit Scope is created. For example:


c#
Audit.Core.Configuration.DataProviderFactory = () => new LazyDataProvider();


Or using the fluent API UseFactory:


c#
Audit.Core.Configuration.Setup()
.UseFactory(() => new LazyDataProvider());


NOTE: If you don't specify a global data provider, it will default to a FileDataProvider that logs events as .json files into the current working directory.


See Configuration section for more information.


To set the data provider per-scope, use the AuditScopeOptions when creating an AuditScope. For example:


c#
var scope = AuditScope.Create(new AuditScopeOptions
{
DataProvider = new MyCustomDataProvider(), ... }
);


Dynamic data providers

As an alternative to creating a data provider class, you can define the mechanism at run time by using the DynamicDataProvider or DynamicAsyncDataProvider classes. For example:


c#
var dataProvider = new DynamicDataProvider();
// Attach an action for insert
dataProvider.AttachOnInsert(ev => Console.Write(ev.ToJson()));
Audit.Core.Configuration.DataProvider = dataProvider;


Or by using the fluent API:


c#
Audit.Core.Configuration.Setup()
.UseDynamicProvider(config => config
.OnInsert(ev => Console.Write(ev.ToJson())));


For async operations you should use the DynamicAsyncDataProvider, for example:


c#
var dataProvider = new DynamicAsyncDataProvider();
dataProvider.AttachOnInsert(async ev => await File.WriteAllTextAsync(filePath, ev.ToJson()));
Audit.Core.Configuration.DataProvider = dataProvider;


Or by using the fluent API:


c#
Audit.Core.Configuration.Setup()
.UseDynamicAsyncProvider(config => config
.OnInsert(async ev => await File.WriteAllTextAsync(filePath, ev.ToJson())));


Data providers included

The Data Providers included are summarized in the following table:


Data Provider | Package | Description | Configuration API |
------------ | ---------------- | ----------------------------------------------------------------- | ------------------ |
FileDataProvider | Audit.NET | Store the audit logs as files. Dynamically configure the directory and path. | .UseFileLogProvider()
EventLogDataProvider | Audit.NET Audit.NET.EventLog.Core | Write the audit logs to the Windows EventLog. | .UseEventLogProvider()
DynamicDataProvider / DynamicAsyncDataProvider | Audit.NET | Dynamically change the behavior at run-time. Define Insert and a Replace actions with lambda expressions. | .UseDynamicProvider() / .UseDynamicAsyncProvider()
SqlDataProvider | Audit.NET.SqlServer | Store the events as rows in a MS SQL Table, in JSON format. | .UseSqlServer()
MySqlDataProvider | Audit.NET.MySql | Store the events as rows in a MySQL database table, in JSON format. | .UseMySql()
PostgreSqlDataProvider | Audit.NET.PostgreSql | Store the events as rows in a PostgreSQL database table, in JSON format. | .UsePostgreSql()
MongoDataProvider | Audit.NET.MongoDB | Store the events in a Mongo DB collection, in BSON format. | .UseMongoDB()
AzureCosmosDataProvider | Audit.NET.AzureCosmos | Store the events in an Azure Cosmos DB container, in JSON format. | .UseAzureCosmos()
AzureStorageBlobDataProvider | Audit.NET.AzureStorageBlobs | Store the events in an Azure Blob Storage container, in JSON format. | .UseAzureStorageBlobs()
AzureTableDataProvider | Audit.NET.AzureStorage | Store the events in an Azure Table. | .UseAzureTableStorage()
UdpDataProvider | Audit.NET.Udp | Send Audit Logs as UDP datagrams to a network. | .UseUdp()
RedisDataProvider | Audit.NET.Redis | Store audit logs in Redis as Strings, Lists, SortedSets, Hashes or publish to a PubSub channel. | .UseRedis()
Log4netDataProvider | Audit.NET.log4net | Store the audit events using Apache log4net™. | .UseLog4net()
EntityFrameworkDataProvider | Audit.EntityFramework | Store EntityFramework audit events in the same EF context. (This data provider can only be used for Entity Framework audits) | .UseEntityFramework()
ElasticsearchDataProvider | Audit.NET.Elasticsearch | Store audit events in Elasticsearch indices. | .UseElasticsearch()
DynamoDataProvider | Audit.NET.DynamoDB | Store audit events in Amazon DynamoDB™ tables. | .UseDynamoDB()
NLogDataProvider | Audit.NET.NLog | Store the audit events using NLog. | .UseNLog()
AmazonQldbDataProvider | Audit.NET.AmazonQLDB | Store the audit events using Amazon QLDB. | .UseAmazonQldb()
KafkaDataProvider | Audit.NET.Kafka | Stream the audit events to Apache Kafka topics. | .UseKafka() / .UseKafka<TKey>()


Event Creation Policy

The audit scope can be configured to call its data provider in different ways:
- Insert on End: (default)
The audit event is inserted when the scope is disposed.



You can set the Creation Policy per-scope, for example to explicitly set the Creation Policy to Manual:
c#
using (var scope = AuditScope.Create(new AuditScopeOptions { CreationPolicy = EventCreationPolicy.Manual }))
{
//...
scope.Save();
}



If you don't provide a Creation Policy, the default Creation Policy configured will be used (see the configuration section).



AuditScope statechart

The following is the internal state machine representation of the AuditScope object:



Configuration
Data provider

To change the default data provider, set the static property DataProvider on Audit.Core.Configuration class. This should be done prior to the AuditScope creation, i.e. during application startup.


For example, to set your own provider as the default data provider:
c#
Audit.Core.Configuration.DataProvider = new MyCustomDataProvider();



If you don't specify a Data Provider, a default FileDataProvider will be used to write the events as .json files into the current working directory.



Creation Policy

To change the default creation policy, set the static property CreationPolicy on Audit.Core.Configuration class. This should be done prior to the AuditScope creation, i.e. during application startup.


For example, to set the default creation policy to Manual:
c#
Audit.Core.Configuration.CreationPolicy = EventCreationPolicy.Manual;



If you don't specify a Creation Policy, the default Insert on End will be used.



Custom Actions

You can configure Custom Actions that are executed for all the Audit Scopes in your application. This allows to globally change the behavior and data, intercepting the scopes after they are created or before they are saved.


Call the static AddCustomAction() method on Audit.Core.Configuration class to attach a custom action.


For example, to globally discard the events under a certain condition:
c#
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, scope =>
{
if (DateTime.Now.Hour == 17) // Tea time
{
scope.Discard();
}
});


Or to add custom fields / comments globally to all scopes:
c#
Audit.Core.Configuration.AddCustomAction(ActionType.OnEventSaving, scope =>
{
if (scope.Event.Environment.Exception != null)
{
scope.SetCustomField("Oops", true);
}
scope.Comment("Saved at " + DateTime.Now);
});


Custom actions can also be asynchronous, for example:
c#
Audit.Core.Configuration.AddCustomAction(ActionType.OnScopeCreated, async scope =>
{
var result = await svcProvider.GetService<InfoService>().GetInfoAsync();
scope.SetCustomField("Info", result);
});


The ActionType indicates when to perform the action. The allowed values are:
- OnScopeCreated: When the Audit Scope is being created, before any saving. This is executed once per Audit Scope.
- OnEventSaving: When an Audit Scope's Event is about to be saved.
- OnEventSaved: After an Audit Scope's Event is saved.


Global switch off

You can disable audit logging by setting the static property Configuration.AuditDisabled to true.
The audit events are globally ignored while this flag is set. For example to disable the audits on certain environment:


c#
if (environment.IsDevelopment())
{
Audit.Core.Configuration.AuditDisabled = true;
}


Global serialization settings

Most of the data providers serializes audit events in JSON format.


The default mechanism for serialization depends on the target framework of your application:



You can change the settings for the default serialization mechanism via the static property Configuration.JsonSettings.


For example, when using Newtonsoft.Json:


c#
Audit.Core.Configuration.JsonSettings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full,
Converters = new List<JsonConverter>() { new MyStreamConverter() }
};


Or, if you target net5.0, using System.Text.Json:


c#
Audit.Core.Configuration.JsonSettings = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
AllowTrailingCommas = true
};


Custom serialization mechanism

If you want to use a custom JSON serialization mechanism, you should create a class implementing IJsonAdapter and assign it to the
static property Configuration.JsonAdapter.



NOTE: Take into account that some of the AuditEvent properties relies on attribute decoration for the serialization / deserialization mechanism (i.e. [JsonExtensionData] and [JsonIgnore]). So if you change the default serialization mechanism, some of the AuditEvent fields could be serialized differently than expected. For example on .NET 5.0, the AuditEvent.CustomFields property is decorated with JsonExtensionData from System.Text.Json, so if the audit event is serialized using Newtonsoft.Json, then the CustomFields property will be serialized as a normal field.



For example:
c#
Audit.Core.Configuration.JsonAdapter = new MyCustomAdapter();


Or by using the fluent API:
c#
Audit.Core.Configuration.Setup()
.JsonAdapter<MyCustomAdapter>()
...


Alternative serialization mechanism

There are also two libraries provided to use the alternative JSON serialization mechanism:



Configuration Fluent API

Alternatively to the properties/methods mentioned before, you can configure the library using a convenient Fluent API provided by the method Audit.Core.Configuration.Setup(), this is the most straightforward way to configure the library.


For example, to set the FileLog Provider with its default settings using a Manual creation policy:
c#
Audit.Core.Configuration.Setup()
.UseFileLogProvider()
.WithCreationPolicy(EventCreationPolicy.Manual);


Configuration examples
File log provider with dynamic directory path and filename:

c#
Audit.Core.Configuration.Setup()
.UseFileLogProvider(config => config
.DirectoryBuilder(_ => [email protected]"C:\Logs\{DateTime.Now:yyyy-MM-dd}")
.FilenameBuilder(auditEvent => $"{auditEvent.Environment.UserName}_{DateTime.Now.Ticks}.json"));


File log provider with an InsertOnStart-ReplaceOnEnd creation policy, and a global custom field set in a custom action:

c#
Audit.Core.Configuration.Setup()
.UseFileLogProvider(config => config
.FilenamePrefix("Event_")
.Directory(@"C:\AuditLogs\1"))
.WithCreationPolicy(EventCreationPolicy.InsertOnStartReplaceOnEnd)
.WithAction(x => x.OnScopeCreated(scope => scope.SetCustomField("ApplicationId", "MyApplication")));


Event log provider with an InsertOnEnd creation policy:

c#
Audit.Core.Configuration.Setup()
.UseEventLogProvider(config => config
.SourcePath("My Audited Application")
.LogName("Application"))
.WithCreationPolicy(EventCreationPolicy.InsertOnEnd);


Dynamic provider to log to the console:

c#
Audit.Core.Configuration.Setup()
.UseDynamicProvider(config => config
.OnInsert(ev => Console.WriteLine("{0}: {1}->{2}", ev.StartDate, ev.Environment.UserName, ev.EventType)));

Extensions

The following packages are extensions to log interactions with different systems such as MVC, WebApi, WCF and Entity Framework:


| Package | Description
------------ | ------------------- | ------------------
| Audit.DynamicProxy | Generate detailed audit logs for any class without changing its code by using a proxy.
| Audit.EntityFramework | Generate detailed audit logs for saving operations on Entity Framework, by inheriting from a provided DbContext or IdentityDbContext. Includes support for EF 6 and EF 7 (EF Core).
| Audit.FileSystem | Generate audit logs by intercepting file system events via FileSystemWatcher.
| Audit.HttpClient | Generate detailed client-side audit logs for HttpClient REST calls, by configuring a provided message handler.
| Audit.MVC | Generate detailed audit logs by decorating MVC Actions and Controllers with an action filter attribute. Includes support for ASP.NET Core MVC.
| Audit.SignalR | Generate audit logs for SignalR invocations by intercepting the hub processing
| Audit.WCF | Generate detailed server-side audit logs for Windows Communication Foundation (WCF) service calls, by configuring a provided behavior.
| Audit.WCF.Client | Generate detailed client-side audit logs for Windows Communication Foundation (WCF) service calls, by configuring a provided behavior.
| Audit.WebApi | Generate detailed audit logs by decorating Web API Methods and Controllers with an action filter attribute, or by using a middleware. Includes support for ASP.NET Core.


Storage providers

Apart from the FileLog, EventLog and Dynamic event storage providers, there are others included in different packages:


| Package | Description
------------- | ------------------- | ------------------
| Audit.NET.AzureCosmos | Store the events in an Azure Cosmos DB container, in JSON format.
| Audit.NET.AzureStorage | Store the events in an Azure Blob Storage container or an Azure Table using the legacy client WindowsAzure.Storage.
| Audit.NET.AzureStorageBlobs | Store the events in an Azure Blob Storage container using the latest client Azure.Storage.Blobs.
| Audit.NET.DynamoDB | Store the audit events in Amazon DynamoDB tables.
| Audit.NET.Elasticsearch | Store the audit events in Elasticsearch indices.
| Audit.NET.log4net | Store the audit events using Apache log4net™.
| Audit.NET.MongoDB | Store the events in a Mongo DB Collection, in BSON format.
| Audit.NET.MySql | Store the events as rows in MySQL database, in JSON format.
| Audit.NET.NLog | Store the audit events using NLog™.
| Audit.NET.PostgreSql | Store the events as rows in a PostgreSQL database, in JSON format.
| Audit.NET.Redis | Store Audit Logs in a Redis database as String, List, Hash, Sorted Set or publishing to a Redis PubSub channel.
| Audit.NET.SqlServer | Store the events as rows in a SQL Table, in JSON format.
| Audit.NET.Udp | Send Audit Logs as UDP datagrams to a network.
| Audit.NET.AmazonQLDB | Store the audit events in Amazon QLDB (Quantum Ledger Database).
| Audit.NET.Kafka | Stream the audit events to an Apache Kafka server.


Change Log

For detailed information on changes in new release refer to the change log.


Contribute

If you like this project please contribute in any of the following ways: