How to inject NLog with an Azure Function - c#

I have the following code to Dependency Inject the NLog logger:
// startup.cs
[assembly: FunctionsStartup(typeof(MyApp.FunctionApp.Startup))]
namespace MyApp.FunctionApp {
public class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
var nLogConfigPath = GetLogPath("nlog.config");
builder.Services.AddLogging(loggingBuilder =>
{
var nLogOptions = new NLogAspNetCoreOptions
{
RegisterHttpContextAccessor = true,
IgnoreEmptyEventId = true,
IncludeScopes = true,
ShutdownOnDispose = true
};
var logFactory = NLogBuilder.ConfigureNLog(nLogConfigPath);
logFactory.AutoShutdown = false;
var nLogConfig = logFactory.Configuration;
loggingBuilder.AddNLog(nLogConfig, nLogOptions);
});
}
}
}
// actual function code
public class ActualFunctionClass {
public ActualFunctionClass (ILogger<ActualFunctionClass> logger) {
logger.LogInformation("log stuff");
}
}
In the nlog.config, I have several targets. How do I make sure that the logger parameter to the ActualFunctionClass is configured with the proper target?
These are the contents of the nlog.config. For the record, I would like to log using the locations-dataload-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"
autoReload="true"
throwExceptions="true"
keepVariablesOnReload="true">
<variable name="logDirectory" value="${basedir}/logs/locations-dataload" />
<variable name="commonLayout" value="${longdate}|${logger}|${uppercase:${level}}|${message}, ${all-event-properties:format=[key]=[value]:separator=, } ${exception}" />
<targets>
<target xsi:type="ApplicationInsightsTarget"
name="locations-dataload-ai"
layout="${commonLayout}" />
<target xsi:type="File"
name="locations-dataload-file"
fileName="${logDirectory}/locations-dataload-file-${shortdate}.log"
layout="${commonLayout}" />
<target xsi:type="Null" name="blackhole" />
</targets>
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="locations-dataload-ai" />
<!--All logs, including from Microsoft-->
<logger name="locations-dataload-local*" minlevel="Trace" writeTo="locations-dataload-ai,locations-dataload-file" />
<!-- Skip Microsoft logs and so log only own logs -->
<logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />
</rules>
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
<add assembly="MyApp.Logging" />
</extensions>
</nlog>

I think this is wrong:
<logger name="locations-dataload-local*" ...
The logger name is by default Namespace.ClassName
You could test this by:
change the rule it to name="*"
<logger name="*" minlevel="Trace" writeTo="locations-dataload-ai,locations-dataload-file" />
and add to your layout ${logger}, e.g.
layout="${logger}|${commonLayout}"
The name filter will be matched against the logger name ${logger). So if the logger name is YourNamespace.ActualFunctionClass then locations-dataload-local* won't match that.

Related

Create sub-folder that contains log-files for each request

I have an application that listens for a hash input, processes that input, and serves it back. I would like to create a separate log directory for each input with the following structure:
- hashcode1
- input1.txt
- output1.txt
- log1.txt
- hashcode2
- input2.txt
- output2.txt
- log2.txt
I've looked into the NLog library but it seems the config requires a hardcoded log location, whereas my implementation requires the hashcode as an input. How can I achieve this with NLog, or any other logging library?
You can create the NLog configuration from code. This allows you to dynamically create targets in your processing loop for the appropriate log outputs:
using NLog;
using NLog.Targets;
// Init the config if we don't have a nlog.config
LogManager.Configuration = new();
// Do the work
var work = new List<string> { "5dd2ec2f", "24e1e841", "05137e70" };
work.ForEach(ProcessHash);
void ProcessHash(string hash)
{
// Create dynamic configuration and logger
var fileTarget = new FileTarget(hash) { FileName = $"{hash}/log.txt" };
LogManager.Configuration.AddRuleForAllLevels(fileTarget, hash);
LogManager.ReconfigExistingLoggers();
var logger = LogManager.GetLogger(hash);
// Process...
logger.Info($"Processing hash {hash}");
/* ... your code here ... */
// Remove the logging target
LogManager.Configuration.RemoveTarget(fileTarget.Name);
}
You can create a scope-property like this:
void ProcessHashCode(string hashInput)
{
using (NLog.MappedDiagnosticsLogicalContext.SetScoped("HashCode", hashInput))
{
NLog.LogManager.GetLogger("HashInput").Info(hashInput);
NLog.LogManager.GetCurrentClassLogger().Info("Received Hashcode");
var hashOutput = hashInput.GetHashCode();
NLog.LogManager.GetLogger("HashOuput").Info(hashOutput);
}
}
Then you can do this in NLog.config
<nlog>
<variable name="logdir" value="${mdlc:HashCode:whenEmpty=${basedir}}" />
<targets>
<target name="hashinput" type="file" filename="${logdir}/input.txt" />
<target name="hashouput" type="file" filename="${logdir}/output.txt" />
<target name="hashlog" type="file" filename="${logdir}/log.txt" />
</target>
<rules>
<logger name="HashInput" writeTo="hashinput" final="true" />
<logger name="HashOuput" writeTo="hashouput" final="true" />
<logger name="*" writeTo="hashlog" />
</rules>
</nlog>
See also: https://github.com/NLog/NLog/wiki/MDLC-Layout-Renderer
Alternative you can use Logger.WithProperty("HashCode", hashInput) like this:
void ProcessHashCode(string hashInput)
{
var inputLogger = NLog.LogManager.GetLogger("HashInput").WithProperty("HashCode", hashInput);
var outputLogger =
NLog.LogManager.GetLogger("HashOutput").WithProperty("HashCode", hashInput);
var hashLogger =
NLog.LogManager.GetCurrentClassLogger.WithProperty("HashCode", hashInput);
inputLogger.Info(hashCode);
hashLogger.Info("Received Hashcode");
var hashOutput = hashInput.GetHashCode();
outputLogger.Info(hashOutput);
}
And instead use variable like this:
<variable name="logdir" value="${event-properties:HashCode:whenEmpty=${basedir}}" />
See also: https://github.com/NLog/NLog/wiki/Context#logevent-properties

AspNet Core 5.0 and register custom NLog LayoutRenderer

I use NLog.Web.AspNetCore 4.10.0 in my ASP Net Core 5.0 application. And I created a simple custom ResultLayoutRenderer (shortened for simplicity):
[LayoutRenderer("result-payload")]
public class ResultLayoutRenderer : AspNetLayoutRendererBase
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpRequest = HttpContextAccessor?.HttpContext?.Request;
var result = httpRequest.Headers["Result"];
builder.Append(result);
}
}
My NLog config is the following:
<?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">
<!-- enable asp.net core layout renderers -->
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<targets>
<target xsi:type="Null" name="blackhole" />
<target name="database" xsi:type="Database"
dbProvider="Npgsql.NpgsqlConnection, Npgsql"
dbHost="${configsetting:name=NlogConnection.DbHost}"
dbDatabase="${configsetting:name=NlogConnection.Database}"
dbUserName="${configsetting:name=NlogConnection.User}"
dbPassword="${configsetting:name=NlogConnection.Password}"">
<commandText>
INSERT INTO logs (Application, Logged, Level, Message, Logger, Result)
VALUES (#Application, #Logged, #Level, #Message, #Logger, #Result);
</commandText>
<parameter name="#application" layout="AspNetCoreNlog" />
<parameter name="#logged" layout="${date}" />
<parameter name="#level" layout="${level}" />
<parameter name="#message" layout="${message}" />
<parameter name="#logger" layout="${logger}" />
<parameter name="#result" layout="${result-payload}" />
</target>
</targets>
</nlog>
I register the ResultLayoutRenderer in the Programm.cs:
public static void Main(string[] args)
{
var logger = NLogBuilder.ConfigureNLog("nlog.config")
.Setup()
.SetupExtensions(s => s.RegisterLayoutRenderer<InputDataLayout>("result-payload"))
.GetCurrentClassLogger();
..............
}
But the layout doesn't work. The NLog internal log file contains the following information:
Error Error parsing layout input-payload will be ignored. Exception: System.ArgumentException: LayoutRenderer cannot be found: 'input-payload'
at NLog.Config.Factory`2.CreateInstance(String itemName)
at NLog.Layouts.LayoutParser.GetLayoutRenderer(ConfigurationItemFactory configurationItemFactory, String name, Nullable`1 throwConfigExceptions)
You are very close. NLogBuilder.ConfigureNLog(...) is soon obsolete, so instead try this:
var logger = NLog.LogFactory.Setup()
.SetupExtensions(s =>
s.AutoLoadAssemblies(false)
.RegisterNLogWeb()
.RegisterLayoutRenderer<ResultLayoutRenderer>("result-payload"))
)
.LoadConfigurationFromFile("nlog.config")
.GetCurrentClassLogger();
See also: https://github.com/NLog/NLog.Web/blob/master/examples/ASP.NET%20Core%205/ASP.NET%20Core%205%20NLog%20Example/Program.cs
This works in my ASP.NET Core 5 Web API:
public static void Main(string[] args)
{
LayoutRenderer.Register<ElapsedTimeLayoutRenderer>("elapsed-time");
var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
// ...
}
https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-5
https://www.carlosjanderson.com/log-the-elapsed-time-between-log-entries-using-nlog/

Xamarin MvvmCross how to use NLog for IMvxLogProvider?

I followed Diagnostic & Logging and install NLog.Config nuget package to Android & iOS project.
on Android project, Setup.cs
public override MvxLogProviderType GetDefaultLogProviderType() => MvxLogProviderType.NLog;
On Xamarin.Forms project,
private static readonly IMvxLog _logger = Mvx.IoCProvider.Resolve<IMvxLogProvider>().GetLogFor<CanvasContainer>();
_logger.Debug($"startScale: {startScale}, currentScale: {currentScale}");
I checked _logger is not null.
Also, I changed NLog.config's Build Action to Embedded Resource.
Here is Android Project's 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"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="false"
internalLogFile="c:\temp\nlog-internal.log">
<targets>
<target xsi:type="File"
name="FileTarget"
fileName="${specialfolder:folder=MyDocuments}/logs/${shortdate}.log"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}" />
<target xsi:type="ColoredConsole"
name="ConsoleTarget"
encoding="utf-8"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}"
useDefaultRowHighlightingRules="true" />
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="FileTarget" />
<logger name="*" minlevel="Debug" writeTo="ConsoleTarget" />
</rules>
</nlog>
Is there anything wrong with this set up? I can not find sample or tutorial for this...
Think you need to help NLog when having NLog.config as Embedded Resource:
public override MvxLogProviderType GetDefaultLogProviderType() => MvxLogProviderType.NLog;
protected override IMvxLogProvider CreateLogProvider()
{
var nlogConfigFile = GetEmbeddedResourceStream(myAssembly, "NLog.config");
if (nlogConfigFile != null)
{
var xmlReader = System.Xml.XmlReader.Create(nlogConfigFile);
NLog.LogManager.Configuration = new XmlLoggingConfiguration(xmlReader, null);
}
return base.CreateLogProvider();
}
private static Stream GetEmbeddedResourceStream(Assembly assembly, string resourceFileName)
{
var resourcePaths = assembly.GetManifestResourceNames()
.Where(x => x.EndsWith(resourceFileName, StringComparison.OrdinalIgnoreCase))
.ToList();
if (resourcePaths.Count == 1)
{
return assembly.GetManifestResourceStream(resourcePaths.Single());
}
return null;
}
See also: https://github.com/NLog/NLog/wiki/Explicit-NLog-configuration-loading#loading-nlog-configuration-from-xamarin-resource

How to include NLog config files in code behind

I have a nlog.config file with the following entry:
<include file="${basedir}/ActiveConfig/NLog/*.config"/>
But I want to solve this via code behind and have not found a method to do so.
LogManager.LoadConfiguration()
is overwriting my existing configuration.
Did I miss something?
You could set it like this:
var config = XmlLoggingConfiguration
.CreateFromXmlString("<include file='${basedir}/ActiveConfig/NLog/*.config' />");
LogManager.Configuration = config; // apply
Think the easy solution is just to load all config-files into an in-memorystream, and then load that into a single XmlLoggingConfiguration:
var xmlReader = System.Xml.XmlReader.Create(memorystream);
NLog.LogManager.Configuration = new XmlLoggingConfiguration(xmlReader, null);
Something like this, where you put the contents of all config-files within the same <nlog>-root:
<nlog>
<!-- XML File 1 -->
<targets>
</targets>
<rules>
</rules>
<! -- XML File 2 -->
<targets>
</targets>
<rules>
</rules>
</nlog>
If you want the AutoReload-feature working also, then this might work:
class MySpecialLoggingConfiguration : XmlLoggingConfiguration
{
private string[] _fileNames;
public MySpecialLoggingConfiguration(string[] fileNames)
{
_fileNames = fileNames;
// Your special concat-logic in memory
}
public override LoggingConfiguration Reload()
{
return new MySpecialLoggingConfiguration(_fileNames);
}
public override IEnumerable<string> FileNamesToWatch => _fileNames;
}

iText7 unable to setup logging

I'm trying to use PdfCleanUpTool with iText7.
However my final PDF is corrupted (it is only 15B in size).
When I start my console app from VS I get this in Output:
no configuration section found - suppressing logging output
I'm trying to setup logging to get error message, but without luck.
I've installed this packages:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Common.Logging" version="3.4.1" targetFramework="net47" />
<package id="Common.Logging.Core" version="3.4.1" targetFramework="net47" />
<package id="Common.Logging.NLog4412" version="3.4.1" targetFramework="net47" />
<package id="itext7" version="7.1.2" targetFramework="net47" />
<package id="itext7.pdfsweep" version="2.0.1" targetFramework="net47" />
<package id="Microsoft.CSharp" version="4.0.1" targetFramework="net47" />
<package id="NLog" version="4.4.12" targetFramework="net47" />
<package id="Portable.BouncyCastle" version="1.8.1.3" targetFramework="net47" />
</packages>
and this is my app.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.NLog.NLogLoggerFactoryAdapter, Common.Logging.NLog4412">
<arg key="configType" value="INLINE" />
</factoryAdapter>
</logging>
</common>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="console" xsi:type="Console" layout="${date:format=HH\:MM\:ss} ${logger} ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="console" />
</rules>
</nlog>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7" />
</startup>
</configuration>
Probably I need to setup a license key, but I'd like to get error message saying that I must so.
My question is:How should I correctly setup NLog with Common.Logging to get errors from iText7.
Here is full example that can be used to verify current behavior:
using Common.Logging;
using Common.Logging.Configuration;
using Common.Logging.Simple;
using iText.Kernel.Colors;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.PdfCleanup;
using System;
using System.Collections.Generic;
using System.IO;
namespace RedactTest
{
class Program
{
static void Main(string[] args)
{
NameValueCollection properties = new NameValueCollection
{
["showDateTime"] = "true",
["level"] = "All"
};
LogManager.Adapter = new ConsoleOutLoggerFactoryAdapter(properties);
using (Stream inputStream = new FileStream("D:\\test.pdf", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
PdfReader reader = new PdfReader(inputStream);
using (Stream outputStream = new FileStream("D:\\test_redact.pdf", FileMode.Create))
{
PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdfDocument = new PdfDocument(reader, writer);
List<PdfCleanUpLocation> cleanUpLocations = new List<PdfCleanUpLocation>
{
new PdfCleanUpLocation(1, new Rectangle(40f, 650f, 200f, 700f),ColorConstants.GRAY),
new PdfCleanUpLocation(1, new Rectangle(40f, 550f, 200f, 590f),ColorConstants.GRAY),
new PdfCleanUpLocation(1, new Rectangle(344f, 650f, 550f, 724f),ColorConstants.GRAY)
};
PdfCleanUpTool cleaner = new PdfCleanUpTool(pdfDocument, cleanUpLocations);
cleaner.CleanUp();
}
}
Console.Write("OK");
Console.ReadLine();
}
}
}
On the original logging related question
One option is to activate the console logger from code, at the start of your program put:
// create properties
NameValueCollection properties = new NameValueCollection();
properties["showDateTime"] = "true";
properties["level"] = "All";
// set Adapter
Common.Logging.LogManager.Adapter = new Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter(properties);
E.g. for the following test
[Test]
public void CreateLogOutput()
{
// create properties
NameValueCollection properties = new NameValueCollection();
properties["showDateTime"] = "true";
properties["level"] = "All";
// set Adapter
Common.Logging.LogManager.Adapter = new Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter(properties);
ILog logger = LogManager.GetLogger(typeof(iText.Kernel.Pdf.PdfReader));
logger.Error("Testing an error log output", new Exception("The exception message"));
}
one gets an output like this:
27.06.2018 17:07:37 [ERROR] iText.Kernel.Pdf.PdfReader - Testing an error log output
=======================================================(inner most exception)===
(1) System.Exception
================================================================================
Method : <unavailable>
Type : <unavailable>
Assembly : <unavailable>
Assembly Path : <unavailable>
Source :
Thread : 15 'NonParallelWorker'
Helplink :
Message:
"The exception message"
Stack Trace:
================================================================================
On the actual issue
After you added the code to the question, the issue became clear: You forgot to close the PdfDocument pdfDocument. Thus, everything works alright, nothing is logged, merely the changes are not written to file as the PDF document object is not closed. Simply close it after the cleaner.CleanUp() call:
PdfCleanUpTool cleaner = new PdfCleanUpTool(pdfDocument, cleanUpLocations);
cleaner.CleanUp();
pdfDocument.Close();
The result now is some 34KB in size and displays like this:

Categories

Resources