Manage logging configuration with NLog in .NET Core 3 - .net-core

I'm using NLog in a .NET Core 3.1 worker service application.
Following the tutorial of NLog I inserted an nlog.config file to manage the configuration.
Now I'm confused because I have three points where I configure the logging:
In the code where I need to create a logger in a dependency injection context
// Other code...
services.AddScoped<IApplyJcdsCommandsJob, ApplyJcdsCommandsJob>(provider =>
{
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.ClearProviders()
.AddFilter("Microsoft", Microsoft.Extensions.Logging.LogLevel.Trace)
.AddFilter("System", Microsoft.Extensions.Logging.LogLevel.Trace)
.AddFilter("ApplyJcdsCommandsJob", Microsoft.Extensions.Logging.LogLevel.Trace)
//.AddConsole()
//.AddEventLog();
.AddNLog(configuration);
});
Microsoft.Extensions.Logging.ILogger logger = loggerFactory.CreateLogger<CommandsJob>();
return new CommandsJob(logger);
})
// Other code...
In appSettings.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Trace",
"System": "Trace",
"Microsoft": "Trace"
}
}
}
In NLog.config
The default config file produced by the nuget package installation:
<!-- a section of the config -->
<targets>
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="f" />
</rules>
<!-- ... -->
What I see is that if I remove the Nlog.config file, the log file will not be created.
Other changes seam to have no effect.
How are this configurations related?
What is the best way to switch on/off the logging and set the level?

People that decide to use NLog usually also want to disable all MEL-filtering to avoid the confusion with two filtering systems. So the NLog wiki-tutorial is targeted those users.
I guess people who are MEL-users first will probably just use new HostBuilder().CreateDefaultBuilder().Build() (Will setup everything with all guns enabled).
But if staying with the simple example, then you need to remove:
loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
And add:
loggingBuilder.AddConfiguration(config.GetSection("Logging"));
So it looks like this:
serviceCollection.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddConfiguration(config.GetSection("Logging"));
loggingBuilder.AddNLog(config);
})
ILoggingBuilder.AddConfiguration can be found at Nuget: Microsoft.Extensions.Logging.Configuration

AddNLog registers NLog like any other Microsoft Extension Logger (MEL) LoggingProvider (Similar to AddConsole).
This means NLog only gets log-output that has been "approved" by the MEL-ILogger. So any filtering configured in MEL will prevent logevents from reaching NLog.
NLog still has the ability to redirect based on Logger-names and LogLevel-severity to the wanted NLog-targets.
You can decide if you want to use MEL-Filtering or NLog-Filtering, or a combination of both. But if you just want to use "pure" NLog then just create an instance of NLog.Extensions.Logging.NLogLoggerFactory. It is a specialized ILoggerFactory that ignores MEL-Filtering-Configuration.
Btw. it is a little weird that you create an isolated LoggerFactory for each CommandsJob-instance. Would think that you would register the type in the dependency injection-framework, and let it inject constructor-parameters. See also this example:
https://github.com/NLog/NLog.Extensions.Logging/blob/master/examples/NetCore2/ConsoleExample/Program.cs
Where LoggerFactory is created with AddLogging(...) and where the Runner is registered in ServiceCollection for dependency-injection. When creating instance of Runner then dependency-injection will automatically provide ILogger as constructor-parameter.

Related

NLog ApplicationInsightsTarget unable to read application insights key from appsettings.json

I am trying to read the application insights instrumentation key from appsettings.json file in my ASP.NET Core 3.1 Web Application but all my tries went in vain so far as the target is still showing as unconfigured.
If I add the key directly in ApplicationInsightsTarget, then it is working fine.
Here is the content of appsettings.json file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"AppInsightsKey": "Instrumentation-Key-From-Azure-Application-Insights-Resource"
}
Here is the content of nlog.config file:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="info"
internalLogFile="c:\temp\internal-nlog-AspNetCore3.txt">
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="Microsoft.ApplicationInsights.NLogTarget" />
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<!-- the targets to write to -->
<targets>
<!--Console Target for hosting lifetime messages to improve Docker / Visual Studio startup detection -->
<target xsi:type="Console" name="lifetimeConsole" layout="${configsetting:item=AppInsightsKey} ${level:truncate=4:lowercase=true}: ${logger}[0]${newline} ${message}${exception:format=tostring}" />
<target name="aiTarget" xsi:type="ApplicationInsightsTarget"
layout="${date:format=yyyy-MM-dd HH\:mm\:ss}: [LOCAL] - ${level} - ${message}${exception:format=ToString}">
<instrumentationKey>${configsetting:item=AppInsightsKey}</instrumentationKey>
<contextproperty name="threadid" layout="${threadid}" />
</target>
</targets>
<!-- rules to map from logger name to target -->
<rules>
<logger name="*" minlevel="Trace" writeTo="lifetimeConsole" />
<logger name="*" minlevel="Trace" writeTo="aiTarget" />
</rules>
</nlog>
As you can see from above, I was trying to grab the application insights key from appsettings.json file by ${configsetting:item=AppInsightsKey}, but in all occasions it's coming as empty.
I've tried with ${configsetting:name=AppInsightsKey} , which didn't help either.
This is the program.cs file content
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Web;
using System;
namespace ASPNETCoreWebAppNLogAppInsightsDemo
{
public class Program
{
public static void Main(string[] args)
{
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try
{
logger.Debug("init main");
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
//NLog: catch setup errors
logger.Error(exception, "Stopped program because of exception");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog(); // NLog: Setup NLog for Dependency injection
}
}
Then I've tried to embed the key in the console target layout, just to ensure nlog is able to read it correctly, which to my surprise is working perfectly fine. So, looks like the issue is with ApplicationInsightsTarget only.
I know, I can pull it from Environment variables and hence in Azure App service it is not required to read from appsettings.json, but I would like to understand about this behavior since already spent too much time fixing myself :-)
Any help will be appreciated.
Try replacing the old code:
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
With this new code:
var logger = LogManager.Setup()
.LoadConfigurationFromAppSettings()
.GetCurrentClassLogger();
The new code will load the appsettings.json, and sure that ${configsetting} are available during initialization of NLog Targets.

${mdlc:item=RequestId} and ${aspnet-request-posted-body} not returning data

I am using ${aspnet-request-posted-body} in my nlog.config file ,I have also added configuartions in startup file. but ${aspnet-request-posted-body} not logging request body and ${mdlc:item=RequestId} also not working
<target xsi:type="File" name="allfile" fileName="c:\Logs\Api_.log"
layout="==>[${event-properties:item=traceLevel}: User=${aspnet-user-identity}; ]
${when:when=('${event-properties:item=traceLevel}' == 'Verbose' and
'${aspnet-request-method}' != 'GET') > 0 and length('${mdlc:item=RequestId}') > 0:inner=${newline}Request-Body\: ${aspnet-request-posted-body}}
${newline}Logger name: ${logger}
${newline}" />
${aspnet-request-posted-body} and ${mdlc:item=RequestId} used in nlog file
To enable ${aspnet-request-posted-body} in startup I used this code.
app.Use(async (context, next) =>
{
context.Request.EnableBuffering();
await next?.Invoke();
});
Would probably start with something simple like this:
<target xsi:type="File" name="allfile" fileName="c:\Logs\Api_.log"
layout="RequestId=${mdlc:item=RequestId} RequestBody=${aspnet-request-posted-body}" />
And then enable the NLog InternalLogger at Debug-Level and look for clues:
https://github.com/NLog/NLog/wiki/Internal-Logging
https://github.com/NLog/NLog/wiki/Logging-troubleshooting
You also need to correctly register NLog as LoggingProvider with the method UseNLog(). See also https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-3
If you are not using Microsoft ILogger for logging within the context for the active HttpRequest, then RequestId will not be available.
NLog.Web.AspNetCore v5.1 needs help from middleware for ${aspnet-request-posted-body} to work:
app.UseMiddleware<NLog.Web.NLogRequestPostedBodyMiddleware>();

Hangfire.io Dashboard mapped to IIS Virtual Directory

I'm having trouble getting the Hangfire (1.5.8) dashboard to work inside of an IIS Virtual Directoy. Everything works beautifully in my dev environment where my application is simply mapped to the root of localhost. Our beta server, on the other hand, uses Virtual Directories to separate apps and app pools.
It's an ASP.Net MVC site using Hangfire with an OWIN Startup class. It gets deployed to http://beta-server/app-name/. When I attempt to access either http://beta-server/app-name/hangfire or http//beta-server/hangfire I get a 404 from IIS.
For the purposes of troubleshooting this, my IAuthenticationFilter simply returns true.
Here is my Startup.cs, pretty basic:
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
GlobalConfiguration.Configuration
.UseSqlServerStorage(new DetectsEnvironment().GetEnvironment());
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AuthorizationFilters = new[] {new AuthenticationFilter()}
});
app.UseHangfireServer();
}
}
Does anyone have a working implementation that gets deployed to a Virtual Directory? Are there any OWIN middleware admin/management tools I can use to dig into what URL is getting registered within IIS?
I ended up fixing this simply by adding the HTTPHandler to the section in web.config.
<system.webServer>
<handlers>
<add name="hangfireDashboard" path="hangfire" type="System.Web.DefaultHttpHandler" verb="*" />
</handlers>
</system.webServer>
I had a similar issue in ASP.NET Core 2.0 and it required proper authorization setup (I use a middleware to protect the route, so I did not rely on authorization in my example):
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new [] {new HangfireDashboardAuthorizationFilter()}
});
/// <summary>
/// authorization required when deployed
/// </summary>
public class HangfireDashboardAuthorizationFilter : IDashboardAuthorizationFilter
{
///<inheritdoc/>
public bool Authorize(DashboardContext context)
{
// var httpContext = context.GetHttpContext();
// Allow all authenticated users to see the Dashboard (potentially dangerous).
// handled through middleware
return true; // httpContext.User.Identity.IsAuthenticated;
}
}
There is not need to change anything in web.config.
For more information check Hangfire documentation about this topic.
I had the exact same problem. In my case, this was because of bad configuration - the Startup class was not called. So try to add the following to your config file:
<add key="owin:appStartup" value="YourProject.YourNamespace.Startup, YourProject" />
<add key="owin:AutomaticAppStartup" value="true" />
Hope this helps.
Martin

Microsoft Unity - Is it possible to change the registered type at runtime?

I am currently using Unity with MOQ to do my unit testing for WCF. In the application's code, I have the following:
private void MyMethod()
{
.....
.....
_proxy = new UnityContainer().LoadConfiguration().Resolve<IMyInterface>();
.....
}
In the application's app.config, I have the following:
<container>
<register type="IMyInterface" mapTo="MyActualObject" />
</container>
In the unit test's app.config, I replace that with my mock object implementation of the proxy.
<container>
<register type="IMyInterface" mapTo="MyMockObject" />
</container>
That is all working fine. But what I would like to do further is for certain tests, I would like to replace MyMockObject with a different mock object implementation.
Is it possible to change the registered type at runtime? I have tried modifying the application config during runtime but it fails to detect the change.
Configuration appConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var section = (UnityConfigurationSection)appConfig.GetSection("unity");
section.Containers[0].Registrations[0].MapToName = "AnotherMockObject";
appConfig.Save();
Thanks!!
Yes its possible.
You can configure Unity as many times as you want. If there's a conflict the most recent definition wins.
In your case if you want to make a runtime change, use the fluent API instead of the config file. Try something like this:
IUnityContainer container = new UnityContainer();
container.LoadConfiguration();
container.RegisterType<IMyInterface, AnotherMockObject>();
// use AnotherMockObject
_proxy = Resolve<IMyInterface>();
The documentation for Registering Types and Type Mappings

Orchard/MVC WCF Service Url with Area

Bertrand created a blog post to specify how to use IoC in WCF Modules for Orchard.
In 1.1, you can create a SVC file using the new Orchard host factory:
<%# ServiceHost Language="C#" Debug="true"
Service="MyModule.IMyService, MyAssembly"
Factory="Orchard.Wcf.OrchardServiceHostFactory, Orchard.Framework" %>
Then register your service normally as an IDependency but with service and operation contract attributes:
using System.ServiceModel;
namespace MyModule {
[ServiceContract]
public interface IMyService : IDependency {
[OperationContract]
string GetUserEmail(string username);
}
}
My question is that all of Orchard's modules are really area's. So how can you build a route that hits the svc file created in the area/module?
Should you use the full physical path to get to the svc file (tried that and it caused a web.config issue since it was bridging a site and area).
http://localhost/modules/WebServices/MyService.svc
Or do you create a ServiceRoute with WebServiceHostFactory/OrchardServiceHostFactory?
new ServiceRoute("WebServices/MyService", new OrchardServiceHostFactory(), typeof(MyService))
Whatever I try I get a 404 when trying to hit the resource. I was able to get this working using a wcf Application project and setting WCF as a stand alone application, my issues started when trying to bring it into Orchard/MVC.
UPDATE
Thanks for the help Piotr,
This is the steps I took to implement the service.
Routes.cs
new RouteDescriptor { Priority = 20,
Route = new ServiceRoute(
"Services",
new WebServiceHostFactory(),
typeof(MyService)) }
If I use OrchardServiceHostFactory() instead of WebServiceHostFactory() I get the following error.
Operation is not valid due to the current state of the object.
Orchard Root Web.Config
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<!--
Configure the WCF REST service base address via the global.asax.cs file and the default endpoint
via the attributes on the <standardEndpoint> element below
-->
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
MyService
[ServiceContract]
public interface IMyService : IDependency
{
[OperationContract]
string GetTest();
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
class MyService : IMyService
{
public string GetTest()
{
return "test";
}
}
I couldn't get the service working by just modifying the module's web.config. I get the following error
ASP.NET routing integration feature requires ASP.NET compatibility.
UPDATE 2
Orchard Root Web.Config
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<!-- ... -->
</system.serviceModel>
Routes.cs
public IEnumerable<RouteDescriptor> GetRoutes() {
return new[] {
new RouteDescriptor { Priority = 20,
Route = new ServiceRoute(
"Services",
new OrchardServiceHostFactory(),
typeof(IMyService))
}
};
}
This works, the key here is that you must call typeof on the object that is referencing IDependency, WorkContextModule.IsClosingTypeOf cant handle the object that consumes the dependancy, it must take the Interface that it is directly called by.
As you stated, Orchard modules are areas in ASP.NET MVC terms, so the URL you provided is incorrect and should be:
http://localhost/Your.Orchard.Module/WebServices/MyService.svc
Where localhost is the virtual directory under which your app runs and /WebServices is a folder in the root of your module.
You can also create a service route programatically without problem. This article tells how to add new routes in Orchard. You can just assign a ServiceRoute to the Route property of a RouteDescriptor instead of a default MVC route (as shown in docs).
The question about adding ServiceRoute in area-enabled ASP.NET MVC app was asked before, check it out as it may help you out.
Btw - You may also check this SO question about prefixed service routes.
HTH

Resources