I have a function: processData(string taskName) and I want to generate multiple log files that contain the taskName parameter in their path using NLog.
for example:
processData("task1") should be logged only to C:\log_task1.txt
processData("task2") should be logged only to C:\log_task2.txt
processData("task3") should be logged only to C:\log_task3.txt
and so on.
NOTE: I don't know the values of the parameters in advance. This was just an example.
In your NLog config, create different rules for each log file:
<targets>
<target name="logfile1"
xsi:type="File"
fileName="C:\log_task1.log" />
<target name="logfile2"
xsi:type="File"
fileName="C:\log_task2.log" />
<target name="logfile3"
xsi:type="File"
fileName="C:\log_task3.log" />
</targets>
<rules>
<logger name="task1"
minlevel="Trace"
writeTo="logfile1" />
<logger name="task2"
minlevel="Trace"
writeTo="logfile2" />
<logger name="task3"
minlevel="Trace"
writeTo="logfile3" />
</rules>
Then, in your method, use the corresponding NLog logger:
public void ProcessData(string param)
{
var logger = NLog.LogManager.GetLogger(param);
}
If you don't know the value of the parameter in advance, you can create the NLog configuration programmatically at runtime (but don't forget to only allow whitelisted parameter values to prevent attacks that overwrite existing files at a location chosen by the attacker). How to programmatically configure NLog is described here:
NLog Configuration API
The code would be along the lines of this (just written down without test):
public void AddLogTarget(string param)
{
if (NLog.LogManager.Configuration.FindTargetByName(param) == null)
{
var target = new FileTarget
{
Name = param,
FileName = #"C:\" + param + ".log"
};
NLog.LogManager.Configuration.AddTarget(param, target);
NLog.LogManager.Configuration.LoggingRules.Add(new LoggingRule(param, LogLevel.Debug, target));
}
}
There is an option that might work, but it will take some effort on your part to use it.
NLog has the ability to name the log file via most LayoutRenderers. In your case, you could configure your File target something like this:
<target name="FileTarget" xsi:type="File" fileName="${gdc:item=Task}.log" layout=${longdate} | ${logger} | ${level} | ${message}" />
Then, when you are executing a task, you can set the GlobalDiagnosticContext based on your task name:
void processData(String taskName)
{
GlobalDiagnosticContext.Set("Task", taskName);
log.Info("Hello from processData. This should be in {0}.log", taskName);
GlobalDiagnosticContext.Remove("Task");
}
This will cause all log messages that are written to be written to the desired log file.
This might work fine if your application is single threaded, so you don't have to worry about collisions between simultaneous executions of processData.
If you are using threads, you might get good results using the ThreadDiagnosticContext in the same way.
Another idea is to manually create LogEventInfo objects and use the Log method to log them yourself. Something like this:
var LogEventInfo le = new LogEventInfo(LogLevel.Info, logger.Name, "Hello!");
le.Properties["Task"] = taskName;
logger.Log(le);
In this case you would use the EventContext LayoutRenderer to build the log filename:
<target name="FileTarget" xsi:type="File" fileName="${event-context:item=Task}.log" layout=${longdate} | ${logger} | ${level} | ${message}" />
You could wrap the NLog logger or you could write an extension method so that you don't have to deal with building up a LogEventInfo object at every logging site.
For example:
public static class NLogExtensions
{
public static void Log(this Logger logger, string task, LogLevel level, string message)
{
var LogEventInfo le = new LogEventInfo(level, logger.Name, message);
le.Properties["Task"] = task;
logger.Log(typeof(NLogExtensions), le);
}
}
The reason for using the Log signature that takes a Type as the first parameter is to ensure that the logging of call site information work correctly.
See my answer here for a very brief discussion of "wrapping" NLog via an extension method:
Can NLog preserve callsite information through c# extension methods?
Related
I haven't found a solution for the problem yet. In some cases I have errors in my application that I have to log to a seperated log file. The problem with some of these errors is that they consists of a lot of data and I'd like to have the log entries in seperated files to analyze them later. Currently I just log the message to the global log file and copy and paste the start and end tags (XML, JSON) of the log entries manually to a different file, save it and open it in a JSON/XML-viewer.
I think best would be to have a directory with unique file names to write for every log entry and have a back reference to the global log file with this file name as a log entry. But that's just my humble opinion, maybe there is a better solution for it. You have one? :)
Currently I am using NLog but I could also image to change to Serilog if this isn't possible with NLog. I also would say that there shouldn't be a different way to log the message in code than this:
public class TestClass
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private void Work()
{
var json/xml = ...
Log.Error(json/xml);
}
}
Because it's concern of the logging configuration how this is written to files, database etc.
Thanks.
The easy solution is just doing this (Ensures the Blob-output suddenly doesn't propagate some where unwanted):
public class TestClass
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static readonly Logger BlobLog = LogManager.GetLogger("BlobLog");
private void Work()
{
var correlationId = Guid.NewGuid().ToString();
Log.WithProperty("BlobGuid", correlationId).Error("Hello");
var json/xml = ...
BlobLog.WithProperty("BlobGuid", correlationId).Error(json/xml);
}
}
Then do this in you NLog.config, where ${event-properties:BlobGuid} ensures new file:
<targets>
<target name="logfile" xsi:type="File" fileName="file.txt" layout="${longdate}|${level}|${logger}|${message} BlobGuid=${event-properties:BlobGuid}" />
<target name="blobfile" xsi:type="File" fileName="blob.${shordate}.${event-properties:BlobGuid}.txt" layout="${message}" />
</targets>
<rules>
<logger name="BlobLog" minlevel="Trace" writeTo="blobfile" final="true" />
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
There are a lot of ways to add context to a LogEvent. See also https://github.com/NLog/NLog/wiki/Context
The LogEvent context can be used in the logging rules filtering. See also https://github.com/nlog/NLog/wiki/Filtering-log-messages
You can also use the LogEvent-context in NLog Layouts, and FileTarget Filename is a NLog Layout. So the same file-target can write to different filenames based on LogEvent-context.
If it is really important to only generate a single LogEvent, then you can do this:
public class TestClass
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private void Work()
{
var json/xml = ...
Log.WithProperty("Blob", json/xml).Error("Hello World);;
}
}
Then do this in you NLog.config:
<targets>
<target name="logfile" xsi:type="File" fileName="file.txt" layout="${message}${when:when=length('${event-properties:Blob}') > 0:inner= BlobGuid-${guid:GeneratedFromLogEvent=true}}"/>
<target name="blobfile" xsi:type="File" fileName="Blob.${shortdate}.${guid:GeneratedFromLogEvent=true}.txt" layout="${event-properties:Blob}" />
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="blobfile">
<filters defaultAction='Ignore'>
<when condition="'${event-properties:Blob}' != ''" action="Log" />
</filters>
</logger>
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
This will reuse the same LogEvent for writing to both files.
Here's a possible approach using Serilog, leveraging the sinks File and Map:
Write regular log messages to a file called Application.log (i.e. anything that does not have a large data in the log message)
Write large data messages to individual files named Data_{uniqueId}.log
Use a property called LargeDataId to store the unique ID for the file where the large data will be stored, and also use this property to determine if a message is a regular log message or not (i.e. if a LargeDataId property exists, then it goes on the individual file, otherwise it's a regular message and goes to Application.log:
public class TestClass
{
private readonly ILogger _logger = Log.ForContext<TestClass>();
public void Work()
{
var jobId = Guid.NewGuid();
// Writes to Application.log
_logger.Error("Error executing job {JobId}", jobId);
var jsonOrXml = "...";
// Writes to Data_{uniqueId}.log
_logger.Error("{LargeDataId}{LargeData}", jobId, jsonOrXml);
}
}
Your Serilog logging pipeline configuration would look something like:
Log.Logger = new LoggerConfiguration()
.WriteTo.Logger(c =>
c.Filter.ByExcluding(e => e.Properties.ContainsKey("LargeData"))
.WriteTo.File("Application.log"))
.WriteTo.Map("LargeDataId", (id, wt) =>
wt.File($"Data_{id}.txt", outputTemplate: "{LargeData}"),
sinkMapCountLimit: 0)
.CreateLogger();
I have the following question. I want to create new .txt file for every test case run with the test case name or test case id (it doesn't matter)as file name but I don't know how to get the current test case name in the NLog.Config file.
<target name="logfile"
xsi:type="File"
layout="${longdate}|${message}"
fileName="${basedir}\ATF\web\utils\reports\${shortdate}\${callsite}.txt" />
I am using ${callsite}, but I have logger in several classes and it creates files with the names of theses classes.
private static readonly Logger Logger = GetCurrentClassLogger();
E.g. I have one class logging that some Selenium action is completed, another class logging the driver is opened successfully. In the test I am using both classes and I don't want to create file for every class I use, but only one file with the test name.
P.S The logger itself is working properly.
I am using NUnit for the tests.
So if anyone knows how because I couldn't find what I want, I will be very thankful.
Have a nice day!
Using NUnit TextContext inside a custom NLog LayoutRenderer:
[LayoutRenderer("nunit-testname")]
public class NUnitTestNameLayoutRenderer : LayoutRenderer
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append(NUnit.Framework.CurrentContext?.Test?.Name);
}
}
Compile the above code into an assembly called "Custom.NLog.NUnit", and then you can load it like this:
<extensions>
<add assembly="Custom.NLog.NUnit" />
</extensions>
<targets>
<target name="logfile"
xsi:type="File"
layout="${longdate}|${message}"
fileName="${basedir}\ATF\web\utils\reports\${shortdate}\${nunit-testname:whenEmpty=UnknownTest}.txt" />
</target>
See also: https://github.com/nunit/docs/wiki/TestContext
See also: https://github.com/NLog/NLog/wiki/How-to-write-a-custom-layout-renderer
Very simple solution using NLog NDLC:
[Test]
public void MyTest()
{
using (NLog.NestedDiagnosticsLogicalContext.Push(System.Reflection.MethodBase.GetCurrentMethod().ToString()))
{
// Testing within "${ndlc}"
}
}
Then you can do this in your NLog-Layout:
target name="logfile"
xsi:type="File"
layout="${longdate}|${message}"
fileName="${basedir}\ATF\web\utils\reports\${shortdate}\${ndlc:topFrames=1:whenEmpty=UnknownTest}.txt" />
See also: https://github.com/NLog/NLog/wiki/Ndlc-Layout-Renderer
I'm trying to test NLog under LINQPad.
I successfully linked it and my code compiles well. However, NLog doesn't write log files because it is not configured.
I tried to make various config files like: NLog.config and LINQPad.config but it looks like I do not do it correctly.
My testing code under LINQPad is:
void Main()
{
try
{
int zero = 0;
int result = 5 / zero;
}
catch (DivideByZeroException ex)
{
Logger logger = LogManager.GetCurrentClassLogger();
logger.ErrorException("Whoops!", ex);
}
}
Config code:
<?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="logfile" xsi:type="File" fileName="logfile.log" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logfile" />
</rules>
</nlog>
Where to put the config file?
You can use LinqPad's extensions to enable logging: everything you'll drop to My Extensions script will be available for every other script.
Add a class to My Extensions with content:
public static class NLogLinqPadExtensions
{
public static void ConfigureLogging()
{
var nlogConfig = #"
<nlog>
<targets>
<target name='console' type='Console' layout='${date:format=dd/MM/yy HH\:mm\:ss\:fff} | ${level:uppercase=true} | ${message} | ${exception:format=tostring}' />
</targets>
<rules>
<logger name='*' minlevel='Debug' writeTo='console' />
</rules>
</nlog>
";
using (var sr = new StringReader(nlogConfig))
{
using (var xr = XmlReader.Create(sr))
{
NLog.LogManager.Configuration = new XmlLoggingConfiguration(xr, null);
NLog.LogManager.ReconfigExistingLoggers();
}
}
}
}
And don't forget to add NLog package to this script.
This example configures the console logger - you'll see your log messages in LinqPad's Results window, which is pretty cool for long-running processes and utility scripts.
To enable logging you have to call this extension from your script. Create a new LinqPad script and try this:
NLogLinqPadExtensions.ConfigureLogging();
var logger = LogManager.GetLogger("Foo");
logger.Debug("Hi");
Thanks to #pasty and #nemesv comments, the problem is fixed.
The config file must be named NLog.config and must be placed in the LINQPad.exe folder.
The log file (logfile.log in my case) appears in the same folder.
LINQPad needs write access to the folder in order to write the log file.
LINQPad must be restarted after setting the config file.
[Edit]
If you start LINQPad, load or write the code and run it, the log file appears in the LINQPad.exe folder.
If you run the code by clicking on saved code file like NLog Test.linq in my case, the log file appears in the folder of the code file. (C:\Users\Miroslav\Documents\LINQPad Queries in my case)
As a minor alternative to the solution offered in Valeriu Caraulean's answer you could add a method like the following to configure NLog but use an existing configuration file:
public static void ConfigureNLog()
{
LogManager.Configuration = new XmlLoggingConfiguration(#"C:\path\to\existing\config\file\NLog.xml");
LogManager.ReconfigExistingLoggers();
}
You'll want to add the following to the namespace imports for your LINQPad query:
NLog
NLog.Config
I am using NLog and following the recommended pattern of having a log declare on each class, for the purpose of being able to track which class/method has written to the log. I do find this very useful to have a bit of a top level 'stack trace' with each log write.
My code used to look like this:
class SomeClass {
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
void DoStuff() {
logger.Debug("stuff"); }
}
I recently had the requirement my single project to write to 3 separate log files, and to do this, I added multiple loggers and targets as specified here: https://stackoverflow.com/a/21711838/191206
However, now in my log files, I have lost the class level name. It now just writes the log name that I specified in the NLog.config. I've considered simply adding the method name myself with a call to
System.Reflection.MethodBase.GetCurrentMethod(); // use Name property
or using something else in Reflection like this
However, I'm wondering if NLog has something built into this that I'm missing? The Debug() method I only see the ability to write a string, with parameters & optionally formatted..
Is this built into NLog?
There is a built in layout renderer called ${callsite} that you can use to include the call site information (class name, method name and source information) in your log entries:
<targets>
<target
name="task1File"
xsi:type="File"
layout="${callsite} - ${message}"
fileName="${basedir}../Data/debugLog1.txt"
archiveAboveSize ="5000000"
maxArchiveFiles="2"/>
<target
name="task2File"
xsi:type="File"
layout="${callsite} - ${message}"
fileName="${basedir}../Data/debugLog2.txt"
archiveAboveSize ="5000000"
maxArchiveFiles="2"/>
</targets>
What should the expected overhead be for logging?
I have tried this example
private class Person
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public string Name { get; private set; }
public Person(string name)
{
Name = name;
logger.Info("New person created with name {0}", name);
}
}
List<Person> people = new List<Person>();
for (int i = 0; i < MAXTEST; i++)
{
people.Add(new Person(i.ToString()));
}
With MAXTEST values of 100,500,1000, 5000
Results in MAXTEST,noLogging, Logging
100, 25ms, 186ms
500, 33ms, 812ms
1000, 33ms, 1554ms
5000, 33ms, 7654ms
Granted one would probably never log this excessive amount, but it this the performance hit one would expect?
I have also tried using the asyncwrapper in the config
<target name="asyncFile" xsi:type="AsyncWrapper">
<target name="file" xsi:type="File" fileName="${basedir}/log.txt" />
</target>
You only need to add the async attribute to your targets element:
<targets async="true">
<target name="file" xsi:type="File" fileName="${basedir}/log.txt" />
instead of
<targets>
<target name="asyncFile" xsi:type="AsyncWrapper">
<target name="file" xsi:type="File" fileName="${basedir}/log.txt" />
</target>
I guess I didn't get that far into the documentation ;-)
Asynchronous target wrapper allows the
logger code to execute more quickly,
by queueing messages and processing
them in a separate thread. You should
wrap targets that spend a non-trivial
amount of time in their Write() method
with asynchronous target to speed up
logging. Because asynchronous logging
is quite a common scenario, NLog
supports a shorthand notation for
wrapping all targets with
AsyncWrapper. Just add async="true" to
the element in the
configuration file. ... your targets go here
...
Keep in mind that using async logging can cause certain messages to be discarded. This is by design.
ref: https://github.com/nlog/NLog/wiki/AsyncWrapper-target#async-attribute-and-asyncwrapper
Async attribute and AsyncWrapper
Don't combine the Async attribute and AsyncWrapper. This will only slow down processing and will behave unreliably.
Async attribute will discard by default
The async attribute is a shorthand for:
xsi:type="AsyncWrapper overflowAction="Discard" queueLimit="10000" batchSize="100" timeToSleepBetweenBatches="50"
For anyone who needs to lose this overhead and is configuring by code, it doesn't look like you can set all targets to asynchronous by default - you have to define it per-target:
// Set up asynchronous database logging assuming dbTarget is your existing target
AsyncTargetWrapper asyncWrapper = new AsyncTargetWrapper(dbTarget);
config.AddTarget("async", asyncWrapper);
// Define rules
LoggingRule rule1 = new LoggingRule("*", LogLevel.Trace, asyncWrapper);
config.LoggingRules.Add(rule1);
Be wary that by default if you queue up too many log items it will just drop items - look at OverflowAction = AsyncTargetWrapperOverflowAction.Block to go back to synchronous behaviour.