Series of posts based around building a cloud solution from scratch covering topics for beginners up to intermediate level .NET developers.
Small disclaimer this series of posts are about me showing you where from you can learn practices and which ones are going to bring you value.
Purpose of the story : Clear up what kind of logging brings most value to developers / users & customers.
I’ll start off with stating the obvious technical logs are not least bit interesting to the users and customers, those are interesting only to us developers. I have used a lot Application Insights for logging, this is an Azure Service that is specifically designed to aid .NET developers in this kind of use case, as an alternative to it some developers are using SEQ for other projects which is basically free of charge to use as long as you have somewhere to run it, and of course there are many others as well.
Next part is the fact that customers always want to have an overview of what is going on in the solutions we built for them so far, somewhere a user can read and get up to date with the latest events that happened inside their system. And most of the time they are coming with some really weird requirements for what interests them. Nonetheless our job is to provide them value that’s why I’ll recreate a raw example of a library that has as concept the need to fill in that requirement.
As a summary : In every system I have participated in building so far there were 2 kinds of information that we needed in order to operate it effectively:
1. Technical information (Logs, traces etc. — used by developers) which is not that interesting to the end user.
2. Business Information (History view — used by the business to work with the system, which an end user would find way more important for them.
Now we are going to create a library that is going to be used to save the events info in our system, here is the link to the git repo where you can find the latest version of it.
Library is divided into a couple of the subfolders for simplicity.
- Contracts : Here we have 2 subfolders “Abstractions” and “Models”, under abstractions we are going to keep the interface that we will use to interact with the module, a facade so to say, the second subfolder will contain all the models the facade needs in order to operate (DTO’s, filters etc.)
- Infrastructure : Here we are keeping all the infrastructure code that the module needs aka: the boots-trapper, database models and configurations etc.
- Service folder will contain the implementation of the facade from the Contracts.
The interface we are going to use for this module is the following:
Here we have only need 3 methods, one to record the events that happen inside the system, one is the one to retrieve them, for retrieval we are using a filter to get only the relevant data and last one to get the total count of entries in the data source.
For filtering we are going to use the following class:
As for recording the event we have the following structure for an event:
In the infrastructure part of the module we have the following:
- Boots-trapper class to wire up the services.
- Database related classes : data models + their respective configurations (using fluent API), an Interface that we are going to inherit in our context that will ensure that we have the tables where we save the data.
- Last but not least IHistoryContext that will work with EF core DbSet’s.
- Validators for the EventDto, using the (FluentValidation) library.
For simplicity’s sake we are going to implement 2 methods inside the controller for the API . One for saving some data, one for getting the data out to the end user. In projects we have worked so far we have used different approaches to fill in the data of the event.
For filling in entity types or event names we have successfully used:
- Enums + Descriptions to the enums
- String constants
A recent practice was that we divided the list of event names into successful and unsuccessful events and grouped the filters on the UI in order to make the experience of the users a little bit more straight forward.
For filling in the UserId’s and User name:
- HTTP Context accessor in order to retrieve those from the Header.
- Localized strings from external sources.
Worth mentioning is the fact that you could call this as a manual approach, as you are responsible for the message of the history record. There is nothing automatic about it, you are responsible for adding the records yourself.
First law of universe: “You can not add value without sweating”. Juval Lowy
Same here you need to fine tune all the details of a system, in order for it to shine and bring real value to the customer and the users.
Next technical logs. Here there are multiple useful tutorials that you could use. One of the tests we put our systems through is setting up the solution and creating a case where multiple things went wrong, next we are not allowed to go to the database or test the solution from the UI (in case of web apps), but we have access only to the logs. If by the end of the test we are able to easily pinpoint the issue the “user” faced that means we have “enough logging”, if not we take incremental steps to log more and more data.
Either way here are a couple of useful links to start with logging:
Link to the GitHub repo where you can find the source code from the article as well as the implementation of the IHistoryContext and Accessor.
The whole story is somewhat still WIP so expect updates to the content from time to time.