NLog AsyncWrapper and FallbackTarget does not work together - c#

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.

Related

Nlog logs location overwritten by code still logging happening at config location

NLog version - 4.4.3
Platform - .Net 4.5.2
Current NLog config -
<nlog autoReload="true" xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<variable name="layout" value="${longdate}|${level:uppercase=true}|${threadid}|${logger}|${message}" />
<variable name="logLocation" value="logs" />
<targets async="true">
<target name="debugger" xsi:type="Debugger" layout="${layout}" />
<target name="console" xsi:type="Console" layout="${layout}" />
<target name="logfile"
xsi:type="File"
fileName="${logLocation}\${processname}.log"
archiveFileName="${logLocation}\\${processname}.{###}.log"
archiveEvery="Day"
archiveAboveSize="2048000"
archiveNumbering="Rolling"
maxArchiveFiles="10"
concurrentWrites="false"
keepFileOpen="false"
layout="${layout}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logfile" />
<logger name="*" minlevel="Debug" writeTo="debugger" />
<logger name="*" minlevel="Info" writeTo="console" />
</rules>
</nlog>
Code to override location
LogManager.ReconfigExistingLoggers();
var target = (FileTarget)LogManager.Configuration.FindTargetByName<AsyncTargetWrapper>("logfile").WrappedTarget;
target.FileName = $#"..\..\..\..\logs\Foobar.log";
What is the current result?
When application/service starts it writes to overwritten location, but sometimes (not sure of scenario - maybe rollover) it start writing to config location.
What is the expected result?
Logs should always be written to overwritten location.
Did you checked the Internal log?
No
Please post full exception details (message, stacktrace, inner exceptions)
No Exception
Are there any workarounds? yes/no
Restart service/application.
Is there a version in which it did work?
No idea. This is the version we started with and sticking to.
Can you help us by writing an unit test?
Unit tests won't help as it is an intermittent scenario.
You have auto reload (<nlog autoReload="true”) enabled, so if it needs to reload (after sleep or change in config), you will lose the changes made in code.
The solution it so disable autoreload, or set the change after reload again. See code example:
static void Main(string[] args)
{
UpdateNLogConfig();
LogManager.ConfigurationReloaded += LogManager_ConfigurationReloaded;
log.Info("Entering Application.");
Console.WriteLine("Press any key to exit ...");
Console.Read();
}
private static void LogManager_ConfigurationReloaded(object sender, LoggingConfigurationReloadedEventArgs e)
{
UpdateNLogConfig();
}
private static void UpdateNLogConfig()
{
//note: don't set LogManager.Configuration because that will overwrite the nlog.config settings
var target = (FileTarget)LogManager.Configuration.FindTargetByName<AsyncTargetWrapper>("logfile").WrappedTarget;
target.FileName = $#"..\..\..\..\logs\Foobar.log";
LogManager.ReconfigExistingLoggers();
}
See also Combine XML config with C# config · NLog/NLog Wiki
Instead of doing target lookup, and modifying target properties directly. Then I suggest making use of the NLog Layout Logic.
<nlog autoReload="true" xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true">
<target name="logfile"
xsi:type="File"
fileName="${gdc:item=logFile:whenEmpty=log/${processname}.log}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logfile" />
</rules>
</nlog>
And then just assign the logLocation:
NLog.GlobalDiagnosticsContext.Set("logFile", $#"..\..\..\..\logs\Foobar.log");
Using GDC will also works very well with autoReload=true and no need to call LogManager.ReconfigExistingLoggers().

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>

set the writeTo attribute of Logger in NLog during runtime

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.

Do not allow users to delete log files

I have a simple app:
public class Program
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static void Main(string[] args)
{
while (true)
{
Logger.Info(DateTime.Now.ToString());
Thread.Sleep(5000);
}
}
}
And configuration:
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="logfile" xsi:type="File" fileName="C:\Logs\log.txt" keepFileOpen="true" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="logfile" />
</rules>
</nlog>
But NLog does not seem to lock log.txt (desipte keepFileOpen property being set to true) - I can delete it. Even worse - the log file is not recreated after it has been deleted. So if user accidentally deletes the file - there will be no logging until the application is restarted (or in more general case until new log file name takes place).
Is there any way to make NLog lock log files or at least recreate them after they have been deleted?
Use enableFileDelete setting to make NLog lock file:
<target name="logfile" xsi:type="File"
fileName="C:\Logs\log.txt"
keepFileOpen="true"
enableFileDelete="false" />

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