Reputation: 11703
My question is very similar to this one: How can I get NLog output to appear in the streaming logs for an Azure Function?. In that question the accepted answer shows how you can configure nlog at the start of the function call. I would like to configure via an nlog.config file or at the very least configure just once at setup and not on every call to the function.
With the code below I see NLog messages in the Application Insight logs, but not in the Log Stream. I would like to get the messages logged from NLog to show in Log Streams as well.
Function code
private static readonly Logger _logger = NLog.LogManager.GetCurrentClassLogger();
[FunctionName("TestLog")]
public static async Task<IActionResult> TestLog([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req, ILogger log)
{
//shows in both Application Insights and Log Stream.
log.LogInformation("Log From ILogger");
//shows in Application Insights but not in Log Stream.
_logger.Info("Log From NLog");
return new OkObjectResult("OK");
}
FunctionStartUp
[assembly: FunctionsStartup(typeof(MyNamespace.TestFunctionApp.Startup))]
namespace MyNamespace.TestFunctionApp
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
//nLog file ends 1 directory up from bins when deployed
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var rootDirectory = Path.GetFullPath(Path.Combine(binDirectory, ".."));
var nLogConfigPath = Path.Combine(rootDirectory, "NLog.config");
//create MicrosoftILoggerTarget (I think this has to be done from code since it needs a ref to an ILogger instance). But this does not seem to work.
var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory();
var azureILogger = loggerFactory.CreateLogger("NLog");
var loggerTarget = new NLog.Extensions.Logging.MicrosoftILoggerTarget(azureILogger);
//setup NLog
LogManager.Setup()
.SetupExtensions(e => e.AutoLoadAssemblies(false))
.LoadConfigurationFromFile(nLogConfigPath, optional: false)
.LoadConfiguration(builder => builder.Configuration.AddRuleForAllLevels(loggerTarget));
}
}
}
NLog.config
<?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">
<extensions>
<add assembly="Microsoft.ApplicationInsights.NLogTarget" />
</extensions>
<targets>
<target name="a" xsi:type="ApplicationInsightsTarget"/>
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="a" />
</rules>
</nlog>
Upvotes: 0
Views: 2392
Reputation: 19867
You can activate log stream from log-files, so it will monitor any files ending in .txt or .log, that are stored in the HOME-folder. Then you can use a File-target:
<?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">
<extensions>
<add assembly="Microsoft.ApplicationInsights.NLogTarget" />
</extensions>
<targets>
<target name="a" xsi:type="ApplicationInsightsTarget"/>
<target name="logfile" xsi:type="File" filename="${environment:HOME:cached=true}/logfiles/application/app-${shortdate}-${processid}.txt" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logfile, a" />
</rules>
</nlog>
Upvotes: 0
Reputation: 19867
Time for my 3rd answer. With NLog.Extensions.Logging ver. 1.7.1 then MicrosoftILoggerTarget can take ILoggerFactory
as input-parameter, and one can override the LoggerName.
So you can setup like this:
var msLoggerFactory = serviceProvider.GetService<ILoggerFactory>();
var nLogToMsLogTarget = new MicrosoftILoggerTarget(msLoggerFactory);
nLogToMsLogTarget.LoggerName = "${mdlc:FunctionName}";
And use it like this:
[FunctionName("TestLog")]
public async Task<IActionResult> TestLog([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req, Microsoft.Extensions.Logging.ILogger log)
{
using (NLog.MappedDiagnosticsLogicalContext.SetScoped("FunctionName", log.ToString())
{
log.LogInformation("Log From ILogger");
LogManager.GetCurrentClassLogger().Info("Log From NLog");
var other = new SomeOtherClassWithNLog();
other.SomeMethod();
return new OkObjectResult("OK");
}
}
No longer restricted with a single static Logger-Name for MicrosoftILoggerTarget
Upvotes: 1
Reputation: 11703
I managed to get something that works starting from Rolf's answers. But it is hacky, and only makes sense if you have just 1 Function in your function app.
FunctionStartUp code:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<NLog.Logger>(SetupNLog);
}
private NLog.Logger SetupNLog(IServiceProvider serviceProvider)
{
//find NLog.config
var executionContextOptions = serviceProvider.GetService<IOptions<ExecutionContextOptions>>().Value;
var appDirectory = executionContextOptions.AppDirectory;
var nLogConfigPath = Path.Combine(appDirectory, "NLog.config");
//setup target to forward NLog logs to ILogger
//NOTE: string passed to CreateLogger must match "function.<function name>" or logs will not show in Log Stream
var msLoggerFactory = serviceProvider.GetService<ILoggerFactory>();
var msLogger = msLoggerFactory.CreateLogger("Function.TestLog");
var nLogToMsLogTarget = new MicrosoftILoggerTarget(msLogger);
//setup NLog
LogManager.Setup()
.SetupExtensions(e => e.AutoLoadAssemblies(false))
.LoadConfigurationFromFile(nLogConfigPath, optional: false)
.LoadConfiguration(c => c.Configuration.AddRuleForAllLevels(nLogToMsLogTarget));
//return an NLog logger
return LogManager.GetLogger("NLog");
}
}
Function code:
public class Function1
{
private readonly NLog.Logger _logger;
public Function1(NLog.Logger logger)
{
_logger = logger;
}
[FunctionName("TestLog")]
public async Task<IActionResult> TestLog([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req, Microsoft.Extensions.Logging.ILogger log)
{
log.LogInformation("Log From ILogger");
_logger.Info("Log From NLog");
var other = new SomeOtherClassWithNLog();
other.SomeMethod();
return new OkObjectResult("OK");
}
}
public class SomeOtherClassWithNLog
{
private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
public void SomeMethod()
{
_logger.Info("Logs From Another Class");
}
}
I have also found that Azure seems to filter logs shown in the log stream to be only those with "Host.*" or "Function.<FunctionName>.*" in the category name. Its filtered even further when looking at the monitor page for a single Function in that case it filters to just "Function.<FunctionName>.*".
So you are stuck either viewing in the Function App level log stream where lots of "not my app" logs also get written. Or viewing in just one of the function level Log streams even if that's not the function your debugging.
I think in the end I will just use the default ILogger, and be ok with the fact that code logging direct to an NLog logger isn't visible in the Log Stream viewer.
Upvotes: 1
Reputation: 19867
Usually the goal is to have the output from Microsoft ILogger to reach NLog Targets. This is done by calling UseNLog()
or AddNLog()
, that will add NLog as LoggingProvider.
Alternative one have an existing library that uses NLog already, but wants the output to be redirected to the Microsoft ILoggerFactory. But one have to be careful if also wanting to add NLog as LoggingProvider because of cyclic/recursive nightmare.
But I guess you can do the following to redirect all output from NLog to Microsoft ILoggerFactory:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var serviceProvider = builder.Services.BuildServiceProvider();
var executionContextOptions = serviceProvider.GetService<IOptions<ExecutionContextOptions>>().Value;
var appDirectory = executionContextOptions.AppDirectory;
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
// Setup NLog redirect all logevents to Microsoft ILoggerFactory
var nlogLoggerName = "NLog";
var nlogLogger = loggerFactory.CreateLogger(nlogLoggerName);
var nlogTarget = new NLog.Extensions.Logging.MicrosoftILoggerTarget(nlogLogger);
var nLogConfigPath = Path.Combine(appDirectory, "NLog.config");
//setup NLog
LogManager.Setup()
.SetupExtensions(e => e.AutoLoadAssemblies(false))
.LoadConfigurationFromFile(nLogConfigPath, optional: false)
.LoadConfiguration(builder => {
// Ignore output from logger named "NLog" to avoid recursion
builder.Configuration.Rules.Add(new LoggingRule() { LoggerNamePattern = nlogLoggerName, MaxLevel = LogLevel.Off, Final = true });
builder.Configuration.AddRuleForAllLevels(loggerTarget));
});
}
}
Upvotes: 1