Extra quotes when logging objects with NLog - c#

I have a code that outputs interpolated string to the log with NLog.
Here is an example
_logger.LogDebug($"REQUEST {webRequest.RequestUri}", Id)
WebResponse webResponse = await _httpService.SendRequestAsync(webRequest);
var response = ParseResponse(webResponse);
_logger.LogDebug($"RESPONSE {(int)response.StatusCode} {JsonConvert.SerializeObject(response.Body)}", Id);
In this example, in a first case of calling the function _logger.LogDebug, I get the expected result as a result:
2019-04-15 09:27:24.5027 DEBUG e1701b07-d228-4543-a320-3cb1b7f2e4b0 REQUEST http://url/
But in the second case, the expected result is wrapped in additional quotes.
2019-04-15 09:27:57.2907 DEBUG "e1701b07-d228-4543-a320-3cb1b7f2e4b0 RESPONSE 200 [{...},{...}]"
Here is the _logger.LogDebug method
using NLog;
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
public void LogDebug(string message, Guid id)
{
Logger.Debug($"{id.ToString()} {message}");
}
The result of the JsonConvert.SerializeObject(response.Body) is a string representation of array of json eg: [{"key":"value","key":"value"},{"key":"value","key":"value"}]
Here is a part of my Nlog.config
<targets>
<target name="csv" xsi:type="File" fileName="${shortdate}-${level}-services.log">
<layout xsi:type="CSVLayout" delimiter="Tab" withHeader="false">
<column name="time" layout="${longdate}" />
<column name="level" layout="${uppercase:${level}}"/>
<column name="message" layout="${message}" />
</layout>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="csv" />
</rules>
Why do I get additional quotes in the second case and how can I avoid it?

As per the docs,
CSV Options quoting - Default Quoting mode for columns.
Default: Auto
Possible values:
Auto - Quote only whose values contain the quote symbol, the separator or newlines (Slow)
All - Quote all column. Useful for data known to be multiline such as Exception-ToString
(Fast) Nothing - Quote nothing (Very Fast)
The default is Auto - which means it is quoting your string since it contains a quote, a tab (the separator) or a new line.
It is important it does this, so that the file is a valid CSV file (otherwise Excel etc don't know where the column data begins and ends.

Related

Nlog config escape colon

I have a NLog.config file with the following variable
<variables>
<variable name="LogsFilePath" value="${appsetting:item=LogsFolderPath:default=C:\Company\Logs}" >
</variables>
<targets>
<target name="File"
xsi:type="File"
fileName="${LogsFilePath}\${event-properties:FileName}.log" />
</targets>
<rules>
<rule logger="DynamicLogger_*" minLevel="Trace" writeTo="File" />
</rules>
When there is no value in app.config file the default value taken is only C (because of the colon).
My target is something like this
Also if I use the variable in the config file like ${var:LogsFilePath} I have problems because of the colon. I have to use it with the static form ${LogsFilePath}
How can I solve this and use a path as default value in the default path of the appsetting variable?
You need to escape it. Replace ":" with "\:", also you need to escape backslash.
I got it working like this
<variables>
<variable name="LogsFolderPath" value="${appsetting:item=LogsFolderPath:default=C\:\\Company\\Logs}" />
</variables>

NLog - 2 separate log files - How to write to one or the other

I want to explicitly write to 2 different log files. Based on the method or type of operations.
How to?
I have seen the following StackOverFlow post
Having NLog loggers with different configuration
How do I configure NLog ? What code do I need too, so I can write to 1 file or the other? using code like :log.Error("My Big Error");
The post contains the following
<targets>
<target name="f1" xsi:type="File" fileName="${logger}.txt" />
<target name="f2" xsi:type="File" fileName="${shortdate}.txt" />
</targets>
So, if it's 'general error' I want write to f1. If it's file operation error I want to write to f2
thx in advance
In the NLog configuration you need to set up some rules to write to the targets (otherwise nothing get logged). In those rules you could add filters and conditions.
Simple example
For example:
<rules>
<logger name="Logger1" writeTo="f1" />
<logger name="Logger2" writeTo="f2" />
</rules>
The name attribute is here a filter, so the first rule means: write to f1 if logger name is equals to "Logger1". The rules are processed from top to bottom.
So when calling LogManager.GetLogger("Logger1").Info("My message") this will write to target f1 and LogManager.GetLogger("Logger2").Info("My message") will write to target f2.
GetCurrentClassLogger
When using LogManager.GetCurrentClassLogger(), the logger name is constructed of the current class and namespace name, e.g. "MyNameSpace.MyClass". This means you could adjust the name attribute to name="MyNameSpace.MyClass", or name="*.MyClass" for matching.
Final
You could also write this:
<rules>
<logger name="Logger1" writeTo="f1" final="true" />
<logger name="*" writeTo="f2" />
</rules>
This will write events of Logger1 to f1, and others to f2. You probably need the filter attribute, otherwise also the events of Logger1 will be written to f2 - and that isn't always what you need.
Other filter rules
There are many more filter options, e.g. min level, max level, but also more advanced filters (not only on logger name). You could read more about that here

Getting Logger Name into Excel file with NLog

Thanks to Rolf's comment in this question:
NLog in C# with severity AND categories
I am able to record to a text file the category of log message (such as "Thermal" or "Database" or "Mechanical". I'm doing this simply by passing a name to the "GetLogger" method.
public MainWindow()
{
InitializeComponent();
var logger = NLog.LogManager.GetCurrentClassLogger();
logger.Info("Hello World");
(NLog.LogManager.GetLogger("Database")).Info("Here is a DB message");
(NLog.LogManager.GetLogger("Thermal")).Info("Here is a Thermal message");
}
The text file looks like this:
2018-05-13 17:40:47.7442|INFO|NLogExperiments.MainWindow|Hello World
2018-05-13 17:40:50.3404|INFO|Database|Here is a DB message
2018-05-13 17:40:50.3404|INFO|Thermal|Here is a Thermal message
which is pretty good. I might ask in a seperate question how to reformat it.
Now I would like to get these messages into a CSV (Excel) file. I'm using:
<target name="excelfile" xsi:type="File" fileName="nLog.csv" archiveAboveSize="50000000" archiveNumbering="Sequence" maxArchiveFiles="3">
<layout xsi:type="CsvLayout">
<!-- Layout Options -->
<column name="time" layout="${longdate}" />
<column name="level" layout="${level}"/>
<column name="name" layout="${name}"/>
<column name="message" layout="${message}" />
<column name="codeLine" layout="${event-context:item=codeLine}" />
</layout>
</target>
but the output is only:
time,level,name,message,codeLine
2018-05-13 17:40:47.7442,Info,,Hello World,
2018-05-13 17:40:50.3404,Info,,Here is a DB message,
2018-05-13 17:40:50.3404,Info,,Here is a Thermal message,
This isn't surprising. I used "name" as a guess.
What is the field in GetLogger called?
More generally, how do I know all the options I can put in the CSV layout?
Finally, is there a good tutorial on using NLog with CSV? I haven't found one.
Thanks,
Dave
What is the field in GetLogger called?
You're looking for ${logger} - see ${logger} docs
More generally, how do I know all the options I can put in the CSV layout?
You could use all layout renderers, see the list with all layout renderers
For options for the CSV formating, see CsvLayout docs

NLog Custom Target for JSNLog

I am currently developing an application which makes use of NLog for .NET logging and JSNLog to send logs from Javascript to NLog for logging. These logs are stored in a SQL database.
The SQL Database has several columns for NLog such as Message, StackTrace, InnerException and AdditionalInfo. However when I log something from JSNLog like so
window.onerror = function (errorMsg, url, lineNumber, column, errorObj) {
JL("databaseLogger").fatalException({
"msg": "Uncaught Exception",
"errorMsg": errorMsg, "url": url,
"line number": lineNumber, "column": column
}, errorObj);
return false;
}
It is simply interpreted by NLog as a simple string log and not an error, so it just adds this giant JSON string to the AdditionalInfo column, and leaves the Message, StackTrace and InnerException columns blank. This makes reading the logs much harder.
I'd like to be able to parse this JSON sent by the JSNLog call and send it to the appropriate NLog variable, i.e:
JL("databaseLogger").fatalException({
"Message": "Uncaught Exception",
"InnerException": errorMsg,
}, errorObj);
would result in the Message and InnerException columns containing the data sent in the JSON. The parsing is not an issue as I know how to do that, but I am not sure how to intercept the call to NLog so I can parse the JSON before sending it onto NLog correctly.
I've looked into writing a custom NLog target which doesn't seem to hard, but I am not sure how to then pass my parsed JSON to Nlog correctly?
EDIT #1: Updated NLog.config for #Julian
Here is my new NLog.config but the Log from the new Write function does not get passed to the database target.
<targets>
<target xsi:type="ParseJsonWrapper"
name="JSONParser">
<target name="database"
xsi:type="Database"
connectionStringName="ErrorLog"
commandText="exec dbo.InsertLog
#level,
#callSite,
#type,
#message,
#stackTrace,
#innerException,
#additionalInfo">
<parameter name="#level" layout="${level}" />
<parameter name="#callSite" layout="${callsite}" />
<parameter name="#type" layout="${exception:format=type}" />
<parameter name="#message" layout="${exception:format=message}" />
<parameter name="#stackTrace" layout="${exception:format=stackTrace}" />
<parameter name="#innerException"
layout="${exception:format=:innerFormat=ShortType,Message,Method:MaxInnerExceptionLevel=1:InnerExceptionSeparator=}" />
<parameter name="#additionalInfo" layout="${message}" />
</target>
</target>
</targets>
<rules>
<logger levels="Trace,Info,Debug,Error,Warn,Fatal" name="JSLogger" writeTo="JSONParser"/>
</rules>
In this case, it's a good solution to create a TargetWrapper so you could combine it with other targets.
The config will looks like this:
<target xsi:type="ParseJsonWrapper"
name="myWrapper">
<target xsi:type="File" ...target properties... />
</target>
The code needed for this: inherit from WrapperTargetBase and override Write. e.g.
using System;
using NLog.Common;
/// <summary>
/// Limits the number of messages written per timespan to the wrapped target.
/// </summary>
[Target("ParseJsonWrapper", IsWrapper = true)]
public class ParseJsonWrapper : WrapperTargetBase
{
protected override void Write(AsyncLogEventInfo logEvent)
{
var json = logEvent.LogEvent.Message;
// parse json
// update log event
logEvent.LogEvent.Exception = new Exception("something");
// write to wrapped target
WrappedTarget.WriteAsyncLogEvent(logEvent)
}
}
Don't forget to register your wrapper (see here) and update your config to write to the wrapper instead of the wrapped target.

NLog create log files based on static variable

How can I set NLog file name based on static variable in my application.
I have windows service that performs different tasks. Reads configuration file with task details.
I would like to create log file based on given task name.
NOTE: class name will not work, since all the tasks call the same code.
NOTE: I am already using ${logger} variable as my current class. Since I need to know where I am as well.
-------------UPDATE--------------
Seems like this is not possible to do.
Modified question: How to set variable values at run time?
I am talking about this:
<variable name="logFileName" value="" />
Thank you.
You cannot, I believe. But you can use something like this:
In code:
static NLog.Logger loggerA = NLog.LogManager.GetLogger("nameA");
static NLog.Logger loggerB = NLog.LogManager.GetLogger("nameB");
void Something()
{
loggerA.Error("Something");
}
void SomethingElse()
{
loggerB.Error("SomethingElse");
}
NLog config:
<nlog ...>
<targets>
<target name="Error" xsi:type="AsyncWrapper">
<target name="file" xsi:type="File" fileName="${basedir}/Logs/Error.txt">
<layout ... />
</target>
</target>
</targets>
<!--other targets pointing to different files.-->
<rules>
<logger name="nameA" minlevel="Warn" writeTo="Error" />
<logger name="nameB" minlevel="Trace" maxLevel="Info" writeTo="Log" />-->
<logger name="*" minlevel="Trace" maxLevel="Info" writeTo="CommonLog" />
</rules>
</nlog>
You can also use SomeNamespace.Component.* as name of a logger and than only logs from SomeNamespace.Component will be logged via it. In that case the logger would be obtained like this:
static NLog.Logger loggerA = NLog.LogManager.GetCurrentClassLogger();
Here is the documentation for Nlog: https://github.com/NLog/NLog/wiki/Tutorial
There is a way to edit NLog configuration programmatically: https://github.com/nlog/NLog/wiki/Configuration-API
The easy solution is to use NLog GlobalDiagnosticsContext:
<target name="file" xsi:type="File" fileName="${gdc:logFileName:whenEmpty=DefaultApp}.txt">
Then you can override it like this:
NLog.GlobalDiagnosticsContext.Set("logFileName", "HelloApp");
See also: https://github.com/nlog/nlog/wiki/Gdc-Layout-Renderer

Categories