I'm writing an ASP .NET MVC3 application and as application is expected to be secure, I need a good enterprise application logging architecture.
So I was seeking over existing loggin frameworks and picked the NLog. So at them moment I'm stuck with making DB schema for logs.
Does anyone have good experience in this area? It's anticipate to log a set of actions such as user interactions with system objects, background works, user membership actions, payment transactions and so on.
I've been using NLog for a while now and i'm very happy with it. What I most like about NLog is that you can configure different loglevels to be written to different files and/or databases. It's a very powerfull logging library.
For logging to the database you can use something like below in your config. This is similar to what I use at the company i'm working.
<target xsi:type="Database"
name="TestDatabaseLogging"
connectionString="Data Source=127.0.0.1;Initial Catalog=NLog_Test;User ID=MyLogin;Password=MyPassword"
dbDatabase="NLog_Test">
<commandText>
insert into INNO_LOG ([createDate], [Origin], [LogLevel], [Message], [Exception], [StackTrace]) values (#createDate, #origin, #logLevel, #message, #exception, #stackTrace)
</commandText>
<parameter name="#createDate" layout="${date}"/>
<parameter name="#origin" layout="${callsite}"/>
<parameter name="#logLevel" layout="${level}"/>
<parameter name="#message" layout="${message}"/>
<parameter name="#exception" layout="${exception:format=Message,StackTrace}"/>
<parameter name="#stackTrace" layout="${stacktrace}"/>
</target>
You can use the rules section to log different levels to different files, see example below;
<rules>
<logger name="*" minlevel="Fatal" writeTo="mail" />
<logger name="*" minlevel="Error" writeTo="TestDatabaseLogging" />
<logger name="*" minlevel="Debug" writeTo="file-debug" />
<logger name="*" minlevel="Info" writeTo="file" />
<!--Log to specific files for specific classes.-->
<logger name="_Default" minlevel="Trace" writeTo="file-default" />
<logger name="TestClass" minlevel="Trace" writeTo="file-testclass" />
</rules>
EDIT: Added possible table layout for logging information.
Id | int
CreateDate | datetime
LogLevel | nvarchar
Message | nvarchar(max)
Exception | nvarchar(max)
StackTrace | nvarchar(max)
SourceUrl | nvarchar(255)
UserId | uniqueidentifier
OrderId | int
The layout above is just an rough idea. It totally depends on what you want to log in this table. Though you have to try if it's possible to add additional paramaters other than the ones used by NLog by default.
Like Rob has answered .. stick with NLog :)
If you need a good viewer, don't forget to use Sentinal as a free nLog viewer :)
Don't forget, you can programatically turn on logging sections, while in production .. to view specific areas of the website. And then see them in real-time using Sentinal.
another option is to take advanctage of something like LoggR.net. This streams data (ie. errors or logging information in this case) in real time. I think it's using the awesome SignalR framework to use WebSockets or Long Polling (2 real-time technologies) :)
Related
I'm trying to setup a CorrelationID across threads to establish a link between calls to my server, and the corresponding calls that I make to external web services. The correlation ID is a GUID which I'm saving in Logical Context structures of NLog (logical contexts work fine across threads, supposedly).
The idea is to have a GUID that is shared between any request to my server, and the corresponding requests that I issue to various web services due to this request. I tried using both MDLC and NDLC.
The problem is that the value is getting stored correctly only for the first request, and it's saving blank values for all subsequent ones, even though a GUID is correctly generated for each new request to my server.
I tried logging either to a database or in a file. The problem seems to solve itself if I add a breakpoint within the code, or if I add a System.Threading.Sleep anywhere around the logging method. What's also weird is that I can add the Sleep either before or after the method that sets the value in the logical context, and it still works either way. Removing the Sleep/breakpoint would cause it to break again.
I am using NLog v4.5.2.
Logging module:
using System;
using System.Web;
using NLog;
namespace Shift.Stardust.Engine.Modules
{
public class LoggingHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += HandleBeginRequest;
}
public void Dispose()
{
}
private void HandleBeginRequest(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(500);
var guid = Guid.NewGuid().ToString();
NestedDiagnosticsLogicalContext.Push(guid);
}
}
}
Placing a breakpoint anywhere in HandleBeginRequest produces correct output. Similarly for adding System.Threading.Thread.Sleep(500). Naturally, I wouldn't want to add such a line in my code just to resolve this issue.
NLog config:
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" internalLogFile="c:\temp\nlog-internal.txt" internalLogLevel="Trace">
<variable name="logDirectory" value="${basedir}/logs"/>
<targets>
<target name="asyncdatabase"
xsi:type="AsyncWrapper"
queueLimit="5000"
overflowAction="Block">
<target xsi:type="Database"
connectionStringName="ConnectionStringHere"
keepConnection="true">
<commandText>[db].[P_Log_Insert] #CreateDate, #ApplicationName, #MachineName, #LoggerName, #LogLevel, #Message, #Exception, NULL, #EngineSessionId, #CorrelationId</commandText>
<parameter name="#CreateDate" layout="${date}"/>
<parameter name="#ApplicationName" layout="${appsetting:name=Shift.Stardust.ApplicationName}"/>
<parameter name="#MachineName" layout="${machinename}"/>
<parameter name="#LoggerName" layout="${logger}"/>
<parameter name="#LogLevel" layout="${level}"/>
<parameter name="#Message" layout="${message}"/>
<parameter name="#Exception" layout="${exception:format=tostring}"/>
<parameter name="#EngineSessionId" layout="${aspnet-sessionid}"/>
<parameter name="#CorrelationId" layout="${ndlc}"/>
</target>
</target>
</targets>
<rules>
<logger name="Http.*" minlevel="Info" writeTo="asyncdatabase" final="true" />
</rules>
</nlog>
I expect to have a different CorrelationID for each incoming request, but this is only true for the first one. All of the subsequent ones have an empty string as value.
I think it's better to write to the HTTP context for this case.
e.g.
HttpContext.Current.Items["myvariable"] = 123;
and usage:
${aspnet-item:variable=myvariable} - produces "123"
See docs
You need the package NLog.Web (ASP.NET non-core) for that.
Note: ASP.NET Core uses should use NLog.Web.AspNetCore instead of NLog.Web
I am using NLog for DB logging. If the DB is down then I only need it to log to a file. Is there a way to do this?
I don't want to record log entries log in both a file and the DB. If the DB target fails to log (if DB is down) only then do I have to log to the file. Is there any fail over or fallback feature with Nlog?
Yes, there is a way. It's called a FallbackGroup.
Documentation can be found here: https://github.com/nlog/NLog/wiki/FallbackGroup-target
Here is an example taken blatantly and ashamedly from the documentation:
<target xsi:type="FallbackGroup"
name="mail"
returnToFirstOnSuccess="true">
<target xsi:type="Mail"
name="mailserver1"
subject="Layout"
to="Layout"
from="Layout"
smtpServer="mx1.example.com"
smtpPort="Integer"
layout="Layout" />
<target xsi:type="Mail"
name="mailserver2"
subject="Layout"
to="Layout"
from="Layout"
smtpServer="mx2.example.com"
smtpPort="Integer"
layout="Layout" />
</target>
<rules>
<logger name="*" minlevel="Trace" writeTo="mail" />
</rules>
It'll try another mail server if the first fails. It'll also return to the first target if it's successful.
I never used it in production, maybe the other options in the documentation can help you fine tune it to your problem.
I have a service running hourly that uses NLog to send mail when exception occurs. So how do I configure it to not log at Saturday and Sunday? Can I even do that?
Here's my target:
<target name="m"
xsi:type="Mail"
html="true"
smtpServer="*******"
smtpPort="*******"
smtpAuthentication="*******"
smtpUserName="*******"
smtpPassword="*******"
enableSsl="true"
subject="Exception"
from="*******"
to="*******"
layout ="${longdate} ${uppercase:${level}}${newline} ${callsite:className=true:includeSourcePath=true:methodName=true}${newline} ${message}${newline}"
useSystemNetMailSettings="false" />
And rule:
<logger name="*" minlevel="Fatal" writeTo="m" />
Maybe use the PostFilteringWrapper, and use a filter condition like:
not equals('${date:format=ddd}', 'Sat')
not equals('${date:format=ddd}', 'Sun')
https://github.com/NLog/NLog/wiki/PostFilteringWrapper-target
https://github.com/NLog/NLog/wiki/When-filter
https://learn.microsoft.com/en-us/dotnet/standard/base-types/how-to-extract-the-day-of-the-week-from-a-specific-date
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#))
I am having a bit of a problem using a stored procedure instead of a SQL INSERT statement when using NLog in a C# web application. The connection string "Logger" is correctly configured in Web.config and works properly when replacing the commandText with a SQL statement. I would appreciate a hint in the right direction. In this example the stored procedure is under the "Logs" schema and it is called "LogError".
<targets>
<target xsi:type="Database"
name="dberrorlog"
connectionStringName="Logger"
keepConnection="true"
commandText="[Logs].[LogError]" >
<parameter name="#ProgName" layout="MyAppName"/>
<parameter name="#CompName" layout="${machinename}"/>
<parameter name="#LogLevel" layout="${level}"/>
<parameter name="#UserName" layout="${identity}"/>
<parameter name="#Error" layout="${exception:format=Message}"/>
<parameter name="#SourceObj" layout="${exception:format=Method}"/>
<parameter name="#StackTrace" layout="${exception:format=StackTrace}"/>
</target>
</targets>
<rules>
<logger name="*" minlevel="Error" writeTo="dberrorlog" />
</rules>
From this NLog forum post, try using the text to execute the stored procedure:
commandtext="exec AddActivityLog
#ApplicationName,
#ApplicationTime,
#Severity,
#Logger,
#SaxoID,
#EventID,
#Message,
#URL,
#URLReferrer,
#RemoteAddress,
#Callsite,
#CurrentUICulture,
#ThreadIdentity,
#WindowsIdentity,
#MachineName,
#ProcessID,
#ThreadID,
#ThreadName,
#Stacktrace,
#Exception,
#Cookie,
#FormVariables,
#QueryString,
#HTTPUserAgent"
Side note: Claus Rathje's answer wouldn't render in my browser, so I had to look a the page source to see the configuration he posted.
Note that while #JeffOgata's solution works, it's probably not the way you want to go about solving the problem.
You can do something like this instead:
commandText="AddActivityLog"
commandType="StoredProcedure"
That way you don't have to worry about properly formatting an EXEC query.