Logging to NLog in Hangfire.io - c#

I am currently using Hangfire to run some jobs in a windows service. The jobs are fired from a WebAPI.
My system is currently working fine when all jobs succeed, but I am getting no logging for when exceptions occur. Has anyone got any experience in using a custom logger to receive messages from Hangfire?
My logger is a basic NLog interface:
public class Logger : ILogger
{
public readonly NLog.Logger logger;
public Logger(string name)
{
if (LogManager.Configuration == null)
{
FallbackInitialisation();
}
logger = LogManager.GetLogger(name);
}
public Trace(string message)
//etc.
}
I am configuring my Hangfire job server in my windows service like so:
SqlServerStorage storage = new SqlServerStorage("myConnectionString");
BackgroundJobServerOptions options = new BackgroundJobServerOptions();
m_server = new BackgroundJobServer(options, storage);
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 0 });
Based on the hangfire documentation, I simply tried following it by adding the required references, and adding the following after my job server setup, however there were still no logs produced:
var properties = new NameValueCollection();
properties["configType"] = "INLINE";
LogManager.Adapter = new NLogLoggerFactoryAdapter(properties);
I am simply simulating an exception by throw new Exception() in the method called by the job. The job is created in the webApi call like so:
[HttpGet]
public void TestStartJob()
{
m_logger.Trace("TestStartJob");
BackgroundJob.Enqueue(() => m_service.TestStartJob());
}
What I am looking for, is if anyone has any experience in getting logging properly configured for use with Hangfire.

I have managed to solve this issue by using the following versions of nuget packages:
NLog - 3.1
Common.Logging - 2.2.0
JVW.Logging.CommonLoggingNLogAdapter - 1.0.0.1
and then configuring the logging as such
var properties = new NameValueCollection();
properties["configType"] = "INLINE";
Common.Logging.LogManager.Adapter = new NLogFactoryAdapter(properties);

Related

C# Azure: How to set Azure timeout and retry policy when running locally?

I am writing C# code that runs against an Azure cloud. My application is an ASP.NET Core web service that exposes methods but no UI.
Sometimes I want to run my code locally using Microsoft Azure Storage Emulator. When my code starts up, one of the first things that happens is this:
var container = new BlobContainerClient(_connectionString, s);
bool exists = await container.ExistsAsync(ct);
if (!exists)
await container.CreateAsync(cancellationToken: ct);
When running locally, I sometimes forget to start Azure Storage Emulator. When that happens, it takes my code like a minute to time out and tell me it can't reach the "cloud".
What I want to achieve is: Make the program give me good error messages quickly when running locally, but use more lenient timeout strategies when actually running in the cloud.
I can reduce the above timeout by doing something like this:
var blobClientOptions = new BlobClientOptions();
blobClientOptions.Retry.MaxRetries = 0;
var container = new BlobContainerClient(_connectionString, s, blobClientOptions);
... but when running against the real cloud I don't want that; I want it to retry. One option might be to set the retries to zero like above, but only when running locally.
I have a development-specific configuration file (appsettings.Development.json). Is it possible to configure such timeout/retry settings in the config file?
Or is there some other best-practice way to accomplish the "fail quickly in development" behaviour that I seek?
Thanks in advance!
create a class that will contain you blobstorage configuration:
public class BlobStorageConfiguration
{
public string ConnectionString {get; set;}
public int MaxRetries {get; set;}
}
in your appsettings.Development.json
{
...
"BlobStorageConfiguration": {
"ConnectionString " : "<your_connection_string>",
"MaxRetries ":0
}
...
}
in your Startup.cs in the ConfigureServices method
..
var blobConfig = new BlobStorageConfiguration ();
Configuration.Bind(nameof(BlobStorageConfiguration ), blobConfig);
services.AddSingleton(blobConfig );
..
now you can inject your config and it will take values from the appsettings.Development.json if you are running it locally:
some controller:
[Route("api/somthing")]
[ApiController]
public class SomethingController : ControllerBase
private readonly ILogger<SomethingController > logger;
public SomethingController (
ILogger<SomethingController > logger,
BlobStorageConfiguration blobConfig)
{
this.logger = logger;
// use your blobConfig (connectionstring and maxRetries)
}

.Net Core Identity SignInManager in Console App w/o DI/WebHost

I'm attempting to write a generic .Net Core 2.2 Console Application that allows me to use Identity. Specifically I have a database and am simply tring to call SignInManager.PasswordSignInAsync() to authenticate the username/password against my DB.
If I run this in a full blown .NetCore WebApp, where the HttpContext and DI are all built out, it works fine. If I strip it down and simply call the base services I get the same error every time.
I've been trying variants for a few days now and simply cannot figure out what I'm missing.
Any assistance would be greatly appreciated.
I have a class which manages the buildout of the services available for the console app.
public class FXLoginProvider
{
private readonly IServiceCollection _svccoll;
private UserManager<FXUser> _um = null;
private SignInManager<FXUser> _sm = null;
public UserManager<FXUser> UserMgr
{
get { return _um ?? (_um = _svccoll.BuildServiceProvider().GetService<UserManager<FXUser>>()); }
}
public SignInManager<FXUser> SignInMgr
{
get { return _sm ?? (_sm = _svccoll.BuildServiceProvider().GetService<SignInManager<FXUser>>()); }
}
public FXLoginProvider()
{
string s = "Data Source=.\\SQLEXPRESS;Initial catalog=csNextGen;Integrated Security=True;TrustServerCertificate=True;ApplicationIntent=ReadWrite";
_svccoll = new ServiceCollection();
_svccoll.AddDbContext<FXDataContext>(options => { options.UseSqlServer(s); });
_svccoll.AddIdentity<FXUser, FXUserRole>().AddDefaultTokenProviders();
_svccoll.AddTransient<IUserStore<FXUser>, FXUserStore>();
_svccoll.AddTransient<IRoleStore<FXUserRole>, FXRoleStore>();
_svccoll.AddLogging();
_svccoll.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
}
Then in my main app...
class Program
{
static void Main(string[] args)
{
try
{
FXUser uu = null;
string sUsername = "user";
string sPassword = "P$sSw0rrD#!";
// create the service provider
FXLoginProvider icp = new FXLoginProvider();
// grab the sign in manager
SignInManager<FXUser> sm1 = icp.SignInMgr;
// fetch the user from the db, this works.
uu = icp.UserMgr.FindByNameAsync(sUsername).Result;
// update my security stamp, this works too
sm1.UserManager.UpdateSecurityStampAsync(uu).GetAwaiter().GetResult();
// I was receiving a Null context error, so I added a default context.
sm1.Context = new DefaultHttpContext();
var r = sm1.PasswordSignInAsync(sUsername, sPassword, false, false).GetAwaiter().GetResult();
Console.WriteLine(r);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
and it always throws the same exception:
Value cannot be null.\r\nParameter name: provider
I do see in the StackTrace it is throwing down in DependencyInjection.ServiceProviderServiceExtensions (source code for DI.SPSE) because the IServiceProvider is null; so I guess I'm missing a service in my list?
I was able to figure out the problem with my implementation. My error was simply that I had not completely filled in the default http context properly.
sm1.Context = new DefaultHttpContext();
should have been
sm1.Context = new DefaultHttpContext() { RequestServices = icp._svccoll.BuildServiceProvider() };
Note: I needed to change the access level of the _svccoll too.
With this change in place I was able to use the SignInManager to authenticate against my back end database.
I battled this problem for days so I'm happy to share my solution (solution is available on GitHub). I hope this helps!
SingInManager relies on cookie, you can’t use it in console app. Instead of it use UserManager<> there a method to verify password

Logging in .Net core console application not working

I am following this tutorial: https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/
and accordingly installed the packages but log is not getting printed anywhere.
This is my code:
var serviceProvider = new ServiceCollection()
.AddLogging()
.AddTransient<IFoo, Foo>(s =>
{
return new Foo()})
.BuildServiceProvider();
//configure console logging
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug);
var logger = serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>();
logger.LogError("Starting application");
Turns out the console logging provider doesn't immediately flush the messages to the console like it did in the net-core-1.x versions. It appears to run on a different thread. See this web page for info: https://github.com/aspnet/Logging/issues/631
You can add at the end of the Main function.
serviceProvider.Dispose();
or you can add .AddDebug()
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug)
.AddDebug();
Creating a new ServiceProvider and HostBuilder may not be worth it if we just want a Logging in Console Application because it's a bit of extra caution to clean it up or dispose of.
Rather, I would suggest just have Logging Factory to use logger and that will solve the logging if that is only what we want.
public static class ApplicationLogging
{
public static ILoggerFactory LogFactory { get; } = LoggerFactory.Create(builder =>
builder.ClearProviders();
// Clear Microsoft's default providers (like eventlogs and others)
builder.AddSimpleConsole(options =>
{
options.IncludeScopes = true;
options.SingleLine = true;
options.TimestampFormat = "hh:mm:ss ";
});
builder.AddApplicationInsights("instrument-key");
});
public static ILogger<T> CreateLogger<T>() => LogFactory.CreateLogger<T>();
}
static void Main(string[] args)
{
var logger = ApplicationLogging.CreateLogger<Program>();
logger.LogInformation("Let's do some work");
logger.LogWarning("I am going Crazy now!!!");
logger.LogInformation("Seems like we are finished our work!");
Console.ReadLine();
}
I landed on this thread trying to troubleshoot why console logging didn't work and this answer documents what I found.
Packages used:
Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Console
Microsoft.Extensions.Logging.Debug
Application:
.NET Core 2.2 Console (Microsoft.NET.Sdk, netcoreapp2.2)
Using Microsoft.Extensions.Hosting.IHost, this is how I added console logging:
var hostBuilder = new HostBuilder()
// Other Configuration omitted for brevity
.ConfigureLogging((hostBuilderContext, loggingBuilder) =>
{
loggingBuilder.AddConfiguration(hostBuilderContext.Configuration.GetSection("Logging"));
loggingBuilder.AddConsole(options =>
{
options.IncludeScopes = true;
});
loggingBuilder.AddDebug();
});
// Start the application
await hostBuilder.RunConsoleAsync();
Interestingly, if I remove the options parameter in the call to AddConsole, I do not see any logging. I believe this is so because I use an ILogger in my code that emits log statements:
public class ClassThatLogs
{
private readonly ILogger<ClassThatLogs> _logger;
public ClassThatLogs(ILogger<ClassThatLogs> logger)
{
_logger = logger;
}
public void DoWork()
{
_logger.LogInformation("Working");
}
}
If I publish the console app as:
single file _log.LogDebug provide no output
when I publish it as not a single file (so I only uncheck the single file option) _log.LogDebug provides outputs on the console all my debugging statements. No other changes than unchecking single file.
So by only unticking "produce single file" my _logging.LogDebug starts to write stuff to the console.
Seems to be this: https://github.com/serilog/serilog-settings-configuration/issues/244

Logging from ASP.NET 5 application hosted as Azure Web App

I have an ASP.NET 5 Web API that I host in Azure as a Web App. I want to log messages from my code using Azure Diagnostics. There are multiple article including Azure docs that suggest that it should be as easy as System.Diagnostics.Trace.WriteLine once enabled. The logs should show up under LogsFiles/Application and in log stream in Azure.
I enabled application logging for the web app:
But the following calls produces no logs:
System.Diagnostics.Trace.TraceError("TEST");
System.Diagnostics.Trace.TraceInformation("TEST");
System.Diagnostics.Trace.TraceWarning("TEST");
System.Diagnostics.Trace.WriteLine("TEST");
I tried to manually define TRACE symbol, but with no luck:
I also tried to use the new Microsoft.Extensions.Logging framework and use ILogger.Log API, but without any results again:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.MinimumLevel = LogLevel.Debug;
var sourceSwitch = new SourceSwitch("Sandbox.AspNet5.ApiApp-Demo");
sourceSwitch.Level = SourceLevels.All;
loggerFactory.AddTraceSource(sourceSwitch,
new ConsoleTraceListener(false));
loggerFactory.AddTraceSource(sourceSwitch,
new EventLogTraceListener("Application"));
}
Any ideas on what am I doing wrong?
If you look at your web.config, it probably has stdoutLogEnabled="false". If you set that to true, then anything that is written to standard output will be written to a file. And the stdoutLogFile determines where it goes, which by default is under d:\home\logfiles.
Now you need to make sure that your logging actually goes to stdout. Doing Console.WriteLine would definitely work. I think it's probably possible to also configure things via ILoggerFactory in your startup.cs to make logs go to stdout.
I've found an simple trick for azure app ,see https://github.com/aspnet/Logging/tree/dev/src/Microsoft.Extensions.Logging.AzureAppServices,
please add the package "Microsoft.Extensions.Logging.AzureAppServices": "1.0.0-preview1-final" and update related dependencies,
add the azure diagnostics in startup.cs like this :
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddAzureWebAppDiagnostics(); // for default setting.
or for custom setting:
loggerFactory.AddAzureWebAppDiagnostics(new AzureAppServicesDiagnosticsSettings( ...)); // add custom setting.
// see here for detailed member properties: https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.AzureAppServices/AzureAppServicesDiagnosticsSettings.cs
And enable the diagnostics log on azure, both logging on blob and file are work well. No need for any
extra configuration. :)
I got Streaming Log output working for an ASP.NET 5 web app by writing the following class (based on David Ebbo's example):
public class AzureApplicationLogTraceListener : TraceListener
{
private readonly string _logPath;
private readonly object _lock = new object();
public AzureApplicationLogTraceListener()
{
string instanceId = Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID");
if (instanceId != null)
{
string logFolder = Environment.ExpandEnvironmentVariables(#"%HOME%\LogFiles\application");
Directory.CreateDirectory(logFolder);
instanceId = instanceId.Substring(0, 6);
_logPath = Path.Combine(logFolder, $"logs_{instanceId}.txt");
}
}
public override void Write(string message)
{
if (_logPath != null)
{
lock (this)
{
File.AppendAllText(_logPath, message);
}
}
}
public override void WriteLine(string message)
{
Write(message + Environment.NewLine);
}
}
and then putting this in my Startup.Configure:
Trace.Listeners.Add(new AzureApplicationLogTraceListener());
This only supports filesystem-based logging (which is sufficient for live log streams).

SignalR server --> client call not working

I'm currently using SignalR to communicate between a server and multiple separate processes spawned by the server itself.
Both Server & Client are coded in C#. I'm using SignalR 2.2.0.0
On the server side, I use OWIN to run the server.
I am also using LightInject as an IoC container.
Here is my code:
public class AgentManagementStartup
{
public void ConfigurationOwin(IAppBuilder app, IAgentManagerDataStore dataStore)
{
var serializer = new JsonSerializer
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
TypeNameHandling = TypeNameHandling.Auto,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple
};
var container = new ServiceContainer();
container.RegisterInstance(dataStore);
container.RegisterInstance(serializer);
container.Register<EventHub>();
container.Register<ManagementHub>();
var config = container.EnableSignalR();
app.MapSignalR("", config);
}
}
On the client side, I register this way:
public async Task Connect()
{
try
{
m_hubConnection = new HubConnection(m_serverUrl, false);
m_hubConnection.Closed += OnConnectionClosed;
m_hubConnection.TraceLevel = TraceLevels.All;
m_hubConnection.TraceWriter = Console.Out;
var serializer = m_hubConnection.JsonSerializer;
serializer.TypeNameHandling = TypeNameHandling.Auto;
serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
m_managementHubProxy = m_hubConnection.CreateHubProxy(AgentConstants.ManagementHub.Name);
m_managementHubProxy.On("closeRequested", CloseRequestedCallback);
await m_hubConnection.Start();
}
catch (Exception e)
{
m_logger.Error("Exception encountered in Connect method", e);
}
}
On the server side I send a close request the following way:
var managementHub = GlobalHost.ConnectionManager.GetHubContext<ManagementHub>();
managementHub.Clients.All.closeRequested();
I never receive any callback in CloseRequestedCallback. Neither on the Client side nor on the server side I get any errors in the logs.
What did I do wrong here ?
EDIT 09/10/15
After some research and modifications, I found out it was linked with the replacement of the IoC container. When I removed everything linked to LightInject and used SignalR as is, everything worked. I was surprised about this since LightInject documented their integration with SignalR.
After I found this, I realised that the GlobalHost.DependencyResolver was not the same as the one I was supplying to the HubConfiguration. Once I added
GlobalHost.DependencyResolver = config.Resolver;
before
app.MapSignalR("", config);
I am now receiving callbacks within CloseRequestedCallback. Unfortunately, I get the following error as soon as I call a method from the Client to the Server:
Microsoft.AspNet.SignalR.Client.Infrastructure.SlowCallbackException
Possible deadlock detected. A callback registered with "HubProxy.On"
or "Connection.Received" has been executing for at least 10 seconds.
I am not sure about the fix I found and what impact it could have on the system. Is it OK to replace the GlobalHost.DependencyResolver with my own without registering all of its default content ?
EDIT 2 09/10/15
According to this, changing the GlobalHost.DependencyResolver is the right thing to do. Still left with no explanation for the SlowCallbackException since I do nothing in all my callbacks (yet).
Issue 1: IoC Container + Dependency Injection
If you want to change the IoC for you HubConfiguration, you also need to change the one from the GlobalHost so that returns the same hub when requesting it ouside of context.
Issue 2: Unexpected SlowCallbackException
This exception was caused by the fact that I was using SignalR within a Console Application. The entry point of the app cannot be an async method so to be able to call my initial configuration asynchronously I did as follow:
private static int Main()
{
var t = InitAsync();
t.Wait();
return t.Result;
}
Unfortunately for me, this causes a lot of issues as described here & more in details here.
By starting my InitAsync as follow:
private static int Main()
{
Task.Factory.StartNew(async ()=> await InitAsync());
m_waitInitCompletedRequest.WaitOne(TimeSpan.FromSeconds(30));
return (int)EndpointErrorCode.Ended;
}
Everything now runs fine and I don't get any deadlocks.
For more details on the issues & answers, you may also refer to the edits in my question.

Categories