Audit Logging | ABP.IO Documentation (original) (raw)

Wikipedia: "An audit trail (also called audit log) is a security-relevant chronological record, set of records, and/or destination and source of records that provide documentary evidence of the sequence of activities that have affected at any time a specific operation, procedure, or event".

ABP provides an extensible audit logging system that automates the audit logging by convention and provides configuration points to control the level of the audit logs.

An audit log object (see the Audit Log Object section below) is typically created & saved per web request. It includes;

Startup templates are configured for the audit logging system which is suitable for most of the applications. Use this document for a detailed control over the audit log system.

Database Provider Support

UseAuditing()

UseAuditing() middleware should be added to the ASP.NET Core request pipeline in order to create and save the audit logs. If you've created your applications using the startup templates, it is already added.

AbpAuditingOptions

AbpAuditingOptions is the main options object to configure the audit log system. You can configure it in the ConfigureServices method of your module:

Configure<AbpAuditingOptions>(options =>
{
    options.IsEnabled = false; //Disables the auditing system
});

Here, a list of the options you can configure:

Entity History Selectors

Saving all changes of all your entities would require a lot of database space. For this reason, audit log system doesn't save any change for the entities unless you explicitly configure it.

To save all changes of all entities, simply use the AddAllEntities() extension method.

Configure<AbpAuditingOptions>(options =>
{
    options.EntityHistorySelectors.AddAllEntities();
});

options.EntityHistorySelectors actually a list of type predicate. You can write a lambda expression to define your filter.

The example selector below does the same of the AddAllEntities() extension method defined above:

Configure<AbpAuditingOptions>(options =>
{
    options.EntityHistorySelectors.Add(
        new NamedTypeSelector(
            "MySelectorName",
            type =>
            {
                if (typeof(IEntity).IsAssignableFrom(type))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        )
    );
});

The condition typeof(IEntity).IsAssignableFrom(type) will be true for any class implements the IEntity interface (this is technically all the entities in your application). You can conditionally check and return true or false based on your preference.

options.EntityHistorySelectors is a flexible and dynamic way of selecting the entities for audit logging. Another way is to use the Audited and DisableAuditing attributes per entity.

AbpAspNetCoreAuditingOptions

AbpAspNetCoreAuditingOptions is the options object to configure audit logging in the ASP.NET Core layer. You can configure it in the ConfigureServices method of your module:

Configure<AbpAspNetCoreAuditingOptions>(options =>
{
    options.IgnoredUrls.Add("/products");
});

IgnoredUrls is the only option. It is a list of ignored URLs prefixes. In the preceding example, all URLs starting with /products will be ignored for audit logging.

AbpAspNetCoreAuditingUrlOptions

AbpAspNetCoreAuditingUrlOptions is the options object to configure audit logging in the ASP.NET Core layer. You can configure it in the ConfigureServices method of your module:

Configure<AbpAspNetCoreAuditingUrlOptions>(options =>
{
    options.IncludeQuery = true;
});

Here, a list of the options you can configure:

Enabling/Disabling Audit Logging for Services

Enable/Disable for Controllers & Actions

All the controller actions are logged by default (see IsEnabledForGetRequests above for GET requests).

You can use the [DisableAuditing] to disable it for a specific controller type:

[DisableAuditing]
public class HomeController : AbpController
{
    //...
}

Use [DisableAuditing] for any action to control it in the action level:

public class HomeController : AbpController
{
    [DisableAuditing]
    public async Task<ActionResult> Home()
    {
        //...
    }

    public async Task<ActionResult> OtherActionLogged()
    {
        //...
    }
}

Enable/Disable for Application Services & Methods

Application service method calls also included into the audit log by default. You can use the [DisableAuditing] in service or method level.

Enable/Disable for Other Services

Action audit logging can be enabled for any type of class (registered to and resolved from the dependency injection) while it is only enabled for the controllers and the application services by default.

Use [Audited] and [DisableAuditing] for any class or method that need to be audit logged. In addition, your class can (directly or inherently) implement the IAuditingEnabled interface to enable the audit logging for that class by default.

Enable/Disable for Entities & Properties

An entity is ignored on entity change audit logging in the following cases;

Otherwise, you can use Audited to enable entity change audit logging for an entity:

[Audited]
public class MyEntity : Entity<Guid>
{
    //...
}

Or disable it for an entity:

[DisableAuditing]
public class MyEntity : Entity<Guid>
{
    //...
}

Disabling audit logging can be necessary only if the entity is being selected by the AbpAuditingOptions.EntityHistorySelectors that explained before.

You can disable auditing only some properties of your entities for a detailed control over the audit logging:

[Audited]
public class MyUser : Entity<Guid>
{
    public string Name { get; set; }
        
    public string Email { get; set; }

    [DisableAuditing] //Ignore the Passoword on audit logging
    public string Password { get; set; }
}

Audit log system will save changes for the MyUser entity while it ignores the Password property which can be dangerous to save for security purposes.

In some cases, you may want to save a few properties but ignore all others. Writing [DisableAuditing] for all the other properties would be tedious. In such cases, use [Audited] only for the desired properties and mark the entity with the [DisableAuditing] attribute:

[DisableAuditing]
public class MyUser : Entity<Guid>
{
    [Audited] //Only log the Name change
    public string Name { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

IAuditingStore

IAuditingStore is an interface that is used to save the audit log objects (explained below) by the ABP. If you need to save the audit log objects to a custom data store, you can implement the IAuditingStore in your own application and replace using the dependency injection system.

SimpleLogAuditingStore is used if no audit store was registered. It simply writes the audit object to the standard logging system.

The Audit Logging Module has been configured in the startup templates saves audit log objects to a database (it supports multiple database providers). So, most of the times you don't care about how IAuditingStore was implemented and used.

Audit Log Object

An audit log object is created for each web request by default. An audit log object can be represented by the following relation diagram:

auditlog-object-diagram

In addition to the standard properties explained above, AuditLogInfo, AuditLogActionInfo and EntityChangeInfo objects implement the IHasExtraProperties interface, so you can add custom properties to these objects.

Audit Log Contributors

You can extend the auditing system by creating a class that is derived from the AuditLogContributor class which defines the PreContribute and the PostContribute methods.

The only pre-built contributor is the AspNetCoreAuditLogContributor class which sets the related properties for an HTTP request.

A contributor can set properties and collections of the AuditLogInfo class to add more information.

Example:

public class MyAuditLogContributor : AuditLogContributor
{
    public override void PreContribute(AuditLogContributionContext context)
    {
        var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
        context.AuditInfo.SetProperty(
            "MyCustomClaimValue",
            currentUser.FindClaimValue("MyCustomClaim")
        );
    }

    public override void PostContribute(AuditLogContributionContext context)
    {
        context.AuditInfo.Comments.Add("Some comment...");
    }
}

After creating such a contributor, you must add it to the AbpAuditingOptions.Contributors list:

Configure<AbpAuditingOptions>(options =>
{
    options.Contributors.Add(new MyAuditLogContributor());
});

IAuditLogScope & IAuditingManager

This section explains the IAuditLogScope & IAuditingManager services for advanced use cases.

An audit log scope is an ambient scope that builds and saves an audit log object (explained before). By default, an audit log scope is created for a web request by the Audit Log Middleware (see UseAuditing() section above).

Access to the Current Audit Log Scope

Audit log contributors, was explained above, is a global way of manipulating the audit log object. It is good if you can get a value from a service.

If you need to manipulate the audit log object in an arbitrary point of your application, you can access to the current audit log scope and get the current audit log object (independent of how the scope is managed). Example:

public class MyService : ITransientDependency
{
    private readonly IAuditingManager _auditingManager;

    public MyService(IAuditingManager auditingManager)
    {
        _auditingManager = auditingManager;
    }

    public async Task DoItAsync()
    {
        var currentAuditLogScope = _auditingManager.Current;
        if (currentAuditLogScope != null)
        {
            currentAuditLogScope.Log.Comments.Add(
                "Executed the MyService.DoItAsync method :)"
            );
            
            currentAuditLogScope.Log.SetProperty("MyCustomProperty", 42);
        }
    }
}

Always check if _auditingManager.Current is null or not, because it is controlled in an outer scope and you can't know if an audit log scope was created before calling your method.

Manually Create an Audit Log Scope

You rarely need to create a manual audit log scope, but if you need, you can create an audit log scope using the IAuditingManager as like in the following example:

public class MyService : ITransientDependency
{
    private readonly IAuditingManager _auditingManager;

    public MyService(IAuditingManager auditingManager)
    {
        _auditingManager = auditingManager;
    }

    public async Task DoItAsync()
    {
        using (var auditingScope = _auditingManager.BeginScope())
        {
            try
            {
                //Call other services...
            }
            catch (Exception ex)
            {
                //Add exceptions
                _auditingManager.Current.Log.Exceptions.Add(ex);
                throw;
            }
            finally
            {
                //Always save the log
                await auditingScope.SaveAsync();
            }
        }
    }
}

You can call other services, they may call others, they may change entities and so on. All these interactions are saved as a single audit log object in the finally block.

The Audit Logging Module

The Audit Logging Module basically implements the IAuditingStore to save the audit log objects to a database. It supports multiple database providers. This module is added to the startup templates by default.

See the Audit Logging Module document for more about it.