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>
Related
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>
I am trying to create 2 loggers (using NLog)
First logger logs all the desired item to log in the solution
The other one traces specific items (I do it to keep things clean and focused, and only run trace here)
Below is the 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"
layout="${longdate} ${level} ${threadid} ${callsite} ${message}"
fileName="${basedir}\Logs\GatewayApplicationDebugAndErrorLog.txt"
archiveNumbering="Rolling"
maxArchiveFiles="10"
archiveAboveSize="10000000"/>
<target name="J1939Trace"
xsi:type="File"
layout="${longdate} ${level} ${threadid} ${callsite} ${message}"
fileName="${basedir}\Logs\J1939Trace.txt"
archiveNumbering="Rolling"
maxArchiveFiles="10"
archiveAboveSize="10000000"/>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="logfile" />
<logger name="J1939Trace" maxlevel="Trace" writeTo="J1939Trace" final="true" />
</rules>
and usage is shown below
private readonly Logger logger = LogManager.GetCurrentClassLogger(); // Generic Logger
private readonly Logger j1939Logger = LogManager.GetLogger("J1939Trace"); // Specific Logger.
What I observe is that the specific logger item is also logged in generic log item and I don't want that duplication. Any ideas what am I doing wrong?
Will this work?
From: NLog: How to exclude specific loggers from a specific rule?
Adding final="true" means that no more rules will be executed for the events produced by "SpammyLogger", but it applies only to the specified levels.(see https://github.com/nlog/nlog/wiki/Configuration-file#rules)
Make sure to read through all the comments in the answer.
I have a unique problem with the NLog. I have a third-party assembly we use into our project. They have used the NLog into their project and referenced NLog configuration into their configuration which I don't have access to.
I initially had a problem even adding the NLog to a project since both version of dlls were different. I had to download the source code of NLog and change the assembly name since alias was not working for me (OR I didn't know how to use it.)
After giving my own assembly name, NLog started wotrking but.. It started logging third-party log information too with it. Looks like they were not careful enough when defining the logger and they just defined (*). Now my question is, how do I avoid their stuff from logging in to my file? I have also tried setting it to final = true.
Here is how my very simple configuration file looks like.
<?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">
<!--
See https://github.com/nlog/nlog/wiki/Configuration-file
for information on customizing logging rules and outputs.
-->
<targets>
<!-- add your targets here -->
<!--
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" />
-->
<target name="errorLog" xsi:type="File"
fileName="${basedir}/logs/error/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}${onexception:${newline}EXCEPTION\: ${exception:format=ToString}}"
/>
<target name="generalLog" xsi:type="File"
fileName="C:/logs/${date:format=yyyy-MM-dd}.log"
layout="${longdate} ${uppercase:${level}} ${message}"/>
</targets>
<rules>
<!-- add your logging rules here -->
<logger name="Errors" writeTo="errorLog" minlevel="Warn"/>
<logger name="EBusiness.Model.*" writeTo="generalLog" levels="Trace,Debug,Info" final="true"/>
<!--
<logger name="*" minlevel="Trace" writeTo="f" />
-->
</rules>
</nlog>
If they are writing to the root logger, you should configure the root logger to not log to anywhere you care (maybe point it at a console logger) and your application can log to a specific logger which does write to your files.
Ugly, but should work.
For example:
Add this target:
<target name="console" xsi:type="Console" />
And Change the * rule to be:
<logger name="*" minlevel="Fatal " writeTo="console" />
Then, Add your own logger rule, say something like:
<logger name="MyNameSpace.*" minlevel="Trace" writeTo="logfile" final="true" />
Assuming you have a logfile target defined pointing at your file.
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.
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.