I would like NServiceBus to use the logger provided by the Castle.Windsor container, rather than providing it myself directly. Is there a way to achieve this? Following doesn't work as an exception raised on start.
public class EndPointConfig : IConfigureThisEndpoint, IWantCustomInitialization, IWantCustomLogging
{
private ILogger _logger = NullLogger.Instance;
public ILogger Logger
{
get { return _logger; }
set { _logger = value; }
}
public void Init()
{
SetLoggingLibrary.Log4Net(null, Logger);
Configure.With()
.CastleWindsorBuilder(Host.Init.CreateSubsystems())
.RunTimeoutManager()
.UseNHibernateTimeoutPersister();
}
}
Host.Init.CreateSubsystems() returns a Castle.Windsor container, with a registered logger.
If you supply a container alreday bootstrapped w/ a ILogger registered to CastleWindsorBuilder there's nothing else to be done: NServiceBus will use your logger settings as per log4net configuration(separate file or within app.config according to logger registration in the container)
SetLoggingLibrary.Log4Net(null, Logger); is no needed.
public class MessageEndpoint : IConfigureThisEndpoint, AsA_Server, IWantCustomInitialization
{
public void Init()
{
var container = new WindsorContainer();
var installerFactory = new MyInstallerFactory();
container.Install(FromAssembly.This(installerFactory));
var logger = container.Resolve<ILogger>();
logger.Debug("Container bootstrapped");
Configure.With()
.DisableTimeoutManager()
.CastleWindsorBuilder(container)
.JsonSerializer();
logger.Debug("Bus configured");
}
}
Related
I need to add logger (ILogger) to existing object of MyDbConnection, this object is created from Factory which is registered in NET Core DI together with MyOptions class
public class MyFactory : IMyFactory
{
private readonly MyOptions _options;
public MyFactory(MyOptions options)
{
_options = options;
}
public MyDbConnection CreateDbA() => new MyDbConnection(_options.ConnStrA);
public MyDbConnection CreateDbB() => new MyDbConnection(_options.ConnStrB);
public MyDbConnection CreateDbC() => new MyDbConnection(_options.ConnStrC);
}
Factory is then injected into service, which then use it to create object and do something
public class MyService : IMyService
{
public MyService(IMyFactory factory)
{
var a = factory.CreateDbA();
var b = factory.CreateDbB();
a.DoSomething();
b.DoSomething();
}
}
MyDbConnection looks like this:
public class MyDbConnection
{
private string connStr;
//private ILogger logger;
//public MyDbConnection(string connStr, ILogger logger)
public MyDbConnection(string connStr)
{
this.connStr = connStr;
//this.logger = Logger;
}
public void DoSomething()
{
//logger.LogWarning();
}
}
Problem is that I can't just add ILogger and inject it from DI container because factory is using 'new' to create MyDbConnection, without using DI. My only solution currently is to use static logger instead of injected one, but that seems to be a bad solution. Is there another way around this? How it should be done properly?
The design will need to be refactored to be able to get the desired behavior.
First MyDbConnection should be refactored accordingly to depend on the appropriate logger
For example
public class MyDbConnection {
private string connStr;
private ILogger logger;
public MyDbConnection(string connStr, ILogger<MyDbConnection> logger) {
this.connStr = connStr;
this.logger = logger;
}
public void DoSomething() {
logger.LogWarning();
}
}
Then the factory refactored to use ActivatorUtilities to initialize and inject the necessary dependencies via an injected service provider.
public class MyFactory : IMyFactory {
private readonly MyOptions options;
private readonly IServiceProvider services;
public MyFactory(MyOptions options, IServiceProvider services) {
this.options = options;
this.services = services;
}
public MyDbConnection CreateDbA() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrA);
public MyDbConnection CreateDbB() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrB);
public MyDbConnection CreateDbC() => ActivatorUtilities.CreateInstance<MyDbConnection>(services, options.ConnStrC);
}
In the example above the specific dependencies are provided just as before when they were manually initialized. All other dependencies (like the logger) will be resolved via the service provider and injected into the target class when it is being initialized.
This way dependency injection can be maintained and manual initialization of MyDbConnection can be avoided.
I want to pass a logger into a constructor of an object. Normally, the logger is dependency injected into a class, but I want to log inside a class that is not dependency injected.
I have a .NET Core worker service. In that service, I have 0-many instances of a class that are created at runtime. I also want to be able to log messages to the logger inside that class.
In my example, I can pass the ILogger that I get from inside the TimedWorker, but the type is wrong.
Currently, I have:
class TimedWorker : BackgroundService
{
private List<Dog> m_dogs;
private ILogger<TimedWorker> m_logger;
private IConfiguration m_configuration;
public TimedWorker(ILogger<TimedWorker> logger, IConfiguration configuration)
{
m_logger = logger;
m_configuration = configuration;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
m_dogs= new List<Dogs>();
List<DogOption> dogOptions= new List<DogOption>();
m_configuration.GetSection("Dogs").Bind(dogOptions);
foreach(DogOption option in dogOptions)
{
m_dogs.Add(new Dog(option, m_logger));
}
}
///...
}
class Dog {
public string Name { get; }
private int Age;
private ILogger m_logger;
public Dog(DogOption option, ILogger logger)
{
m_logger = logger;
Name = option.Name;
Age = option.Age;
m_logger.LogInformation($"Dog '{Name}' created");
}
}
The problem is that when it logs, the logger is of type TimedWorker, not Dog. So when I look at the logs, they are labeled under TimedWorker instead of Dog. I do not know how to pass in a ILogger into the Dog class. Any advice?
You can inject the IServiceProvider and resolve the dependency manually.
You can do it like this:
public class Worker
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Start()
{
var specificLogger = _serviceProvider.GetService(typeof(ILogger<Dog>)) as ILogger<Dog>;
var dog = new Dog(specificLogger);
}
}
public class Dog
{
public Dog(ILogger<Dog> logger)
{
logger.LogInformation("Hello");
}
}
This creates the specific log based on the class.
2020-11-18 18:41:58.1275||INFO|ConsoleApp.Dog|Hello
You can create a logger directly. For example:
var logger = LoggerFactory.Create(options => {}).Create<Worker>();
I have written a Windows Service which logs its operations using Serilog
The application also uses Autofac for dependency Injection support and Seq for structured logging.
using Autofac;
public class ContainerInitiator
{
public static IContainer BuildContainer()
{
var _builder = new ContainerBuilder();
var logger = LoggerUtility.CreateLogger();
_builder.RegisterInstance(logger).As<ILogger>().SingleInstance();
var container = _builder.Build();
ContainerFactory.SetContainer(container);
return container;
}
}
Here is Log utility class
public class LoggerUtility
{
private static string connectionString = ConfigurationManager.AppSettings["applicationName"];
public static ILogger CreateLogger()
{
return GetLoggerConfiguration().CreateLogger();
}
private static LoggerConfiguration GetLoggerConfiguration()
{
var config = new LoggerConfiguration()
.ReadFrom.AppSettings();
config = config.
Enrich.WithProperty("ApplicationName", connectionString).
Enrich.WithExceptionDetails().
Enrich.WithMachineName().
Enrich.WithProcessId().
Enrich.WithThreadId().
ReadFrom.AppSettings();
return config;
}
}
Here is my service class, which is logging the activities
public class Engine : IEngine
{
private readonly ILogger _logger;
public RedistributeEngine(IContainerFactory containerFactory)
{
_logger = containerFactory.GetInstance<ILogger>();
}
public void Start()
{
_logger.Information("Engine started!");
}
}
As you see below Seq logs, it has two entries on the same time!
You're reading the configuration from App.Settings twice:
private static LoggerConfiguration GetLoggerConfiguration()
{
var config = new LoggerConfiguration()
.ReadFrom.AppSettings(); // <<<<<<<<<<<< *#*#*#*#*#*#*#*#
config = config.
Enrich.WithProperty("ApplicationName", connectionString).
Enrich.WithExceptionDetails().
Enrich.WithMachineName().
Enrich.WithProcessId().
Enrich.WithThreadId().
ReadFrom.AppSettings(); // <<<<<<<<<<<< *#*#*#*#*#*#*#*#
return config;
}
I have a base controller (i didn't create it btw) in my net core api that basically starts with following:
public abstract class MyBaseController<T> : ControllerBase where T : MyBaseController<T>
{
private ILogger<T> _logger;
protected ILogger<T> Logger => _logger ?? (_logger = HttpContext?.RequestServices.GetService<ILogger<T>>());
}
When i am unit testing my other controller that inherits the base controller how do deal with this logger?
currently my unit test class has a constructer with something like
_controller = new cartController(_cartService);
but then i get stuck.
I will be using xUnit and Moq in the test project.
Any help is appreciated.Thanks
Here's a minimal example from this article on how to inject an ILogger dependency and then verify a call afterward with Moq:
public class LogTest
{
private readonly ILogger _logger;
public const string InformationMessage = "Test message";
public const string ErrorMessage = "Not implemented {recordId}";
public LogTest(ILogger<LogTest> logger)
{
_logger = logger;
}
public void Process()
{
_logger.LogInformation(InformationMessage);
}
}
_loggerMock.Verify(l => l.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Exactly(1));
In general you should rely on DI in tests as well as in runtime. The following library contains a test logger which you may use in tests: https://www.nuget.org/packages/com.github.akovac35.Logging.Testing/
Usage samples are available here: https://github.com/akovac35/Logging.Samples
Disclaimer: I am the author of the above.
Basically you would proceed as follows:
Use NullLogger by default:
public abstract class MyBaseController<T> : ControllerBase
{
private ILogger _logger = NullLogger.Instance;
protected MyBaseController(ILogger<MyBaseController<T>> logger = null)
{
if (logger != null) _logger = logger;
}
}
Derived classes should inject logger:
public class MyBaseControllerVariant<T> : MyBaseController<T>
{
private ILogger _logger = NullLogger.Instance;
public MyBaseControllerVariant(ILogger<MyBaseControllerVariant<T>> logger = null, ILogger<MyBaseController<T>> baseLogger = null): base(baseLogger)
{
if (logger != null) _logger = logger;
}
}
Now wire up everything:
using com.github.akovac35.Logging.Testing;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Shared.Mocks;
using System;
namespace TestApp
{
[TestFixture]
public class TestLoggingExamples
{
[OneTimeSetUp]
public void OneTimeSetUp()
{
customOnWrite = writeContext => {
Console.WriteLine(writeContext);
};
customOnBeginScope = scopeContext => {
Console.WriteLine(scopeContext);
};
serviceCollection = new ServiceCollection();
serviceCollection.AddTransient(typeof(MyBaseControllerVariant<>));
// Register TestLogger using extension method
serviceCollection.AddTestLogger(onWrite: customOnWrite, onBeginScope: customOnBeginScope);
}
private IServiceCollection serviceCollection;
private Action<WriteContext> customOnWrite;
private Action<ScopeContext> customOnBeginScope;
[Test]
public void Test_WithLoggingToTestConsole_Works()
{
// The service provider should be defined on per-test level or logger writes will accumulate and may result in OOM - clean them with testSink.Clear()
var serviceProvider = serviceCollection.BuildServiceProvider();
var controller = serviceProvider.GetRequiredService<MyBaseControllerVariant<object>>();
controller.Invoke();
var testSink = serviceProvider.GetRequiredService<ITestSink>();
Assert.IsTrue(testSink.Writes.Count > 0);
Assert.IsTrue(testSink.Scopes.Count > 0);
}
}
}
I'm trying to use TopShelf together with Quartz.net and Autofac. The code I have below works just fine. However, this line:
cfg.UsingQuartzJobFactory(() => container.Resolve<IJobFactory>());
seems like the wrong way of doing things. Is there a better way of telling Topshelf to use the custom autofac jobfactory? What lifetime scope will the jobfactory have? I'm concerned this line of code is going to cause me some headaches sometime in the future. How do I release the jobfactory when it's no longer needed? Is this line okay as-is?
class Poller : IJob
{
private readonly ILogger _log;
public Poller(ILogger log)
{
_log = log;
_log.Info("Instantiating...");
}
public void Execute(IJobExecutionContext context)
{
_log.Info("Executing...");
}
}
class Program
{
static Autofac.IContainer BuildContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule<NLogModule>();
builder.RegisterModule<QuartzAutofacFactoryModule>();
builder.RegisterModule(new QuartzAutofacJobsModule(typeof(Poller).Assembly));
var container = builder.Build();
return container;
}
static void Main(string[] args)
{
var container = BuildContainer();
HostFactory.Run(cfg =>
{
cfg.UseNLog();
cfg.UseAutofacContainer(container);
cfg.SetDescription("DESCRIPTION");
cfg.SetDisplayName("DISPLAY");
cfg.SetServiceName("NAME");
cfg.UsingQuartzJobFactory(() => container.Resolve<IJobFactory>());
cfg.ScheduleQuartzJobAsService(q =>
{
q.WithJob(() => JobBuilder.Create<Poller>().Build());
q.AddTrigger(() => TriggerBuilder.Create().WithSimpleSchedule(b => b.WithIntervalInSeconds(20).RepeatForever()).Build());
});
cfg.StartAutomatically();
cfg.RunAsLocalSystem();
});
}
}
For reference: TopShelf.Quartz.ScheduleHobHostConfiguratorExtensions
Also reference: Autofac.Extras.Quartz.QuartzAutofacFactoryModule
I think you should initialize quartz Server with container, this example use unity, but I am sure that work with other containers.
try
{
var container = new UnityContainer();
schedulerFactory = CreateSchedulerFactory();
quartzscheduler = GetScheduler();
SyncPost.Initialize.RepositoryConfig(container);
SyncPost.Initialize.AddToSchedulerContextCustomVars(quartzscheduler, container);
quartzscheduler.JobFactory = new JobFactoryInjection(container);
}
catch (Exception e)
{
logger.Error("Server initialization failed:" + e.Message, e);
throw;
}
where JobFactoryInjection implement IJobFactory:
public class JobFactoryInjection : IJobFactory
{
private readonly UnityContainer container = new UnityContainer();
public JobFactoryInjection(UnityContainer container)
{
if (container == null) throw new ArgumentNullException("container", "Container is null");
this.container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {
// Return job registrated in container
bundle.JobDetail.JobDataMap.Put(SyncUtils.ContextKeyCenterCode, scheduler.Context.Get(SyncUtils.ContextKeyCenterCode));
return (IJob)container.Resolve(bundle.JobDetail.JobType);
}
public void ReturnJob(IJob job) {
}
}
About JobFactory lifetime, don't worry about it. From Quartz documentation:
"JobFactory simply activates a new instance of the job class. You may want to create your own implementation of JobFactory to accomplish things such as having your application's IoC or DI container produce/initialize the job instance"