nblumhardt / autofac-serilog-integration Goto Github PK
View Code? Open in Web Editor NEWContextual logger injection for Autofac using Serilog
License: Apache License 2.0
Contextual logger injection for Autofac using Serilog
License: Apache License 2.0
Related to Autofac issues:
I have a scenario where I have a specific ILogger
instance to be registered with Autofac via the RegisterLogger
extension method`:
var builder = new ContainerBuilder();
builder.RegisterLogger(_mySpecificILoggerInstance);
However, later in the code, there's a call to RegisterAssemblyModules
which will pickup the AutofacSerilogIntegration assembly and will register the ContextualLoggingModule
again, but now with a null
instance of ILogger
and will cause it to use the static instance Log.Logger
- which is not the instance desired.
e.g.
var assembliesInAppDomain = AppDomain.CurrentDomain.GetAssemblies().ToArray();
builder.RegisterAssemblyModules(assembliesInAppDomain);
I think the ideal scenario would be to have a way to tell Autofac to skip registering the ContextualLoggingModule
given we want it to be registered only via the RegisterLogger
extension method.
A quick attempt to make ContextualLoggingModule
internal, produce the same results (Autofac registers it anyway, by design I think).
A second quick attempt to make ContextualLoggingModule
constructor's also internal, causes an exception during registration as Autofac can still see the module but can't find a suitable constructor, and bombs.
My goal is to inject the Serilog into the host project (.net4.8) and in the domain projects (I use some DDD pattern) use ILogger from 'Microsoft.Extensions.Logging.Abstractions'.
But I see that registering the Serilog is for only the Serilog.ILogger interface and this suggests that I should only use Serilog.ILogger.
Or is the current nuget package not for my case?...
I saw another nuget (link) which allows the use of abstraction from Microsoft.
Would it be possible for you to reference serilog 2.x and create a new package?
I am using Serilog 2.4 and just because I use AutofacSerilogIntegration, I have to add Assembly Redirects to Serilog 2.0.0.0 in every project now...
Thanks for the code!
Hi,
I have a sample code here: https://github.com/pawelpabich/serilog-autofac-injectunsetproprties. I can't resolve the MyType object directly from the container. InjectUnsetProperties sets the Logger property but SourceContext does not seem to be populated. I will try to figure out why when I get some time.
Firstly, I don't believe I've correctly identified the problem but will describe as best to my understanding.
In a net core core 3.1 app, we have background services that trigger at startup. What was being observed was sourcecontext was set on any log messages, yet it does appear fine on the main web process.
Pulled the code locally and updated to autofac 5, the signature for AttachToComponentRegistration was different so updated. Created sample scenario site, and observed that the SourceContext prop is now being correctly applied to the backgroundService.
After updating to AutofacSerilogIntegration v5.0.0 and then trying to upgrade a previous installation of our WiX installer built MSI, AutofacSerilogIntegration.dll was the only file which did not upgrade. Looking at the MSI debug log, it said that the reason why the dll did not upgrade was because the file versions were the same. Checking the file details for the v3.0.0 dll, the file version was 1.0.0. Then checking file details for v5.0.0, the file version is still 1.0.0 even though the product version changed to 5.0.0. Since the WiX installer is looking at the file version, it believes that the versions are the same and therefore won't upgrade the dll.
The file version for the autofacSerilogIntegration.dll should be incrementing with each new version even if the file version does not necessarily match the product version.
This exception is introduced since version 3.0.0. In version 2.1.0. it is working.
Steps to reproduce
Create a new ASP.NET Core Web API application.
Add nuget packages
<PackageReference Include="Autofac" Version="6.0.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.1.0" />
<PackageReference Include="AutofacSerilogIntegration" Version="4.0.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
Modify Program.cs
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
Log.Information("Hello World!");
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Modify Startup.cs
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterLogger();
}
Running the application will result in
Unhandled exception. System.MissingMethodException: Method not found: 'Void Autofac.Core.IComponentRegistration.add_Preparing(System.EventHandler`1<Autofac.Core.PreparingEventArgs>)'.
at AutofacSerilogIntegration.ContextualLoggingModule.AttachToComponentRegistration(IComponentRegistryBuilder componentRegistry, IComponentRegistration registration)
at Autofac.Module.<AttachToRegistrations>b__4_0(Object sender, ComponentRegisteredEventArgs e)
at Autofac.Core.Registration.ComponentRegistryBuilder.add_Registered(EventHandler`1 value)
at Autofac.Module.AttachToRegistrations(IComponentRegistryBuilder componentRegistry)
at Autofac.Module.Configure(IComponentRegistryBuilder componentRegistry)
at Autofac.ContainerBuilder.Build(IComponentRegistryBuilder componentRegistry, Boolean excludeDefaultModules)
at Autofac.ContainerBuilder.Build(ContainerBuildOptions options)
at Autofac.Extensions.DependencyInjection.AutofacServiceProviderFactory.CreateServiceProvider(ContainerBuilder containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
When I downgrade the AutfacSerilogIntegration to version 2.1.0 it is working. Am I missing something or is this a bug?
Recently we've encountered a performance issue in a project using AutofacSerilogIntegration
. The performance profiling was indicating a lot of time spent in registration.Preparing
handler attached by ContextualLoggingModule
; the strange thing, though, was that none of the components in the dependency tree were actually using an ILogger
. So I did a little digging and found out that if you register a delegate factory, the integration is always enabled for it (because, of course, there's no way of knowing if it requires ILogger
or not).
Enter the benchmark (Autofac 5.1.2, AutofacSerilogIntegration 3.0.0). Here are there most relevant parts of the registration:
containerBuilder.RegisterInstance(new InstanceDependency());
containerBuilder.Register(_ => new DelegateDependency());
containerBuilder.Register(_ => new DelegateDependency2());
containerBuilder.RegisterType<ReflectionDependency>();
containerBuilder.RegisterType<ReflectionDependencyWithLogger>();
Only ReflectionDependencyWithLogger
has ILogger
as a dependency. And the consumers are very simple:
public class Service<T>
{
private readonly T _dependency;
public Service(T dependency) => _dependency = dependency;
}
public class Service2
{
private readonly DelegateDependency _dependency;
private readonly DelegateDependency2 _dependency2;
public Service2(DelegateDependency dependency, DelegateDependency2 dependency2)
{
_dependency = dependency;
_dependency2 = dependency2;
}
}
Let's start with pure Autofac, no logger registration
Method | Mean | Error | StdDev | Ratio | RatioSD |
---|---|---|---|---|---|
ReflectionNoLogger | 1.452 μs | 0.0220 μs | 0.0349 μs | 1.00 | 0.00 |
InstanceNoLogger | 1.174 μs | 0.0106 μs | 0.0094 μs | 0.80 | 0.02 |
DelegateNoLogger | 1.285 μs | 0.0124 μs | 0.0103 μs | 0.88 | 0.02 |
TwoDelegatesNoLogger | 1.733 μs | 0.0159 μs | 0.0149 μs | 1.19 | 0.04 |
No wonders here, provided instance is the fastest, delegate is faster than reflection, when we have two delegates it becomes slower. Everything is as expected. Now we add the logger:
Method | Mean | Error | StdDev | Ratio | RatioSD |
---|---|---|---|---|---|
Reflection | 1.511 μs | 0.0264 μs | 0.0234 μs | 1.00 | 0.00 |
ReflectionWithLogger | 3.382 μs | 0.0674 μs | 0.0526 μs | 2.24 | 0.05 |
Instance | 1.202 μs | 0.0237 μs | 0.0421 μs | 0.82 | 0.03 |
Delegate | 2.807 μs | 0.0253 μs | 0.0197 μs | 1.86 | 0.03 |
TwoDelegates | 4.960 μs | 0.0822 μs | 0.0729 μs | 3.28 | 0.08 |
When we use the logger (ReflectionWithLogger
), it is understandably slower. Provided instance is again the fastest. But what happened with the delegates (which, I remind you, don't use ILogger
)? They are almost as slow as if we were using the logger, and it grows with the number of consumed delegates.
Here's the culprit:
var ra = registration.Activator as ReflectionActivator;
if (ra != null)
{
//...some checks...
// Ignore components known to be without logger dependencies
if (!usesLogger)
return;
}
So, for every non-ReflectionActivator
component we'll have the Preparing
handler attached and firing and the logger being created even if it's not consumed. Which, in our case, was responsible for roughly 3x slowdown (almost the same as in the benchmarks above - we were consuming two delegate dependencies).
As far as I know, Autofac provides 3 activators out-of-the-box: Reflection
, ProvidedInstance
and Delegate
. Reflection
is the one the integration properly handles. For the ProvidedInstance
the handlers are redundant (nothing will consume the created Parameter
), but because it's called only once in the lifecycle, the impact is negligible.
The Delegate
s, though, are an issue. I'd argue that the handlers are redundant as well, because even while it is possible to consume the Parameter
in the factory (using the Register<T>(Func<IComponentContext, IEnumerable<Parameter>, T>)
overload) one would have to rely on the private implementation details in the Preparing
handler to guess what to consume. It is much easier to simply resolve the logger from the IComponentContext
and call ForContext
on it. But this is my personal take.
So we have some options here:
ReflectionActivator
, which, I'd say, will cover all the expected uses, but, in theory, might break somebody's edge caseReflection
, do not provide for Delegate
and ProvidedInstance
), which is same as above but covers for someone's custom activator just in caseRegisterLogger(..., bool onlyForKnownConsumers: false)
, which will enable the behavior above for implementation cases like oursinterface ILoggerAttachmentStrategy
{
bool ShouldAttachToRegistration(IComponentRegistration registration, out PropertyInfo[] targetProperties)
}
with a default implementation from existing code and allow passing it from outside: RegisterLogger(..., ILoggerAttachmentStrategy strategy)
, which is a more complex version of 3 with a lot more control for the consumers.
I would personally vote for the option 2, with the option 4 being my next choice.
Currently, this integration wraps everything required to resolve context-aware instances of ILogger
from Autofac in a single Autofac module. While extremely convenient (it really doesn't get any easier than builder.RegisterLogger()
), I'm currently facing the problem that I need to inject instances of ILogger
into imported MEF components (which are being resolved using Autofac with the Autofac.Integration.Mef package), however Autofac doesn't expose any dependencies for resolved MEF components unless they are declared as exports (using IRegistrationBuilder<...>.Export()
). Which means I need to change how ILogger
is being registered.
Is there a way do to that with this integration? All I can see is that the extension method optionally takes an existing instance of ILogger
or allows to declare whether properties are being autowired. And it returns a IModuleRegistrar
instead of a IRegistrationBuilder
, which is normally expected when registering components on the ContainerBuilder
.
The package is not installed if your project framework version = v4.0
Install-Package : Could not install package 'AutofacSerilogIntegration x.x.x'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.0', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.
Hi @nblumhardt,
Love this integration and am just trying to figure out which version to implement.
We currently have Autofac v3.5.2
in our project and haven't made the leap to 4.x
yet (though it's on my list).
Is there anything I should be aware of related to v1.0.15 of this package as opposed to v2.0.0 which depends on Autofac v4? I couldn't find release notes anywhere, but I might not be looking in the correct place.
Hey,
I really like the idea of this module for enriching log entries, however I have one issue with it - where to call Log.CloseAndFlush(). The module specifies the dependency as ExternallyOwned, however if in a project an ILogger instance is always retrieved via Autofac, I would like it to handle the Disposal as well (not relying on Application_End, etc).
How about adding a generic Lambda Action as another parameter to the SerilogContainerBuilderExtensions.RegisterLogger extension method, that would allow something like this:
Action<IRegistrationBuilder<ILogger, SimpleActivatorData, SingleRegistrationStyle>> registrationAction = registration =>
{
registration.SingleInstance()
.OnRelease(logger => Log.CloseAndFlush());
};
builder.RegisterLogger(registrationAction);
In order to be backwards compatible, the parameter would be nullable, and the default Action would specify the dependency as being .ExternallyOwned().
If you agree and like this approach, I can submit a PR with this change...
System.MissingMethodException: Method not found: 'Void Autofac.Core.IComponentRegistration.add_Preparing(System.EventHandler1<Autofac.Core.PreparingEventArgs>)'. at AutofacSerilogIntegration.ContextualLoggingModule.AttachToComponentRegistration(IComponentRegistryBuilder componentRegistry, IComponentRegistration registration) at Autofac.Module.<AttachToRegistrations>b__4_0(Object sender, ComponentRegisteredEventArgs e) at Autofac.Core.Registration.ComponentRegistryBuilder.add_Registered(EventHandler
1 value)
at Autofac.Module.AttachToRegistrations(IComponentRegistryBuilder componentRegistry)
at Autofac.Module.Configure(IComponentRegistryBuilder componentRegistry)
at Autofac.ContainerBuilder.Build(IComponentRegistryBuilder componentRegistry, Boolean excludeDefaultModules)
at Autofac.ContainerBuilder.UpdateRegistry(IComponentRegistryBuilder componentRegistry)
at Autofac.Module.Configure(IComponentRegistryBuilder componentRegistry)
at Autofac.ContainerBuilder.Build(IComponentRegistryBuilder componentRegistry, Boolean excludeDefaultModules)
at Autofac.ContainerBuilder.Build(ContainerBuildOptions options)
at Autofac.Extensions.DependencyInjection.AutofacServiceProviderFactory.CreateServiceProvider(ContainerBuilder containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
I have an application with the child scope built using this manual: https://github.com/autofac/Documentation/blob/master/docs/integration/aspnetcore.rst#using-a-child-scope-as-a-root
I have this code for registering Serilog in the child container
public void ConfigureContainer(AutofacChildLifetimeScopeConfigurationAdapter adapter)
{
adapter.Add(builder =>
{
builder.RegisterLogger();
});
}
Parent container has builder.RegisterLogger();
as well.
Context is missing in all cases:
UseSerilog
UseSerilog
onlyConfigureContainer
call from the sample aboveSample log entry with the missing context between <>
:
2021-02-03 16:53:37Z [4] DBG <> User login was denied. Reason = InvalidUsernameOrPassword.
Hi,
The Autofac team did a lot of fundametal changes in version 6 (what's new).
Specifically:
RegistrationBuilder.RegistrationData no longer exposes the configured ActivatedHandlers, ActivatingHandlers or PreparingHandlers, and IComponentRegistration no longer exposes Preparing, Activating or Activated events.
And current "autofac-serilog-integration" version (4.0) throw an exception, because they've removed a couple of used by you methods.
Are you going to fix it soon ?
Thanks,
When I add ILogger parameter to regular class Worker it is resolved as instance with context Worker and errors are correctly logged with SourceContext set to Worker.
However, when ILogger is parameter of UserController it is resolved as instance without context. So error messages don't contain SourceContext.
API is .NET Core 2.0.
Hi,
I'm trying to use SeriLog, Console Sink, Autofac, AutofactIntegration package in Azure Functions - v1 runtime.
followed the steps
https://github.com/nblumhardt/autofac-serilog-integration
everything works fine if i perform the same in console application/test
but fails to execute in Azure function runtime.
System.MissingMethodException : Method not found: 'Autofac.Core.Registration.IModuleRegistrar AutofacSerilogIntegration.SerilogContainerBuilderExtensions.RegisterLogger(Autofac.ContainerBuilder, Serilog.ILogger, Boolean)'.
Any pointers?
Regards
Akhilesh
Doesn't add SourceContext in ASP.Net 5 controllers with ILogger injected
Controller get registered with the Registration.Preparing event, but doesn't appear that this ever gets called for a controller when the controller is instantiated by the container...not sure why.
when I upgrade Autofac to vsesion 6.0.0 I got the exception
System.MissingMethodException: Method not found: 'Void Autofac.Core.IComponentRegistration.add_Preparing(System.EventHandler1<Autofac.Core.PreparingEventArgs>)'. at AutofacSerilogIntegration.ContextualLoggingModule.AttachToComponentRegistration(IComponentRegistryBuilder componentRegistry, IComponentRegistration registration) at Autofac.Module.<AttachToRegistrations>b__4_0(Object sender, ComponentRegisteredEventArgs e) at Autofac.Core.Registration.ComponentRegistryBuilder.add_Registered(EventHandler
1 value)
at Autofac.Module.AttachToRegistrations(IComponentRegistryBuilder componentRegistry)
at Autofac.Module.Configure(IComponentRegistryBuilder componentRegistry)
at Autofac.ContainerBuilder.Build(IComponentRegistryBuilder componentRegistry, Boolean excludeDefaultModules)
at Autofac.ContainerBuilder.Build(ContainerBuildOptions options)
at Autofac.Extensions.DependencyInjection.AutofacServiceProviderFactory.CreateServiceProvider(ContainerBuilder containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.