Adding method name in NLog - c#

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>

Related

Why is NLog not logging scope data to Application Insights custom Dimensions

I am currently logging to Application Insights using NLog configured in an nlog.config file. I don't have IncludeScopes set anywhere (it is true by default).
I am trying to log custom properties using scope. It works when logging to a file or the console but not when logging to the Application Insights customDimensions.
This is how I am logging my scope:
using (_logger.BeginScope(new Dictionary<string, object> { ["ActivityId"] = Guid.NewGuid()})
{
_logger.LogInformation("Logging from with scope");
}
and this is the nlog.config file:
<target name="applicationInsights" xsi:type="ApplicationInsightsTarget" >
<instrumentationKey>8d9f67d5-fe36-45cf-935f-2f87bb240b12</instrumentationKey>
<!-- Only required if not using ApplicationInsights.config -->
<contextproperty name="threadId" layout="${threadid}" />
<contextproperty name="processName" layout="${processname}" />
<!-- Can be repeated with more context -->
</target>
Unfortunately I don't see the ActivityId when I look in the customDimensions in Application Insights.
I am running my Console app in Azure so have registered a worker service (which processes messages) like this:
services.AddHostedService<PositionMessageProcessor>()
What do I need to do get the logging scope to log my ActivityId in Application Insights?
Update
I have managed to it logging the ActivityId by adding it as a specific contextProperty. I don't really want to have to update the config file everytime I call BeginScope(...) with different properties.
Is there a generic way to get it to work for all scope properties?
Is there a generic way to get it to work for all scope properties?
I assume you mean that it sends all the scoped properties to application insights without specifying which keys.
Currently this isn't supported by the target, see source.
In NLog 4, the scope properties are pushed to the NestedDiagnosticsLogicalContext.
You could do that by creating your own target:
Copy the target from source
Loop over NestedDiagnosticsLogicalContext.GetAllObjects() in BuildPropertyBag inside ApplicationInsightsTarget.
Register your target, see NLog-Register your custom component
You can have scope-context included as blob-data by using a <contextproperty> with a JsonLayout:
<target type="ApplicationInsightsTarget" name="aiTarget">
<contextProperty name="scopeproperties">
<layout type="JsonLayout" includeMdlc="true" />
</contextProperty/>
</target>

How can I get test case name and use it as file name NLog

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

Use different log files according to method parameter

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?

NLog performance

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.

How do you configure and enable log4net for a stand-alone class library assembly?

Background
I am writing a class library assembly in C# .NET 3.5 which is used for integration with other applications including third-party Commercial-Off-The-Shelf (COTS) tools. Therefore, sometimes this class library will be called by applications (EXEs) that I control while other times it will be called by other DLLs or applications that I do not control.
Assumptions
I am using C# 3.0, .NET 3.5 SP1, and Visual Studio 2008 SP1
I am using log4net 1.2.10.0 or greater
Constraints
Any solution must:
Allow for the class library to enable and configure logging via it's own configuration file, if the calling application does not configure log4net.
Allow for the class library to enable and configuring logging via the calling applications configuration, if it specifies log4net information
OR
Allow for the class library to enable and configuring logging using it's own configuration file at all times.
Problem
When my stand-alone class library is called by a DLL or application that I do not control (such as a third-party COTS tool) and which doesn't specify log4net configuration information, my class library is unable to do any of it's logging.
Question
How do you configure and enable log4net for a stand-alone class library assembly so that it will log regardless if the calling application provides log4net configuration?
Solution 1
A solution for the first set of constraints is to basically wrap the log4net.LogManager into your own custom LogManager class like Jacob, Jeroen, and McWafflestix have suggested (see code below).
Unfortunately, the log4net.LogManager class is static and C# doesn't support static inheritance, so you couldn't simply inherit from it and override the GetLogger method.
There aren't too many methods in the log4net.LogManager class however, so this is certainly a possibility.
The other drawback to this solution is that if you have an existing codebase (which I do in my case) you would have to replace all existing calls to log4net.LogManager with your wrapper class. Not a big deal with today's refactoring tools however.
For my project, these drawbacks outweighed the benefits of using a logging configuration supplied by the calling application so, I went with Solution 2.
Code
First, you need a LogManager wrapper class:
using System;
using System.IO;
using log4net;
using log4net.Config;
namespace MyApplication.Logging
{
//// TODO: Implement the additional GetLogger method signatures and log4net.LogManager methods that are not seen below.
public static class LogManagerWrapper
{
private static readonly string LOG_CONFIG_FILE= #"path\to\log4net.config";
public static ILog GetLogger(Type type)
{
// If no loggers have been created, load our own.
if(LogManager.GetCurrentLoggers().Length == 0)
{
LoadConfig();
}
return LogManager.GetLogger(type);
}
private void LoadConfig()
{
//// TODO: Do exception handling for File access issues and supply sane defaults if it's unavailable.
XmlConfigurator.ConfigureAndWatch(new FileInfo(LOG_CONFIG_FILE));
}
}
Then in your classes, instead of:
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
Use:
private static readonly ILog log = LogManagerWrapper.GetLogger(typeof(MyApp));
Solution 2
For my purposes, I have decided to settle on a solution that meets the second set of constraints. See the code below for my solution.
From the Apache log4net document:
"An assembly may choose to utilize a named logging repository rather than the default repository. This completely separates the logging for the assembly from the rest of the application. This can be very useful to component developers that wish to use log4net for their components but do not want to require that all the applications that use their component are aware of log4net. It also means that their debugging configuration is separated from the applications configuration. The assembly should specify the RepositoryAttribute to set its logging repository."
Code
I placed the following lines in the AssemblyInfo.cs file of my class library:
// Log4Net configuration file location
[assembly: log4net.Config.Repository("CompanyName.IntegrationLibName")]
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "CompanyName.IntegrationLibName.config", Watch = true)]
    
References
LogManagerMembers
Jacob's Answer
Jeroen's Answer
McWafflestix's Answer
log4net Manual - Repositories
log4NET from a class library (dll)
You can probably code something around the XmlConfigurator class:
public static class MyLogManager
{
// for illustration, you should configure this somewhere else...
private static string configFile = #"path\to\log4net.config";
public static ILog GetLogger(Type type)
{
if(log4net.LogManager.GetCurrentLoggers().Length == 0)
{
// load logger config with XmlConfigurator
log4net.Config.XmlConfigurator.Configure(configFile);
}
return LogManager.GetLogger(type);
}
}
Then in your classes, instead of:
private static readonly ILog log = LogManager.GetLogger(typeof(MyApp));
Use:
private static readonly ILog log = MyLogManager.GetLogger(typeof(MyApp));
Of course, it would be preferable to make this class a service and dynamically configure it with the IoC container of your choice, but you get the idea?
EDIT: Fixed Count() problem pointed out in comments.
In your code you can check if there are any loggers via
log4net.LogManager.GetCurrentLoggers().Count()
You could then for example use an XmlConfigurator to load a default configuration from a file:
log4net.Config.XmlConfigurator.Configure(configFile)
You could do the initialization in a static or regular constructor.
class Sample
{
private static readonly log4net.ILog LOG;
static Sample()
{
if (log4net.LogManager.GetCurrentLoggers().Count() == 0)
{
loadConfig();
}
LOG = log4net.LogManager.GetLogger(typeof(Sample));
}
private static void loadConfig()
{
/* Load your config file here */
}
public void YourMethod()
{
LOG.Info("Your messages");
}
}
In your standalone class library, have a singleton which loads the log4net configuration file using the log4net.Config.XmlConfigurator.
Specifically, you can define all of your code to use your own custom logging class; this class can just be a simple wrapper of the log4net logging calls, with one addition; make a static member which contains the log information you want to log to; initialize that with a call to the XmlConfigurator in the static constructor for that class. That's all you have to do.
You can find a good description here:
log4net: A quick start guide
As the article describes, to configure it fore each assembly separately, create an XML file for your assembly named AssemblyName.dll.log4net and place the following XML code into it:
<?xml version="1.0" encoding="utf-8"?>
<log4net debug="false">
<appender name="XmlSchemaFileAppender" type="log4net.Appender.FileAppender">
<file value="AppLog.xml" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.XmlLayout" />
</appender>
<root>
<level value="WARN" />
<appender-ref ref="XmlSchemaFileAppender" />
</root>
</log4net>
It further describes, to instantiate a new logger, simply declare it as a variable for the entire class as follows:
public class LogSample
{
private static readonly log4net.ILog Log
= log4net.LogManager.GetLogger(typeof(LogSample));
// Class Methods Go Here...
}
You can then use the private variable Log inside your class like:
Log.Info("Sample message");
Likewise you can use Log.Error("Error occurred while running myMethod", ex) to log errors along with the exception details.
What I found is the following:
Don't forget to call log4net.Config.XmlConfigurator.Configure(); to activate your configuration
If you need to know the path of the file(s) written, here some code how to obtain it from Log4Net
I hope this helps.
This works for me for a shared library
protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.Assembly.GetExecutingAssembly().GetType());

Categories

Resources