Struggling to add custom NLog layout renderer in .Net 6 Web API - c#

I have a .Net 6 Web API. I use NLog for my logging. I'm trying to log a custom field with NLog, but I am struggling to get it to work. I'm following this sample: https://nlog-project.org/2015/06/30/extending-nlog-is-easy.html.
So my nlog.config currently looks like this (I'm just showing the db target as that's the only relevant part I believe):
<target name="db"
xsi:type="Database"
dbProvider="MySqlConnector.MySqlConnection, MySqlConnector"
connectionString="some_connection_string"
commandType="StoredProcedure"
commandText="`abc`.`InsertLog`"
>
<parameter name="machineName" layout="${machinename}" />
<parameter name="logged" layout="${date}" />
<parameter name="logLevel" layout="${level}" />
<parameter name="message" layout="${message}" />
<parameter name="logger" layout="${logger}" />
<parameter name="properties" layout="${all-event-properties:separator=|}" />
<parameter name="callsite" layout="${callsite:fileName:true}" />
<parameter name="exception" layout="${exception:tostring}" />
<parameter name="callsiteLineNumber" layout="${callsite-linenumber}" />
<parameter name="stackTrace" layout="${stacktrace}" />
</target>
The db target basically calls a MySql Stored Proc.
Now I'd like to save an extra, custom field, so I add this line just before the closing tag:
<parameter name="newField" layout="${newField}" />
( I also added the relevant field to the MySql Stored Proc)
Then I create a new class, inheriting from LayoutRenderer:
using NLog;
using NLog.LayoutRenderers;
using System;
using System.Text;
namespace PropWorx.API
{
[LayoutRenderer("newField")]
public class NLogLayoutRenderer : LayoutRenderer
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append("test");
}
}
}
I'm not sure if that's all I need? Because it's not working. I get this error when I try to start up the API:
NLogConfigurationException: Error when setting property 'Layout' on
DatabaseParameterInfo
ArgumentException: LayoutRenderer cannot be found: 'newField'
This happens on the very fist line of my Program.cs file:
public static void Main(string[] args)
{
// Exception is thrown on the following line:
NLog.Logger logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try
{
logger.Info("Init main");
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
logger.Error(exception, "Stopped program because of exception");
throw;
}
finally
{
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog();
}
Any ideas where I;m going wrong? Thanks

Replace this line:
NLog.Logger logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
With this line:
NLog.Logger logger = NLog.LogManager.Setup().SetupExtensions(s =>
s.RegisterLayoutRenderer<PropWorx.API.NLogLayoutRenderer>("newField")
).LoadConfigurationFromAppSettings().GetCurrentClassLogger();
See also: https://github.com/NLog/NLog/wiki/Register-your-custom-component#register-nlog-extensions-from-assembly-at-runtime
See also: https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-6#3-update-programcs

Related

Config nLog .NET Core 3.1, missing debug messages

I have looked at numerous configurations for nLog in .net core, all is working, except that I cannot get a debug message to log. If I write an information message or error message those work just fine. So this seems like a logging level problem, but try as I might I can't seem to get the level low enough to write out debug messages.
I have also looked at this: Missing trace\debug logs in ASP.NET Core 3?
Nlog.Web.AspNetCore 4.11
.Net Core 3.1
Here is my configuration:
nothing logging specific in startup.cs
program.sc
public static void Main(string[] args)
{
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try
{
logger.Debug("init main");
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
//NLog: catch setup errors
logger.Error(exception, "Stopped program because of exception");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog();
appsettings.json
"Logging": {
"LogLevel": {
"Default": "Trace",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
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"
autoReload="true"
throwConfigExceptions="true"
internalLogLevel="info"
internalLogFile="c:\temp\Nlog.log">
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<targets>
<target name="database" xsi:type="Database" connectionString="${appsettings:name=ConnectionStrings.applog}" >
<commandText>
INSERT INTO [NLog] (
[ApplicationName],
[AppUserIdentity],
[LogDate] ,
[LogLevel] ,
[LogSource],
[LogAssembly] ,
[LogMessage] ,
[MachineName] ,
[DomainUser],
[CallSite],
[LogThread] ,
[LogException] ,
[ClientIp]
)
VALUES (
#ApplicationName,
#AppUserIdentity,
#LogDate ,
#LogLevel ,
#LogSource,
#LogAssembly,
#LogMessage ,
#MachineName ,
#DomainUser ,
#CallSite ,
#LogThread ,
#LogException,
#ClientIp
);
</commandText>
<parameter name="#ApplicationName" layout="${appsettings:name=AppName:default=Missing-Config}" />
<parameter name="#AppUserIdentity" layout="${aspnet-user-identity}" />
<parameter name="#LogDate" layout="${date:format=yyyy-MM-dd HH\:mm\:ss.fff}" />
<parameter name="#LogLevel" layout="${level}" />
<parameter name="#LogSource" layout="${logger} " />
<parameter name="#LogAssembly" layout="${assembly-version}" />
<parameter name="#LogMessage" layout="${message}" />
<parameter name="#MachineName" layout="${machinename}" />
<parameter name="#DomainUser" layout="${windows-identity:domain=true}" />
<parameter name="#CallSite" layout="${callsite}" />
<parameter name="#LogThread" layout="${threadid}" />
<parameter name="#LogException" layout="${exception}" />
<parameter name="#ClientIp" layout="${aspnet-request-ip}" />
</target>
</targets>
<rules>
<logger name="*" minlevel="Trace" maxlevel="Error" final="true" writeTo="database" />
</rules>
</nlog>
injected into a service. This call will not log, but if it were _logger.LogInformation("xx"), it will log. This ILogger reference is Microsoft.Extensions.Logging.ILogger
public TaxCalculationService(IUnderwritingRepository repository, ILogger<TaxCalculationService> logger)
{
_repository = repository;
_logger = logger;
}
public TaxCalculationResponseDto CalculateTaxes(TaxCalculationRequestDto request)
{
_logger.LogDebug("calculateTaxes request: " + JsonConvert.SerializeObject(request));
Please try to upgrade to NLog.Web.AspNetCore v4.12.0, and then change UseNLog() to use RemoveLoggerFactoryFilter = true like this:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
})
.UseNLog(new NLogAspNetCoreOptions() { RemoveLoggerFactoryFilter = true });
See also: https://github.com/NLog/NLog.Extensions.Logging/pull/482
You might want to update the NLog-LoggingRules to ignore noise from Microsoft-Loggers:
<rules>
<!-- Block output from noisy loggers -->
<logger name="Microsoft.*" maxlevel="Info" final="true" />
<logger name="System.Net.Http.*" maxlevel="Info" final="true" />
<!-- Write the good stuff to the databse -->
<logger name="*" minlevel="Debug" writeTo="database" />
</rules>

Custom logging with NLog on database

I have some problem with NLog. According to here that said
If you want custom layout properties (NLog calls them layout renderers) you can use the EventProperties Layout Renderer.
I wrote some config :
<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="false"
internalLogLevel="on" internalLogFile="c:\temp\nlog-internal.log">
<targets>
<!-- database target -->
<target name="databaseauthentication"
xsi:type="Database"
connectionString="Data Source = [....]; Initial Catalog = [....]; User Id = [....]; Password = [....]"
commandText="exec dbo.InsertAuthentication
#company,
#firstname,
#lastname,
#ip,
#pcname,
#additionalInfo">
<parameter name="#company" layout="${event-properties:item=companyValue}" />
<parameter name="#firstname" layout="${event-properties:item=firstnameValue}" />
<parameter name="#lastname" layout="${event-properties:item=lastnameValue}" />
<parameter name="#ip" layout="${event-properties:item=ipValue}" />
<parameter name="#pcname" layout="${event-properties:item=pcnameValue}" />
<parameter name="#additionalInfo" layout="${event-properties:item=additionalInfoValue}" />
</target>
</targets>
<rules>
<logger levels="Info" name="asyncdatabaseauthenticationLogger" writeTo="asyncdatabaseauthentication"/>
<logger levels="Info" name="databaseauthenticationLogger" writeTo="databaseauthentication"/>
</rules>
</nlog>
And used that like :
public static void SendLogin()
{
var eventInfo = new LogEventInfo(LogLevel.Info, databaseAuthenticateLogger.Name, "Message");
eventInfo.Properties["firstnameValue"] = "My Fist Name;
eventInfo.Properties["lastnameValue"] = "My Last Name";
eventInfo.Properties["companyValue"] = "My Company";
eventInfo.Properties["ipValue"] = "IP";
eventInfo.Properties["pcnameValue"] = "PC Name";
eventInfo.Properties["additionalInfoValue"] = "Login";
databaseAuthenticateLogger.Log(eventInfo);
}
But this code don't work. Can anyone tell me where is my mistake?
The Above code is true, But We need to add this part in c# code :
static Logger databaseAuthenticateLogger = LogManager.GetLogger("databaseauthenticationLogger");
static Logger asyncdatabaseAuthenticateLogger = LogManager.GetLogger("asyncdatabaseauthenticationLogger");
Everythings works good.

add record in nlog to field with dataType = date

I use nlog dll to write to database - oracle with entity frameWork in the line :
logger.Log(logLevel, "try");
I get in the logs of nlog the following error:
The literal does not match the template string
the code is:
SetPropGDC(LogEntity);
NLog.LogLevel logLevel = SetLogLevel(Level.Debug);
logger.Log(logLevel, "try");
ClearGDC();
private void SetPropGDC(LogEntity LogEntity)
{
GlobalDiagnosticsContext.Set(processId, LogEntity.PROCESS_ID.ToString());
GlobalDiagnosticsContext.Set("TIME_STAMP", DateTime.Now);
GlobalDiagnosticsContext.Set(customerId, LogEntity.CUSTOMER_ID.ToString());
}
<targets>
<target name="TRACEDatabase" type="DataBase" keepConnection="false"
dbProvider="Oracle.ManagedDataAccess.Client" connectionString="${gdc:connectionString}"
commandText="insert into TLOG_SITE_GENERAL_TRACE( PROCESS_ID,TIME_STAMP,CUSTOMER_ID)
values(:PROCESS_ID,:TIME_STAMP,:CUSTOMER_ID)">
<parameter name="PROCESS_ID" layout="${gdc:PROCESS_ID}" />
<parameter name="TIME_STAMP" layout="${gdc:TIME_STAMP}" />
<parameter name="CUSTOMER_ID" layout="${gdc:CUSTOMER_ID}" />
</target>
I tryed in the Web.config to change the line:
<parameter name="TIME_STAMP" layout="${gdc:TIME_STAMP}" />
to:
<parameter name="TIME_STAMP" layout="${longDate}" />
and I got the same error
Can anyone help?
NLog DatabaseTarget parameters converts to string by default. You can change the datatype by specifying dbType so it matches the database-column:
<target name="TRACEDatabase" type="DataBase">
<parameter name="PROCESS_ID" layout="${event-properties:PROCESS_ID}" />
<parameter name="TIME_STAMP" layout="${date}" dbType="DateTime" />
<parameter name="CUSTOMER_ID" layout="${event-properties:CUSTOMER_ID}" />
</target>
Btw. it is a bad idea to use global variables for transfering context specific details.
Instead you should make use of NLog LogEventInfo Properties:
var logLevel = SetLogLevel(Level.Debug);
var theEvent = new NLog.LogEventInfo(logLevel, null, "try");
theEvent.Properties["PROCESS_ID"] = LogEntity.PROCESS_ID.ToString();
theEvent.Properties["CUSTOMER_ID"] = LogEntity.CUSTOMER_ID.ToString();
log.Log(theEvent);
See also: https://github.com/NLog/NLog/wiki/EventProperties-Layout-Renderer

Value does not fall within the expected range using nlog with entity frameWork

I use nlog dll(version 3.1.0.0) to write to database - oracle with entity frameWork in the line :
logger.Log(logLevel, "try");
I get in the logs of nlog the following error:
Error Error when writing to database System.ArgumentException: Value
does not fall within the expected range. at
Oracle.ManagedDataAccess.Client.OracleCommand.set_CommandType(CommandType
value) at
NLog.Targets.DatabaseTarget.WriteEventToDatabase(LogEventInfo
logEvent) at NLog.Targets.DatabaseTarget.Write(LogEventInfo
logEvent)
the code is:
SetPropGDC(LogEntity);
NLog.LogLevel logLevel = SetLogLevel(Level.Debug);
logger.Log(logLevel, "try");
ClearGDC();
private static LogLevel SetLogLevel(Level level)
{
switch (level)
{
case Level.Debug: return LogLevel.Debug;
case Level.Error: return LogLevel.Error;
case Level.Fatal: return LogLevel.Fatal;
case Level.Info: return LogLevel.Info;
default: return LogLevel.Error;
}
}
private void SetPropGDC(LogEntity LogEntity)
{
GlobalDiagnosticsContext.Set("connectionString", _unitOfWork.getConnectionString());
GlobalDiagnosticsContext.Set(processId, LogEntity.PROCESS_ID.ToString());
GlobalDiagnosticsContext.Set("TIME_STAMP", LogEntity.TIME_STAMP.ToString());
GlobalDiagnosticsContext.Set(customerId, LogEntity.CUSTOMER_ID.ToString());
GlobalDiagnosticsContext.Set("REQUEST", LogEntity.REQUEST.ToString());
GlobalDiagnosticsContext.Set("RESPONSE", LogEntity.RESPONSE.ToString());
GlobalDiagnosticsContext.Set("EXCEPTION", LogEntity.EXCEPTION.ToString());
GlobalDiagnosticsContext.Set("STACK_TRACE", LogEntity.STACK_TRACE.ToString());
GlobalDiagnosticsContext.Set("DETAILS", LogEntity.DETAILS.ToString());
}
<targets>
<target name="TRACEDatabase" type="DataBase" keepConnection="false"
dbProvider="Oracle.ManagedDataAccess.Client" connectionString="${gdc:connectionString}"
commandText="insert into TLOG_SITE_GENERAL_TRACE( PROCESS_ID,TIME_STAMP,CUSTOMER_ID,LOG_LEVEL,REQUEST,RESPONSE,EXCEPTION,STACK_TRACE,MESSAGE)
values(:PROCESS_ID,:TIME_STAMP,:CUSTOMER_ID,:LOG_LEVEL,:REQUEST,:RESPONSE,:EXCEPTION,:STACK_TRACE,:MESSAGE)">
<parameter name="PROCESS_ID" layout="${gdc:PROCESS_ID}" />
<parameter name="TIME_STAMP" layout="${gdc:TIME_STAMP}" />
<parameter name="CUSTOMER_ID" layout="${gdc:CUSTOMER_ID}" />
<parameter name="LOG_LEVEL" layout="${level:uppercase=true}" />
<parameter name="REQUEST" layout="${gdc:REQUEST}" />
<parameter name="RESPONSE" layout="${gdc:RESPONSE}" />
<parameter name="EXCEPTION" layout="${gdc:EXCEPTION}" />
<parameter name="STACK_TRACE" layout="${gdc:STACK_TRACE}" />
<parameter name="MESSAGE" layout="${message}" />
</target>
Can anyone help?
I think you're looking for type support for database parameters, that's introduced in NLog 4.6.
PS: Please note that event properties (on LogEventInfo) are a more robust than the GlobalDiagnosticsContext in your example

Cannot get NLog custom target to work

I am really struggling to get a basic custom target working with NLog.
Program.cs
using NLog;
using NLog.Targets;
namespace NLogTestConsole
{
class Program
{
private static Logger logger = null;
static Program()
{
logger = LogManager.GetCurrentClassLogger();
Target.Register<NLogTestConsole.Gerald>("Gerald");
}
static void Main(string[] args)
{
logger.Trace("Sample trace message");
logger.Debug("Sample debug message");
logger.Info("Sample informational message");
logger.Warn("Sample warning message");
logger.Error("Sample error message");
logger.Fatal("Sample fatal error message");
}
}
}
NLogCustomTarget.cs
using NLog;
using NLog.Targets;
namespace NLogTestConsole
{
[Target("Gerald")]
public sealed class Gerald : TargetWithLayout
{
public Gerald()
{
// this.Host = "localhost";
}
//[RequiredParameter]
//public string Host { get; set; }
protected override void Write(LogEventInfo logEvent)
{
// Breakpoint here never gets hit
string logMessage = this.Layout.Render(logEvent);
System.Console.WriteLine("MYCUSTOMMSG: " + logMessage);
}
private void SendTheMessageToRemoteHost(string host, string message)
{
// TODO - write me
}
}
}
NLog.config
<?xml version="1.0" encoding="utf-8" ?>
<nlog autoReload="true"
throwExceptions="false"
internalLogLevel="Off"
internalLogFile="c:\temp\nlog-internal.log">
<!-- optional, add some variables
https://github.com/nlog/NLog/wiki/Configuration-file#variables -->
<variable name="myvar" value="myvalue"/>
<!-- See https://github.com/nlog/nlog/wiki/Configuration-file
for information on customizing logging rules and outputs. -->
<targets>
<!-- add your targets here
See https://github.com/nlog/NLog/wiki/Targets for possible targets.
See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers. -->
<!-- Write events to a file with the date in the filename.
<target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} ${uppercase:${level}} ${message}" /> -->
<target name="logfile" type="File" fileName="file.txt" />
<target name="console" type="Console" />
<target name="debugger" type="Debugger"/>
<target name="Gerald" type="Gerald"/>
</targets>
<rules>
<!-- Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace) to "f"
<logger name="*" minlevel="Debug" writeTo="f" /> -->
<logger name="*" minlevel="Trace" writeTo="logfile" />
<logger name="*" minlevel="Info" writeTo="console" />
<logger name="*" minlevel="Trace" writeTo="debugger" />
<logger name="*" minlevel="Trace" writeTo="Gerald" />
</rules>
</nlog>
If I comment out the "Gerald" target I get logging to file, console, and debug output. With that line there, nothing works
What have I got wrong?
Thanks,
Adam.
Enable the internal log and see the error in c:\temp\nlog-internal.log:
internalLogLevel="Warn"
You need to fix your code so that the logger is created after you register your target with NLog. Please see highlighted and annotated lines of code below:
using NLog;
using NLog.Targets;
namespace NLogTestConsole
{
class Program
{
private static Logger logger = null;
static Program()
{
// **********************************************************
// the next two code lines are swapped around,
// now the custom logger will work...
// **********************************************************
Target.Register<NLogTestConsole.Gerald>("Gerald");
logger = LogManager.GetCurrentClassLogger();
}
static void Main(string[] args)
{
logger.Trace("Sample trace message");
logger.Debug("Sample debug message");
logger.Info("Sample informational message");
logger.Warn("Sample warning message");
logger.Error("Sample error message");
logger.Fatal("Sample fatal error message");
}
}
}

Categories