Custom log entry string formatting in NLog? - c#

I'm new to NLog and wanted to know if there's a way I write log entries formatted in some custom way ? For example, here are some things I want to do to my log entries:
--> Have nested log entry messages (excluding log entry metadata) to make the depth of the logging call stack easier to follow. This can be done by indenting the log entry string proportionally by how deep you are in the logging call stack
--> Log a "change ID" (basically some sort of ID linked to some request that's being processed) with all relevant log entries
I know NLog has some templates you can use and stuff, but I would prefer having more control, in the form of a custom formatting class or something, which would intercept the logging request every time I do Log.Info(...), and then format my log entry string appropriately according to the behavior I choose.
Is this possible ?
EDIT:
Here's the contents of my current NLog.config 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" xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd" autoReload="true" throwExceptions="true" internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
<targets>
<target name="logfile" xsi:type="File" fileName="logfile.log" />
<target name="logconsole" xsi:type="Console" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="logconsole" />
<logger name="*" minlevel="Trace" writeTo="logfile" />
</rules>
</nlog>

One way is create a custom layout renderer.
There are 2 options to do this:
Lambda method
Create a lambda methods and register before loading other NLog things (beware of statics), e.g. in main().
The lambda receives a LogEventInfo, which includes the following properties: Level, Message (unformated), FormattedMessage, Parameters (send to Log.Info), optional Exception and properties (named parameters)
//Create ${message-length}
// NLog 4.7+ syntax
NLog.LogManager.Setup().SetupExtensions(s =>
s.RegisterLayoutRenderer("message-length", (logevent) => logEvent.FormattedMessage.Length)
);
Class
If you need more control and options, you could also create a class that inherits from NLog.LayoutRenderers.LayoutRenderer
1. Setup
/// <summary>
/// E.g. usage: ${MyFormatter:MyLayout:${uppercase}} or ${MyFormatter:${uppercase}}
/// </summary>
[LayoutRenderer("MyFormatter")]
public class MyFormatterLayoutRenderer: LayoutRenderer
{
[RequiredParameter]
public Layout MyLayout { get; set; }
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
var text = MyLayout.Render(logEvent);
builder.Append(text);
}
}
2. Register
You need to register the layout renderer:
// NLog 4.7+ syntax
NLog.LogManager.Setup().SetupExtensions(s =>
s.RegisterLayoutRenderer<MyNamespace.MyFormatterLayoutRenderer>("MyFormatter")
);
3. Usage
Use it as follows, set the layout attribute of a target. For example:
<target name="logfile" xsi:type="File" fileName="logfile.log" layout="${message-length} - ${level} - ${message} - ${exception}" />
<target name="logconsole" xsi:type="Console" layout="${message-length} - ${level} - ${message} - ${exception}" />
Read more here

Related

NLog - Archive Start / Stop Time

Just started working with NLog and have it running with the following configuration:
<?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"
throwConfigExceptions="true">
<targets async="true">
<target name="logfile"
xsi:type="File"
layout="${longdate} [${level:uppercase=true}] (${threadid}) ${logger}: ${message} ${onexception:${newline}Exception\: ${exception:format=type,message,method,stacktrace:maxInnerExceptionLevel=5:innerFormat=shortType,message,method}}"
fileName="logs/current.log"
archiveFileName="logs/Archive/${ticks}.log"
archiveEvery="Minute"
archiveOldFileOnStartup="true"
keepFileOpen="false"
/>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>
Everything is working as expected. However, I need to have the rotated file be in the format of ${archive_start_ticks}_${arhive_end_ticks}.log rather than the current format which is ${archive_end_ticks}.log.
I was initially hoping I could name the active log file as ${ticks} and then, on archive, use the active log file's name as a parameter into the archive file to compose some like:
fileName="logs/${ticks}"
archiveFileName="logs/Archive/${fileName}_${ticks}.log"
Of course, there's two issues here:
Using ${ticks} for the active file creates a new file for each log line.
I can't seem to reference the original fileName as an input variable into archiveFileName.
That said, what is the best way to achieve this goal? Is this something NLog can handle natively or with minor extensions?
Updating in case anyone ever cares:
I bailed on using the FileTarget with configurations and wrote my own Target wrapped in a BufferedWrapper. On each flush, I use the first and last LogEvents to determine the timespan which gives me what I need to for the required file format with little custom code to support.

NLog Header (Ignore layout)

Is is possible to write a line to the log file that's not a log?
I'd like to write a "Note" that will ignore the layout in my NLog.config and just write the text exactly as I pass it for one or two lines.
This could be used for writing a header on the log file.
I'm not sure if this will matter to the solution but I'm using a separate ClassLogger (LogManager.GetCurrentClassLogger()) for every class and it's not an option for me to change that.
I was able to do this with a few creative rules.
First in my code I created a logger specifically for the header like this:
var loggerNoLayout = LogManager.GetLogger("NoLayout");
loggerNoLayout.Info("<Header Line 1>");
loggerNoLayout.Info("<Header Line 2>");
loggerNoLayout.Info("<Header Line 3>");
Then I modified my NLog.config 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="NoLayout"
xsi:type="File"
fileName="${specialfolder:folder=Desktop}\LogFile.log"
layout="${message}" />
<target name="FileTrace"
xsi:type="File"
fileName="${specialfolder:folder=Desktop}\LogFile.log"
layout="${level:padding=-5:fixedLength=true} | ${logger:padding=-62:fixedLength=true:alignmentOnTruncation=right}| ${date:format=HH\:mm\:ss} | ${message:padding=-100}"
deleteOldFileOnStartup="true" />
</targets>
<rules>
<logger writeTo="NoLayout"
levels="Info"
name="NoLayout"
final="true" />
<logger writeTo="FileTrace"
minlevel="Trace"
name="*" />
</rules>
</nlog>
The key here is that the NoLayout target's layout only writes the message and the rule for writing to NoLayout only works if the logger is named NoLayout. The final step is to add the final="true" so that my normal (FileTrace) logger does not pick up the header logs.
Sure. Write a string to the log that starts with a newline. Then write your content.
var longMultilineMessage = "pretend this variable contains\n a long multiline message";
logger.Info($"\n{longMultilineMessage}");
will show up in logs as :
2017-01-01 INFO
pretend this variable contains
a long multiline message

NLog 2.1 EventId for EventLog not working when not specified

I am using Nlog 2.1 and trying to write errors into Windows Event logger with different eventId. To better distinguish different errors.
If I specify eventId property it's working, but if don't I am not seeing any record in Windows Event Logger.
NLog.config 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="console" xsi:type="ColoredConsole"
layout="${date:format=HH\:mm\:ss}|${level:uppercase=true}|${message}" />
<target xsi:type="EventLog"
name="eventlog"
layout="{${newline}
"Logger": "${logger}",${newline}
"StackTrace": "${stacktrace}",${newline}
"Message": "${message}",${newline}
"Exception": "${exception:format=ToString,Data}"${newline}}"
machineName="."
source="CareFusion Analytics Agent Service"
eventId="${event-properties:EventID}"
log="Application" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="console" />
<logger name="*" minlevel="Error" writeTo="eventlog" />
</rules>
</nlog>
Usage:
private static void Main(string[] args)
{
Logger logger = LogManager.GetCurrentClassLogger();
logger.Error("Sample error message"); //This is not working
LogEventInfo logEvent = new LogEventInfo()
{
Level = LogLevel.Error,
Message = "Hello",
LoggerName = logger.Name
};
logEvent.Properties.Add("EventID", 400);
logger.Log(logEvent); //This is working
Console.WriteLine("Press any key....");
Console.ReadKey();
}
The call logger.Error("Sample error message"); goes wrong as the EventLogTarget tries to convert the ${event-properties:EventID} to a integer.
Because layout renders in NLog never return null (but string.Empty), this will give a exception - which will be caught by NLog. In the NLog's internal log you should see a FormatException.
So we need to specify a default value, you could do that with the whenEmpty:
<target xsi:type="EventLog"
...
eventId="${event-properties:EventID:whenEmpty=0}" />
PS: tested it with NLog 4.3.11

NLog not writing from referenced dll

I have a dll that I created for sending email. I have NLog included in that project that logs to c:\logs{logfilename.log} <--This is either an error or event log.
When working with the project locally it works just fine and writes out to the file during testing.
When I reference the emailing dll from another project that also has NLog it is not outputting to the log files. The config from the email dll is in the bin directory of the new project that is referencing it. I can create logs from the new project using a trace but it didn't print the email dll entries. Is there something special I need to do in my new project to get the email dll to write the logs? I've searched for an answer to this but the keywords do not produce the results I would need. I'm new to NLog, please be gentle.
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">
<targets>
<target xsi:type="File"
name="default"
layout="${longdate} - ${level:uppercase=true}: ${message}${onexception:${newline}EXCEPTION\: ${exception:format=ToString}}"
fileName="C:\logs\default.log"
keepFileOpen="false"
archiveFileName="C:\logs\NTC_Utility\default.{##}.log"
archiveNumbering="Sequence"
archiveEvery="Day"
maxArchiveFiles="30"
/>
<target xsi:type="File"
name="error"
layout="${longdate} - ${level:uppercase=true}: ${message}${onexception:${newline}EXCEPTION\: ${exception:format=ToString}}"
fileName="C:\logs\error.log"
keepFileOpen="false"
archiveFileName="C:\logs\NTC_Utility\error.{##}.log"
archiveNumbering="Sequence"
archiveEvery="Day"
maxArchiveFiles="90"
/>
<target xsi:type="File"
name="emailLog"
layout="-------------------- ${message} (${longdate}) --------------------${newline}
From: ${event-context:item=From}${newline}
To: ${event-context:item=To}${newline}
BCC: ${event-context:item=Bcc}${newline}
CC: ${event-context:item=CC}${newline}
Subject: ${event-context:item=Subject}${newline}
Body: ${event-context:item=Body}${newline}
Attachments: ${event- context:item=Attachments}${newline}--------------------------------------------------------------------${newline}"
fileName="C:\logs\EmailLog.log"
keepFileOpen="false"
archiveFileName="C:\logs\NTC_Utility\EmailLog_.{##}.log"
archiveNumbering="Sequence"
archiveEvery="Day"
maxArchiveFiles="90"
/>
</targets>
<rules>
<logger name="*" writeTo="error" level="Error" final="true" />
<logger name="*" writeTo="emailLog" level="Info" final="true" />
<logger name="*" writeTo="default" minLevel="Debug" />
</rules>
</nlog>
This is my Log.cs from the compiled utility dll
using NLog;
namespace NTC.Utility
{
internal static class Log
{
public static Logger Instance { get; private set;}
static Log()
{
LogManager.ReconfigExistingLoggers();
Instance = LogManager.GetCurrentClassLogger();
}
}
}
This line calls my Logging Method after the email is sent.
LogEmailSent(imperEmail);
Which calls this method...
private void LogEmailSent(EmailMessage email)
{
Logger logger = LogManager.GetCurrentClassLogger();
LogEventInfo thisEvent = new LogEventInfo(LogLevel.Info, "default","Email Sent");
thisEvent.Properties["From"] = email.From;
thisEvent.Properties["To"] = EmailCollectionToCsv(email.ToRecipients);
thisEvent.Properties["Bcc"] = EmailCollectionToCsv(email.BccRecipients);
thisEvent.Properties["CC"] = EmailCollectionToCsv(email.CcRecipients);
thisEvent.Properties["Subject"] = email.Subject;
thisEvent.Properties["Body"] = email.Body;
thisEvent.Properties["Attachments"] = AttachmentCollectionToCsv(email.Attachments);
logger.Log(thisEvent);
}
Ok, so I finally figured out what was going on after logging the trace... I noticed that it was only showing the error rule as loaded.... so I moved the "emaillog" rule above the "error" rule and all worked perfectly.
check your nlog.config
if not. are there any posibilities some configurations are injected within the code..
http://www.codeproject.com/Articles/10631/Introduction-to-NLog

Log only to a specific target at runtime

I am using NLog with two targets:
<?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 async="true">
<target name="logfile" xsi:type="File" fileName="my.log"/>
<target name="console" xsi:type="Console"/>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="logfile"/>
<logger name="*" minlevel="Info" writeTo="console"/>
</rules>
</nlog>
Is it possible to log a message only to the "logfile" target, without having the message written to the "console" target as well?
EDIT
To clarify: I want to direct messages from the same class to different loggers at run time (w/o having to change the XML). Something like:
class Program
{
static Logger _logger = LogManager.GetCurrentClassLogger();
static void Main(string[] args)
{
// Note - _logger.InfoToTarget() does not really exist
_logger.InfoToTarget("logfile", "This is my very detailed message, possibly with object dumps");
_logger.InfoToTarget("console", "Short message");
}
}
I'm aware that this couples my code with the NLlog.config file.
One way to accomplish the functionality you are looking for is to name your logger
_logger = LogManager.GetLogger("MyConsoleLogger")
_logger.Info("This will log to the console...");
_logger = LogManager.GetLogger("MyFileLogger")
_logger.Trace("This will log to a file...");
rather than using
LogManager.GetCurrentClassLogger().
In your config file you could then list in the rules
<rules>
<logger name="MyFileLogger" minlevel="Trace" writeTo="logfile"/>
<logger name="MyConsoleLogger" minlevel="Info" writeTo="console"/>
</rules>
This is by far not the most pretty solution to look at, but it does give you the functionality that you are looking for.
There are a few ways to do this, and the correct method depends on your situation.
Keep in mind that you typically want to avoid having your app know too much about the inner-workings of logging. If possible, it's best to configure nlog to decide where things should get logged.
Is there a specific namespace that should not be logged to console? That's easy to configure. Also, you can use the "When" filter (https://github.com/nlog/nlog/wiki/When-filter) or conditions (https://github.com/nlog/nlog/wiki/Conditions)
It may also be best to have multiple logger instances, so you can call the one that is appropriate for each situation (logger per class) (Why do loggers recommend using a logger per class?).
Absolutely, however I am assuming you mean at release you no longer wish to log to the console. You can do this very easily by removing or commenting out the listener that writes to the console target. Now it will only write to the logfile target.
<?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 async="true">
<target name="logfile" xsi:type="File" fileName="my.log"/>
<target name="console" xsi:type="Console"/>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="logfile"/>
<!--<logger name="*" minlevel="Info" writeTo="console"/>-->
</rules>
</nlog>
The rule that writes to the console is now deactivated, but the log file is active. If this is during release you probably want to change your rule to not process your trace logging as the min level for the log file since it will slow down your app with excessive IO. I have asked this question in the past and it appears that best practice is to do this via the XML configuration files. (Logging in Release Build of Application (C#))

Categories

Resources