Simple audit logging for .NET Core with EntityFramework Core support
dotnet add package Skoruba.AuditLogging.EntityFramework --version 1.0.0-beta5
services.AddAuditLogging(options =>
{
options.UseDefaultSubject = true;
options.UseDefaultAction = true;
})
.AddDefaultHttpEventData(subjectOptions =>
{
subjectOptions.SubjectIdentifierClaim = ClaimsConsts.Sub;
subjectOptions.SubjectNameClaim = ClaimsConsts.Name;
},
actionOptions =>
{
actionOptions.IncludeFormVariables = true;
})
.AddDefaultStore(options => options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbContext"),
optionsSql => optionsSql.MigrationsAssembly(migrationsAssembly)))
.AddDefaultAuditSink();
services.AddAuditLogging(options =>
{
options.UseDefaultAction = false;
})
.AddStaticEventSubject(subject =>
{
subject.SubjectType = AuditSubjectTypes.Machine;
subject.SubjectIdentifier = EmailServiceConsts.Name;
subject.SubjectName = Environment.MachineName;
})
.AddDefaultEventAction()
.AddStore<ApplicationDbContext, AuditLog, AuditLoggingRepository<ApplicationDbContext, AuditLog>>(options =>
options.UseSqlServer(configuration.GetConnectionString("ApplicationDbConnection"),
optionsSql => optionsSql.MigrationsAssembly(migrationsAssembly)))
.AddDefaultAuditSink();
// Create fake product
var productDto = new ProductDto
{
Id = Guid.NewGuid().ToString(),
Name = Guid.NewGuid().ToString(),
Category = Guid.NewGuid().ToString()
};
// Log this action
var productGetUserEvent = new ProductGetEvent
{
Product = productDto
};
await _auditEventLogger.LogEventAsync(productGetUserEvent);
var productGetMachineEvent = new ProductGetEvent
{
Product = productDto,
SubjectType = AuditSubjectTypes.Machine,
SubjectName = Environment.MachineName,
SubjectIdentifier = Environment.MachineName,
Action = new { Method = nameof(Get), Class = nameof(AuditController) }
};
await _auditEventLogger.LogEventAsync(productGetMachineEvent, options =>
{
options.UseDefaultSubject = false;
options.UseDefaultAction = false;
});
ProductAddedEvent.cs
public class ProductAddedEvent : AuditEvent
{
public ProductDto ProductDto { get; set; }
}
Property | Description |
---|---|
Event | Name of event |
Category | Event category |
SubjectIdentifier | Identifier of caller which is responsible for the event |
SubjectName | Name of caller which is responsible for the event |
SubjectType | Subject Type (eg. User/Machine) |
SubjectAdditionalData | Additional information for subject |
Action | Information about request/action |
Property | Description |
---|---|
Id | Database unique identifier for event |
Event | Name of event |
Category | Event category |
SubjectIdentifier | Identifier of caller which is responsible for the event |
SubjectName | Name of caller which is responsible for the event |
SubjectType | Subject Type (eg. User/Machine) |
SubjectAdditionalData | Additional information for subject |
Action | Information about request/action |
Data | Data which are serialized into JSON format |
Created | Date and time for creating of the event |
Default subject implementation for HTTP calls:
public class AuditHttpSubject : IAuditSubject
{
public AuditHttpSubject(IHttpContextAccessor accessor, AuditHttpSubjectOptions options)
{
SubjectIdentifier = accessor.HttpContext.User.FindFirst(options.SubjectIdentifierClaim)?.Value;
SubjectName = accessor.HttpContext.User.FindFirst(options.SubjectNameClaim)?.Value;
SubjectAdditionalData = new
{
RemoteIpAddress = accessor.HttpContext.Connection?.RemoteIpAddress?.ToString(),
LocalIpAddress = accessor.HttpContext.Connection?.LocalIpAddress?.ToString(),
Claims = accessor.HttpContext.User.Claims?.Select(x=> new { x.Type, x.Value })
};
}
public string SubjectName { get; set; }
public string SubjectType { get; set; } = AuditSubjectTypes.User;
public object SubjectAdditionalData { get; set; }
public string SubjectIdentifier { get; set; }
}
Default action implementation for HTTP calls:
public class AuditHttpAction : IAuditAction
{
public AuditHttpAction(IHttpContextAccessor accessor, AuditHttpActionOptions options)
{
Action = new
{
TraceIdentifier = accessor.HttpContext.TraceIdentifier,
RequestUrl = accessor.HttpContext.Request.GetDisplayUrl(),
HttpMethod = accessor.HttpContext.Request.Method,
FormVariables = options.IncludeFormVariables ? HttpContextHelpers.GetFormVariables(accessor.HttpContext) : null
};
}
public object Action { get; set; }
}
dotnet ef migrations add DbInit -c DefaultAuditLoggingDbContext -o Data/Migrations
dotnet ef database update -c DefaultAuditLoggingDbContext
- By default it is used database sink via EntityFramework Core, for registration this default sink - it is required to register this method:
.AddDefaultStore(options => options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbContext"),
optionsSql => optionsSql.MigrationsAssembly(migrationsAssembly)))
.AddDefaultAuditSink()
-
This method register default implementation of:
-
DefaultAuditLoggingDbContext
- Default DbContext for access to database -
AuditLog
- Entity for logging all audit stuff -
AuditLoggingRepository
- Repository for access to database, which contains GRUD method for access toAuditLog
table. -
In the background it is used method called:
AddStore
- which is possible to use instead of AddDefaultStore and specify individual implementation of these objects above
builder.AddStore<DefaultAuditLoggingDbContext, AuditLog, AuditLoggingRepository<DefaultAuditLoggingDbContext, AuditLog>>(dbContextOptions);
- This method is for registration of default Sink:
builder.AddAuditSinks<DatabaseAuditEventLoggerSink<AuditLog>>();
- It is necessary to implement interface
IAuditEventLoggerSink
and one single method called:
Task PersistAsync(AuditEvent auditEvent);
- Then you can register your new sink via method -
.AddAuditSinks<>
- which has overload for maximum 8 sinks.
- Please, check out the project
Skoruba.AuditLogging.Host
- which contains example with Asp.Net Core API - with fake authentication for testing purpose only. 😊
{
"Id":1,
"Event":"ProductGetEvent",
"Category":"Web",
"SubjectIdentifier":"30256997-4096-428d-bfc7-8593d263b8eb",
"SubjectName":"bob",
"SubjectType":"User",
"SubjectAdditionalData":{
"RemoteIpAddress":"::1",
"LocalIpAddress":"::1",
"Claims":[
{
"Type":"name",
"Value":"bob"
},
{
"Type":"sub",
"Value":"30256997-4096-428d-bfc7-8593d263b8eb"
},
{
"Type":"role",
"Value":"31fad6ad-9df3-4e7f-b73f-68dc7d2636c6"
}
]
},
"Action":{
"TraceIdentifier":"80000025-0000-ff00-b63f-84710c7967bb",
"RequestUrl":"https://localhost:44319/api/audit",
"HttpMethod":"GET"
},
"Data":{
"Product":{
"Id":"7d7138b6-e5c3-4548-814c-9119ddb1f785",
"Name":"c9bc91fe-79f2-439b-8bfa-be3f71947b63",
"Category":"b3f2f9d2-67d5-4b52-8156-04232adf0c4b"
}
},
"Created":"2019-09-09T12:03:12.7729634"
}
This repository is licensed under the terms of the MIT license.
NOTE: This repository uses the source code from https://github.com/IdentityServer/IdentityServer4 which is under the terms of the Apache License 2.0.
- This library is inspired by EventService from IdentityServer4.