I'm using Serilog and Autofac and I would like to register an ILogger
using .ForContext()
at resolution time with the type of the object to be injected with the ILogger
. Using the debugger, I can walk the IComponentContext
and see what I want but the types to get to it are all internal so I can't (as far as I'm aware) actually get to it in my code. My registration code (which doesn't work as stated above) looks like this:
builder.RegisterType<SerilogLoggerFactory>().As<ISerilogLoggerFactory>().SingleInstance();
builder.Register(context =>
{
var defaultResolveRequestContext = (Autofac.Core.Resolving.Pipeline.DefaultResolveRequestContext)context;
var resolveOperation = (Autofac.Core.Resolving.ResolveOperation)defaultResolveRequestContext.Operation;
var initiatingRequestService = (Autofac.Core.TypedService)resolveOperation.InitiatingRequest.Service;
return context.Resolve<ISerilogLoggerFactory>().Create().ForContext(initiatingRequestService.ServiceType);
});
DefaultResolveRequestContext
, ResolveOperation
, and InitiatingRequest
are all inaccessible due to their protection level.
Any thoughts or ideas would be appreciated.
Thanks to @Travis Illig's response, we created the following implementation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac.Core;
using Autofac.Core.Resolving.Pipeline;
using Serilog;
namespace YOUR_COMPANY_NAME.Core.Logging.Serilog;
/// <summary>
/// Injects resolved types with <see cref="ILogger.ForContext(Type)"/> for <see cref="ILogger"/>.
/// Supports both constructor and property injection.
/// Inspired by <see href="link">https://autofac.readthedocs.io/en/latest/examples/log4net.html</see>.
/// </summary>
public class AutofacSerilogMiddleware : IResolveMiddleware
{
private readonly ILogger _rootLogger;
private readonly Dictionary<Type, List<PropertyInfo>> _loggerPropertiesCache = new();
public PipelinePhase Phase => PipelinePhase.ParameterSelection;
public AutofacSerilogMiddleware(ILogger rootLogger)
{
_rootLogger = rootLogger;
}
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next)
{
context.ChangeParameters(context.Parameters.Union(new[] { new ResolvedParameter((p, i) => p.ParameterType == typeof(ILogger), (p, i) => p.Member.DeclaringType != null ? _rootLogger.ForContext(p.Member.DeclaringType) : _rootLogger), }));
// Continue the resolve.
next(context);
// Has an instance been activated?
if (!context.NewInstanceActivated || context.Instance == null)
{
return;
}
var instanceType = context.Instance.GetType();
if (!_loggerPropertiesCache.TryGetValue(instanceType, out var propertyInfos))
{
// Get all the injectable properties to set.
propertyInfos = instanceType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.PropertyType == typeof(ILogger) && p.CanWrite && p.GetIndexParameters().Length == 0).ToList();
_loggerPropertiesCache[instanceType] = propertyInfos;
}
if (!propertyInfos.Any())
{
return;
}
var contextLogger = _rootLogger.ForContext(instanceType);
foreach (var propertyInfo in propertyInfos)
{
//Performance could be improved by generating and caching setter delegates instead of using PropertyInfo.SetValue
propertyInfo.SetValue(context.Instance, contextLogger, null);
}
}
}