Logging System Design Advice for .NET CQRS Modular Monolith
I need to design a logging system for a .NET modular monolith application that uses CQRS pattern. Key requirements:
Using custom mediator implementation (not MediatR)
Logs don't need to be in PostgreSQL (though we currently use it)
Need to capture full context for every action (Organization, Service Provider, Customer)
Must log user actions (create, edit, delete, import, export,active,deactive,copy)
Need to support saving log filters as views and CSV exports
GDPR compliance with data anonymization
Plan to add technical logs to files in the future
Which approach would you recommend as best practice: decorator, dispatcher, or EF Core interceptor? Any experience implementing similar logging systems?
Should I kepp all logs in one table or create separate tables for each context ?
4 Replies
That's a loaded question
Is this like some kind of uni / homework exercise?
I'd definitely use ILogger in general
For capturing contexts, you can use logging scopes and / or something like https://github.com/serilog/serilog/wiki/Enrichment when using Serilog
GitHub
Enrichment
Simple .NET logging with fully-structured events. Contribute to serilog/serilog development by creating an account on GitHub.
The entire storage side of things I'd ideally solve by using something battle tested like Loki or Seq for logging sinks
Building a logging sink yourself is quite a bit of work, especially in a high throughput scenario, due to the need for buffering / flushing and all sorts of potential bugs involved with that
What I'm not sure about yet is where you'd ideally capture this - I'd generally question what cqrs / mediatr solves in your case, but assuming it's there and established, does everything pass through that? In that case, does your mediatr-like construct offer behaviours or pipeline middlewares?
That could be an ideal place to solve logging without being invasive in every other part of your codebase
First, I'm not sure I understand your question. Have you considered Log4Net? We use it extensively. It supports both .NET Framework and .NET. It has multiple configuration options and you can write your own adapter. We have some configurations that simultaneously have a rotating log to the disk, that gets picked up by Alloy and fed into Grafana for an amazing log and dashboard experience. It also logs to our TSQL database and to the console. My point is that you use one interface and it gets logged in many ways of your choosing in a customizable format for each adapter.
Also, you control the loggers. You can have a single logger for an application and you provide access and every code piece uses that one logger. Or, each class in your application can have its own logger and you will be able to see the distinctions in the logs. You can filter down exactly to which logger is logging what. Sometimes we have one logger for the whole application, sometimes we have individual loggers we care to zoom in on and filter out the rest.
So your configuration can be on where you want your logs to go (e.g., TSQL, filesystem, Alloy, whatever) and you can also have loggers that use that configuration so that in all of those places you also have the logger information as to where it originated.
Sometimes, we use the nameof(class the code is in) as the logger so we can just search for that namespace and find the code.
Sometimes, we set it to a different string.