Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adding AuditScopeFactory, IAuditScopeFactory and IAuditScope #322

Merged
merged 3 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ All notable changes to Audit.NET and its extensions will be documented in this f

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [16.0.0] - 2020-08-06
### Modified
- Audit.NET: Moving the AuditScope creation to an `AuditScopeFactory` implementing an interface `IAuditScopeFactory`.
Also added `IAuditScope` interface for unit testing. (#315, #319)
- Audit.NET: Enable disposable async for netstandard2.0 (#318)

## [15.2.4] - 2020-07-22
### Modified
- Audit.EntityFramework.Core: Changing version for Microsoft.EntityFrameworkCore reference when targeting .NET Standard 2.0 or 2.1 (now referencing Microsoft.EntityFrameworkCore 3.1.0) (#310)
Expand Down
85 changes: 54 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,50 @@ The **Audit Scope** is the central object of this framework. It encapsulates an
The **Audit Event** is an extensible information container of an audited operation.
See the [audit scope statechart](#auditscope-statechart).

Create an Audit Scope by calling the static `AuditScope.Create` method.



There are several ways to create an Audit Scope:

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

```c#
var factory = new AuditScopeFactory();
var scope = factory.Create(new AuditScopeOptions(...));
```

- Using the overloads of the static methods `Create()` / `CreateAsync()` on `AuditScope`, for example:

```c#
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:

```c#
var scope = AuditScope.Create(new AuditScopeOptions()
{
EventType = "Order:Update",
TargetGetter = () => order,
ExtraFields = new { MyProperty = "value" }
});
```

#### AuditScopeOptions properties

Option field | 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 | `obejct` | Anonymous object that contains additional fields to be merged into the audit event
DataProvider | `AuditDataProvider` | The [data provider](#data-providers) to use. Defaults to the DataProvider configured on `Audit.Core.Configuration.DataProvider`
CreationPolicy | `EventCreationPolicy` | The [creation policy](#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#
Expand All @@ -62,24 +104,6 @@ using (AuditScope.Create("Order:Update", () => 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.

The first parameter of the `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, pass `null` when you don't want to track a specific object.

There is also a unified overload of the `Create` method that accepts an instance of [`AuditScopeOptions`](https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.NET/AuditScopeOptions.cs). Use this class to configure any of the available options for the scope:

```c#
var options = new AuditScopeOptions()
{
EventType = "MyEvent",
CreationPolicy = EventCreationPolicy.Manual,
ExtraFields = new { Action = this.Action },
AuditEvent = new MyCustomAuditEvent()
};
using (var scope = AuditScope.Create(options))
{
// ...
}
```

> When using the [extensions](#extensions) that logs interactions with different systems, like [Audit.EntityFramework](https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.EntityFramework/README.md), [Audit.WebApi](https://github.com/thepirat000/Audit.NET/blob/master/src/Audit.WebApi/README.md), etc. you don't need to explicitly create the `AuditScope` or `AuditEvent`, they are created internally by the extension.

### Simple logging
Expand All @@ -104,7 +128,12 @@ public class SomethingThatStartsAndEnds
public void Start()
{
// Create a manual scope
auditScope = AuditScope.Create("MyEvent", () => Status, EventCreationPolicy.Manual);
auditScope = AuditScope.Create(new AuditScopeOptions()
{
EventType = "MyEvent",
TargetGetter = () => this.Status,
CreationPolicy = EventCreationPolicy.Manual
});
}

public void End()
Expand Down Expand Up @@ -140,7 +169,7 @@ public async Task SaveOrderAsync(Order order)
}
```

> Note: On older .NET framework versions [the `Dispose` method was always synchronous](https://github.com/dotnet/roslyn/issues/114), 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 .NET Core starting on version 3.0 and C# 8, you can simply use the `await using` statement, since the `AuditScope` implements the [`IAsyncDisposable` interface](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncdisposable).
> Note: On older .NET framework versions [the `Dispose` method was always synchronous](https://github.com/dotnet/roslyn/issues/114), 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](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncdisposable).

## Output

Expand Down Expand Up @@ -317,7 +346,7 @@ The `AuditScope` object has a `Discard()` method to allow the user to discard an
For example, if you want to avoid saving the audit event under certain condition:

```c#
using (var scope = AuditScope.Create("SomeEvent", () => someTarget))
using (var scope = AuditScope.Create(new AuditScopeOptions("SomeEvent", () => someTarget)))
{
try
{
Expand Down Expand Up @@ -409,15 +438,9 @@ Audit.Core.Configuration.Setup()

See [Configuration section](#configuration) for more information.

You can also set the data provider per-scope, by using an appropriate overload of the `AuditScope.Create` method. For example:
```c#
AuditScope.Create("Order:Update", () => order, EventCreationPolicy.Manual, new MyCustomDataProvider());
```

Or:

You can also set the data provider per-scope. For example:
```c#
AuditScope.Create(new AuditScopeOptions { DataProvider = new MyCustomDataProvider() });
AuditScope.Create(new AuditScopeOptions { DataProvider = new MyCustomDataProvider(), ... });
```


Expand Down
4 changes: 2 additions & 2 deletions src/Audit.DynamicProxy/Audit.DynamicProxy.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs by intercepting operation calls on any class without changing its code.</Description>
<Copyright>Copyright 2019</Copyright>
<AssemblyTitle>Audit.DynamicProxy</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;net45</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand All @@ -31,7 +31,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Castle.Core" Version="4.3.0" />
<PackageReference Include="Castle.Core" Version="4.4.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>

Expand Down
11 changes: 6 additions & 5 deletions src/Audit.DynamicProxy/AuditInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class AuditInterceptor : IInterceptor
/// <summary>
/// Intercept an asynchronous operation that returns a Task.
/// </summary>
private static async Task InterceptAsync(Task task, IInvocation invocation, InterceptEvent intEvent, AuditScope scope)
private static async Task InterceptAsync(Task task, IInvocation invocation, InterceptEvent intEvent, IAuditScope scope)
{
try
{
Expand All @@ -53,7 +53,7 @@ private static async Task InterceptAsync(Task task, IInvocation invocation, Inte
/// <summary>
/// Intercept an asynchronous operation that returns a Task Of[T].
/// </summary>
private static async Task<T> InterceptAsync<T>(Task<T> task, IInvocation invocation, InterceptEvent intEvent, AuditScope scope)
private static async Task<T> InterceptAsync<T>(Task<T> task, IInvocation invocation, InterceptEvent intEvent, IAuditScope scope)
{
T result;
try
Expand All @@ -72,7 +72,7 @@ private static async Task<T> InterceptAsync<T>(Task<T> task, IInvocation invocat
/// <summary>
/// Ends the event for asynchronous interceptions.
/// </summary>
private static void EndAsyncAuditInterceptEvent(Task task, IInvocation invocation, InterceptEvent intEvent, AuditScope scope, object result)
private static void EndAsyncAuditInterceptEvent(Task task, IInvocation invocation, InterceptEvent intEvent, IAuditScope scope, object result)
{
intEvent.AsyncStatus = task.Status.ToString();
if (task.Status == TaskStatus.Faulted)
Expand Down Expand Up @@ -220,7 +220,8 @@ public void Intercept(IInvocation invocation)
DataProvider = Settings.AuditDataProvider,
AuditEvent = auditEventIntercept
};
var scope = AuditScope.Create(scopeOptions);
var auditScopeFactory = Settings.AuditScopeFactory ?? Configuration.AuditScopeFactory;
var scope = auditScopeFactory.Create(scopeOptions);
AuditProxy.CurrentScope = scope;
// Call the intercepted method (sync part)
try
Expand All @@ -239,7 +240,7 @@ public void Intercept(IInvocation invocation)
{
if (typeof(Task).IsAssignableFrom(returnType))
{
invocation.ReturnValue = InterceptAsync((dynamic) invocation.ReturnValue, invocation, intEvent, scope);
invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue, invocation, intEvent, scope);
return;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Audit.DynamicProxy/AuditProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public class AuditProxy
{
private static readonly ProxyGenerator Generator = new ProxyGenerator();
[ThreadStatic]
private static AuditScope _currentScope;
private static IAuditScope _currentScope;

/// <summary>
/// Gets the scope for the current running thread. Get this property value from the audited methods to customize the Audit.
/// This property should be accessed from the thread that is executing the audited operation.
/// Calling this property from a different thread will return NULL or an unexpected value.
/// </summary>
/// <value>The current scope related to the running thread, or NULL.</value>
public static AuditScope CurrentScope
public static IAuditScope CurrentScope
{
get => _currentScope;
internal set => _currentScope = value;
Expand Down
4 changes: 4 additions & 0 deletions src/Audit.DynamicProxy/InterceptionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,9 @@ public class InterceptionSettings
/// Gets or sets the event creation policy to use for this interception. Default is NULL to use the globally configured creation policy.
/// </summary>
public EventCreationPolicy? EventCreationPolicy { get; set; }
/// <summary>
/// Gets or sets the custom audit scope factory. Default is NULL to use the general AuditScopeFactory.
/// </summary>
public IAuditScopeFactory AuditScopeFactory { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs from EntityFramework context changes</Description>
<Copyright>Copyright 2016</Copyright>
<AssemblyTitle>Audit.EntityFramework.Core</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.0;net461;netstandard2.1;net472</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME;EF_CORE</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs from EntityFramework identity context changes</Description>
<Copyright>Copyright 2018</Copyright>
<AssemblyTitle>Audit.EntityFramework.Identity.Core</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.1;net461</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Generate Audit Logs from EntityFramework identity context changes</Description>
<Copyright>Copyright 2018</Copyright>
<AssemblyTitle>Audit.EntityFramework.Identity</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.0;net45;netstandard2.1</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// </summary>
public AuditDataProvider AuditDataProvider { get; set; }

/// <summary>
/// To indicate a custom audit scope factory. (Default is NULL to use the Audit.Core.Configuration.DefaultAuditScopeFactory).
/// </summary>
public IAuditScopeFactory AuditScopeFactory { get; set; }

/// <summary>
/// Indicates if the Audit is disabled.
/// Default is false.
Expand Down Expand Up @@ -192,7 +197,7 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeCreated(AuditScope auditScope)
public virtual void OnScopeCreated(IAuditScope auditScope)
{
}

Expand All @@ -201,7 +206,7 @@ public virtual void OnScopeCreated(AuditScope auditScope)
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeSaving(AuditScope auditScope)
public virtual void OnScopeSaving(IAuditScope auditScope)
{
}

Expand Down
9 changes: 7 additions & 2 deletions src/Audit.EntityFramework.Identity/AuditIdentityDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// </summary>
public AuditDataProvider AuditDataProvider { get; set; }

/// <summary>
/// To indicate a custom audit scope factory. (Default is NULL to use the Audit.Core.Configuration.DefaultAuditScopeFactory).
/// </summary>
public IAuditScopeFactory AuditScopeFactory { get; set; }

/// <summary>
/// Indicates if the Audit is disabled.
/// Default is false.
Expand Down Expand Up @@ -180,7 +185,7 @@ public AuditIdentityDbContext(DbConnection existingConnection, DbCompiledModel m
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeCreated(AuditScope auditScope)
public virtual void OnScopeCreated(IAuditScope auditScope)
{
}

Expand All @@ -189,7 +194,7 @@ public virtual void OnScopeCreated(AuditScope auditScope)
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeSaving(AuditScope auditScope)
public virtual void OnScopeSaving(IAuditScope auditScope)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/Audit.EntityFramework/Audit.EntityFramework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Description>Generate Audit Logs from EntityFramework context changes</Description>
<Copyright>Copyright 2016</Copyright>
<AssemblyTitle>Audit.EntityFramework</AssemblyTitle>
<VersionPrefix>15.3.0</VersionPrefix>
<VersionPrefix>16.0.0</VersionPrefix>
<Authors>Federico Colombo</Authors>
<TargetFrameworks>netstandard1.5;netstandard2.0;net45;netstandard2.1;net472</TargetFrameworks>
<DefineConstants>$(DefineConstants);STRONG_NAME</DefineConstants>
Expand Down
9 changes: 7 additions & 2 deletions src/Audit.EntityFramework/AuditDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ protected AuditDbContext() : base()
/// </summary>
public virtual AuditDataProvider AuditDataProvider { get; set; }

/// <summary>
/// To indicate a custom audit scope factory. (Default is NULL to use the Audit.Core.Configuration.DefaultAuditScopeFactory).
/// </summary>
public virtual IAuditScopeFactory AuditScopeFactory { get; set; }

/// <summary>
/// Optional custom fields added to the audit event
/// </summary>
Expand Down Expand Up @@ -149,15 +154,15 @@ protected AuditDbContext() : base()
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeCreated(AuditScope auditScope)
public virtual void OnScopeCreated(IAuditScope auditScope)
{
}
/// <summary>
/// Called after the EF operation execution and before the AuditScope saving.
/// Override to specify custom logic.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public virtual void OnScopeSaving(AuditScope auditScope)
public virtual void OnScopeSaving(IAuditScope auditScope)
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/Audit.EntityFramework/AuditEventExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public static class AuditEventExtensions
/// Gets the Entity Framework Event portion of the Audit Event on the given scope.
/// </summary>
/// <param name="auditScope">The audit scope.</param>
public static EntityFrameworkEvent GetEntityFrameworkEvent(this AuditScope auditScope)
public static EntityFrameworkEvent GetEntityFrameworkEvent(this IAuditScope auditScope)
{
return auditScope?.Event.GetEntityFrameworkEvent();
}
Expand Down
Loading