NLog Concurrent Requests into different files - c#

As the title suggests, I am trying to log separate requests to my .NET Web API controllers to separate files.
With the introduction of {var}, here is what I am trying inside my c# code.
private static Logger logger = LogManager.GetLogger("test");
[HttpPost, Route("xyz")]
public IHttpActionResult Post(Obj value)
{
var guid = Guid.NewGuid();
LogManager.Configuration.Variables["test"] = guid.ToString();
logger.Info(value);
//do my execution here.
}
My Nlog.Config looks like this:
<variable name="test" value=""/>
<targets async="true">
<target xsi:type="File"
name="concurrents"
fileName="${basedir}/nlogs/${logger}/${var:test}.log"
layout="${uppercase:${level}} | ${callsite} | ${date:format=HH\:mm\:ss.fff} | "${message}""
createDirs="true" />
</target>
</targets>
<rules>
<logger name="test" minlevel="Info" writeTo="concurrents"></logger>
</rules>
As you all might already know,
When concurrent requests come in, I am trying to log each and every requests by creating a creating a new Guid and assinging it to the {var} and creating the file in the name of the var.
The logs somehow gets mixed up and one half of the request is in one file and the other half in another.
To be quite honest, I think I am missing some fundamental thing that I should know about but not sure where.
It would be great if someone could point to the right direction and help me understand how can I go about logging current request in different files.
Cheers

Variables are at global level and that won't work with a multi-threaded environment (http request)
A good solution is to set the guid on HttpContext level:
HttpContext.Current.Items["myvariable"] = guid.ToString();
and use
${aspnet-item:variable=myvariable}
You need NLog.Web.AspNetCore package for ASP.NET Core - for ASP.NET non-core you need NLog.Web

Related

How to include logging scope into the log file using NLog

I'm using NLog.Extensions.Logging.
When registering a logger factory using the method AddNLog(), it is possible to enable logging scope using NLogProviderOptions.IncludeScopes.
But how to make NLog write logging scope to a file?
I haven't found anything similar in the list of available layouts
An example:
Log like this:
// logger is here of type Microsoft.Extensions.Logging.ILogger
using (logger.BeginScope(new[] { new KeyValuePair<string, object>("userid", request.UserId) }))
{
logger.LogDebug("My log message");
}
Render like this: ${mdlc:userid}.
For example in the file target:
<target name="file" xsi:type="File"
layout="${longdate} ${logger} ${message}${exception:format=ToString}, user: ${mdlc:userid}"
fileName="${basedir}/${shortdate}.log" />
Note: NLogProviderOptions.IncludeScopes is enabled by default.
NLog directly
The syntax is a bit clumsy, but that is because Microsoft's abstraction is a bit limited. See also this issue: .NET - Logging structured data without it appearing in the text message
If you refer NLog directly, you could also do:
using (NLog.MappedDiagnosticsLogicalContext.SetScoped("userid", request.UserId))
{
// logger here of type NLog.Logger
logger.Info("My log message");
}
Also this is rendered with ${mdlc:userid}
More examples and different scopes for NLog explained here
Docs
PS: I have updated available layouts, so you could find it easier :)
New (in V5) is to use ${scopenested}.
See https://github.com/NLog/NLog/wiki/ScopeNested-Layout-Renderer.

How do you pass a directory into NLog.config?

There is a global variable that the user can set to define where NLog will log files. However, I don't know how to pass that variable to NLog.config.
I'd like to be able to use it like ${basedir}, but instead have it be ${userdir}. I think this is doable without having to pass the variable using event-properties every time I log, but I don't know how. I'd like to define it once when I write
static private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
Does anyone know if this is doable?
There are several ways to pass (context) information to NLog. I think it this case the "GDC" (GlobalDiagnosticsContext) is the best way:
Set in your code:
GlobalDiagnosticsContext.Set("userdir", myDirectory);
Usage in nlog.config:
<target name="file" xsi:type="File"
fileName="${gdc:item=userdir}/${shortdate}.log" ... />
See GDC docs

ASP.NET Core Logging with Func() argument

I am using ASP.NET core with NLog, using it as a replacement for the original ASP.NET Core logger with the NLog.Web.AspNetCore nugget package.
NLog contains a useful Func() delegate signature that allows to performs arguments evaluation only if the corresponding logging level is enabled:
static readonly Logger log = LogManager.GetCurrentClassLogger();
log.Trace(() => request.JsonSerializer.Serialize(body));
I am using ASP.NET with NLog, but it sounds like this feature is not available:
private ILogger<MyController> log;
log.Trace(() => request.JsonSerializer.Serialize(body));
Before undertaking to write myself a method, I would like to know if I missed something, I have not find anything about such logging methods with a delegate argument using ASP.NET Core with NLog.
There is no such thing in the Microsoft.Extensions.Logging abstractions, and the way it is built, it isn’t exactly easy to do such a thing. While you can easily add extension methods to it, and actually all log calls are extension methods, the base Log method is what determines whether or not to log somethings since it is the only thing that actually has access to the configured log level.
That being said, the logging abstractions to use something that may make it possible to do something similar to this. For that, consider the signature of the ILogger.Log method:
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
As you can see, there isn’t actually a string being passed to it, but just a state and a formatter. In the default extension methods, the state is a FormattedLogValues object and the formatter is just a method that calls ToString() on the state, i.e. the FormattedLogValues object.
The FormattedLogValues is what actually builds the formatted string, and that’s also where the structured logging is happening. So it is actually a bad idea to serialize some object in your log message; you can just pass that directly to the logger.
But what you could do here is provide your own overloads to Log that take a function instead which is then wrapped into some state object that executes the function when ToString() is being called.
There is not much change in Nlog Implementation for Asp.net core 2.0.
Setup 1: you need to install Nuget package Click here
Setup 2: you need to create Nlog config file with below configuration.
<nlog>
<!-- the targets to write to -->
<targets>
<!-- write logs to file -->
<target filename="${basedir}/logs/${shortdate}.log" layout="
-----------Time Stamp: ${longdate}----------
Log Level: ${level}${newline}
Logger Name : ${logger}${newline}
Log Message : ${message}${newline}
Exception Message: ${event-context:item=ErrorMessage}${newline}
Browser Detail: ${event-context:item=BrowserDetail}${newline}
Session Id: ${event-context:item=SessionId}" name="file" xsi:type="File">
<target br="" connectionstring="${gdc:item=defaultConnection}" dbprovider="Oracle.ManagedDataAccess.Client.OracleConnection,
Oracle.ManagedDataAccess, Version=2.0.12.0, Culture=neutral, PublicKeyToken=89b483f429c47342" keepconnection="false" name="database" xsi:type="Database">
commandText="INSERT INTO TableName (LOG_LEVEL,LOGGER_NAME,SESSION_ID,BROWSER_DETAIL) values(:LOGLEVEL,:LOGGERNAME,:SESSIONID,:BROWSERDETAIL)">
<parameter layout="${level:uppercase=true}" name="LOGLEVEL">
<parameter layout="${logger}" name="LOGGERNAME">
<parameter layout="${event-context:item=SessionId}" name="SESSIONID">
<parameter layout="${event-context:item=BrowserDetail}" name="BROWSERDETAIL">
</parameter></parameter></parameter></parameter></target>
</target></targets>
<rules>
<!--All logs, including from Microsoft-->
<logger minlevel="Error" name="*" writeto="file">
<logger minlevel="Trace" name="*" writeto="database">
<!--Skip non-critical Microsoft logs and so log only own logs-->
<logger final="true" maxlevel="Info" name="Microsoft.*">
<!-- BlackHole -->
</logger></logger></logger></rules>
</nlog>
Setup 3: Need to update Startup file.
NLog.GlobalDiagnosticsContext.Set("defaultConnection", Connection string); NLog.LogManager.LoadConfiguration(env.ContentRootPath + "\\NLog.config");
Setup 4: We have created custom Nlog manager.
public static class NLogManager {
public static ILogger _logger = NLog.LogManager.GetCurrentClassLogger();
public static void InfoLog(NLogData nLogData) {
LogEventInfo theEvent = new LogEventInfo(LogLevel.Info, NLogManager._logger.Name, nLogData.Message);
SetLogEventInfo(theEvent, nLogData);
_logger.Log(theEvent);
}
public static void DebugLog(NLogData nLogData) {
LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, NLogManager._logger.Name, nLogData.Message);
SetLogEventInfo(theEvent, nLogData);
_logger.Log(theEvent);
}
public static void ErrorLog(NLogData nLogData) {
LogEventInfo theEvent = new LogEventInfo(LogLevel.Error, NLogManager._logger.Name, nLogData.Message);
SetLogEventInfo(theEvent, nLogData);
_logger.Log(theEvent);
}
}
Custom Event parameter for logging :
private static void SetLogEventInfo(LogEventInfo theEvent, NLogData nLogData) {
theEvent.Properties["SessionId"] = nLogData.SessionId;
theEvent.Properties["BrowserDetail"] = nLogData.BrowserDetail;
}
Model for NLog logging.
public class NLogData {
public string SessionId {
get;
set;
}
public string BrowserDetail {
get;
set;
}
}

NLog: How to determine if a named logger was not found in config

Let's say I have this partial configuration, with NLog:
<rules>
<logger name="ExistsInConfig" writeTo="Console"/>
</rules>
..and then I write this code:
var configuredLogger = LogManager.GetLogger("ExistsInConfig");
configuredLogger.Log(LogLevel.Info, "hello, cruel world!");
var missingLogger = LogManager.GetLogger("NotInConfig");
missingLogger.Log(LogLevel.Info, "goodbye, cruel world!");
In the console output I will see only the first logging statement, because the second named logger was not found in the config file.
How can I programatically detect that the second logger was not found, and therefore will produce no output?
If you have the instance of Logger, you could ask it to it:
bool hasConfigRuleForInfo = missingLogger.IsEnabled(LogLevel.Info)
If not, then you need some tricks, some possibilities:
or create your own LogManager class remember which remembers which loggers are used
or read with reflection the private propertiy LogManager.factory.loggerCache (not supported of course ;))
add a wildcard( *) rule to your config (API or XML) and write to MemoryTarget or a Custom Target. This could effect your performance. PS. with ${logger} you get the logger name. You will also need the final option on other rules.
I think this is the best way:
if (!NLog.LogManager.Configuration.ConfiguredNamedTargets.Any(t => t.Name.Equals("NameToValidate")))
{
//config not found
}
With #Julian 's answer, you could have the Config you are looking for but not the level you are comparing within. You could even have the Config without any level activated in your NLog config.
In those cases you would get an incorrect check response.

Get values from the web.config section in an app.config file?

I'm trying to unit test values that will eventually wind up in a web.config file. In my test project, I created an app.config file with a web.config section to hold the settings. In a normal situation, I would call System.Configuration.ConfigurationSettings.AppSettings, but in this case, that doesn't work. I saw this question, which is very similar, but doesn't address how to get the NameValueCollection out of the config file. Here is an example of the config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<membership defaultProvider="CustomMembershipProvider">
<providers>
<clear/>
<add
name="CustomMembershipProvider"
applicationName="SettlementInfo"
enablePasswordRetrieval="false"
enablePasswordReset="false"
requiresQuestionAndAnswer="true"
writeExceptionsToEventLog="true" />
</providers>
</membership>
</system.web>
</configuration>
Has anyone dealt with this before?
I guess I'm confused here; it looks like you're trying to test that ASP.NET is using your custom membership provider appropriately. Correct?
If so, I'm 99.999% sure that you cannot unit test this using the MS framework; you must integration test it by deploying it to the webserver (or running Cassini in VS) and typing a username/password into your login page.
Now, it's possible I've misunderstood your request. If so, let me know and I'll edit my answer accordingly.
Edit:
For right now, I'm really just trying
to test the NameValue pairs coming out
of the config file, to make sure that
if the values aren't present, my
defaults are being applied. In other
words, I want to try to pull
applicationName, and verify that it
equals "SettlementInfo", and so on.
After that, I will be using
integration testing to ensure that
ASP.NET is using the custom framework
in place of the default one. Does that
make sense?
I need more than a comment to reply, so I'm editing. If I read you correctly, you are wanting to unit test your program to ensure that it deals with configuration correctly, yes? Meaning you want to ensure that your code grabs, for example, the correct AppSettings key and handles a null value therein, correct?
If that's the case, you're in luck; you don't need an app.config or web.config at all, you can set the values you need as part of your test setup.
For example:
[TestMethod]
public void Test_Configuration_Used_Correctly()
{
ConfigurationManager.AppSettings["MyConfigName"] = "MyConfigValue";
MyClass testObject = new MyClass();
testObject.ConfigurationHandler();
Assert.AreEqual(testObject.ConfigurationItemOrDefault, "MyConfigValue");
}
[TestMethod]
public void Test_Configuration_Defaults_Used_Correctly()
{
// you don't need to set AppSettings for a non-existent value...
// ConfigurationManager.AppSettings["MyConfigName"] = "MyConfigValue";
MyClass testObject = new MyClass();
testObject.ConfigurationHandler();
Assert.AreEqual(testObject.ConfigurationItemOrDefault, "MyConfigDefaultValue");
}
I believe you only have access to the webconfig file while your application is actually beeing started up. The solution is rather easy -> "Fake" your config. Use a NameValueCollection and use that instead:
private static NameValueCollection CreateConfig()
{
NameValueCollection config = new NameValueCollection();
config.Add("applicationName", "TestApp");
config.Add("enablePasswordReset", "false");
config.Add("enablePasswordRetrieval", "true");
config.Add("maxInvalidPasswordAttempts", "5");
config.Add("minRequiredNonalphanumericCharacters", "2");
config.Add("minRequiredPasswordLength", "6");
config.Add("requiresQuestionAndAnswer", "true");
config.Add("requiresUniqueEmail", "true");
config.Add("passwordAttemptWindow", "10");
return config;
}
Now you could easily pass that collection into your class that parses data from it.
You should be able to use the ConfigurationManager.GetSection() method to pull out whatever you want.
Actually, if you are using NUnit, you can stick that in an App.config in your test project.
Then add this line to your Post-build event:
copy /Y “$(ProjectDir)App.config” “$(TargetDir)$(TargetFileName).config”
When you create the new provider in your tests, NUnit will pass the values in your app.config to the provider in the initialize method.
Why not just stick it in the web.config file?

Categories