set the writeTo attribute of Logger in NLog during runtime - c#

My C# program has below 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"
internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
<targets>
<target xsi:type="File" name="file1" fileName="file1.log" layout="${message}" />
<target xsi:type="File" name="file2" fileName="file2.log" layout="${message}" />
<target xsi:type="File" name="file3" fileName="file3.log" layout="${message}" />
<target xsi:type="File" name="file4" fileName="file4.log" layout="${message}" />
<target xsi:type="File" name="file5" fileName="file5.log" layout="${message}" />
<target xsi:type="File" name="file6" fileName="file6.log" layout="${message}" />
</targets>
<rules>
<logger name="logger1" minlevel="Debug" writeTo="file1" />
</rules>
</nlog>
My program needs to log messages to different sets of log files according user config, for example, sometimes write to "file1,file2" and sometimes write to "file2,file3,file4".
So, is it possible to customize the "writeTo" attribute in "logger1" during runtime according to the user configures?
Thanks a lot.

If you want to write to multiple files you can simply use a comma separated string for the "writeTo" attribute in Rule as "file1,file2,file3". There are 6 targets and rule says write to 3 files therefore file1,file2 and file 3 will be considered for logging.
But what you require is to do it at the run time. Below is the C# code.
Console.Out.WriteLine("Logget started");
Console.Out.WriteLine("");
Logger logger = LogManager.GetCurrentClassLogger();
var config = new LoggingConfiguration();
//Define targets
var fileTarget = new FileTarget();
config.AddTarget("file7", fileTarget);
var fileTarget2 = new FileTarget();
config.AddTarget("file8", fileTarget);
// Set target properties
fileTarget.FileName = "file7.log";
fileTarget.Layout = "${message}";
fileTarget2.FileName = "file8.log";
fileTarget2.Layout = "${message}";
// Define rules
var rule1 = new LoggingRule("*", LogLevel.Debug, fileTarget);
config.LoggingRules.Add(rule1);
var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget2);
config.LoggingRules.Add(rule2);
LogManager.Configuration = config;
logger.Trace("trace log message");
logger.Debug("debug log message");
logger.Info("info log message");
logger.Warn("warn log message");
logger.Error("error log message");
logger.Fatal("fatal log message");
logger.Log(LogLevel.Info, "Sample informational message");
Console.ReadKey();
}
As the code comments explained it defines new targets and rules. NLog.config will be redefined at the runtime.
Below is my NLog.config targets and rules
<targets>
<target xsi:type="File" name="file1" fileName="file1.log" layout="${message}" />
<target xsi:type="File" name="file2" fileName="file2.log" layout="${message}" />
<target xsi:type="File" name="file3" fileName="file3.log" layout="${message}" />
<target xsi:type="File" name="file4" fileName="file4.log" layout="${message}" />
<target xsi:type="File" name="file5" fileName="file5.log" layout="${message}" />
<target xsi:type="File" name="file6" fileName="file6.log" layout="${message}" />
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="file1,file2,file3" />
</rules>
These targets and rules will be redefined at the run time by our C# code and only file7.log and file8.log will be considered for logging.

Related

NLog - Parameter for File Target FileName

I am using NLog in a .NET application. Currently, my config section in my App.config looks like this:
<nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="assemblyLogFile" xsi:type="File" fileName="${basedir}/logs/${shortdate}/assembly.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
<target name="appLogFile" xsi:type="File" fileName="${basedir}/logs/app.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
</targets>
<rules>
<logger name="Assembly" minlevel="Trace" writeTo="assemblyLogFile" />
<logger name="*" minlevel="Trace" writeTo="appLogFile" />
</rules>
</nlog>
My app is dynamically loading the assemblies. I would like to log each assemblies logs to their own file. In essence, I would like to update the filename attribute on the target element to something like this:
fileName="${basedir}/logs/${shortdate}/assembly.[Name].log"
In this pattern, "[Name]" is defined in the code. My question is, is there a way to programmatically pass a variable to a target via NLog? If so, how?
If you want this to be completely dynamic, I think you have two approaches.
1) Create a custom log factory and wrapper for NLog.ILogger that keeps track of and injects the assembly name into the NLog logEvent. This means all assemblies must use your logger factory, and not NLog directly.
2) Create a NLog extension that lets you access the assembly name as a layout variable, derived from the logger name. This works if you use the default LogManager.GetCurrentClassLogger() in all the assemblies.
Here is a naive caching renderer that uses reflection to find the correct assembly. You could probably make this more efficient by scanning assemblies when you load them - or maybe just do string wrangling on the logger name.
[NLog.LayoutRenderers.LayoutRenderer("assemblyName")]
public class AssemblyNameLayoutRenderer : NLog.LayoutRenderers.LayoutRenderer
{
static ConcurrentDictionary<string, string> loggerNameToAssemblyNameCache = new ConcurrentDictionary<string, string>();
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
var fullClassName = logEvent.LoggerName;
var assemblyName = FindAssemblyNameFromClassName(fullClassName);
if (!string.IsNullOrEmpty(assemblyName))
builder.Append(assemblyName);
}
private string FindAssemblyNameFromClassName(string fullClassName)
{
return loggerNameToAssemblyNameCache.GetOrAdd(fullClassName, cl =>
{
var klass = (
from a in AppDomain.CurrentDomain.GetAssemblies()
from t in a.GetTypes()
where t.FullName == fullClassName
select t).FirstOrDefault();
return klass?.Assembly.GetName().Name;
});
}
}
And then use this in the config file like this:
<extensions>
<add assembly="AssemblyContainingCustomRenderer" />
</extensions>
<targets>
<target xsi:type="FilteringWrapper" name="assemblyLogFile" condition="'${assemblyName}' != ''">
<target name="realAssemblyLogFile" xsi:type="File" fileName="logs/${shortdate}/assembly.${assemblyName}.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}" /
</target>
</targets>
If the assembly being loaded creates the logger using NLog.LogManager.GetLogger("assemblyName"), then you can do this (Works best with NLog ver. 4.5 or higher):
<nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="assemblyLogFile" xsi:type="File" fileName="${basedir}/logs/${shortdate}/${logger}.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
<target name="appLogFile" xsi:type="File" fileName="${basedir}/logs/app.log" createDirs="true" keepFileOpen="true" encoding="utf-8" layout="${longdate} ${message}${exception:format=ToString}"></target>
</targets>
<rules>
<logger name="AssemblyName" minlevel="Trace" writeTo="assemblyLogFile" />
<logger name="*" minlevel="Trace" writeTo="appLogFile" />
</rules>
</nlog>

How to make NLog thread-safely write to separate file target at runtime?

I have an application which runs many threads (~50), every thread acquires a payment with lock to be processed from a database, and then processes it.
However, 50 threads make logs unreadable, mixed up and of enormous filesize.
Now, I want to make NLog write to a separate file for every payment ID, so that all history on payment processment is stored in one file.
NLog configuration 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">
<targets>
<target name="file" xsi:type="AsyncWrapper" queueLimit="10000" overflowAction="Discard">
<target name="f" xsi:type="File" fileName="C:\LogFiles\Immediate\${shortdate}.server.log" layout="${longdate}|${level}|${processid}|${threadid}|${level:upperCase=true}|${callsite:className=false}|${message}" encoding="utf-8"/>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="file"/>
</rules>
</nlog>
My code now:
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); // "2017-07-20.log"
while (!_cancellationToken.IsCancellationRequested)
{
using (var session = _connectionFactory.GetSession())
{
AcquirePaymentWithUpdlockReadpast(session,
Logger, // "2017-07-20.log"
payment => {
if (payment == null)
return;
Logger.Debug($"Payment {payment.Id} has been acquired for processment");
ProcessPayment(session, Logger, payment); // "2017-07-20.log"
});
}
Thread.Sleep(50);
}
What I expect it to be:
private static readonly ILogger GeneralLogger = LogManager.GetCurrentClassLogger(); // "2017-07-20.General.log"
while (!_cancellationToken.IsCancellationRequested)
{
using (var session = _connectionFactory.GetSession())
{
AcquirePaymentWithUpdlockReadpast(session,
GeneralLogger, // "2017-07-20.General.log"
payment => {
if (payment == null)
return;
var paymentLogger = LogManager.GetCurrentClassLogger();
paymentLogger.FileName = $"Payment-{payment.Id}.log"; // <-- I want this.
paymentLogger.Debug($"Payment has been acquired for processment");
ProcessPayment(session, paymentLogger, payment); // "2017-07-20.Payment-123.log"
});
}
Thread.Sleep(50);
}
I have found some solutions which use LogManager.Configuration, but it doesn't seem to be thread-safe.
Maybe something like this:
<?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">
<targets>
<target name="file" xsi:type="AsyncWrapper" queueLimit="10000" overflowAction="Discard">
<target name="f" xsi:type="File" fileName="C:\LogFiles\Immediate\${shortdate}.server.log" layout="${longdate}|${level}|${processid}|${threadid}|${level:upperCase=true}|${callsite:className=false}|${message}" encoding="utf-8"/>
</target>
<target name="paymentFile" xsi:type="AsyncWrapper" queueLimit="10000" overflowAction="Discard">
<target name="p" xsi:type="File" fileName="C:\LogFiles\Immediate\Payment-${event-properties:PaymentID:whenEmpty=0}.log" layout="${longdate}|${level}|${processid}|${threadid}|${level:upperCase=true}|${callsite:className=false}|${message}" encoding="utf-8"/>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="paymentFile">
<condition="'${event-properties:PaymentID:whenEmpty=0}'!='0'" action="LogFinal" />
</logger>
<logger name="*" minlevel="Debug" writeTo="file"/>
</rules>
</nlog>
Then you can do the logging like this (Can be improved with NLog-Fluent):
LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, $"Payment has been acquired for processment");
theEvent.Properties["PaymentID"] = payment.Id;
Logger.Debug(theEvent);
Alternative solution could be like this:
var paymentLogger = LogManager.GetLogger($"Payment-{payment.Id}");
paymentLogger.Debug($"Payment has been acquired for processment")
And then change the logger-filter to this:
<logger name="Payment-*" minlevel="Debug" writeTo="paymentFile" />
And then change paymentFile filename to this:
fileName="C:\LogFiles\Immediate\{logger}.log"
Thanks to Rolf Kristensen's answer, I have used the second approach, but have found a similar, but shorter solution which uses name property:
<?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">
<targets>
<target name="all" xsi:type="AsyncWrapper" queueLimit="10000" overflowAction="Discard">
<target name="f" xsi:type="File" fileName="C:\LogFiles\${shortdate}.All.log" layout="${longdate}|${level}|${processid}|${threadid}|${level:upperCase=true}|${callsite:className=false}|${message}" encoding="utf-8"/>
</target>
<target name="perLoggerName" xsi:type="AsyncWrapper" queueLimit="10000" overflowAction="Discard">
<target name="f" xsi:type="File" fileName="C:\LogFiles\${shortdate}.${logger}.log" layout="${longdate}|${level}|${processid}|${threadid}|${level:upperCase=true}|${callsite:className=false}|${message}" encoding="utf-8"/>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="all"/>
<logger name="Document-*" minlevel="Debug" writeTo="perLoggerName"/>
<logger name="Job-*" minlevel="Debug" writeTo="perLoggerName"/>
</rules>
</nlog>

NLog AsyncWrapper and FallbackTarget does not work together

I have my custom target defined as follows.
namespace TargetLib
{
[NLog.Targets.Target("TestTarget")]
public class TestTarget : TargetWithLayout
{
protected override void Write(AsyncLogEventInfo[] logEvents)
{
throw new ApplicationException("Test");
foreach (AsyncLogEventInfo info in logEvents)
{
DoNothing(info.LogEvent);
}
}
protected override void Write(LogEventInfo logEvent)
{
DoNothing(logEvent);
}
private void DoNothing(LogEventInfo logEvent)
{
Console.WriteLine(logEvent.Message);
}
}
}
My NLog.config file looks as follows. I define a fallback group and then async wrappers inside it for each target.
<extensions>
<add assembly="TargetLib" />
</extensions>
<targets>
<target name="defaultTarget" xsi:type="FallbackGroup"
returnToFirstOnSuccess="true">
<target name="inMemoryTargetAsync" xsi:type="AsyncWrapper" timeToSleepBetweenBatches="1000" overflowAction="Grow">
<target name="inMemoryTarget" xsi:type ="TestTarget" />
</target>
<target name="fileTargetAsync" xsi:type="AsyncWrapper" timeToSleepBetweenBatches="1000" overflowAction="Grow">
<target name="file" xsi:type="File"
layout="${longdate} ${logger} ${message}"
fileName="${basedir}/logs/logfile.txt"
keepFileOpen="false"
encoding="iso-8859-2" />
</target>
</target>
</targets>
<rules>
<!-- add your logging rules here -->
<logger name="*" minlevel="Info" writeTo="defaultTarget" />
</rules>
I have a simple console application to test this as follows:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press enter to start logging:");
Console.ReadLine();
Logger logger = LogManager.GetLogger("Default");
for (int i = 0; i < 200; i++)
{
logger.Log(LogLevel.Warn, "Test warning" + i.ToString());
}
Console.WriteLine("Logging complete. Press enter to exit.");
Console.ReadLine();
}
}
The problem that I am facing is that since my custom target throws exception. So I am expecting all my log messages to be in FileTarget. But all log messages do not go into the file. Log messages are being missed.
Also I tried using following NLog.config other way around. First I defined AsyncWrapper and then fallback target as follows. But in this case nothing appears in file as my TestTarget synchronous LogEventInfo method is invoked every time. So no exception occurs and thus it does not fallback. But I want async method of my custom target to be invoked. Although this is dummy target, but in actual target I want to optimize batch writes. Please help.
<extensions>
<add assembly="TargetLib" />
</extensions>
<targets>
<target name="defaultTarget" xsi:type="AsyncWrapper" timeToSleepBetweenBatches="1000" overflowAction="Grow">
<target name="fallbackGrp" xsi:type="FallbackGroup" returnToFirstOnSuccess="true">
<target name="inMemoryTarget" xsi:type ="TestTarget" />
<target name="file" xsi:type="File"
layout="${longdate} ${logger} ${message}"
fileName="${basedir}/logs/logfile.txt"
keepFileOpen="false"
encoding="iso-8859-2" />
</target>
</target>
</targets>
<rules>
<!-- add your logging rules here -->
<logger name="*" minlevel="Info" writeTo="defaultTarget" />
</rules>
I don't know the relation between AsyncWrapper and FallbackTarget, I'm trying to find myself which should wrap which, but I can tell you that you need to call LogManager.Flush() for the AsyncWrapper to write logs, you may also wrap the whole thing into AutoFlushTargetWrapper, I thinks it's same as via XML conf autoFlush="true" like:
...
<target name="logfile" xsi:type="File" fileName="${basedir}/logs.txt" autoFlush="true">
This wraps FileTarget in AutoFlushTargetWrapper.
Unfortunately I can't advise what would be the correct wrapping order.
I hope this answer at least point you in to right direction.

Send ALL messages to one log file, and RavenDB logs to a different log file

This sounds trivial, but I somehow cannot manage to do it. I have the below 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" autoReload="true" throwExceptions="true">
<variable name="logDirectory" value="${basedir}/App_Data/logs"/>
<targets>
<target name="file" xsi:type="AsyncWrapper">
<target xsi:type="File" name="f1" fileName="${logDirectory}\log1.txt" layout="${longdate} ${callsite} ${level} ${message} (File 1)"/>
</target>
<target xsi:type="File" name="fileGeneral" fileName="${logDirectory}\log_${shortdate}.txt" >
<layout xsi:type="Log4JXmlEventLayout"/>
</target>
<target xsi:type="File" name="fileRaven" fileName="${logDirectory}\raven_${shortdate}.txt" >
<layout xsi:type="Log4JXmlEventLayout"/>
</target>
</targets>
<rules>
<logger name="Raven.*" minlevel="Trace" writeTo="fileRaven"></logger>
<logger name="*" minlevel="Trace" writeTo="fileGeneral"></logger>
</rules>
</nlog>
This is ending up with Raven + ALL logs to 'log_[date].txt', and another copy of just RavenDB logs in 'raven_[date].txt'. How should this be done?
<logger name="Raven.*" minlevel="Trace" writeTo="fileRaven" final="true"></logger>
Where final="true" means that no more rules for Raven.* will be executed will do what you are asking (if I understood you correctly).
Unfortunately this is not possible.
The reason is that the loggers are evaluated from top to bottom, the first one matching will be used, and does below will not be evaluated.
In you case this means that when logging anything Raven related the first logger will be used and stops the flow.

nLog missing log data

Sometime nLog is not logging all debug information (some of log.Debug... are missing in debug file), does anyone know why is this happening and how to avoid that?
Here is my nLog configuration
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<variable name="appTitle" value="Service"/>
<targets async="true">
<target name="fallbackDebug" xsi:type="FallbackGroup" returnToFirstOnSuccess="true">
<target xsi:type="File" fileName="C:\Logs\${date:format=yyyyMMdd}_Debug.txt" layout="..."/>
<target xsi:type="File" fileName="C:\Logs\${date:format=yyyyMMdd}_Debug_bu.txt" layout="..."/>
</target>
</targets>
<rules>
<logger name="*" levels="Trace,Debug,Error,Fatal" writeTo="fallbackDebug" />
</rules>
</nlog>
In code I'm using following
private static Logger log = LogManager.GetCurrentClassLogger();
...
log.Debug("Some debug info");
Thanks!
You must call LogManager.Flush() method at the end.
Example:
private static Logger log = LogManager.GetCurrentClassLogger();
...
log.Debug("Some debug info");
...
LogManager.Flush();

Categories