Im new to hangfire, and im trying to setup a way to log the events using my existing serilog logger in an asp.net web api. Here is my logger class:
public static class LoggerInitializer
{
private static ILogger CreateLog()
{
var settings = Settings.Instance;
Log.Logger = new LoggerConfiguration().
MinimumLevel.Debug().
WriteTo.RollingFile(settings.LoggerDebugDirectory +"Debug-{Date}.txt", restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] {Message}{NewLine}{Exception}").
WriteTo.RollingFile(settings.LoggerVerboseDirectory + "Debug-{Date}.txt").
CreateLogger();
return Log.Logger;
}
static ILogger _logger;
public static ILogger GetLogger()
{
if (_logger != null)
return _logger;
return _logger = CreateLog();
}
}
and in my startup file I add the code from the hangfire documentation:
GlobalConfiguration.Configuration
.UseSqlServerStorage(Settings.Instance.NLSContextConnectionString);
app.UseHangfireDashboard();
app.UseHangfireServer();
My hangfire works perfectly, but how do i enable make hangfire use my serilog?
It's possible that Hangfire is initializing and caching its own internal logger before CreateLog() is being called by the application.
To test this theory, try moving the code that initializes Log.Logger to the very beginning of the app's startup code, e.g. in Global.Application_Start() or similar.
In Hangfire 1.6.19 (and maybe before that, I did not check) adding the NuGet Package to your project gives you an extension method on IGlobalConfiguration :
configuration.UseSerilogLogProvider();
Related
I am completely new to wcf and serilog and autofac, and I am trying to integrate all three of them. I am trying to log all events into a txt file and I somehow got it to work using this code in global asax:
var logFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "Log/Log.txt");
ILogger logger = new LoggerConfiguration()
.WriteTo.File(logFilePath)
.CreateLogger();
Log.Logger = logger;
logger.Information("Application Startup...");
and not this code:
var builder = new ContainerBuilder();
builder.RegisterType<Service1>();
builder.Register<ILogger>((c, p) =>
{
return new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.File(logFilePath)
.CreateLogger();
}).SingleInstance();
AutofacHostFactory.Container = builder.Build();
What is the difference between this two codes?
With the first code I can already log information in other classes, but I have come to find out that it is not good practice to manually log. I have tried using dependency injection like below in other classes:
ILogger _logger;
public AddCard(ILogger logger)
{
_logger = logger;
}
But for some reason it does not work. I am trying to make serilog log events at a global scale yet I do not understand how serilog and autofac tie in together in wcf. I have more than 40 tabs open trying to piece together what info I can, but Im still finding it hard to understand.
I'm working into net core identity Server project. in the StartUp class this code create a log object
public Startup(IConfiguration configuration, ILogger<Startup> logger)
{
StaticConfig = configuration;
_logger = logger;
}
the same code is inside the api project. Both IdentityServer and Api needs to call functions from an old Soap service. So i create a new net core class Library called SoapClient to share the functions between the two projects.
this the code to instantiate soapClient from IS and Api projects:
SoapClient.Client client = new SoapClient.Client(
Startup.StaticConfig.GetValue<string>("soapClient:server")
);
Now i dont know how to inject the log instance inside SoapClient Library. i Tried this into the
constructor but logger is always null:
public Client(string serviceUrl, ILogger<Client> logger = null)
{
_ServiceUrl = serviceUrl;
logger.LogDebug("Created");
}
if remove the default null to logger parameter, the code does compiler because logger is expected to be passed when the costructor is called from IS project and Api project.
You should use dependency injection for this kind of scenarios. The callers should not be responsible of knowing how to create an instance of SoapClient.Client.
Just register your SoapClient.Client into DI. And naturally you can have both the config (via IOptions) and logger injected automatically.
If for whatever reason you don't want to use DI, the caller will have to have an ILoggerFactory injected and you can create ILogger instances using the ILoggerFactory.
I have ASP.NET Web API application. The application is using Unity as IoC container. The application is also using Hangfire and I am trying to configure Hangfire to use Unity.
So based on documentation i am using Hangfire.Unity which registers the unity container as a current job activator in Hangfire.
I have a class which has dependency on IBackgroundJobClient
public class MyService
{
private MyDBContext _dbContext = null;
private IBackgroundJobClient _backgroundJobClient = null;
public MyService(MyDbContext dbContext, IBackgroundJobClient backgroundJobClient)
{
_dbContext = dbContext;
_backgroundJobClient = backgroundJobClient;
}
}
However even after configuring Hangfire.Unity it could not create & pass instance of BackgroundJobClient
So i had to register every dependency of BackgroundJobClient with unity container.
Unity Registration
public class UnityConfig
{
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<MyDbContext>(new HierarchicalLifetimeManager(), new InjectionFactory(x => new MyDbContext()));
// register hangfire dependencies
container.RegisterType<IBackgroundJobClient, BackgroundJobClient>();
container.RegisterType<JobStorage, SqlServerStorage>(new InjectionConstructor("HangfireConnectionString"));
container.RegisterType<IJobFilterProvider, JobFilterAttributeFilterProvider>(new InjectionConstructor(true));
container.RegisterType<IBackgroundJobFactory, BackgroundJobFactory>();
container.RegisterType<IRecurringJobManager, RecurringJobManager>();
container.RegisterType<IBackgroundJobStateChanger, BackgroundJobStateChanger>();
}
}
OWIN Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var container = UnityConfig.GetConfiguredContainer();
Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage("HangfireConnectionString");
Hangfire.GlobalConfiguration.Configuration.UseUnityActivator(container);
// if i dont call UseSqlServerStorage() above then UseHangfireDashboard() method fails with exception
//JobStorage.Current property value has not been initialized. You must set it before using Hangfire Client or Server API.
app.UseHangfireDashboard();
app.UseHangfireServer();
RecurringJob.AddOrUpdate<MyService>(x => x.Prepare(), Cron.MinuteInterval(10));
}
}
Code is working with such configuration. However i have questions:
Is this the correct way of configuring Unity with Hangfire?
Why do i need to invoke Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage("HangfireConnectionString") in OWIN startup even though SqlServerStorage is already registered with Unity container as JobStorage?
If i dont invoke UseSqlServerStorage() method in OWIN startup then i get exception on app.UseHangfireDashboard() method.
JobStorage.Current property value has not been initialized. You must
set it before using Hangfire Client or Server API.
I believe there is a problem where you want to kick off Hangfire outside of the Unity ecosystem, but also want Unity to understand how to instantiate the appropriate Hangfire interfaces with the associated implementations. Since Hangfire itself doesn't use Unity, you will need to start up Hangfire with the appropriate configuration, such as the SQL Server connection string, and then use that configuration to inform Unity how to instantiate the Hangfire interfaces. I was able to solve this problem by setting the global Hangfire configuration for SQL and then use that same Hangfire static instance to set up Unity.
Here's example code where first you will see how I start the hangfire dashboard and server with a connection string:
public void Configuration(IAppBuilder app)
{
var configuration = new Configuration(); // whatever this is for you
GlobalConfiguration.Configuration.UseSqlServerStorage(
configuration.GetConnectionString());
GlobalConfiguration.Configuration.UseActivator(
new HangfireContainerActivator(UnityConfig.GetConfiguredContainer()));
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] {new HangfireAuthorizationFilter()}
});
app.UseHangfireServer();
}
As the second example, here's the configuration of Unity for Hangfire; notice how this code is using the static JobStorage Hangfire object to instantiate any requests for JobStorage.
public static void RegisterHangfire(IUnityContainer container)
{
container.RegisterType<JobStorage>(new InjectionFactory(c => JobStorage.Current));
container.RegisterType<IJobFilterProvider, JobFilterAttributeFilterProvider>(new InjectionConstructor(true));
container.RegisterType<IBackgroundJobFactory, BackgroundJobFactory>();
container.RegisterType<IRecurringJobManager, RecurringJobManager>();
container.RegisterType<IBackgroundJobClient, BackgroundJobClient>();
container.RegisterType<IBackgroundJobStateChanger, BackgroundJobStateChanger>();
}
I believe this approach gives you the best of both worlds where you only set up your SQL Server connection once and you do it early to kick off Hangfire, but then you use that instance to tell Unity how to behave.
I have a console application project that is using Ninject and Log4Net.
When i run the app on my machine, the logging is working fine. When i run the app on the production server, the logging is working fine. When i run the program via TaskScheduller task which is being set so that is is being run by some other user, i get no logging output by any of the appenders. I'm using RollingFileAppender, SmtpAppender and AdoNetAppender.
The strange thing is, that the program is running fine, it just doesnt log anything.
I presume that because the app is working if i run it locally, the log4net configuration is fine.
I resolve logger in the main method of the program and then inject it via constructor parameter when needed. This is how i get the logger in the main method of the app:
XmlConfigurator.ConfigureAndWatch(new FileInfo("log4net.config"));
var kernel = new StandardKernel();
var loggerFactory = kernel.Get<Log4NetLoggerFactory>();
ILogger logger = loggerFactory.GetCurrentClassLogger();
logger.Info(" Test ");
Any hints, pointers or anything....as i don't know what else to try.
The extension is normally used like this:
public class MyClass
{
private readonly ILogger log;
public MyClass(ILogger log)
{
this.log = log;
log.Info("Created MyClass");
}
}
XmlConfigurator.ConfigureAndWatch(new FileInfo("log4net.config"));
using (IKernel kernel = new StandardKernel())
{
kernel.Bind<MyClass>().ToSelf();
kernel.Get<MyClass>(); // will cause the log message to print
}
Just let Ninject worry about injecting the ILogger into your class. You can't request an ILogger from the IKernel in the same place you declare the kernel because you've got no context.
You can do this though:
ILogger log = new Log4NetLoggerFactory().GetCurrentClassLogger();
log.Info("Test");
So, i used Mark Seemann's example to do dependency injection with Windsor in MVC 4 RC Web Api, but i get an exception saying that it can't resolve the dependencies to my ApiController
public class StatisticsController : ApiController
{
private readonly ILogger _logger;
private readonly IClickMessageProducer _producer;
public StatisticsController(ILogger logger,
IClickMessageProducer clickMsgProducer)
{
_logger = logger;
_producer = clickMsgProducer;
}
public string Get(string msg, string con) {...}
}
My Global.asax looks like this:
protected void Application_Start()
{
// different configs removed for brevity
BootstrapContainer();
}
private static IWindsorContainer _container;
private static void BootstrapContainer()
{
_container = new WindsorContainer()
.Install(FromAssembly.This(), new ProducerInstaller())
.Install(FromAssembly.This(), new WebWindsorInstaller());
GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerActivator),
new WindsorHttpControllerActivator(_container));
}
The Installers gives Windsor the references needed to IClickMessageProducer. I have it working with IController in a genuine MVC 4 project so i'm confident that part is working.
To specify, this is the error message i get, when trying to access a method in StatisticsController with a GET call to the API:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Can't create component 'APIMVC.Controllers.StatisticsController'
as it has dependencies to be satisfied.
'APIMVC.Controllers.StatisticsController' is waiting for the following
dependencies: - Service 'Castle.Core.Logging.ILogger' which was not registered.
</ExceptionMessage>
<ExceptionType>Castle.MicroKernel.Handlers.HandlerException</ExceptionType>
<StackTrace>...</StackTrace>
</Error>
The call being something like this:
"http://localhost:60000/api/statistics?msg=apitest&con=apimvc"
If anyone has a working example or just a comment to the problem about my Windsor implementation i'll be happy to see it.
Your ILogger implementation isn't registered with Windsor. Remove the ILogger parameter from StatisticsController and try again. If it works, you're going to need to register an ILogger implementation.
_container = new WindsorContainer().Install(FromAssembly.This(), new ProducerInstaller()).Install(FromAssembly.This(), new WebWindsorInstaller());
this was the part at fault. As you can see I call Install(FromAssembly.This()) twice witch caused the LoggerInstaller to try to add a LoggingFacilitytwice causing an error.
The new implementation would look like this:
_container = new WindsorContainer().Install(FromAssembly.This(), new ProducerInstaller(), new WebWindsorInstaller());