NLog Custom Target for JSNLog - c#

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.

Related

How to log single SQL table using NLog in C#?

I am trying to log the single table using NLog bypassing parameter values into each column using NLog. But somehow I couldn't able to log in to the SQL table.
I tried by passing values by passing code like this and added the targets and rules in web.config.
Code in the class
private static Logger _logger;
public CustomToken()
{
_logger = LogManager.GetLogger("apiUsageLogger");
}
_logger.Info("{clientname}", "test");
_logger.Info($"clientusername", "test");
_logger.Info($"route", "test");
_logger.Info($"parameters", "test");
_logger.Info($"isuserauthenticated", 1);
In the web.config
<target name="apiUsageLog" xsi:type="Database" connectionStringName="connStringName">
<commandtext>
INSERT INTO Table
(ClientName, ClientUserName, Route, Parameters, IsUserAuthenticated, Machine)
VALUES
(#clientname, #clientusername, #route, #parameters, #isuserauthenticated, #machine)
</commandtext>
<parameter name="#clientname" layout="${clientname}" />
<parameter name="#clientusername" layout="${clientusername}" />
<parameter name="#route" layout="${route}" />
<parameter name="#parameters" layout="${parameters}" />
<parameter name="#isuserauthenticated" layout="${isuserauthenticated}" />
<parameter name="#machine" layout="${machinename}" />
</target>
</targets>
<rules>
<<logger name="apiUsageLogger" minlevel="Info" writeTo="apiUsageLog" />
</rules>
Somehow data is not getting populated into the table.
Is there a way I can able to achieve in populating the data into appropriate columns? NLog is the right way of doing it?
Not sure if it's clear, but with database target one log message will be one record in the database. I would recommend to read the tutorial. Also in this case the database target options are good to check.
Your config isn't working as ${clientusername} doesn't exist in NLog.
I will show 3 examples, hope that make things clear
Example 1: simple logs to database target
Logger call:
logger.Info("my info message");
config:
<target name="apiUsageLog" xsi:type="Database" connectionStringName="connStringName">
<commandtext>
INSERT INTO Table
(message, machinename)
VALUES
(#message, #machinenameParam)
</commandtext>
<parameter name="#messageParam" layout="${message}" /> <!-- this will be "my info message"-->
<parameter name="#machinenameParam" layout="${machinename}" /> <!-- defined in NLog, see https://nlog-project.org/config/?tab=layout-renderers-->
</target>
</targets>
This will create a log record in the database with my info message and the machine name.
Example 2: with custom properties:
I will use structured logging here. See structured logging
Logger call:
logger.Info("my info message with {Property1}", "value1");
config:
<target name="apiUsageLog" xsi:type="Database" connectionStringName="connStringName">
<commandtext>
INSERT INTO Table
(message, machinename, property1)
VALUES
(#message, #machinenameParam, #propertyParam1)
</commandtext>
<parameter name="#messageParam" layout="${message}" /> <!-- this will be "my info message"-->
<parameter name="#machinenameParam" layout="${machinename}" /> <!-- defined in NLog, see https://nlog-project.org/config/?tab=layout-renderers-->
<parameter name="#propertyParam1" layout="${event-properties:Property1}" /> <!-- this will be "value1" -->
</target>
</targets>
This will create a log record in the database with my info message with "Value1" , the machine name and the custom property with "value1".
Example 3: custom properties, not all in the message
This combines structured logging and WithProperty. You need at least NLog 4.6.3 for this.
Logger call:
logger.WithProperty("Property2", "value2")
.Info("my info message {Property1}", "value1");
config:
<target name="apiUsageLog" xsi:type="Database" connectionStringName="connStringName">
<commandtext>
INSERT INTO Table
(message, machinename, property1, property2)
VALUES
(#message, #machinenameParam, #propertyParam2)
</commandtext>
<parameter name="#messageParam" layout="${message}" /> <!-- this will be: my info message with "value1"-->
<parameter name="#machinenameParam" layout="${machinename}" /> <!-- defined in NLog, see https://nlog-project.org/config/?tab=layout-renderers-->
<parameter name="#propertyParam1" layout="${event-properties:Property1}" /> <!-- this will be "value1" -->
<parameter name="#propertyParam2" layout="${event-properties:Property2}" /> <!-- this will be "value2" -->
</target>
</targets>
This will create a log record in the database with my info message with "Value1" , the machine name and the custom properties "value1" and "value2"
Note, now "value1" is in the message and "value2" isn't.

Conditionally execute NLog target statement

I have one Enum class given below
public enum ScanStatus
{
Success,
Failure,
Exception
}
From .cs file I have passing the value of Exception to Nlog.config file using code
GlobalDiagnosticsContext.Set("ExceptionStatus", Convert.ToString((int)ScanStatus.Exception));
In nlog.config, i have configured to log the message in database using below code.
<target name="database" xsi:type="Database" keepConnection="true"
dbProvider="System.Data.SqlClient"
connectionStringName="PaymentScan"
commandText="INSERT INTO [dbo].[LoggingTrace] (PaymentId, ExceptionStatus, TraceDetails) VALUES (#ItemId, #ExceptionStatus, #Message)">
<parameter name="#message" layout="${message}"/>
<parameter name="#ItemId" layout="${gdc:ItemId}"/>
<!-- custom field! -->
<parameter name="#ExceptionStatus" layout="${gdc:ExceptionStatus}"/>
<!-- custom field! -->
</target>
I want this target statement should execute only when any exception occurs in the code. To handle this, I have written the rules given below
<rules>
<logger name="*" minlevel="Info" writeTo="database">
<filters>
<when condition="equals('${gdc:ExceptionStatus}', '2')" action="Log" />
</filters>
</logger></rules>
I am facing issue that When exception does not occurs, the target is getting executed, means equals statement written in rules in not working.
Could any body let me know the solution for this.

How do I configure NLog to write to a database?

I'm trying to get NLog to write to a database, however with my current code it throws an exception when I attempt to debug, the exception is: The type initializer for 'NotifyIcon.Program' threw an exception.
my NLog configuration file code is below, as this seems to be causing the issue as it's the only code I've changed.
<?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" autoReload="true">
<!--
See http://nlog-project.org/wiki/Configuration_file
for information on customizing logging rules and outputs.
-->
<targets>
<!-- add your targets here -->
<target name="database" xsi:type="Database" />
<target xsi:type="Database"
name="String"
dbUserName="Layout"
dbProvider="sqlserver"
useTransactions="false"
connectionStringName="String"
connectionString="Data Source=AC-02\SQLEXPRESS;Initial Catalog=master;Integrated Security=True"
keepConnection="true"
dbDatabase="Layout"
dbPassword="Layout"
dbHost="Layout"
installConnectionString="Layout"
commandText="INSERT INTO Logs (Machine_Name, Username, Logon_Time, Screensaver_On, Screensaver_Off, Logoff_Time, Program_Start) Values #MachineName, #Username, #LogonTime, #Screensaver_On, #Screensaver_Off, #LogoffTime, #ProgramStart "/>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="database" />
</rules>
</nlog>
any and all help would be greatly appreciated =]
You seem to be missing the parameters that are to be inserted.
See the examples at http://justinpdavis.blogspot.com/2010/04/logging-to-database-with-nlog.html
The nLog web page doesn't make it very clear that these are required, but if you squint your eyes and read https://github.com/nlog/NLog/wiki/Database-target you should find that they are required.
A simple example,
Config:
<target type="Database" name="database" connectionstring="Server=localhost;Database=NLog;Trusted_Connection=True;">
<commandText>
INSERT INTO NLogEntries ([Origin], [Message], [LogLevel],[CreatedOn],[OrderId]) VALUES (#Origin,#Message,#LogLevel,#Date, #OrderId);
</commandText>
<parameter name="#Date" layout="${date}" dbType="DbType.Date"/>
<parameter name="#Origin" layout="${callsite}"/>
<parameter name="#LogLevel" layout="${level}"/>
<parameter name="#message" layout="${message}"/>
<parameter name="#OrderId" layout="${event-properties:MyOrderId}" dbType="DbType.Int32"/> <!-- custom field! Note also the DB Type. Using Logger.WithProperty -->
</target>
Note, NLog 4.6 has also support for DbType - See https://nlog-project.org/2019/03/20/nlog-4-6-is-live.html
U also wrote 2 targets. And also a lot of attributes that u don't need to set. Should just be:
<target name="DbLog" xsi:type="Database" connectionString="YourConStr"
commandText="insert into [blablablabal] (Col1) values (#val1)">
<parameter name="#val1" layout="${level}" /></target>
Something like this. Easy no? :)
It looks your insert string is not in the right format? You are missing () around the parameters list.
commandText="INSERT INTO Logs (Machine_Name, Username, Logon_Time, Screensaver_On, Screensaver_Off, Logoff_Time, Program_Start) Values (#MachineName, #Username, #LogonTime, #Screensaver_On, #Screensaver_Off, #LogoffTime, #ProgramStart) "

How do I log a custom field in NLog to database?

I currently use NLog on a lot of projects. On some, I log to a database.
Here is what I would like to do:
CREATE TABLE [dbo].[NLogEntries](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Origin] [nvarchar](100) NOT NULL,
[LogLevel] [nvarchar](20) NOT NULL,
[Message] [nvarchar](3600) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[OrderId] [int] NULL --Custom field!
)
And NLog.config with this target:
<target type="Database" name="database" connectionstring="Server=localhost;Database=NLog;Trusted_Connection=True;">
<commandText>
INSERT INTO NLogEntries ([Origin], [Message], [LogLevel],[CreatedOn],[OrderId]) VALUES (#Origin,#Message,#LogLevel,#Date, #OrderId);
</commandText>
<parameter name="#Date" layout="${date}"/>
<parameter name="#Origin" layout="${callsite}"/>
<parameter name="#LogLevel" layout="${level}"/>
<parameter name="#message" layout="${message}"/>
<parameter name="#OrderId" layout="${orderId}"/> <!-- custom field! -->
</target>
And then log something like this:
var logger = LogManager.GetCurrentClassLogger();
var orderId = 123;
logger.Debug("What is going on here", orderId);
Is there a good way to do this and keep using NLog? Or do I have to roll my own logger and skip NLog when these are the requirements?
UPDATE 11 Feb 2022: newer versions of NLOG have other solutions- see Julian’s answer.
Rather than using GDC, which is for global static data and fails on concurrent logging, it is better to use EventProperties-Layout-Renderer that allows to pass custom  properties specific for the event
LogEventInfo theEvent = new LogEventInfo(logLevel, "", message);
theEvent.Properties["OrderId"] =orderId;`
log.Log(theEvent);
... and in your NLog.config file:
${event-context:item=OrderId} -- obsolete
${event-properties:item=OrderId} -- renders OrderId
Here is one approach using the GlobalContext.
Configuration:
<target type="Database" name="database" connectionstring="Server=localhost;Database=NLog;Trusted_Connection=True;">
<commandText>
INSERT INTO NLogEntries ([Origin], [Message], [LogLevel],[CreatedOn],[OrderId]) VALUES (#Origin,#Message,#LogLevel,#Date, #OrderId);
</commandText>
<parameter name="#Date" layout="${date}"/>
<parameter name="#Origin" layout="${callsite}"/>
<parameter name="#LogLevel" layout="${level}"/>
<parameter name="#message" layout="${message}"/>
<parameter name="#OrderId" layout="${gdc:OrderId}"/> <!-- custom field! -->
</target>
Call site:
var logger = LogManager.GetCurrentClassLogger();
GlobalDiagnosticsContext.Set("OrderId",123);
logger.Debug("What is going on here"); //If you use the logging configuration above, 123 will be logged to the OrderId column in your database
With a little more effort, you could wrap the NLog logger using one of the techniques illustrated here.
Or, you could create your own "context" object and write a custom LayoutRenderer to pull the values from it and write them to the log. Custom LayourRenderers are easy to write. You can see one example in my first answer to this question. There, I show how to write your own LayoutRenderer that appends the current value of System.Diagnostics.Trace.CorrelationManager.ActivityId to the log message.
NLog 4.5 introduces structured logging, so you can do this:
logger.Debug("What is going on here. OrderId={MyOrderId}", orderId);
With NLog 4.6.3 it possible to create temporary Logger, that is imbued with the desired property using WithProperty:
Call
int orderId = 123;
logger.WithProperty("MyOrderId", orderId).Info("This is my message!");
Config:
<target type="Database" name="database" connectionstring="Server=localhost;Database=NLog;Trusted_Connection=True;">
<commandText>
INSERT INTO NLogEntries ([Origin], [Message], [LogLevel],[CreatedOn],[OrderId]) VALUES (#Origin,#Message,#LogLevel,#Date, #OrderId);
</commandText>
<parameter name="#Date" layout="${date}" dbType="DbType.Date"/>
<parameter name="#Origin" layout="${callsite}"/>
<parameter name="#LogLevel" layout="${level}"/>
<parameter name="#message" layout="${message}"/>
<parameter name="#OrderId" layout="${event-properties:MyOrderId}" dbType="DbType.Int32"/> <!-- custom field! Note also the DB Type -->
</target>
Note, NLog 4.6 has also support for DbType - See https://nlog-project.org/2019/03/20/nlog-4-6-is-live.html
If that's all one needs, as of NLog version 4.3.3 there's an easier way to declare and access custom variables. Beware: none of these solutions are thread-safe.
Add the following to the NLog.config
<nlog ...
<!-- optional, add some variables -->
...
<variable name="myvarone" value="myvalue"/>
<variable name="myvartwo" value=2/>
...
</nlog>
Variables can be changed/accessed in the code by:
LogManager.Configuration.Variables["myvarone"] = "New Value"
LogManager.Configuration.Variables["myvartwo"] = 2
The values can be referenced in NLog.config:
"${var:myvarone}" -- renders "New Value"
"${var:myvartwo}" -- renders 2
As I mentioned above var and LogEventInfo objects are global. So if multiple instances are defined, changing a value would change the value for all instances. I'm very interested if anyone knows a way to declare per instance custom variables for NLog.
You can use the MDC code:
var logger = LogManager.GetCurrentClassLogger();
MDC.Set("OrderId", 123);
MDC.Set("user", HttpContext.Current.User.Identity.Name);
// ... and so on
also check this http://weblogs.asp.net/drnetjes/archive/2005/02/16/374780.aspx

NLog not saving large string In Database with Buffer and asynchronously options

I am working on a project which need to log SOAP messages request. Some requests have very large string more then 14 million XML characters. Issue is when these kind of messages are send to database, these messages are not saved neither any kind of exception occur in application. I try to switch on N-Log exception property but nothing happened. You can find my configuration settings below
<targets>
<target name="main" xsi:type="AutoFlushWrapper" asyncFlush="true">
<target name="database_buffer"
xsi:type="BufferingWrapper"
bufferSize="50"
flushTimeout="120000">
<target xsi:type="FallbackGroup"
name="String"
returnToFirstOnSuccess="true">
<target xsi:type="Database"
name="database_inner"
connectionString="${event-context:item=dbConnectionString}"
commandText="INSERT INTO [Log] ([Level],[Message],[Application],[MethodInfo],[Exception]) VALUES(#Level,#Message,#ApplicationName,#MethodInfo,#Exception)">
<parameter name="#Level" layout="${level:uppercase=true}" />
<parameter name="#Message" layout="${event-properties:item=Message}" />
<parameter name="#ApplicationName" layout="${event-properties:item=SourceName}" />
<parameter name="#MethodInfo" layout="${event-properties:item=MethodInfo}" />
<parameter name="#Exception" layout="${event-properties:item=Exception}" />
</target>
</target>
</target>
</target>
</targets>
There is no internal log file created or application crash in debug mode. On the same end small strings and normal length messages are successfully going into database. In database I have Nvarchar Max datatype for column.
I can't find option here to upload file so I am attaching this link to download exact message which I am trying to save in database. Here is it.
This file text successfully insert in database when I remove Buffer and asynchronously options or I insert with Ado.net components.
So please guide me the where I am wrong and what is the issue.

Categories