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;
}
}
Related
I have an ASP.NET Core 5 application. There are some log providers that I have as from box. Other words I cannot configure theese providers. But I need to add additional info to every logging message.
For example, code invoke:
logger.LogInfo("Hello World");
But logging provider must get, for example "UserId: 123; Hello World"
How can I reach this goal?
you can create an extension method for this like this,
public static class LogExtensions
{
public static void CustomLogInformation(this ILogger log, string message)
{
log.LogInformation($"prefix{message}");
}
}
you could also pass Exception exception as a parameter if you require it.
In this example, prefix is added, but you can change the value as per your requirements.
If the prefix values changes then you can pass that as well by adding one more parameter to this method. If it's the same for all scenarios then you can define it in some config file.
and if you look at the source code, LogInformation itself is an extension method.
namespace Microsoft.Extensions.Logging
{
public static class LoggerExtensions
{
public static void LogInformation(this ILogger logger, string message, params object[] args)
{
logger.Log(LogLevel.Information, message, args);
}
}
}
NLog Layout enables you to capture additional NLog Context besides what is provided with the actual logevent.
You could update your NLog Target Layout to include ${aspnet-user-identity} in the output:
<target xsi:type="console"
name="console"
layout="${aspnet-user-identity} ${level} ${message}" />
Another alternative could be ${aspnet-user-claim:ClaimTypes.Name}. If your user-information comes from custom location, then you can also implement a custom layout-renderer.
In nLog I am able to setup (in nlog.config or via Code) some so called ConditionBasedFilters like:
<filters defaultAction='Log'>
<when condition="equals('${event-context:item=EventId}','Microsoft.EntityFrameworkCore.Model.Validation.DecimalTypeKeyWarning')" action="Ignore" />
</filters>
With this I can filter log messages based on the actual LogMessage or EventId etc. (see
Official nLog doku: https://github.com/NLog/NLog/wiki/Filtering-log-messages#example-in-c )
Is there some similar feature in asp.net core Logging? (see: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0#how-filtering-rules-are-applied )
As far as I can see it only supports filtering on LogLevel, Category and Provider but not on for instance the actual LogMessage or EventId - Is there some feature, that I am missing?
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
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.
I am having difficulty configuring NLog using class name filters.
<rules>
<logger name="My.NameSpace.MyController" minlevel="Debug" writeTo="file" />
</rules>
It doesn't log if I specify a fully qualified class name, but it does work if I specify name="*"
I am using Castle Windsor to set this up:
Container.AddFacility<LoggingFacility>(f => f.UseNLog("NLog.config"));
Making this call in the MyController class
Logger = MvcApplication.Container.Resolve<ILogger>();
Is there anything else I have to specify to get this to work with the class rule?
I believe Castle Windsor replaces your class with a proxy and logger name you set in config does not match actual instance type.
You could explicitly set log's name like
Logger = LogManager.GetLogger("My.NameSpace.MyController").
Extra tip: in many cases resolving logger using inversion of control does not bring many benefits and you can avoid doing this, otherwise you should tweak up you logger registration in Castle Windsor.
If all classes under a given namespace use the same target, you can configure your rules like this:
<rules>
<logger name="MyApp.Controllers.*" minlevel="Debug" writeTo="fileTarget1" />
<logger name="MyApp.AnotherNamespace.*" minlevel="Debug" writeTo="fileTarget2" />
<logger name="*" minlevel="Error" writeTo="fileTarget3" />
</rules>
If you name your loggers with GetType().FullName in each controller (under MyApp.Controllers), the above configuration will write debug-level (and above) entries to fileTarget1, and error-level (and above) to both fileTarget1 and fileTarget3.
Bottomline, the * wildcard is tremendously useful when you don't know (or care about) the exact names of the types that will use a given logging rule, so in your case this would work for all classes under My.NameSpace:
<logger name="My.NameSpace.*" minlevel="Debug" writeTo="file" />
This works for me with Ninject, but as far as IoC is concerned it's just another dependency being injected, the container doesn't know it's a logging helper. So if Castle is generating the proxy AND you only need 1 target, you can probably get away with Castle.* as your filter.
If you are directly requesting an ILogger from the container (e.g with Logger = MvcApplication.Container.Resolve<ILogger>();) Windsor will always give you a default ILogger called Default.
You can change this default name with the ToLog method when configuring the LoggingFacility:
container.AddFacility<LoggingFacility>(f => f.UseNLog("NLog.config")
.ToLog("MyDefaultname"));
If you want to have the "current class logger" instead of a the default one then you need to let Windsor injects your ILogger instead of manually resolving it.
So if you using constructor injection:
public MyController
{
private ILogger logger;
public MyController(ILogger logger)
{
this.logger = logger;
}
}
or property injection:
public MyController
{
public ILogger Logger { get; set; }
}
Windsor will create an ILogger for you with the proper My.NameSpace.MyController name.
If you need more fine grained control of the logger names you can depend/resolve the Castle.Core.Logging.ILoggerFactory interface and you can create your own ILoggers with that.