C#: Regarding Nlog settings and usage - c#

i am curious to use nlog. i did not use it previously. so i like to use it now in my project. i have a question.
<rules>
<logger name="SomeNamespace.Component.*" minlevel="Trace" writeTo="logfile" final="true" />
<logger name="*" minlevel="Info" writeTo="logfile" />
</rules>
what does mean here SomeNamespace.Component.* ? show me the usage of this type of rule with sample code
what is minlevel="Info" ? what does mean minlevel here ?
what other option can be set for minlevel ?
thanks

what does mean here SomeNamespace.Component.* ?
It means that this rule will match any loggers which have name starting with SomeNamespace.Component.. Usually name of logger equals name of class where you are creating logger (but you also can provide custom logger name):
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
So rule configuration above will match following logger names:
SomeNamespace.Component.MyClass
SomeNamespace.Component.Cool.Other.Namespace.YourClass
// etc
I.e. any logger created in class from SomeNamespace.Component. namespace will match rule. No matter how many classes with loggers you have there.
what is minlevel="Info" ?
It's a minimal level of log messages which will be logged by logger.
Logger.Debug("This will not be logged");
Logger.Info("This will be logged");
NLog supports following log levels (you can use any of them to control which messages will be logged):
Trace
Debug
Info
Warn
Error
Fatal
Check description of each level at NLog wiki. Usually you should use min level Info which will log any error messages and some high-level details of what system is doing. For debugging purpose you can turn-on Trace or Debug level, but your log file can become huge very quickly. Performance also will hurt when you are writing lot of log messages.

Related

NLog: how to obtain level of a specific target programatically

I currently use NLog and allow admin users to set the level at runtime using a variable as follows:
<logger name="*" minLevel="${var:myFileLevel}" writeTo="file" />
I would like to know the level of this logger at runtime (so I cannot obtain it from the configuration file due to the variable)
I am able to easily obtain the Target as follows:
Target target= LogManager.Configuration.FindTargetByName("file");
but unfortunately there are no relevant methods on the target object to obtain the level. Is it possible to obtain the logging level at runtime?
The enabled logging level is configured at the logging rule.
So you could do this:
Add a rulename, so you could find the rule easier:
<logger name="*" minLevel="${var:myFileLevel}" writeTo="file" ruleName="myrule" />
Find the rule and check the Levels property. See LoggingConfiguration.FindRuleByName Method
var rule = LogManager.Configuration.FindRuleByName("myrule");
var levels = rule.Levels; // enabled levels
For this case, another option is to read the myFileLevel variable value. For that, you need to render it, you could use LogEventInfo.CreateNullEvent() for that.
var myFileLevelLayout = LoggingConfiguration.Variables["myFileLevel"]; // Type SimpleLayout
string value = myFileLevelLayout.Render(LogEventInfo.CreateNullEvent())
See
LoggingConfiguration.Variables Property

NLog - Write log entry to different file

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();

Adding method name in NLog

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>

Holding a reference to logger in a static class

I am using log4net in which I am obtaining a reference to the logger within a static class like so:
internal static class Constants
{
public static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
}
and using that reference throughout the app like so:
Constants.Log.Info(_message);
However I've got the feeling that this may cause problems and I realise that constant (global?) variables can be a bad thing.
Is there anything wrong with this method?
What are the reasons?
What can be done to resolve these?
You are correct, that is not the best approach, if you want to be able to easily differentiate log levels and use different log targets ("appenders") for each class.
It's usually recommended that each class has a static ILog instance, named after it's fully qualified type:
namespace Animals
{
public class Dog
{
private static readonly ILog Log = LogManager.GetLogger(typeof(Dog));
}
public class Cat
{
private static readonly ILog Log = LogManager.GetLogger(typeof(Cat));
}
}
While this looks like more work compared to having a singleton, it proves very handy in the long term. Once you've done this, it is easy to differentiate Dog and Cat log levels through your configuration file (example shown with log4net syntax):
<log4net>
<!-- appender definitions ommited -->
<!-- default for all loggers in Animals namespace -->
<logger name="Animals">
<level value="WARN"/>
</logger>
<!-- ...but we need full debug for Dogs -->
<logger name="Animals.Dog">
<level value="DEBUG"/>
</logger>
<!-- ...and we want to send Cat messages over e-mail -->
<logger name="Animals.Cat">
<level value="INFO"/>
<appender-ref ref="SmtpAppender"/>
</logger>
</log4net>
Logging frameworks like log4net also use the notion of hierarchical logging: a logger is said to be an ancestor of another logger if its name followed by a dot is a prefix of the descendant logger name. To make use of this feature, it's best to avoid naming loggers manually (using hardcoded strings), but rather use type information to include the entire namespace.
This essentially creates a singleton, although there are many of problems with that, including making effective unit tests. Unit tests don't need to log and this introduces an unnecessary dependency.
You should consider using an IoC container and dependency injection.

dynamic log4net appender name?

Let's say i have 3 smtp appenders in same log4net file whose names are:
<appender name = "emailDevelopment".. />
<appender name = "emailBeta".. />
<appender name = "emailProduction".. />
Let's say i have 3 different servers(Dev, Beta, Production). Depending upon the server, i want to fire the log. In case of Development server, it would fire log from "emailDevelopment". I have a system variable in each server named "ApplicationEnvironment" whose value is Development, Beta, Production based on the server names. Now is there anyway i can setup root in log4net so that it fires email depending upon the server name.
<root>
<priority value="ALL" />
<appender-ref ref="email<environment name from whose appender should be used>" />
</root>
This doesn't directly answer your question, but another approach is to simply have multiple log4net configuration files and call XmlConfigurator.Configure() on the right one. For example, you might have Logging.Development.Config, Logging.Beta.Config and so on.
Somewhere in code, you determine the "environment" and configure using the file you want.
I've even gone so far as to have multiple config files and pull different parts of them out into a single XML representing the "true" config, and then calling the Configure() method on that. For example, Logging.Appenders.Config which has all the appenders, and takes all of them and combines it with one of your environment-specific config files above; the environment-specific ones simply reference what they need, and the rest are effectively inactive/unreferenced for that environment.
Even after having written the only XSD file for log4net configuration I'm still not aware of an easy way to achieve this.
You might be able to do something like:
log4net.GlobalContext.Properties["host"] = new ClassThatToStringsHost();
class ClassThatToStringsHost
{ public override string ToString() { return "whatever"; } }
Now you can reference this value from the Log format with: "%property{host}"
To perform the filtering you will need to use a filter configuration in the adapter(s):
<appender name="file" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.PropertyFilter">
<Key value="host" />
<StringToMatch value="whatever" />
</filter>
<!-- Anything not accepted by the above should be excluded -->
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
There may even be a built-in property you could leverage and this should work. See also this post: http://geekswithblogs.net/rgupta/archive/2009/03/03/dynamic-log-filenames-with-log4net.aspx
For me, myself, and I... I would approach it another way all together. I would derive my own SMTP appender from the default and in the ActivateOptions() method I'd configure the values according to the environment. This would allow you to use one SMTP appender with consistent rules and yet provide three public properties for each of the email addresses you want to send from. It's not hard, give it a try!

Categories

Resources