Hangfire does not log exception using configured Logger - hangfire

As per the hangfire documentation
Starting from Hangfire 1.3.0, you are not required to do anything, if
your application already uses one of the following libraries through
the reflection (so that Hangfire itself does not depend on any of
them). Logging implementation is automatically chosen by checking for
the presence of corresponding types in the order shown below.
Serilog
NLog
Log4Net
EntLib
Logging
Loupe
Elmah
I have ASP.NET Core 2.0 application that is using Serilog like below
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseApplicationInsights()
.UseUrls("http://*:40006")
.ConfigureAppConfiguration((hostingContext, config) =>
{
// removed for bravity
})
.ConfigureLogging((hostingContext, logging) =>
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(hostingContext.Configuration)
.CreateLogger();
logging.AddSerilog();
})
.Build();
}
The application is configured to use Hangfire. During background job processing if there is any exception occurs, Hangfire re-tries the job 10 times with increasing delay as expected, and it shows the exceptions in dashboard.
Issue
The Hangfire dashboard shows the exception on UI however it does not log the exception into configured Serilog sink.
Note: The Hangfire dashboard shows exception but it formats the exception see here which hides critical information about the exception.
I think if it logs the exception, Serilog logger would log the complete exception.

I had the same issue with the latest Hangfire version using .NET Core 3.1.
The solution was to call the UseSerilogLogProvider() method of IGlobalConfiguration in StartUp.cs.
For example:
GlobalConfiguration.Configuration.UseSerilogLogProvider();
or
services.AddHangfire(configuration => configuration
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSerilogLogProvider());
Normally this call shouldn't be needed but there are some cases where automatic wiring isn't working (an example would be referencing multiple logging providers). You can read more about this in the official Hangfire documentation: Configure Logging

Related

ILogger in Asp.net Core and Serilog

I have a question,
Is there any concern if I use ILogger in Serilog on behalf of Microsoft logger?
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(Log.Logger);
}
Then use from ILogger in Serilog namespace.
_logger.Error(exception, "Error", exception.Message, exceptionId);
What is your idea?
Microsoft.Extensions.ILogger is an abstraction that decouples your application code (the code that writes log entries) from the underlying logging framework.
Using this abstraction brings the benefit, that you could easily change the Logging Framework underneath (e.g. replace Serilog with NLog) and don't have to update all references in your application code.
Also using Microsoft's abstractions also allows you to configure logging of your application code and logging of Microsoft SDKs you might use at a single place.
The downside of abstractions is that you have to aggree to a common minimum interface provided by all logging frameworks. It's not so easy to use Framework-Specific features this way.
So most of the time i would advise using the abstraction.
If you have very specific features from Serilog you would like to interact with you could think about using ILogger from Serilog directly.
However you can configure serilog in the provider registration as well to a high degree and probably get the best of both worlds.
You would configure Serilog factory interface to be used instead of built-in Logger factory for creating ILogger
First in program.cs, add the Serilog ILoggerFactory to your IHostBuilder with UserSerilog() method in CreateHostBuilder():
public static IHostBuilder CreateHostBuilder(string[] args) =>
new HostBuilder()
.ConfigureHostConfiguration(builder => { /* Host configuration */ })
.ConfigureAppConfiguration(builder => { /* App configuration */ })
.ConfigureServices(services => { /* Service configuration */})
.UseSerilog(); // <- Add this line
}
How the library works behind the scenes
On the face of it, completely replacing the default ASP.NET Core logging system to use a different one seems like a big deal. Luckily, thanks to the use of interfaces, loose coupling, and dependency injection, the code is remarkably simple! The whole extension method we used previously is shown below:
public static class SerilogHostBuilderExtensions
{
public static IHostBuilder UseSerilog(this IHostBuilder builder,
Serilog.ILogger logger = null, bool dispose = false)
{
builder.ConfigureServices((context, collection) =>
collection.AddSingleton<ILoggerFactory>(services => new
SerilogLoggerFactory(logger, dispose)));
return builder;
}
}
The UseSerilog() extension calls the ConfigureServices method on the IHostBuilder and adds an instance of the SerilogLoggerFactory as the application's ILoggerFactory. Whenever an ILoggerFactory is required by the app (to create an ILogger), the SerilogLoggerFactory will be used.
for more Information check this Link
"The downside of abstractions is that you have to aggree to a common minimum interface provided by all logging frameworks. It's not so easy to use Framework-Specific features this way."
I think we can always create extension methods to overcome this downside.

IWebHostBuilder.Configure() not executing in ASP.NET Core

Why is that a call to IWebHostBuilder.Configure() extension method seemingly doesn't do anything in ASP.NET Core (experienced in version 3.4.0)?
For example in this scenario:
public static IHostBuilder CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
webBuilder
.UseSerilog( ... )
.Configure(appBuilder => // Doesn't do anything.
appBuilder.UseSerilogRequestLogging( ... ))
.UseStartup<Startup>();
});
Here, UseSerilogRequestLogging() is called inside Configure() to add Serilog's request logging middleware before Startup executes, in order to place it at the beginning of the request pipeline, and also to keep logging-related configuration at one place.
But Configure() literally doesn't do anything, and the middleware is not added.
The reason is that the IWebHostBuilder.Configure() method is not just a general configuration method. It actually registers the provided delegate as IStartup in the ServiceCollection. See source (albeit old) here.
This means that when UseStartup<Startup>() is called subsequently, it replaces the previously registered delegate, and thus the configuration in Configure() is not executed.
This behavior can be further confirmed if you place Configure() after UseStartup<>(). In this case, Configure() will be the one that replaces UseStartup<>(), and UseStartup() won't execute.
The documentation on Configure() method actually hints at this:
//
// Summary:
// Specify the startup method to be used to configure the web application.
//
(Answered my own question, to spare some time for someone else who might end up being as perplexed as I was.)

ASP.net Core 2.2 configuration

Coming from a webforms background, I'm trying to understand how configuration and environment translation works in .net core 2.2 MVC web apps. Gone are the web.config files and the ConfigurationSettings.AppSettings property. I'm finding the documentation a little unclear.
The documentation states I need to call AddJsonFile or AddXmlFile during application startup. Like this:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config.AddJsonFile(
"config.json", optional: true, reloadOnChange: true);
})
.UseStartup<Startup>();
The project template I use already has the following logic:
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
My project has appsettings.json and appsettings.development.json files. When I put a breakpoint on the Startup method of the Startup class, I can inspect the configuration parameter and see the two json configuration files exposed as what looks to be a dictionary.
Questions
So do I have to explicitly call AddJSonFile, or is this actually done for me somehow by the framework?
How do I handle transforming configuration for different deployments?
What is the best way to access this configuration in a controller?
So do I have to explicitly call AddJSonFile, or is this actually done for me somehow by the framework?
This is done in the framework. Most notably the "DefaultBuilder" adds in both appsettings.json and appsettings.{Environment}.json, among other things. https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.webhost.createdefaultbuilder?view=aspnetcore-2.2
How do I handle transforming configuration for different deployments?
You need to set the Environment variable on the host machine (This is the easiest way althought here are other ways to do it). So for example if you set the environment to be Production, then it will first load appsettings.json, then it will load appsettings.Production.json and override the default settings. More info here : https://dotnetcoretutorials.com/2017/05/03/environments-asp-net-core/
What is the best way to access this configuration in a controller?
There are two ways. You can use the Options pattern built into the framework : https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2
Or you can use good old fashioned POCO's (https://dotnetcoretutorials.com/2016/12/26/custom-configuration-sections-asp-net-core/).
All you need to do there is load out your configuration in your ConfigureServices method and bind it to a singleton :
services.AddSingleton(Configuration.GetSection("myConfiguration").Get<MyConfiguration>());
Then you can simply request it in your controller via DI:
public class ValuesController : Controller
{
private readonly MyConfiguration _myConfiguration;
public ValuesController(MyConfiguration myConfiguration)
{
_myConfiguration = myConfiguration;
}
}

RedisTimeoutException is crashing my aspnet core application

When my application traffic gets high, StackExchange.Redis starts to throw RedisTimeoutException and after some minutes, my asp.net core application crashes.
The Windows event viewer says The process was terminated due to an unhandled exception. Exception Info: StackExchange.Redis.RedisTimeoutException.
Ok, I understand that there is some issue between my app and Redis, but while I can't solve this, how can I prevent the application to shutdown?
Inside startup.cs, I tried to put:
TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs eventArgs) =>
{
eventArgs.SetObserved();
eventArgs.Exception.Handle(ex => true);
};
no success....
Any help ?
Tks
How are you creating the ConnectionMultiplexer instances?
Maybe you are not reusing a multiplexer instance and creating a lot of connections.
The ConnectionMultiplexer object should be shared and reused between callers. It is not recommended to create a ConnectionMultiplexer per operation. Check StackExchange.Redis documentation here for more information.
About the exception handling on Asp.NET Core, you can use the UseExceptionHandler diagnostic middleware to handle exceptions globally. Check this article for a complete explanation
Have you tried to put the block that throws the exception in a try/catch block? And perhaps make it try a few times with Polly when there is a timeout. https://github.com/App-vNext/Polly
Normally it shouldn't terminate your app, but since you didn't share any code, We can not be sure.
If you create a service class like below, you can encapsulate all of your redis calls, therefore catch the exceptions.
public class EmptyClass
{
private readonly ConnectionMultiplexer _connectionMultiplexer;
public EmptyClass(ConnectionMultiplexer connectionMultiplexer)
{
_connectionMultiplexer = connectionMultiplexer;
}
public void Execute(Action<ConnectionMultiplexer> action)
{
try
{
action.Invoke(_connectionMultiplexer);
}
catch(RedisTimeoutException ex)
{
}
}
public void TestRun()
{
Execute((ConnectionMultiplexer obj) =>
{
//do stuff with obj.
});
}
}
I agree with #thepirat000's answer, reason is ConnectionMultiplexer
You can use ConnectionMultiplexer according your Redis package (StackExchange.Redis or ServiceStack.Redis) and according your deployment environment
In my aspnet core application (like you) i have used StackExchange.Redis and i have deployed to windows server without any error within below Startup.cs settings
#region Redis settings ConnectionMultiplexer
services.AddDataProtection().ProtectKeysWithDpapi(protectToLocalMachine: true);
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(#"c:\temp-keys"))
.ProtectKeysWithDpapiNG($"CERTIFICATE=HashId:{thumbPrint}", flags: Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiNGProtectionDescriptorFlags.None);
services.AddDataProtection().ProtectKeysWithDpapiNG();
services.Configure<StorageConfiguration>(new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", optional: true, reloadOnChange: true).Build());
var redisConf = Configuration.GetSection("RedisConnection").Get<RedisConnection>();
ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(redisConf.Host.ToString() + ":" + redisConf.Port.ToString());
services.AddDataProtection().PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys");
services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(redisConf.Host.ToString() + ":" + redisConf.Port.ToString()));
#endregion
Look here for basic usage https://stackexchange.github.io/StackExchange.Redis/Basics.html

how can i configure hangfire dashboard in console application?

i am using hangfire nuget package to schedule the jobs in asp.net core console application
i tried all the ways to configure the dashboard to the console application
how can i host the webpage from console application???
i have created startup.cs class for dashboard configuration
using Hangfire;
using Microsoft.AspNetCore.Builder;
namespace PulsarHangFire
{
public class Startup
{
public void Configuration(IApplicationBuilder app)
{
app.UseHangfireDashboard("/hangfire");
app.UseHangfireServer();
}
}
}
can anyone tell me how can i move forward
Create a Startup.cs file (or get one from the .NET Core Web App template) and configure the following:
public void ConfigureServices(IServiceCollection services)
{
// ... other required services ...
services.AddHangfire(configuration =>
{
// Do pretty much the same as you'd do with
// GlobalConfiguration.Configuration in classic .NET
// NOTE: logger and activator would be configured automatically,
// and in most cases you don't need to configure those.
configuration.UseSqlServerStorage(...);
// ... maybe something else, e.g. configuration.UseConsole()
});
}
Finally add the Hangfire dashboard:
public void Configure(IApplicationBuilder app, IRecurringJobManager recurringJobManager)
{
// ... previous pipeline stages, e.g. app.UseAuthentication()
app.UseHangfireDashboard(...);
// ... next pipeline stages, e.g. app.UseMvc()
// then you may configure your recurring jobs here:
recurringJobManager.AddOrUpdate(...);
}
Source