EventLog is a library that identifies application events, records diagnostic and statistical information about them, and stores them in a relational database for further analysis. Within the scope of an application event, it has the ability to track the state of application domain models.
$ dotnet add package AHSW.EventLog
EventLog is a library that identifies application events, records diagnostic and statistical information about them, and stores them in a relational database for further analysis. Within the scope of an application event, it has the ability to track the state of application domain models.
The concept behind using EventLog is to gather information about application activity and domain model mutations for various purposes. Some possible examples of its use include:
As an example, imagine the following code is an API endpoint that creates a domain model, Book, and saves it in storage.
As a wrapper, EventLog records the AddBooksOnShelf event, adds some detailed information to the EventLogEntry, executes the initial repository method while simultaneously adding an EntityLogEntry related to the EventLogEntry, and records the Book property value states.
var book = GetBookEntity();
// Create event scope. For example, it can be API endpoint
await services.EventLog.CreateEventScopeAndRun(
// Define event type
EventType.AddBooksOnShelf,
async eventLogScope =>
{
// Addtional usefule data can be added to the event log record
eventLogScope.EventLogEntry.CreatedBy = userId;
eventLogScope.EventLogEntry.Details = "Adding a book";
// Trackable object is changed
book.Title = "Book Title - Rev1";
// Gathering object delta changing and save object and then event
// IMPORTANT: data modification must be completed before this invocation
await eventLogScope.SaveAndLogEntitiesAsync(
// Invoke object saving by EF data context
() => services.BookRepository.AddOrUpdateAsync(book),
options => options
// Define tracked objects and define tracked properties
.AddEntityLogging(
new[] { book },
PropertyType.BookTitle,
PropertyType.BookLikeCount));
});
As a result, EventLog table data can be viewed and analysed using SQL queries. Expand below text to see the examples.
| Id | Initiator | EventType | Status | CreatedAt | DurationInMs | Details | FailureDetails |
|---|---|---|---|---|---|---|---|
| 8 | 3 | Update books | Successful | 2025-04-05 13:22:59.2916081 | 2 | No one observable property is not changed | |
| 7 | 3 | Update books | Successful | 2025-04-05 13:22:59.2836144 | 5 | Observable property is changed | |
| 6 | 3 | Add books | Successful | 2025-04-05 13:22:59.2754546 | 7 | Adding a book | |
| 5 | 3 | Add books | Successful | 2025-04-05 13:22:59.2566239 | 16 | Adding a shelf and 2 books | |
| 4 | 3 | Add books | Successful | 2025-04-05 13:22:59.2509625 | 3 | Adding a book | |
| 3 | 3 | Add books | Successful | 2025-04-05 13:22:59.1869002 | 63 | Adding a book | |
| 2 | 3 | Add shelf | Successful | 2025-04-05 13:22:59.0944167 | 91 | Adding a shelf | |
| 1 | 3 | Add books | Successful | 2025-04-05 13:22:58.9072985 | 185 | Adding a shelf and 2 books |
| EventType | TotalCount | ErrorCount | MedianInMs | MeanInMs |
|---|---|---|---|---|
| Add books | 7 | 2 | 63 | 81 |
| Add shelf | 1 | 0 | 92 | 92 |
| Update books | 2 | 0 | 4 | 4 |
| CreatedAt | Action | EntityId | Entity | Property | Value | ValueType | InitiatorId | Event |
|---|---|---|---|---|---|---|---|---|
| 2025-04-05 13:22:59 | Create | 8 | Book | Title | EventLog Manual - 43 Edition | String | 3 | Add books |
| 2025-04-05 13:22:59 | Create | 8 | Book | Published | 2025-04-05 13:22:59.2968132 | DateTime | 3 | Add books |
| 2025-04-05 13:22:59 | Create | 8 | Book | IsAvailable | 1 | Bool | 3 | Add books |
| 2025-04-05 13:22:59 | Create | 8 | Book | Condition | 1 | Int32 | 3 | Add books |
| 2025-04-05 13:22:59 | Create | 8 | Book | Labels | 3 | Int32 | 3 | Add books |
| 2025-04-05 13:22:59 | Create | 8 | Book | LikeCount | 96 | Int32 | 3 | Add books |
| 2025-04-05 13:22:59 | Create | 8 | Book | Price | 43.0 | Double | 3 | Add books |
| 2025-04-05 13:22:59 | Update | 7 | Book | Title | EventLog Manual - 63 Edition - Revision_1 | String | 3 | Update books |
| 2025-04-05 13:22:59 | Update | 7 | Book | Published | 2025-04-05 13:22:59.2832816 | DateTime | 3 | Update books |
| 2025-04-05 13:22:59 | Update | 7 | Book | FirstSale | 2025-04-06 13:22:59.2832817 | DateTime | 3 | Update books |
Bookstore is a sample console application to demonstrate the way of configuring EventLog and the most common use cases.
These enums will be filled later, when required event and property logging is added to the code. It is desirable to follow the next suggestions.
// Preferable to use the short underlying type to keep EventLog data compact
internal enum EventType : short
{
// Books = 1000
AddBooksOnShelf = 1001, // Explicit number is assigned to each enum member
UpdateBooksOnShelf = 1002,
// Shelves = 2000
AddShelf = 2001,
DeleteShelf = 2002,
}
// Preferable to use the short underlying type to keep EventLog data compact
internal enum PropertyType : short
{
// Book = 1000 - All properties grouped by entity
BookTitle = 1001,
BookPublished = 1002,
BookIsAvailable = 1003,
BookLikeCount = 1004,
BookPrice = 1005,
BookFirstSale = 1006,
BookCondition = 1007,
BookLabels = 1008,
// Shelf = 2000
ShelfHeight = 2001
// Preferable to use the short underlying type to keep EventLog data compact
internal enum EntityType ; short
{
Book = 1000, // PropertyType has Book entity group that starts with 1000 as well
Shelf = 2000
}
modelBuilder.ApplyEventLogConfigurations<EventType, EntityType, PropertyType>(); to the overriden OnModelCreating method in the application database context class. Make sure it goes first in the settings in roder to apply all database schemes correctly protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyEventLogConfigurations<EventType, EntityType, PropertyType>(); // Must be first
modelBuilder.ApplyConfigurationsFromAssembly(typeof(BookstoreDbContext).Assembly);
}
Predefine methods with observable property collections to enable convenient and consistent usage across the application.
internal static class ObservableProperties
{
public static PropertyType[] GetForBookEntity() =>
new []
{
PropertyType.BookTitle,
PropertyType.BookCondition,
};
public static PropertyType[] GetForShelfEntity() =>
new []
{
PropertyType.ShelfHeight
};
}
services.AddEventLog<BookstoreDbContext, EventType, EntityType, PropertyType>();
Setting up enum member custom names is a convenient way to make look of SQL query results more user-friendly.
All observable properties and appropriate entities must be resigtered in the service configuration.
See more detail example in the Program.cs.
EventLogService<EventType, EntityType, PropertyType>.Configure(
configurationBuilder => configurationBuilder
// Optional customization
.UseCustomTypeDescriptions(context,
options => options
// Customize event type descriptions
.AddEventTypeDescription(EventType.AddBooksOnShelf, "Add books on a shelf")
.AddEventTypeDescription(EventType.UpdateBooksOnShelf, "Update books on a shelf")
// Customize entity type descriptions
.AddEntityTypeDescription(EntityType.Book, "Book")
.AddEntityTypeDescription(EntityType.Shelf, "Shelf")
// Customize ptoperty type descriptions
.AddPropertyTypeDescription(PropertyType.BookTitle, "Title")
.AddPropertyTypeDescription(PropertyType.ShelfHeight, "Height")
)
.RegisterEntity<BookEntity>(EntityType.Book, x => ((BookEntity)x).Id,
options => options
.RegisterProperty(PropertyType.BookTitle,
x => x.Title, nameof(BookEntity.Title))
)
.RegisterEntity<ShelfEntity>(EntityType.Shelf, x => ((ShelfEntity)x).Id,
options => options
.RegisterProperty(PropertyType.ShelfHeight,
x => x.Height, nameof(ShelfEntity.Height))
)
);
});
All entity changes must be made before SaveAndLogEntitiesAsync() invocation. Inside this method entities must be only updated in repository and saves.
See more examples in the Program.cs.
var book = CreateBookEntity();
await services.EventLog.CreateEventScopeAndRun(
// Specify event logging level here
EventType.AddBooksOnShelf,
async eventLogScope =>
{
eventLogScope.EventLogEntry.CreatedBy = userId;
eventLogScope.EventLogEntry.Details = "Adding a book";
await eventLogScope.SaveAndLogEntitiesAsync(
// All entity changes must be made before SaveAndLogEntitiesAsync() invocation
() => services.BookRepository.AddOrUpdateAsync(book),
options => options
// Specify entity level logging here
.AddEntityLogging(
// Specify property level logging here
new[] { book }, ObservableProperties.GetForBookEntity)
);
}
);
All samples of SQL queries can be found here: