NLog class filter not working - c#

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.

Related

NLog, .net core: How to get access to a specific logger in my service through dependency injection

Software versions:
.NET Core v5.0
NLog v4.7.6
Microsoft Extensions Logging v5
VisualStudio2019
I'm using NLog along with Microsoft logging extensions in my .net core console application.
The application contains multiple services that run in background as hosted services.
Each service has access to its typed logger that is set in its constructor through the usual dependency injection. But I also need to add access to a special email logger that will send out an email on critical exceptions. Is there any other way to get a special logger injected into my serivce instead of creating it from "NLog.LogManager.GetLogger()" in every service?
public class TestService: IHostedService
{
private readonly ILogger<TestService> _logger;
private readonly ILogger _emailOnFatalError;
public TestService(ILogger<TestService> logger)
{
_logger = logger;
//_emailOnFatalError = (ILogger) NLog.LogManager.GetLogger("emailOnFatalError");
}
}
Instead of having ILogger as constructor-parameter, then use ILoggerFactory:
public class TestService: IHostedService
{
private readonly ILogger _logger;
private readonly ILogger _emailOnFatalError;
public TestService(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger(GetType().ToString());
_emailOnFatalError = loggerFactory.CreateLogger("emailOnFatalError");
}
}
Alternative consider adding an event-property like this:
_logger.LogError("Something awful - {EmailAlert}", "Important");
Then you can use NLog filtering to only send log-events that contains ${event-property:EmailAlert}:
<logger name="*" writeTo="emailOnFatalError" minLevel="Error">
<filters defaultAction="Log">
<when condition="'${event-property:EmailAlert}' == ''" action="Ignore" />
</filters>
</logger>

Is there a way for a simple way for ClassLibrary to get Session Value?

I have an external ClassLibrary Project that needs to get session value set from HomeController in the Main Project.
Is there a simple way to accomplish this?
Or is there an alternative to transfer a value from HomeController to an external ClassLibrary?
You can use the IHttpContextAccessor class
For other framework and custom components that require access to
HttpContext, the recommended approach is to register a dependency
using the built-in dependency injection container. The dependency
injection container supplies the IHttpContextAccessor to any classes
that declare it as a dependency in their constructors.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
}
In the following example:
UserRepository declares its dependency on IHttpContextAccessor.
The dependency is supplied when dependency injection resolves the dependency chain and creates an instance of UserRepository.
.
public class UserRepository : IUserRepository
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserRepository(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void LogCurrentUser()
{
var username = _httpContextAccessor.HttpContext.User.Identity.Name;
service.LogAccessRequest(username);
}
}
Don't forget to add services.AddHttpContextAccessor(); to make the dependency injection work.
The single-responsibility principle dictates that a class should do just one thing. While you can inject something like IHttpContextAccessor that then requires the class to have knowledge of concepts like HttpContext, Session, the fact that it's being used in a web environment in the first place, etc.
The correct approach is to inject or pass values. If the class needs a particular value from a session variable, access the session in your controller, where that logic actually belongs, and then pass only the value from the session to your external class.
If u use Abp template, the Abp application service ApplicationService already contains property AbpSession, you can inherit this class.

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.

Unity named mapping issue

I have two implementations of IEmailService, one for testing and one for live (is-A). And I have a BusinessService that has-A IEmailService reference.
BusinessService
IEmailService (has-A)
IEmailService
TestEmailService (is-A)
LiveEmailService (is-A)
In unity config, I register the two IEmailService implementations as follows.
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type="DataAccess.IEmailService, DataAccess"
mapTo="DataAccess.LiveEmailService, DataAccess"
name="Live">
<lifetime type="singleton" />
</register>
<register type="DataAccess.IEmailService, DataAccess"
mapTo="DataAccess.TestEmailService, DataAccess"
name="Test">
<lifetime type="singleton" />
</register>
<container>
</unity>
Based on the appSetting for IEmailService I want Unity to pick the correct implementation. This will help while testing.
<appSettings>
<add key="IEmailService" value="Test"/>
</appSettings>
The issue is when unity resolves BusinessService, it tries to resolve (none) named mapping of IEmailService instead of Live or Test and throws an ResolutionFailedException.
container.Resolve<BusinessService>(); throws below exception:
BusinessServices.Test.BusinessServiceTest_Integration.Test103:
Microsoft.Practices.Unity.ResolutionFailedException : Resolution of the dependency failed, type = "BusinessServices.BusinessService", name = "(none)".
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The current type, DataAccess.IEmailService, is an interface and cannot be constructed. Are you missing a type mapping?
-----------------------------------------------
At the time of the exception, the container was:
Resolving BusinessServices.BusinessService,(none)
Resolving parameter "emailService" of constructor BusinessServices.BusinessService(DataAccess.IEmailService emailService)
Resolving DataAccess.IEmailService,(none)
----> System.InvalidOperationException : The current type, DataAccess.IEmailService, is an interface and cannot be constructed. Are you missing a type mapping?
The workaround I came up with is to specify the registrations in code as well and have a wrapper method around container.RegisterType to register IEmailService with (none) named mapping as well based on the appSetting value.
IUnityContainer container;
// registering unity
static void Load()
{
container = new UnityContainer().LoadConfiguration();
RegisterType<IEmailService, TestEmailService>("Test");
RegisterType<IEmailService, LiveEmailService>("Live");
}
// register the `Test` or `Live` implementation with `(none)` named mapping as per appSetting
static void RegisterType<TFrom, TTo>(string name)
where TTo : TFrom
{
var tFromAppSetting= ConfigurationManager.AppSettings[typeof(TFrom).Name];
if (!string.IsNullOrEmpty(tFromAppSetting) && tFromAppSetting == name)
container.RegisterType<TFrom, TTo>();
}
This works, but I end up specifying the registrations in two places - config as well as code. Is there a better way for doing this?
Update
I actually had got it correct by code. I do not need the unity config at all. The RegisterType<TFrom, TTo>(string name) registers either the Test or Live implementation as (none) named mapping depending on appSetting value. BusinessService is also resolved without exception.
As there is no unity config, I do not have load the configuration.
container = new UnityContainer();
In my opinion the only point of having registrations in config is to not have them in code and being able to replace implementation without recompilation. So you are write in trying to remove it form code. What I don't understand is why you want to have both registrations in config in the first place. Simply remove the Live one from config for tests and the Test from config for application and register them both without name.
So for instance in application app.config:
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<container>
<register type="DataAccess.IEmailService, DataAccess"
mapTo="DataAccess.LiveEmailService, DataAccess">
<lifetime type="singleton" />
</register>
Since you really are determent to do it your way:
The other way around this is to register in code only a way of determining which instance is the default one:
container.RegisterType<IEmailService>(new InjectionFactory((c)=>
{
var name = GetImplementationsNameFromAppSettings();
return c.Resolve<IEmailService>(name);
});

Categories