How log an unhandled exception with NLog in ASP.NET MVC - c#

In my ASP.NET MVC project I have an action with [LogErrors] attribute as below:
[LogErrors]
public ActionResult Index()
{
var i = 0;
var c = 10 / i;
return View();
}
I made an aunhandled exception without trycatch(devide 10 by 0) in this action and I must log this exception error text and else log in which action this exception happened in a text file with NLog. I made the [LogErrors] as below:
public class LogErrorsAttribute : FilterAttribute, IExceptionFilter
{
void IExceptionFilter.OnException(ExceptionContext filterContext)
{
if (filterContext != null && filterContext.Exception != null)
{
string controller = filterContext.RouteData.Values["controller"].ToString();
string action = filterContext.RouteData.Values["action"].ToString();
string loggerName = string.Format("{0}Controller.{1}", controller, action);
NLog.LogManager.GetLogger(loggerName).Error(string.Empty, filterContext.Exception);
}
}
}
and my NLog.config is as below:
<?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" >
<targets>
<target name="file" xsi:type="File" fileName="C:\Logs\${shortdate}.log"
layout="
--------------------- ${level} (${longdate}) ----------------------${newline}
IP: ${aspnet-request-ip}${newline}
Call Site: ${callsite}${newline}
${level} message: ${message}${newline}
Id: ${activityid}${newline}
aspnet-sessionid: ${aspnet-sessionid}${newline}
aspnet-request-method: ${aspnet-request-method}${newline}
aspnet-request-host: ${aspnet-request-host}${newline}
aspnet-request-form: ${aspnet-request-form}${newline}
aspnet-request-cookie: ${aspnet-request-cookie}${newline}
aspnet-request: ${aspnet-request:serverVariable=HTTP_URL}${aspnet-request:queryString}${newline}
aspnet-mvc-controller: ${aspnet-mvc-controller}${newline}
aspnet-mvc-action: ${aspnet-mvc-action}${newline}
aspnet-appbasepath: ${aspnet-appbasepath}${newline}
" encoding="UTF8"/>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="file" />
</rules>
</nlog>
How fix my configs to log this exception error text and else log that in which action this exception happened in a text file? Any help will be appriciated!

You need to send a message to .Error and send the Exception as first parameter.
Otherwise it roughly translated to string.Format("", filterContext.Exception).
So something like this:
NLog.LogManager.GetLogger(loggerName)
.Error(filterContext.Exception, "Unhandled exception in controller");
If that isn't working, then please check the NLog troubleshooting guide

Related

Don't log payload if error occurred in particular endpoint

I am using NLog to log errors into a file when they occur and I also log the payload in the body request. But I don't want to log the payload if, for example, an error occurred while authenticating, because I have a username and password there.
What are your solutions for this issue?
I use a generic class to handle my HttpClient requests.
private static async Task<ApiMethodResult<string>> SendGenericRequestAsync(this HttpClient client,
HttpMethod method,
string requestString, object payload = null)
{
HttpRequestMessage requestMessage = new HttpRequestMessage
{
RequestUri = new Uri(ConnectionUrl.ExternalUrl + requestString),
Method = method
};
if (payload != null && method != HttpMethod.Get)
{
HttpContent requestContent = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8,
"application/json");
requestMessage.Content = requestContent;
}
ApiMethodResult<string> result = new ApiMethodResult<string>();
HttpResponseMessage responseMessage;
try
{
responseMessage = await client.SendAsync(requestMessage);
}
catch (Exception)
{
string errorMessage = $"Cannot connect to external data API. Requested url: {requestString}";
result.SetErrorMessage(errorMessage);
StaticLogger.LogError(errorMessage);
return result;
}
string httpContent = await responseMessage.Content.ReadAsStringAsync();
result.ApiData = httpContent;
return result;
}
This is my 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" internalLogLevel="Trace"
internalLogFile="C:\Users\albug\source\repos\orgill-vendor-portal-v2\InnerLog.txt">
<extensions>
<add assembly="NLog.Web.AspNetCore" />
</extensions>
<targets>
<target name="default" xsi:type="File"
fileName="LogFolderPath"
layout="Logger: ${logger}${newline}
Date: ${shortdate}${newline}
Time: ${time}${newline}
LogType: ${level}${newline}
URL: ${aspnet-request-url:IncludeQueryString=true}${newline}
Payload: ${aspnet-request-posted-body}${newline}
Controller: ${aspnet-mvc-controller}${newline}
Endpoint: ${aspnet-mvc-action}${newline}
Message: ${message}${newline}"/>
</targets>
<rules>
<logger name="*" minlevel="Warn" writeTo="default" />
</rules>
</nlog>
Ok, so after a random search I actually found out that you can use a conditional operator-ish in the NLog layout.
What I did is this:
Payload: ${when:when='${aspnet-mvc-controller}'=='Account':inner=restricted:else=${aspnet-request-posted-body}}${newline}
So basically if the controller inside which the log action was triggered is "Account", it will put "restricted", else it will put the actual request body.
Here is the actual documentation.
${when:when=[condition to be verified]:inner=[what to log if true]:else=[what to print if not]}

NLog include EventId in log file

The ILogger in .NETCore 2.2 has a EventId parameter.
public static void LogError(this ILogger logger, EventId eventId, string message, params object[] args);
How can I get NLog to output this to the log file?
My 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">
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>
<targets>
<target xsi:type="File" name="f" fileName="${aspnet-appbasepath}/Logs/${shortdate}.log"
layout=" ${event-properties:item=EventId.Id} ${eventId} ${eventId.Id} | ${longdate} ${uppercase:${level}} ${callsite} ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="f" />
</rules>
</nlog>
The issue: the EventId never makes it to the log file
| | 2019-06-21 13:13:44.8984 INFO RMD_LPCB_Web_Portal.Program.Main init main
| | 2019-06-21 13:13:48.5197 WARN Microsoft.AspNetCore.HttpsPolicy.Internal.HttpsLoggingExtensions.FailedToDeterminePort Failed to determine the https port for redirect.
| | 2019-06-21 13:13:54.3064 INFO RMD_LPCB_Web_Portal.Pages.Account.LoginModel+<AuthenticateEmail>d__20.MoveNext LOGIN : Jesse.lay#state.nm.us IP: ::1 SESSION : f52d9758-bc32-7131-d7d4-7ec5224853f5
| | 2019-06-21 13:13:54.4070 INFO RMD_LPCB_Web_Portal.Pages.Account.LoginModel.AuthenticateActiveDirectory Jesse.lay AD
| | 2019-06-21 13:13:54.4432 INFO RMD_LPCB_Web_Portal.Pages.Account.LoginModel.AuthenticateActiveDirectory Jesse.lay AD logged in
| | 2019-06-21 13:13:54.4432 INFO RMD_LPCB_Web_Portal.Pages.Account.LoginModel+<AuthenticateEmail>d__20.MoveNext LOGIN : Jesse.lay#state.nm.us IP: ::1 SESSION : f52d9758-bc32-7131-d7d4-7ec5224853f5
| | 2019-06-21 13:13:54.4603 INFO RMD_LPCB_Web_Portal.Pages.Account.LoginModel+<OnPostAsync>d__17.MoveNext User Jesse.Lay#state.nm.us logged in at 6/21/2019 7:13:54 PM.
| | 2019-06-21 13:13:57.2319 ERROR RMD_LPCB_Web_Portal.Pages.Eoc.IndexModel.OnGet at RMD_LPCB_Web_Portal.Pages.Eoc.IndexModel.OnGet() in C:\usr\tfs_Workspace\TSSB\RMD LPCB\Apps\RMD LPCB Web Portal\Source\RMD_LPCB_Web_Portal\Pages\Eoc\Index.cshtml.cs:line 44
| | 2019-06-21 13:13:58.6918 ERROR Microsoft.AspNetCore.Diagnostics.Internal.DiagnosticsLoggerExtensions.UnhandledException An unhandled exception has occurred while executing the request.
You could use
${event-properties:EventId} - will print the name of EventId or if empty, then the id of EventId
${event-properties:EventId_Id} - print the id
${event-properties:EventId_Name} - print the name
Please note, empty Event ids aren't logged by default - that is, id = 0 or name if empty string or null.
If you need the empty Event id, then set the option IgnoreEmptyEventId to false in the setup:
loggerFactory.AddNLog(new NLogProviderOptions { IgnoreEmptyEventId = false });
To be complete, some possible logger calls:
logger.LogError(new EventId(2, "eventId2"), "Message with event id number and event id name");
logger.LogError(new EventId(2), "Message with only event id number");
logger.LogError(2, "Message with only event id number");

How to change the NLog layout at run time?

I created the 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"
throwExceptions="false"
throwConfigExceptions="true"> <!--make NLog complain, when something is wrong with the configuration-->
<targets>
<target name="logfile" xsi:type="File"
fileName="${basedir}/Logs/Log_${shortdate}.txt"
layout="[${longdate}] [${uppercase:${level}}] ${message} ${exception:format=tostring}"
/>
<target name="logconsole" xsi:type="ColoredConsole"
layout="[${longdate}] ${uppercase:${level}}: ${message} ${exception:format=tostring}"
/>
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logconsole" />
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>
When I call logger.Info("Hello World"); I get an output both in the console and the file:
[2018-03-17 15:34:24.2843] INFO: Hello World
Which is fine.
Now in some cases I do not want to display the date-time part, and I want to display it only in the console.
How would I do that with NLog?
A pseudo solution I would want is:
logger.Info("Hello World", writeTo: "logconsole", layout: "[${uppercase:${level}}] ${message}");
I tried adding a new taget
<target name="logconsole_simple" xsi:type="ColoredConsole"
layout="${uppercase:${level}}: ${message} ${exception:format=tostring}"/>
and new rule:
<logger name="logconsole_simple" level="Info" writeTo="logconsole_simple" />
And call it like:
logger.Log(LogEventInfo.Create(LogLevel.Info, "logconsole_simple", "simple message!"));
But it still displays the date-time part and logs it to the file also (which I don't want).
How to fix this?
Add a target and rule for your special case and reference your loggers by name.
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"
throwExceptions="false"
throwConfigExceptions="true"><!--make NLog complain, when something is wrong with the configuration-->
<targets>
<target name="logfile" xsi:type="File"
fileName="${basedir}/Logs/Log_${shortdate}.txt"
layout="[${longdate}] [${uppercase:${level}}] ${message} ${exception:format=tostring}"
/>
<target name="logconsole_default" xsi:type="Console"
layout="[${longdate}] ${uppercase:${level}}: ${message} ${exception:format=tostring}"
/>
<target name="logconsole_other" xsi:type="Console"
layout="${uppercase:${level}}: ${message} ${exception:format=tostring}"
/>
</targets>
<rules>
<logger name="default" minlevel="Debug" writeTo="logfile" />
<logger name="default" minlevel="Info" writeTo="logconsole_default" />
<logger name="other" minlevel="Info" writeTo="logconsole_other" />
</rules>
</nlog>
Usage:
internal class Program
{
private static readonly Logger DefaultLogger = LogManager.GetLogger("default");
private static readonly Logger OtherLogger = LogManager.GetLogger("other");
private static void Main(string[] args)
{
for (var i = 0; i < 2; i++)
{
if (i % 2 == 0)
{
DefaultLogger.Info("normal log message");
}
else
{
OtherLogger.Info("special case");
}
}
}
}
EDIT: If you really want to do it in code by modifying your existing target and rule, you can (this will work with the config from your original post):
using System.Linq;
using NLog;
using NLog.Layouts;
using NLog.Targets;
internal class Program
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static void Main(string[] args)
{
for (var i = 0; i < 2; i++)
{
if (i % 2 == 0)
{
Logger.Info("normal log message");
}
else
{
// reconfigure
var consoleTarget = LogManager.Configuration.FindTargetByName<ColoredConsoleTarget>("logconsole");
var oldLayout = consoleTarget.Layout;
consoleTarget.Layout = new SimpleLayout { Text = "${uppercase:${level}}: ${message} ${exception:format=tostring}" };
var fileRule = LogManager.Configuration.LoggingRules.Single(r => r.Targets.Any(t => t.Name == "logfile"));
LogManager.Configuration.LoggingRules.Remove(fileRule);
LogManager.Configuration.Reload();
Logger.Info("special case");
// replace originals
consoleTarget.Layout = oldLayout;
LogManager.Configuration.LoggingRules.Add(fileRule);
LogManager.Configuration.Reload();
}
}
}
}

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");
}
}
}

Logging with NLog and runtime parameters to Database

I am trying to log some info to database with NLog, but I can retrieve some DB parameters only at runtime. Tried few solutions, but unsuccessfully.
There is my 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">
<variable name="sqlDate" value="[${date:format=yyyy-MM-dd HH\:mm\:ss\.fff}]"/>
<targets>
<target name="dataBase"
xsi:type="Database"
dbProvider="System.Data.SqlClient"
connectionString="${gdc:item=myConnectionstring}"
dbDatabase="${gdc:item=myDataBase}">
<commandText>
INSERT INTO [TABLE_NAME]([COL1], [COL2] )
VALUES(#val1, #val2)
</commandText>
<parameter name="#val1" layout="${event-context:item=val1}"/>
<parameter name="#val2" layout="${event-context:item=val2}"/>
</target>
</targets>
<rules>
<logger name="_dataBase" levels="Info" writeTo="dataBase" final="true" />
</rules>
</nlog>
And there is my .NET C# code:
public static class Log
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
private static Config _config = LoadConfig();
public static void Info(string val1, string val2)
{
DatabaseTarget databaseTarget = (NLog.Targets.DatabaseTarget)NLog.LogManager.Configuration.FindTargetByName("dataBase");
databaseTarget.ConnectionString = _config.ConnectString;
databaseTarget.DBDatabase = _config.DBName;
NLog.LogManager.ReconfigExistingLoggers();
LogEventInfo info = new LogEventInfo(LogLevel.Info, "_dataBase", "Test_message");
info.Properties["val1"] = val1;
info.Properties["val2"] = val2;
_logger.Log(info);
}
}
And it just do nothing: not crashing, not writing any data to DB. Here is my internal NLog log: http://pastebin.com/0tLi9zJ1
How can I fix this?
P.S. Sorry for my last try to ask this question, it looks like I forgot to put to it some necessary information.
So, the working code is:
<?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">
<variable name="sqlDate" value="${date:format=yyyy-MM-dd HH\:mm\:ss\.fff}"/>
<targets>
<target name="dataBase"
xsi:type="Database"
dbProvider="System.Data.SqlClient"
connectionString="${gdc:item=myConnectionString}"
dbDatabase="${gdc:item=myDataBase}">
<commandText>
INSERT INTO [TABLE_NAME]([COL1], [COL2] )
VALUES(#val1, #val2)
</commandText>
<parameter name="#val1" layout="${event-context:item=val1}"/>
<parameter name="#val2" layout="${event-context:item=val2}"/>
</target>
</targets>
<rules>
<logger name="_dataBase" levels="Info" writeTo="dataBase" final="true" />
</rules>
</nlog>
public static class Log
{
private static Config _config = LoadConfig();
public static void Info(string val1, string val2)
{
NLog.Logger logger = NLog.LogManager.GetLogger("_dataBase");
GlobalDiagnosticsContext.Set("myConnectionString", _config.ConnectString);
GlobalDiagnosticsContext.Set("myDataBase", _config.DBName);
LogEventInfo info = new LogEventInfo(LogLevel.Info, "_dataBase", "Test_message");
info.Properties["val1"] = val1;
info.Properties["val2"] = val2;
logger.Log(info);
}
}

Categories