Audit.NET 23.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Audit.NET --version 23.0.0                
NuGet\Install-Package Audit.NET -Version 23.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Audit.NET" Version="23.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Audit.NET --version 23.0.0                
#r "nuget: Audit.NET, 23.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Audit.NET as a Cake Addin
#addin nuget:?package=Audit.NET&version=23.0.0

// Install Audit.NET as a Cake Tool
#tool nuget:?package=Audit.NET&version=23.0.0                

Audit.NET

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

issues build status chat / support donations
issues-openissues-closed build-status Gitter Gitter backers paypal

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, RavenDB, MongoDB, AzureBlob, AzureTables, 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, MongoClient and HttpClient.

IMPORTANT NOTE - SUPPORT FOR OLDER .NET FRAMEWORKS

Beginning with the next major version 23.*, this library and its extensions will discontinue support for older .NET Framework and Entity Framework (versions that lost Microsoft support before 2023).

For reference, please consult the following links:

This library and its extensions will maintain support for the following minimum .NET framework versions:

  • .NET Framework 4.6.2 (net462)
  • .NET Standard 2.0 (netstandard2.0)
  • .NET 6 (net6.0)

The following frameworks are slated for deprecation and removal from the list of target frameworks:

  • net45, net451, net452, net461
  • netstandard1.3, netstandard1.4, netstandard1.5, netstandard1.6
  • netcoreapp2.1, netcoreapp3.0
  • net5.0

This deprecation will result in the following changes:

  • All library versions will now default to using System.Text.Json (Newtonsoft.Json will be deprecated but still usable via the JsonAdapter).
  • Audit.EntityFramework.Core libraries will cease support for EF Core versions 3 and older, with the minimum supported version being EF Core 5 (Audit.EntityFramework will continue supporting .NET Entity Framework 6).
  • Audit.EntityFramework.Core.v3 and Audit.EntityFramework.Identity.Core.v3 libraries will be deprecated.
  • Audit.NET.JsonSystemAdapter will also be deprecated.

NuGet

NuGet Status NuGet Count

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

PM> Install-Package Audit.NET

Changelog

Check the CHANGELOG.md file.

Introduction

The Audit Scope and Audit Event are the central objects of this framework.

Audit Scope

The AuditScope serves as the central object in this framework, representing the scope of an audited operation or event. It acts as a context for auditing, capturing pertinent details like the start time, involved entities, and any additional custom information. Essentially, the AuditScope encapsulates an AuditEvent, controlling its life cycle.

The AuditScope is a disposable object, commonly utilized within a using statement to ensure proper finalization and recording of audit information upon exiting the scope.

See the audit scope statechart.

Audit Event

The AuditEvent functions as an extensible information container that captures the details of the audited operation, is the representation of the audited information within an Audit Scope. It includes details about the audited operation, such as the event type, timestamp, execution duration, and any custom fields or properties.

The AuditEvent is typically serialized into a format suitable for storage or transmission, such as JSON.

IMPORTANT NOTE - SUPPORT FOR OLDER .NET FRAMEWORKS

Beginning with the version 23.0.0, this library and its extensions has discontinued support for older .NET Framework and Entity Framework (versions that lost Microsoft support before 2023).

For reference, please consult the following links:

This library and its extensions will maintain support for the following minimum .NET framework versions:

  • .NET Framework 4.6.2 (net462)
  • .NET Standard 2.0 (netstandard2.0)
  • .NET 6 (net6.0)

The following frameworks were deprecated and removed from the list of target frameworks:

  • net45, net451, net452, net461
  • netstandard1.3, netstandard1.4, netstandard1.5, netstandard1.6
  • netcoreapp2.1, netcoreapp3.0
  • net5.0

This discontinuation led to the following modifications:

  • All library versions will now use System.Text.Json as the default (Newtonsoft.Json will be deprecated but can still be used through the JsonAdapter).
  • Support for EF Core versions 3 and earlier has been discontinued in the Audit.EntityFramework.Core libraries. The minimum supported version is now EF Core 5 (Audit.EntityFramework will continue to support .NET Entity Framework 6).
  • The libraries Audit.EntityFramework.Core.v3 and Audit.EntityFramework.Identity.Core.v3 have been deprecated.
  • Audit.NET.JsonSystemAdapter has been deprecated.

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.

There are several ways to create an Audit Scope:

  • Calling the Create() / CreateAsync() method of an AuditScopeFactory instance, for example:

    var factory = new AuditScopeFactory();
    var scope = factory.Create(new AuditScopeOptions(...));
    
  • Using the overloads of the static methods Create() / CreateAsync() on AuditScope, for example:

    var scope = AuditScope.Create("Order:Update", () => order, new { MyProperty = "value" });
    

    The first parameter of the AuditScope.Create method is an event type name intended to identify and group the events. The second is the delegate to obtain the object to track (target object). This object is passed as a Func<object> to allow the library to inspect the value at the beginning and at the disposal of the scope. It is not mandatory to supply a target object.

    You can use the overload that accepts an AuditScopeOptions instance to configure any of the available options for the scope:

    var scope = AuditScope.Create(new AuditScopeOptions()
    {
      EventType = "Order:Update",
      TargetGetter = () => order,
      ExtraFields = new { MyProperty = "value" }
    });
    
  • Using the provided fluent API, for example:

    var scope = AuditScope.Create(_ => _
        .EventType("Order:Update")
        .ExtraFields(new { MyProperty = "value" })
        .Target(() => order));
    
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:

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:

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

Note

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.

Note

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:

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:

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:

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();
    }
}

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:

{
    "EventType": "Order:Update",
    "Environment": {
        "UserName": "Federico",
        "MachineName": "HP",
        "DomainName": "HP",
        "CallingMethodName": "Audit.UnitTest.AuditTests.TestUpdate()",
        "Exception": null,
        "Culture": "en-GB"
    },
    "Activity": {
        "StartTimeUtc": "2023-12-01T17:36:52.2256288Z",
		"SpanId": "23a93b9e8cbc457f",
		"TraceId": "2d3e5e90f790c7d2274d9bb047531f66",
		"ParentId": "0000000000000000",
		"Operation": "Update"
    },
    "StartDate": "2016-08-23T11:33:14.653191Z",
    "EndDate": "2016-08-23T11:33:23.1820786Z",
    "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:

Field Name Type Description
EventType string User-defined string to group the events
Environment Environment Contains information about the execution environment
StartDate DateTime Date and time when the event has started
EndDate DateTime Date and time when the event has ended
Duration integer Duration of the event in milliseconds
Target Target User-defined tracked object
Comments Array of strings User-defined comments
CustomFields Dictionary User-defined custom fields
Field Name Type Description
UserName string Current logged user name
MachineName string Executing machine name
DomainName string Current user domain
CallingMethodName string Calling method signature information
StackTrace string The full stack trace at the moment of the audit scope creation (NULL unless it's enabled by configuration)
Exception string Indicates if an Exception has been detected (NULL if no exception has been thrown)
Culture string Current culture identifier
Field Name Type Description
Type string Tracked object type name
Old Object Value of the tracked object at the beginning of the event
New Object Value of the tracked object at the end of the event

Custom Fields and Comments

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

  • Use SetCustomField() method to add any object as an extra field to the event.
  • Use Comment() to add textual comments to the event's Comments array.

For example:

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:

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:

using (var audit = AuditScope.Create("Order:Update", () => order, new { ReferenceId = orderId }))
{
    audit.Event.CustomFields["ReferenceId"] = orderId;
}

Note

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:

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:

{
    "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:

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:

  • InsertEvent: should store the event and return a unique ID for it.
  • ReplaceEvent: should update an event given its ID. This method is only called for Creation Policies Manual or InsertOnStartReplaceOnEnd.

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

  • InsertEventAsync: Asynchronous implementation of the InsertEvent method.
  • ReplaceEventAsync: Asynchronous implementation of the ReplaceEvent method.

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

  • GetEvent: Retrieves an event by id.
  • GetEventAsync: Asynchronous implementation of the GetEvent method.

For example:

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, CancellationToken cancellationToken = default)
    {
        var fileName = $"Log{Guid.NewGuid()}.json";
        await File.WriteAllTextAsync(fileName, auditEvent.ToJson(), cancellationToken);
        return fileName;
    }
    public override async Task ReplaceEventAsync(object eventId, AuditEvent auditEvent, CancellationToken cancellationToken = default)
    {
        var fileName = eventId.ToString();
        await File.WriteAllTextAsync(fileName, auditEvent.ToJson(), cancellationToken);
    }
    public override async Task<T> GetEventAsync<T>(object eventId, CancellationToken cancellationToken = default)
    {
        var fileName = eventId.ToString();
        return await GetFromFileAsync<T>(cancellationToken);
    }
}

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:

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

Or using the fluent API UseCustomProvider method:

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:

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

Or using the fluent API UseFactory:

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:

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:

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:

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

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

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:

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
AmazonQldbDataProvider Audit.NET.AmazonQLDB Store the audit events using Amazon QLDB. .UseAmazonQldb()
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()
AzureStorageTableDataProvider Audit.NET.AzureStorageTables Store the events in an Azure Table Storage. .UseAzureTableStorage()
DynamoDataProvider Audit.NET.DynamoDB Store audit events in Amazon DynamoDB™ tables. .UseDynamoDB()
DynamicDataProvider / DynamicAsyncDataProvider Audit.NET Dynamically change the behavior at run-time. Define Insert and a Replace actions with lambda expressions. .UseDynamicProvider() / .UseDynamicAsyncProvider()
ElasticsearchDataProvider Audit.NET.Elasticsearch Store audit events in Elasticsearch indices. .UseElasticsearch()
EntityFrameworkDataProvider Audit.EntityFramework Store EntityFramework audit events in the same EF context. (This data provider can only be used for Entity Framework audits) .UseEntityFramework()
EventLogDataProvider Audit.NET Audit.NET.EventLog.Core Write the audit logs to the Windows EventLog. .UseEventLogProvider()
FileDataProvider Audit.NET Store the audit logs as files. Dynamically configure the directory and path. .UseFileLogProvider()
InMemoryDataProvider Audit.NET Store the audit logs in memory in a thread-safe list. Useful for testing purposes. .UseInMemoryProvider()
KafkaDataProvider Audit.NET.Kafka Stream the audit events to Apache Kafka topics. .UseKafka() / .UseKafka<TKey>()
Log4netDataProvider Audit.NET.log4net Store the audit events using Apache log4net™. .UseLog4net()
MongoDataProvider Audit.NET.MongoDB Store the events in a Mongo DB collection, in BSON format. .UseMongoDB()
MySqlDataProvider Audit.NET.MySql Store the events as rows in a MySQL database table, in JSON format. .UseMySql()
NLogDataProvider Audit.NET.NLog Store the audit events using NLog. .UseNLog()
PostgreSqlDataProvider Audit.NET.PostgreSql Store the events as rows in a PostgreSQL database table, in JSON format. .UsePostgreSql()
SerilogDataProvider Audit.NET.Serilog Store the audit events using Serilog™ .UseSerilog()
SqlDataProvider Audit.NET.SqlServer Store the events as rows in a MS SQL Table, in JSON format. .UseSqlServer()
RavenDbDataProvider Audit.NET.RavenDB Store the events as documents in a Raven DB database table, in JSON format. .UseRavenDB()
RedisDataProvider Audit.NET.Redis Store audit logs in Redis as Strings, Lists, SortedSets, Hashes, Streams or publish to a PubSub channel. .UseRedis()
UdpDataProvider Audit.NET.Udp Send Audit Logs as UDP datagrams to a network. .UseUdp()

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.

  • Insert on Start, Replace on End: The event (on its initial state) is inserted when the scope is created, and then the complete event information is replaced when the scope is disposed.

  • Insert on Start, Insert on End: Two versions of the event are inserted, the initial when the scope is created, and the final when the scope is disposed.

  • Manual: The event saving (insert/replace) should be explicitly invoked by calling the Save() method on the AuditScope.

You can set the Creation Policy per-scope, for example to explicitly set the Creation Policy to Manual:

using (var scope = AuditScope.Create(new AuditScopeOptions { CreationPolicy = EventCreationPolicy.Manual }))
{
    //...
    scope.Save();
}

Note

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:

<img src="https://i.imgur.com/7WqGECe.png" alt="AuditScope statecart" />

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:

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

Note

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:

Audit.Core.Configuration.CreationPolicy = EventCreationPolicy.Manual;

Note

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:

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:

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:

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.

Stack Trace

To include the stack trace details into the event environment, ensure that the IncludeStackTrace configuration is set to true. Default is false.

Audit.Core.Configuration.IncludeStackTrace = true;

or

Audit.Core.Configuration.Setup()
    .IncludeStackTrace();

Activity Trace

To include the activy trace details from System.Diagnostics.Activity API into the event, ensure that the IncludeActivityTrace configuration is set to true. Default is false.

It will include the current Activity operation name, ID, StartTime, along with associated Tags and Events.

Audit.Core.Configuration.IncludeActivityTrace = true;

or

Audit.Core.Configuration.Setup()
    .IncludeActivityTrace();

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:

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

Global serialization settings

Most of the data providers serializes audit events in JSON format. Audit.NET uses System.Text.Json by default for serialization and deserialization of audit events.

If you want to change the behavior, you can change the settings via the static property Configuration.JsonSettings.

For example:

Audit.Core.Configuration.JsonSettings = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
    AllowTrailingCommas = true
};
Custom serialization mechanism

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

For example:

Audit.Core.Configuration.JsonAdapter = new MyCustomAdapter(); 

Or by using the fluent API:

Audit.Core.Configuration.Setup()
    .JsonAdapter<MyCustomAdapter>()
    ...

Note

Take into account that some of the AuditEvent properties relies on attribute decoration for serialization and deserialization. The recommendation is to use the default adapter and, when needed, use the Newtonsoft Json adapter provided (see next section).

Alternative serialization mechanism

This library offers the option to configure an alternative JSON serialization mechanism through the following adapter:

  • Audit.NET.JsonNewtonsoftAdapter

    Use this when you prefer employing Newtonsoft.Json as the serialization mechanism.

    Assign an instance of JsonNewtonsoftAdapter to the static configuration property Configuration.JsonAdapter. For example:

    var settings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.All
    };
    Audit.Core.Configuration.JsonAdapter = new JsonNewtonsoftAdapter(settings);
    

    Alternatively, you can use the fluent configuration API with the JsonNewtonsoftAdapter() method, like this:

    Audit.Core.Configuration.Setup()
        .JsonNewtonsoftAdapter(settings)
        ...
    

    NOTE: This JsonNewtonsoftAdapter takes into account JsonExtensionDataAttribute and JsonIgnoreAttribute decorators from both System.Text.Json and Newtonsoft.Json, so the Audit Events will be properly serialized.

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:

Audit.Core.Configuration.Setup
    .IncludeStackTrace()
    .IncludeActivityTrace()
    .UseFileLogProvider()
    .WithCreationPolicy(EventCreationPolicy.Manual);

Configuration examples

File log provider with dynamic directory path and filename:
Audit.Core.Configuration.Setup()
    .UseFileLogProvider(config => config
        .DirectoryBuilder(_ => $@"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:
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:
Audit.Core.Configuration.Setup()
    .UseEventLogProvider(config => config
        .SourcePath("My Audited Application")
        .LogName("Application"))
    .WithCreationPolicy(EventCreationPolicy.InsertOnEnd);
Dynamic provider to log to the console:
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:

<a></a> Package Description
<img src="https://i.imgur.com/hVMM5WF.png" alt="icon" width="90"/> Audit.DynamicProxy Generate detailed audit logs for any class without changing its code by using a proxy.
<img src="https://i.imgur.com/wdVHFoc.png" alt="icon" width="90"/> 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).
<img src="https://i.imgur.com/Fn4thn0.png" alt="icon" width="90"/> Audit.FileSystem Generate audit logs by intercepting file system events via FileSystemWatcher.
<img src="https://i.imgur.com/8lV5DRk.png" alt="icon" width="90" /> Audit.HttpClient Generate detailed client-side audit logs for HttpClient REST calls, by configuring a provided message handler.
<img src="https://i.imgur.com/ap6CeoG.png" alt="icon" width="90"/> Audit.MVC Generate detailed audit logs by decorating MVC Actions and Controllers with an action filter attribute. Includes support for ASP.NET Core MVC.
<img src="https://i.imgur.com/GB2e52X.jpg" alt="icon" width="90"/> Audit.SignalR Generate audit logs for SignalR and SignalR Core invocations by intercepting the hub processing
<img src="https://i.imgur.com/p6knit4.png" alt="icon" width="90" /> Audit.WCF Generate detailed server-side audit logs for Windows Communication Foundation (WCF) service calls, by configuring a provided behavior.
<img src="https://i.imgur.com/p6knit4.png" alt="icon" width="90" /> Audit.WCF.Client Generate detailed client-side audit logs for Windows Communication Foundation (WCF) service calls, by configuring a provided behavior.
<img src="https://i.imgur.com/9go2b0f.png" alt="icon" width="90"/> 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.
<img src="https://i.imgur.com/1nMVLQo.png" alt="icon" width="90"/> Audit.MongoClient Generate detailed audit logs by adding a Command Event Subscriber into the configuration of the MongoDB Driver.

Storage providers

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

<a></a> Package Description
<img src="https://i.imgur.com/C0Xu3iX.png" alt="icon" width="80"/> Audit.NET.AmazonQLDB Store the audit events in Amazon QLDB (Quantum Ledger Database).
<img src="https://i.imgur.com/yeBZZiP.png" alt="icon" width="80"/> Audit.NET.AzureCosmos Store the events in an Azure Cosmos DB container, in JSON format.
<img src="https://i.imgur.com/ouaw5CX.png" alt="icon" width="80"/> Audit.NET.AzureStorage Store the events in an Azure Blob Storage container or an Azure Table using the legacy client WindowsAzure.Storage.
<img src="https://i.imgur.com/ouaw5CX.png" alt="icon" width="80"/> Audit.NET.AzureStorageBlobs Store the events in an Azure Blob Storage container using the latest client Azure.Storage.Blobs.
<img src="https://i.imgur.com/ouaw5CX.png" alt="icon" width="80"/> Audit.NET.AzureStorageTables Store the events in an Azure Table Storage using the latest client Azure.Data.Tables.
<img src="https://i.imgur.com/kIGe4Z5.png" alt="icon" width="80"/> Audit.NET.DynamoDB Store the audit events in Amazon DynamoDB tables.
<img src="https://i.imgur.com/PbeWVKz.png" alt="icon" width="80"/> Audit.NET.Elasticsearch Store the audit events in Elasticsearch indices.
<img src="https://i.imgur.com/C0Xu3iX.png" alt="icon" width="80"/> Audit.NET.Kafka Stream the audit events to an Apache Kafka server.
<img src="https://i.imgur.com/qxbK98k.png" alt="icon" width="80"/> Audit.NET.log4net Store the audit events using Apache log4net™.
<img src="https://i.imgur.com/1nMVLQo.png" alt="icon" width="80"/> Audit.NET.MongoDB Store the events in a Mongo DB Collection, in BSON format.
<img src="https://i.imgur.com/NHRBp86.png" alt="icon" width="80"/> Audit.NET.MySql Store the events as rows in MySQL database, in JSON format.
<img src="https://i.imgur.com/qxbK98k.png" alt="icon" width="80"/> Audit.NET.NLog Store the audit events using NLog™.
<img src="https://i.imgur.com/ZxbDxAU.png" alt="icon" width="80"/> Audit.NET.PostgreSql Store the events as rows in a PostgreSQL database, in JSON format.
<img src="https://i.imgur.com/C0Xu3iX.png" alt="icon" width="80"/> Audit.NET.RavenDB Store the events as documents in a Raven DB database, in JSON format.
<img src="https://i.imgur.com/abs6duI.png" alt="icon" width="80"/> Audit.NET.Redis Store Audit Logs in a Redis database as String, List, Hash, Sorted Set, Streams or publishing to a Redis PubSub channel.
<img src="https://i.imgur.com/lmzs1gw.png" alt="icon" width="80"/> Audit.NET.SqlServer Store the events as rows in a SQL Table, in JSON format.
<img src="https://i.imgur.com/POysCvd.jpg" alt="icon" width="80"/> Audit.NET.Serilog Store the audit events using Serilog™
<img src="https://i.imgur.com/FItQD9n.png" alt="icon" width="80"/> Audit.NET.Udp Send Audit Logs as UDP datagrams to a network.

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:

  • Star this project on GitHub.
  • Request a new feature or expose any bug you found by creating a new issue.
  • Ask any questions about the library on StackOverflow.
  • Subscribe to and use the Gitter Audit.NET channel.
  • Support the project by becoming a Backer: Backer    
  • Spread the word by blogging about it, or sharing it on social networks: <p class="share-buttons"> <a href="https://www.facebook.com/sharer/sharer.php?u=https://nuget.org/packages/Audit.NET/&t=Check+out+Audit.NET" target="_blank"> <img width="24" height="24" alt="Share this package on Facebook" src="https://nuget.org/Content/gallery/img/facebook.svg" / > </a> <a href="https://twitter.com/intent/tweet?url=https://nuget.org/packages/Audit.NET/&text=Check+out+Audit.NET" target="_blank"> <img width="24" height="24" alt="Tweet this package" src="https://nuget.org/Content/gallery/img/twitter.svg" /> </a> </p>
  • Make a donation via PayPal: paypal
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 is compatible.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (65)

Showing the top 5 NuGet packages that depend on Audit.NET:

Package Downloads
Audit.EntityFramework.Core

Generate Audit Logs from EntityFramework context changes

Audit.NET.PostgreSql

Store Audit.NET Trail Logs into a PostgreSQL database

Audit.NET.MongoDB

Store Audit.NET Trail Logs into a MongoDB database

Audit.WebApi.Core

Generate detailed Audit Logs for AspNet Core Web API Controller calls.

Reo.Core.AutoHistory

Package Description

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on Audit.NET:

Repository Stars
thepirat000/spleeter-api
Audio separation API using Spleeter from Deezer
Version Downloads Last updated
27.1.1 33,145 10/28/2024
27.1.0 7,804 10/24/2024
27.0.3 77,476 9/25/2024
27.0.2 18,633 9/19/2024
27.0.1 40,310 9/4/2024
27.0.0 7,106 9/3/2024
26.0.1 53,534 8/22/2024
26.0.0 134,506 7/19/2024
25.0.7 116,824 7/4/2024
25.0.6 43,011 6/24/2024
25.0.5 31,690 6/18/2024
25.0.4 426,151 3/24/2024
25.0.3 23,532 3/13/2024
25.0.2 5,193 3/12/2024
25.0.1 45,534 2/28/2024
25.0.0 172,537 2/16/2024
24.0.1 40,782 2/12/2024
24.0.0 4,165 2/12/2024
23.0.0 310,166 12/14/2023
22.1.0 100,699 12/9/2023
22.0.2 40,666 12/1/2023
22.0.1 105,499 11/16/2023
22.0.0 12,382 11/14/2023
21.1.0 154,116 10/9/2023
21.0.4 127,461 9/15/2023
21.0.3 456,811 7/9/2023
21.0.2 15,068 7/6/2023
21.0.1 189,409 5/27/2023
21.0.0 237,261 4/15/2023
20.2.4 315,316 3/27/2023
20.2.3 245,103 3/17/2023
20.2.2 25,100 3/14/2023
20.2.1 28,606 3/11/2023
20.2.0 36,266 3/7/2023
20.1.6 123,442 2/23/2023
20.1.5 128,328 2/9/2023
20.1.4 117,637 1/28/2023
20.1.3 298,677 12/21/2022
20.1.2 59,308 12/14/2022
20.1.1 24,300 12/12/2022
20.1.0 48,332 12/4/2022
20.0.4 61,854 11/30/2022
20.0.3 207,210 10/28/2022
20.0.2 24,570 10/26/2022
20.0.1 38,183 10/21/2022
20.0.0 353,289 10/1/2022
19.4.1 177,095 9/10/2022
19.4.0 101,473 9/2/2022
19.3.0 68,806 8/23/2022
19.2.2 69,664 8/11/2022
19.2.1 37,935 8/6/2022
19.2.0 88,796 7/24/2022
19.1.4 432,404 5/23/2022
19.1.3 15,475 5/22/2022
19.1.2 32,239 5/18/2022
19.1.1 161,130 4/28/2022
19.1.0 291,732 4/10/2022
19.0.7 344,310 3/13/2022
19.0.6 45,277 3/7/2022
19.0.5 267,232 1/28/2022
19.0.4 69,111 1/23/2022
19.0.3 278,036 12/14/2021
19.0.2 18,152 12/11/2021
19.0.1 243,159 11/20/2021
19.0.0 59,279 11/11/2021
19.0.0-rc.net60.2 4,957 9/26/2021
19.0.0-rc.net60.1 271 9/16/2021
18.1.6 1,147,533 9/26/2021
18.1.5 147,479 9/7/2021
18.1.4 65,579 9/6/2021
18.1.3 50,530 8/19/2021
18.1.2 39,136 8/8/2021
18.1.1 28,699 8/5/2021
18.1.0 35,933 8/1/2021
18.0.1 12,276 7/30/2021
18.0.0 22,907 7/26/2021
17.0.8 108,158 7/7/2021
17.0.7 83,156 6/16/2021
17.0.6 36,230 6/5/2021
17.0.5 28,496 5/28/2021
17.0.4 110,638 5/4/2021
17.0.3 17,041 5/1/2021
17.0.2 67,492 4/22/2021
17.0.1 30,360 4/18/2021
17.0.0 144,970 3/26/2021
16.5.6 20,832 3/25/2021
16.5.5 34,288 3/23/2021
16.5.4 35,838 3/9/2021
16.5.3 29,067 2/26/2021
16.5.2 13,153 2/23/2021
16.5.1 25,870 2/21/2021
16.5.0 34,331 2/17/2021
16.4.5 25,087 2/15/2021
16.4.4 26,720 2/5/2021
16.4.3 22,777 1/27/2021
16.4.2 22,448 1/22/2021
16.4.1 11,963 1/21/2021
16.4.0 42,146 1/11/2021
16.3.3 10,372 1/8/2021
16.3.2 23,466 1/3/2021
16.3.1 13,036 12/31/2020
16.3.0 25,808 12/30/2020
16.2.1 141,471 12/27/2020
16.2.0 232,811 10/13/2020
16.1.5 59,964 10/4/2020
16.1.4 63,565 9/17/2020
16.1.3 20,198 9/13/2020
16.1.2 19,767 9/9/2020
16.1.1 41,639 9/3/2020
16.1.0 367,177 8/19/2020
16.0.3 63,091 8/15/2020
16.0.2 47,491 8/9/2020
16.0.1 10,624 8/8/2020
16.0.0 32,101 8/7/2020
15.3.0 598,043 7/23/2020
15.2.3 47,258 7/14/2020
15.2.2 261,987 5/19/2020
15.2.1 67,163 5/12/2020
15.2.0 14,074 5/9/2020
15.1.1 34,048 5/4/2020
15.1.0 72,195 4/13/2020
15.0.5 134,864 3/18/2020
15.0.4 49,836 2/28/2020
15.0.3 10,611 2/26/2020
15.0.2 220,960 1/20/2020
15.0.1 42,499 1/10/2020
15.0.0 35,727 12/17/2019
14.9.1 138,778 11/30/2019
14.9.0 10,153 11/29/2019
14.8.1 32,620 11/26/2019
14.8.0 55,339 11/20/2019
14.7.0 175,855 10/9/2019
14.6.6 10,518 10/8/2019
14.6.5 74,081 9/27/2019
14.6.4 15,178 9/21/2019
14.6.3 97,541 8/12/2019
14.6.2 22,833 8/3/2019
14.6.1 9,440 8/3/2019
14.6.0 40,557 7/26/2019
14.5.7 43,558 7/18/2019
14.5.6 72,795 7/10/2019
14.5.5 23,706 7/1/2019
14.5.4 34,452 6/17/2019
14.5.3 31,657 6/5/2019
14.5.2 30,336 5/30/2019
14.5.1 22,062 5/28/2019
14.5.0 48,596 5/24/2019
14.4.0 13,028 5/22/2019
14.3.4 25,782 5/14/2019
14.3.3 11,906 5/9/2019
14.3.2 53,105 4/30/2019
14.3.1 12,948 4/27/2019
14.3.0 14,834 4/24/2019
14.2.3 21,501 4/17/2019
14.2.2 31,929 4/10/2019
14.2.1 34,572 4/5/2019
14.2.0 75,129 3/16/2019
14.1.1 29,577 3/8/2019
14.1.0 42,172 2/11/2019
14.0.4 62,966 1/31/2019
14.0.3 19,838 1/22/2019
14.0.2 72,762 12/15/2018
14.0.1 42,376 11/29/2018
14.0.0 42,682 11/19/2018
13.3.0 11,421 11/16/2018
13.2.2 11,134 11/15/2018
13.2.1 12,907 11/13/2018
13.2.0 23,542 10/31/2018
13.1.5 10,305 10/31/2018
13.1.4 16,100 10/25/2018
13.1.3 12,866 10/18/2018
13.1.2 33,442 9/12/2018
13.1.1 11,560 9/11/2018
13.1.0 9,937 9/11/2018
13.0.0 28,714 8/29/2018
12.3.6 11,587 8/29/2018
12.3.5 43,892 8/22/2018
12.3.4 9,830 8/21/2018
12.3.3 34,063 8/21/2018
12.3.2 18,562 8/20/2018
12.3.1 9,152 8/20/2018
12.3.0 8,934 8/20/2018
12.2.2 11,784 8/15/2018
12.2.1 13,966 8/9/2018
12.2.0 9,420 8/8/2018
12.1.11 15,720 7/30/2018
12.1.10 21,903 7/20/2018
12.1.9 17,333 7/10/2018
12.1.8 12,974 7/2/2018
12.1.7 113,900 6/7/2018
12.1.6 33,884 6/4/2018
12.1.5 10,474 6/2/2018
12.1.4 17,620 5/25/2018
12.1.3 14,575 5/16/2018
12.1.2 10,198 5/15/2018
12.1.1 11,440 5/14/2018
12.1.0 16,363 5/9/2018
12.0.7 21,749 5/5/2018
12.0.6 10,784 5/4/2018
12.0.5 10,208 5/3/2018
12.0.4 13,767 4/30/2018
12.0.3 10,220 4/30/2018
12.0.2 9,846 4/27/2018
12.0.1 10,571 4/25/2018
12.0.0 10,947 4/22/2018
11.2.0 19,255 4/11/2018
11.1.0 12,662 4/8/2018
11.0.8 10,629 3/26/2018
11.0.7 10,358 3/20/2018
11.0.6 16,734 3/7/2018
11.0.5 17,765 2/22/2018
11.0.4 12,662 2/14/2018
11.0.3 10,139 2/12/2018
11.0.2 11,498 2/9/2018
11.0.1 14,081 1/29/2018
11.0.0 16,904 1/15/2018
10.0.3 12,602 12/29/2017
10.0.2 10,335 12/26/2017
10.0.1 10,033 12/18/2017
10.0.0 10,676 12/18/2017
9.3.0 9,010 12/17/2017
9.2.0 8,764 12/17/2017
9.1.3 22,129 12/5/2017
9.1.2 11,347 11/27/2017
9.1.1 10,682 11/21/2017
9.1.0 8,609 11/21/2017
9.0.1 8,982 11/11/2017
9.0.0 8,827 11/10/2017
8.7.0 23,407 11/9/2017
8.6.0 8,886 11/9/2017
8.5.0 22,586 10/3/2017
8.4.0 8,845 10/3/2017
8.3.1 13,700 9/8/2017
8.3.0 8,712 9/8/2017
8.2.0 8,697 9/4/2017
8.1.0 10,394 8/22/2017
8.0.0 25,938 8/19/2017
7.1.3 8,615 8/14/2017
7.1.2 8,507 8/2/2017
7.1.1 8,018 7/26/2017
7.1.0 11,086 7/5/2017
7.0.9 7,551 6/28/2017
7.0.8 7,527 6/19/2017
7.0.6 21,187 4/7/2017
7.0.5 6,860 3/21/2017
7.0.4 6,120 3/21/2017
7.0.3 6,245 3/20/2017
7.0.2 5,863 3/13/2017
7.0.0 7,581 3/1/2017
6.2.0 8,244 2/25/2017
6.1.0 10,229 2/14/2017
6.0.0 5,682 2/9/2017
5.3.0 5,068 2/5/2017
5.2.0 5,770 1/26/2017
5.1.0 5,134 1/19/2017
5.0.0 4,984 1/7/2017
4.11.0 5,004 1/5/2017
4.10.0 4,898 12/31/2016
4.9.0 4,968 12/26/2016
4.8.0 5,261 12/17/2016
4.7.0 5,135 12/8/2016
4.6.5 5,039 12/4/2016
4.6.4 5,045 11/25/2016
4.6.2 7,787 11/18/2016
4.6.1 4,753 11/15/2016
4.6.0 4,829 11/11/2016
4.5.9 5,336 11/2/2016
4.5.8 4,706 11/2/2016
4.5.7 4,737 10/26/2016
4.5.6 4,882 10/6/2016
4.5.5 4,680 10/3/2016
4.5.4 4,655 10/2/2016
4.5.3 4,635 9/30/2016
4.5.2 4,709 9/28/2016
4.5.1 4,549 9/28/2016
4.5.0 4,686 9/28/2016
4.4.0 4,521 9/23/2016
4.3.0 4,511 9/22/2016
4.2.0 4,355 9/19/2016
4.1.0 4,034 9/13/2016
4.0.1 4,856 9/9/2016
4.0.0 4,088 9/9/2016
3.6.0 4,648 9/7/2016
3.4.0 4,936 9/7/2016
3.3.0 3,476 9/4/2016
3.2.0 3,431 9/3/2016
3.1.0 3,594 9/2/2016
3.0.0 4,455 8/31/2016
2.5.0 4,648 8/27/2016
2.4.0 3,979 8/26/2016
2.3.0 3,620 8/22/2016
2.2.0 1,852 8/22/2016
2.1.0 3,519 8/22/2016
2.0.0 3,101 8/21/2016
1.0.0.4 3,108 8/19/2016
1.0.0.3 3,353 8/18/2016
1.0.0.1 3,763 8/18/2016
1.0.0 2,441 8/18/2016