Dotnettency icon indicating copy to clipboard operation
Dotnettency copied to clipboard

Per tenant Logging

Open dazinator opened this issue 4 years ago • 0 comments

When logging is added / configured using the typical microsoft provided AddLogging method, this registers a singleton ILoggerFactory type. When this type is activated, all registered ILoggerProviderare injected into it, and then later as you request ILogger instances, they get created from the factory and they wrap all of those those providers.

This can present a problem when wanting to AddLogging() also at the per-tenant level, in order to add a tenant specific ILoggerProvider. When you do this, you will find that your ILogger resolved at tenant level will now wrap the ILoggerProvideryou registered at tenant level, but also the one/s you registered at platform level. In some situations this may not be desierable - suppose you want to forward each tenant's logs to their own seq instance, or log file "tenant-foo.txt" etc, and you only want platform level logs going to platform-logs.txt. Well with the way things are by default, your tenant level logs will end up going to both the tenant file and the platform level file.

Another example of this, suppose you want this tenants logs to have a minimum log level of "Warning" in order for them to be collected, but you always want platform level logs collected so when you AddLogging at the root level you set the logging level to Debug. You might also AddSeq at the root level. Well when you log events at the tenant scope you will find that log events below log level "Warning" correctly won't get collected - by your tenant level ILoggerProvider, but as the platform level ILoggerProviders are also part of the equation, you still end up with all of the log events Debug and higher being forwarded to your seq instance.

It may well be desirable to set things up this way, if for example you want each tenant to have its own independent copy of logs made available to them and subject to their own log level setting, whilst simultaneously you want the ability to still collect all tenants logs yourself, subject to your platform level log level settings. This is basically two different distributions of the logs, controlled by two independent log levels.

However for performance reasons, I usually want only 1 distribution of the logs. I want each tenant to have a log level setting that controls whether log events logged within the scope of that tenant will make it into that distribution. In order to do this I add a ILoggerProvider at the tenant level that has its own minimum log level, and simply forwards log events to the underlying platform level ILoggerProvider/s only if the log event meets the configured tenants log level. This allows me to set up Seq at the platform level, but then each tenant can adjust their log level, and that controls the level of log detail we see for that tenant in seq, but doesn't impact other tenants.

If like me you want this "single distribution" model, the way to solve this, is to register a new instance of the ILoggerFactory at the tenant level that is built with a specific set of ILoggerProvider added only to that factory and not injected via DI (which would end up pulling in ILoggerProvider from the root container as well). Then if using something like Serilog, you can set that ILoggerProvider log level, but you can also give it a collection of ILoggerProvider's to sink to- this would be your too providers.

So as a feature of dotnettency, i'm considerng providing a fluent configration API to make it easier to configure per tenant logging where you want to be in precise control over the ILoggerProviders used at tenant level. It would look something like:

                       // etc
                      // Using this means that ILoggerProviders registered at root level will not automatically also be available at tenant level, you have to explicitly register ILoggerProviders you want at tenant level here..
                        .ConfigureTenantLogging((builder) =>
                        {
                           builder.AddSerilog(); // etc.
                        })
                    

dazinator avatar Feb 28 '20 22:02 dazinator