Given several types of class constructor injection, i.e.;
public class DataService :IDataService
{
public DataService(ILogger logger) { ... }
}
and,
public class Logger
{
public Logger(IDataService service) { ... }
}
or, should I do this instead;
public class DataService : IDataService, ILogger, IDisposable
{
public DataService() { ... }
}
However, I actually don't like to do this on every repository classes or other classes that needs data services and logging at the same time;
public class SomeRepository : IRepostiory
{
public SomeRepository (IDataService service, ILogger logger) { ... }
}
I'm fine with this model;
public interface IRepository : ILogger { ... }
or,
public interface IDataService : ILogger { ... }
Which one is preferred as best practice design? Also, how do we determine if we would like to log the data service process, and at the same time we also would like to log other components that injected ILogger services or using repository?
This is a tongue in cheek answer, but there is no "right" way to implement ILogger. You should probably use the existing framework to construct an ILogger, or hand it over to NLog, log4net, or Serilog. You shouldn't directly implement ILogger unless you plan to build your own logging framework. You should use the logging libary's extension methods to wireup the ILogger Here is an article about this.
Here is a minimal example of how to get console logging up and running:
var hostBuilder = Host.CreateDefaultBuilder().
ConfigureLogging((builderContext, loggingBuilder) =>
{
loggingBuilder.AddConsole((options) =>
{
//This displays arguments from the scope
options.IncludeScopes = true;
});
});
var host = hostBuilder.Build();
var logger = host.Services.GetRequiredService<ILogger<LogTest>>();
//This specifies that every time a log message is logged, the correlation id will be logged as part of it
using (logger.BeginScope("Correlation ID: {correlationID}", 123))
{
logger.LogInformation("Test");
logger.LogInformation("Test2");
}
I'll like to add that the BeginScope method could take any kind of object, not only string. You may pass a more complex object that could represent better the context/scope, like User Id, HostName, CorrelationId, Tennant Id ...
In my case where a created an external Api Logger service, and thus create my own ILogger where BeginScope with those values make more sense.
public IDisposable BeginScope<TState>(TState state)
{
_scopeContextManager = state as ScopeContextManager;
return default!;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
// use _scopeContextManager
}
I struggled for a long time before figuring this out.
Related
We have a common architecture for many of our projects, and this architecture requires some amount of boilerplate that is generic for every project. I'm trying to tie all this boilerplate into a single reusable NuGet package to make maintenance easier, but am running into issues with getting the DI to work with me.
Specifically, I'm struggling with the concept of services. In the NuGet, I'll have to define basic service interfaces so I can hook some pipelines to use these services. However, every application that will be using this NuGet will need to be able to extend these services with application specific methods.
Let's go over an example with the "User authentication pipeline", which should answer common questions like "Is this user in role x" and application specific questions like "Can this user modify z based on its owner y".
First, our application layer is structured based on CQRS using a common interface, which is implemented by every Query and Command:
public interface IApplicationRequestBase<TRet> : IRequest<TRet> { //IRequest from MediatR
Task<bool> Authorize(IUserServiceBase service, IPersistenceContextBase ctx);
void Validate();
}
IUserServiceBase is an interface providing access to the current user (I'm skipping the IPersistenceContextBase, which is just an empty interface):
public interface IUserServiceBase {
string? CurrentUserExternalId { get; }
bool IsUserInRole(params string[] roleNames);
...
And in the authentication pipeline
public class RequestAuthorizationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IApplicationRequestBase<TResponse> { //MediatR IPipelineBehavior
private readonly IUserServiceBase _userService;
private readonly IPersistenceContextBase _ctx;
public RequestAuthorizationBehaviour(IUserServiceBase userService, IPersistenceContextBase ctx) {
_userService = userService;
_ctx = ctx;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next) {
if (await request.Authorize(_userService, _ctx)) {
return await next();
}
throw new UnauthorizedAccessException();
}
}
}
And finally the NuGet DI definition:
public static class DependencyInjection {
public static IServiceCollection AddApplicationInfra(this IServiceCollection services) {
...
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestAuthorizationBehaviour<,>));
return services;
}
}
All well and good in the NuGet side, now the application. This approach has me trying to extend the interfaces directly, and this is the easiest way to visualize what I wish to accomplish.
The application has a bunch of app-specific authorization checks, so we have a custom interface for that:
public interface IUserService : IUserServiceBase {
public string LocalUserIdClaimKey { get; }
Guid CurrentUserLocalId { get; }
/// <summary>
/// Shortcut for checking if the user has any role allowing read access to notifications
/// </summary>
bool CurrentUserCanReadNotifications { get; }
...
The UserService class implements all the functionality required in the IUserService interface, meaning the IUserServiceBase methods as well. It is defined in a different project (Infrastructure) than the interface (Application).
public class UserService : IUserService {
private readonly IHttpContextAccessor _contextAccessor;
public UserService(IHttpContextAccessor contextAccessor) {
_contextAccessor = contextAccessor;
}
public string? CurrentUserExternalId {
get {
var user = _contextAccessor.HttpContext.User;
if (user != null) {
return user.FindFirst(JwtClaimTypes.Subject)?.Value;
}
return null;
}
}
...
And finally, in our Command, where it all should come together:
public class UpdateSubsequentTreatmentFacilitiesCommand : IApplicationRequestBase<int> {
public async Task<bool> Authorize(IUserService service, IPersistenceContext ctx) {
//Application specific authorization check
}
public void Validate() {
}
Now, here we get a build error, stating that 'UpdateSubsequentTreatmentFacilitiesCommand' does not implement interface member 'IApplicationRequestBase<int>.Authorize(IUserServiceBase, IPersistenceContextBase)'. This is probably what I'm encountering here (though I still can't figure out why exactly...).
So, to reiterate:
Goal is to package common project boilerplate to a single NuGet
We need to be able to extend the services defined in the NuGet with application specific functionality
IApplicationRequestBase defines the type of the service parameter as IUserServiceBase, but UpdateSubsequentTreatmentFacilitiesCommand tried to use IUserService. OO programming and inheritance doesn't let you change method signatures.
If you can change IApplicationRequestBase, adding a TService generic parameter will let you get around it:
public interface IApplicationRequestBase<TRet, TService> : IRequest<TRet>
where TService is IUserServiceBase
{
Task<bool> Authorize(TService service, IPersistenceContextBase ctx);
void Validate();
}
public class UpdateSubsequentTreatmentFacilitiesCommand : IApplicationRequestBase<int, IUserService>
{
public async Task<bool> Authorize(IUserService service, IPersistenceContext ctx)
{
// method body
}
// rest of class
}
However, given that IUserService is an interface, if it is the only thing that extends/implements IUserServiceBase, then it sounds like a case of overengineering. There's a saying that perfection is the enemy of good. In other words, attempting to be too generic, too reusable, where it's not actually needed, is just slowing down progress. By all means, strive to have a high quality codebase, but you also need to be pragmatic.
If other apps that use IApplicationRequestBase have their own user service, not the same IUserService as your app, then you'll need to find another approach, given that C# is a strongly typed language. You could just typecast the IUserServiceBase into an IUserService in the method body. Rather than extending the interface, you could have an extension method. If you're creative, you might think of other approaches as well.
However, looking at IUserService, my guess is that it exists only to improve performance of checking certain commonly used roles. If I'm wrong and it's about convenience and not performance, then an extension method should be sufficient. If the concern is performance, then make sure that the implementation of IsUserInRole does caching. Looking up a string still won't be as fast as returning a property's backing field. But changing your software architecture to improve performance for something you haven't profiled to confirm that it is a performance bottleneck is the definition of premature optimization. If IsUserInRole does basic caching, you'll probably find the the performance is good enough, and helper/extension methods solve whatever readability/code quality issue you're trying to solve.
I'm working with a project which utilizes Simple Injector as dependency injector. On the other hand, this project uses Microsoft.Extensions.Logging in order to log the events that occurs in certain classes.
My technical issue is pretty simple to explain.
I want to register in my DI the ILogger independently of the class T which is being invoked, but I DO NEED to do it from my ILoggerFactory.CreateLogger<T>() method because this gets the logger configuration using Microsoft.Extensions.Configuration.
I need to use something like this in order to instance my logger:
private Microsoft.Extensions.Logging.ILogger CreateLogger<T>()
{
var factory = this.ResolveService<ILoggerFactory>();
var logger = factory.CreateLogger<T>();
return logger;
}
I could achieve the injection by doing:
Container.Register(typeof(ILogger<>), typeof(Logger<>));
And this allows us to resolve something like:
public class SomeApiController : ApiController
{
public SomeApiController(ILogger<SomeApiController> logger)
{
//logger is well instantiated, but doesn't got the configuration
logger.LogInformation("test log.");
}
}
But as I said, this does it without passing through the configuration obtained from the Microsoft.Extensions.Logging.ILoggerFactory class, so this isn't useful.
Is there a way to register ILogger<T> by using my CreateLogger<T>?
Use the following registrations:
container.RegisterInstance<ILoggerFactory>(loggerFactory);
container.RegisterSingleton(typeof(ILogger<>), typeof(Logger<>));
Or, in case you are integrating Simple Injector into a generic host or ASP.NET Core application, make use of the .AddLogging() extension method to even inject a non-generic ILogger into your application components, as demonstrates in this ASP.NET Core Startup class:
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(); // Adds logging to the framework
// AddSimpleInjector enables "cross wiring," which means you can let
// Simple Injector-resolved components to depend on the generic
// ILogger<T> abstraction.
services.AddSimpleInjector(container, options =>
{
options.AddAspNetCore();
// AddLogger allows Simple Injector-resolved components to depend on
// the non-generic Microsoft.Extensions.Logging.ILogger interface.
// Simple Injector will automatically inject the correct ILogger<T>
// for you.
options.AddLogging();
});
}
...
}
For a full example, see the ASP.NET Core and ASP.NET Core MVC Integration Guide.
Letting application components depend on ILogger instead of ILogger<T>, makes your code simpler, easier to test, and less error prone. If you're using Simple Injector without Service Collection integration (as the previous example showed, you can use the following registration to let Simple Injector ensure the correct Logger<T> is still injected whenever an ILogger is injected:
container.RegisterConditional(
typeof(ILogger),
c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
This ensures that every application component gets its own Logger<T> instance, where T is the type of the component the logger is injected into. Take the following class for example that depends on ILogger:
public class ComponentA : IService
{
public ComponentA(ILogger logger) { ... }
}
The above registration will ensure that ComponentA is injected with a Logger<ComponentA>, even though it simply depends on ILogger and not on ILogger<T>.
You can stop reading here if the above suits your needs... or continue reading if you're interested in a more SOLID solution.
A SOLID solution
Instead of letting application components depend on the framework-defined ILogger abstraction, you could also choose to define an application-specific logger abstraction, as prescribed by the Dependency Inversion Principle (DIP).
The DIP states that abstractions should be defined by the application itself—this means you define your own logger abstraction (also see this for an explanation of why you want to do this) and on top of that you build an adapter, much like described here. You can simply derive your generic adapter from the described MicrosoftLoggingAdapter as follows:
public sealed class MicrosoftLoggingAdapter<T> : MicrosoftLoggingAdapter
{
public MicrosoftLoggingAdapter(ILoggerFactory factory)
: base(factory.CreateLogger<T>()) { }
}
Using this generic adapter, you can configure Simple Injector as follows:
container.RegisterInstance<ILoggerFactory>(factory);
container.RegisterConditional(
typeof(MyApplication.Abstractions.ILogger),
c => typeof(MicrosoftLoggingAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
Based on Steven's solution, I post my answer to help anyone else:
private void RegisterServices()
{
Container.Register(ConfigureLogger, Lifestyle.Singleton);
Container.Register(typeof(ILogger<>), typeof(LoggingAdapter<>));
}
private ILoggerFactory ConfigureLogger()
{
LoggerFactory factory = new LoggerFactory();
var config = new ConfigurationBuilder()
.AddJsonFile("logging.json")
.Build();
//serilog provider configuration
var log = new LoggerConfiguration()
//.ReadFrom.Configuration(config)
.WriteTo
.RollingFile(ConfigSettings.LogsPath)
.CreateLogger();
factory.AddSerilog(log);
return factory;
}
public class LoggingAdapter<T> : ILogger<T>
{
private readonly Microsoft.Extensions.Logging.ILogger adaptee;
public LoggingAdapter(ILoggerFactory factory)
{
adaptee = factory.CreateLogger<T>();
}
public IDisposable BeginScope<TState>(TState state)
{
return adaptee.BeginScope(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return adaptee.IsEnabled(logLevel);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
adaptee.Log(logLevel, eventId, state, exception, formatter);
}
}
As you can see, my solution is using Serilog as a provider for logging in Microsoft.Extensions.Logging.
Hope it helps!
Essentially, there are two different ways I get ILogger instances. One works perfectly fine, the other doesn't.
I have an Azure Function like this:
class AzureFunctionClass {
private readonly ISomeClass _someclass;
public AzureFunctionClass(ISomeClass someClass){
_someclass = someClass;
}
public Task<IActionResult> AzureFunction(ILogger log){
log.LogInformation("This works, I see this message when run");
_someclass.ExecuteMethod();
}
}
Another class, not containing Azure functions, like this:
class SomeClass : ISomeClass {
private readonly ILogger<SomeClass> _log;
public SomeClass(ILogger log){
_log = log;
}
public void ExecuteMethod(){
_log.LogInformation("This doesn't crash so _log isn't null, but it
doesn't write anything");
}
}
Startup.cs:
class Startup : IWebJobsStartup {
public void Configure(IWebJobsBuilder builder){
builder.Services.AddScoped<ISomeClass, SomeClass>();
builder.Services.AddTransient(typeof(ILogger<>), typeof(Logger<>));
builder.Services.AddScoped<ILogger<SomeClass>, Logger<SomeClass>>();
}
}
And no, I'm afraid that AzureFunctionClass cannot just pass its ILogger instance to ISomeClass as a parameter.
I've also looked everywhere for log files, such as in Azure Storage Explorer, to see if it's possibly just not writing to the Azure Portal console. Every log file I found had logs for the working case described above, and none of them had logs for the other case.
Current syntax shown has some issues with this injected dependencies.
class SomeClass : ISomeClass {
private readonly ILogger _log;
public SomeClass(ILogger<SomeClass> log) {
_log = log;
}
public void ExecuteMethod() {
_log.LogInformation("This doesn't crash so _log isn't null, but it doesn't write anything");
}
}
Second issue is that logging is added by default and your manually added settings are overriding the default setup.
class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
builder.Services.AddScoped<AzureFunctionClass>();
builder.Services.AddScoped<ISomeClass, SomeClass>();
//...
}
}
Technically all you needed to add was your function class and its dependencies.
Reference Use dependency injection in .NET Azure Functions
At present, the function runtime has a bug due to which it filters out any log that is created with a category that doesn't start with string Function..
See these GitHub issues:
#4425 - ILogger is not injected when using new DI functionality
#4345 - Remove filters for ILoggers created by customer DI
The logger injected in the function method is done by the function runtime which creates the logger with category set to Function.<FunctionName>.User. So this gets logged properly. But the logger that is injected into the constructor is done by the asp.net core DI framework, which sets the category name for the logger as Type.FullName (type in your example case is SomeClass). Because it's fullname doesn't start with Function, the lines logged with this category are filtered out.
There are two ways to workaround this.
Option 1: Change host.json to not filter logs from your namespace
{
"version": "2.0",
"logging": {
"logLevel": {
"<YourNameSpace>": "Information"
}
}
}
Option 2: Inject ILoggerFactory in your ctor, and create a logger with a category that won't get filtered
class SomeClass : ISomeClass {
private readonly ILogger _log;
public SomeClass(ILoggerFactory loggerFactory){ // Note that we inject ILoggerFactory
this._log = loggerFactory.CreateLogger(
LogCategories.CreateFunctionUserCategory(this.GetType().FullName)); // Must use CreateFunctionUserCategory to create the log category name otherwise the log gets filtered out.
}
public void ExecuteMethod(){
_log.LogInformation("This should get logged correctly.");
}
}
Note that, ILogger is already registered into DI framework by the function runtime (as mentioned in NKosi's answer), so those lines can be removed.
I need to programatically create loggers for each controller (let's say: Documents, Customers and Warehouses, to log some operations inside them) and for a filter above controllers (to log parameters sent to each action). Each controller logic should be logged to another file ex. Documents.csv, Customers.csv and Warehouses.csv.
Currently I have a wrapper for nlog logger. The wrappers are injected to controllers instances via constructor and then nlog logger inside in initialized via LogManager. Initialization creates new target and loglevel and assigns it to LogManager.Configuration.
The problem is that after a few requests each loggers logs to each file, so ex. logic from Customers is logged to Customers.csv and vice versa, same thing with logger from Filters.
How should I then configure separate blog loggers with different target for each controller and separate for filter?
I prefer programmatic configuration than via xms etc.
Adapter as requested:
public class Logger : ILogger
{
private NLog.Logger _logger;
string _deployVersion;
public Logger(string deploymentVersion)
{
_deployVersion = deploymentVersion;
}
public void Init(string loggerName)
{
_logger = NLog.LogManager.GetLogger(loggerName);
}
public void Init(string loggerName, string header, string layout, Level level)
{
LoggingConfiguration config;
if (NLog.LogManager.Configuration == null)
{
config = new LoggingConfiguration();
}
else
{
config = LogManager.Configuration;
}
if (config.FindTargetByName(loggerName) == null)
{
var target = CreateTarget(loggerName, header, layout, level); //configures target: path, archives and layout
config.AddTarget(loggerName, target);
var logLevel = GetLogLevel(level); //translates Level enum to NLog level
var rule1 = new LoggingRule("*", logLevel, target);
config.LoggingRules.Add(rule1);
LogManager.Configuration = config;
}
_logger = LogManager.GetLogger(loggerName);
}
...
//Info, Debug etc. methods
Great, you've used an interface here already which makes a pattern for this easier to produce.
Your main problem here is responsibility, as your controller code will (I assume) call Init(string loggerName, string header, string layout, Level level). This is probably not best practice as you may have to repeat this code a lot and the controller probably shouldn't care about where the log goes or what it's formatted like...just the fact that a log is used.
Instead of injecting these directly, use a Factory to obtain the correct logger type. For example, CustomerLogger : ILogger:
public class LogFactory
{
public ILogger Get<T>() where T : ILogger
{
ILogger instance = null;
if (typeof(T) == typeof(CustomerLogger))
{
instance = (T)Activator.CreateInstance(typeof(T), "CustomerLogger", "Header", "Layout", Level.Verbose);
}
else if (...)
{
...etc
}
return instance;
}
}
This means you can leave the management and creation of ILogger concretes up to the Factory.
Notice I'm passing in string loggerName, string header, string layout, Level level so you can call Init in the CustomerLogger constructor. You then have different implementations of ILogger to suit your needs.
You can then either make the LogFactory use an interface and inject that into your controller, or simply new up the factory in the controller, either way you call LogFactory.Get<T>(). Option A would be better for unit testing purposes.
Hope this helps!
We have a websolution with autofac. Now we want to reuse things in a windows service/console app where things are only available when a message comes in from an enterprise bus.
I have the following service to reuse
public SettingsService : ISettingsService {
public readonly ITenantIdentifer _tenantIdentifier
public SettingsService (ITenantIdentifier tenantIdentifier) {
this._tenantIdentifier =tenantIdentifier;
}
// do other stuff
}
The current working setup
the ITenantIdentifier for the webcontext is simply registered for the webapplication using builder.RegisterType<WebTenantIdentifier>().As<ITenantIdentifier>();.
Evething works fine.
Our enterprise bus
The enterprise bus can not resolve the ITenantIdentifier until the message is available. So we created a MessageTenantIdentifier and registered a factory.
public class MessageTenantIdentifier : ITenantIdentifier
{
public delegate MessageTenantIdentifier Factory(int tenantId);
public MessageTenantIdentifier(int tenantId, IOtherDependency stuff)
{
_tenantId = tenantId;
// ...
}
}
// somewhere else the this is registered
builder.RegisterType<MessageTenantIdentifier >().As<ITenantIdentifier>().AsSelf();
builder.RegisterGeneratedFactory<MessageTenantIdentifier.Factory>();
The problem
The factory can only be used when the message is being handled in a
public class MsgTypeHandler : IHandleMessages<MsgType>
{
public MsgTypeHandler(ISettingsService settingsService, MessageTenantIdentifier factory) { ...}
public async Task Handle(MsgType message)
{
var tenantId = message.TenantId;
// THIS IS THE MOMENT I CAN CONFIGURE THE MessageTenantIdentifier
var tenantIdentifier = factory.Invoke(tenantId);
// but this factory is not used against the ISettingsService. The service to be reused. <== THE REAL PROBLEM
}
}
The question
So, how can I solve this issue? E.g. how should I setup the registration of the MessageTenantIdentifier in the servicebus?
Or is my dependency setup just plain wrong?
If the MsgTypeHandler class needs an ISettingsService, but the entire object graph can't be resolved until the tenant ID is available, that means that the MsgTypeHandler is the Composition Root. That's OK, but that means that this is where you resolve your entire object graph, so don't inject individual services here; instead, inject the factory you need:
public class MsgTypeHandler : IHandleMessages<MsgType>
{
public MsgTypeHandler(ISettingsServiceFactory factory) {...}
public async Task Handle(MsgType message)
{
var tenantId = message.TenantId;
ISettingsService svc = this.factory.Create(tenantId);
// User svc here...
}
}