Nlog log level rules not changed - c#

Having for example this class that inits the NLog config programmatically:
public class NLogConfig
{
public NLogConfiguration(string traceFileName, LogLevel minLogleven, LogLevel maxLogLevel)
{
var config = new NLog.Config.LoggingConfiguration();
// Targets where to log to file
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = $"{traceFileName}.log" };
logfile.Layout = #"${date:format=HH\:mm\:ss}|${level}|${message} ${exception:format=tostring}";
// Rules for mapping loggers to targets
config.AddRule(minLogleven, maxLogLevel, logfile);
// Apply config
LogManager.Configuration = config;
}
}
Now, I call this config in two different classes, e.g.:
public class A
{
public A()
{
var nlogConfig = new NLogConfig("Trace1", LogLevel.Trace, LogLevel.Fatal);
}
}
public class B
{
public B()
{
var nlogConfig = new NLogConfig("Trace2", LogLevel.Info, LogLevel.Fatal);
}
}
Now the problem is, that the two different log level rules will not be taken by the Nlog configuration, so in that case the Trace1.log and the Trace2.log log both with the trace level, while I expected the Trace1.log to log with the level Trace and the Trace2.log to log with the info log level.
How to fix this behavior, so, I can change the log level dynamically?

At the beginning I would try to overview general design of your app and only then I would give you code samples.
Design overview.
We [developers] like to keep things as simple as possible. One of the ways to achieve this is to separation of responsibilities of software modules.
In your case class A (or B) should NOT care what destination file name is or log level. It should just log records and this's all!
But you can ask how to write into different files with different settings? For this purpose you should use "targets" and "rules" concepts. Basically, target is just a log destination. It can be a file, console etc. A rule is filter to select only messages you need . It can be just a log level or/and even special filter by class name or/and way more. Actually, the configuration your are showing in the first example is setup of targets and rules.
This design should help to separate and simplify things. I hope you understand what I'm talking about otherwise imagine simulation when you require to change logging settings in project with 100 classes that uses logger.
Possible solution.
Classes A and B should create log instance and use it.
public class A
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public A()
{
Logger.Info("Class 'A' created.");
}
}
Nlog configuration usually done at the application startup to execute it only one time during application lifetime.
public static class Program
{
public static void Main()
{
LogConfiguration();
}
private static void LogConfiguration()
{
var config = new LoggingConfiguration()
// Targets where to log to: File and Console
var f1 = new FileTarget("file1") { FileName = "file1.txt" };
var f2 = new FileTarget("file2") { FileName = "file2.txt" };
// Rules for mapping loggers to targets
config.AddRule(LogLevel.Info, LogLevel.Fatal, f1);
config.AddRule(LogLevel.Debug, LogLevel.Fatal, f2);
// Apply config
NLog.LogManager.Configuration = config;
}
}
Possible feature.
From your code seems that you might want to write into "file2.txt" only messages from class B. This is also possible. You need to specify logger name during rule creation as shown here. Small tip: the name you want to use is name of full your class.
From this example you will get what I'm talking about.

Related

Injecting eventloggers with different configurations in ASP.NET app

In a ASP.NET Core5 app:
I'd like to inject a logger like this:
public class X {
X( ILogger<X> loggerX )...
}
public class Y {
X( ILogger<Y> loggerY )...
}
When I inject these loggers I'd like both loggers to log to the Windows Eventlog, but I'd like loggerX to set the Source field to "X" and loggerY to set the Source field to "Y".
This way, I'd be able to differentiate easily between logs generated by either class. (The classes in my case would typically be controllers).
My question is:
How do I configure the loggers in the DI container to have this behaviour? (customized logger configuration based on the type of logger extracted).
And also, is there a way to configure the DI Container so that the above scenario would create loggerX to write to a new Log called "X" and logger "Y" to a log called "Y" ? (So, instead of setting the Source, write to different logs).....
Finally, and I realize that this might be asking for a lot, but I may as well ask and learn... Is there a way to achieve the above through "mostly" configuration(appsettings.json) instead of writing loads of code? I don't mind writing the code, but I am trying to make as much as possible configurable where I can and where it makes sense.
I know 2 way for this issue:
First way :
1.Regsiter services in Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMessageService, EmailService>();
services.AddTransient<IMessageService, SmsService>();
}
2.Resolve Services
public MessagesController(IEnumerable<IMessageService> messageServices)
{
_messageServices = messageServices;
}
var emailService = _messageServices.OfType<EmailService>().First();
var messageServices = serviceProvider.GetServices<IMessageService>();
Second way
1.Regsiter services in Startup class
services.AddTransient<EmailService>();
services.AddTransient<SmsService>();
services.AddTransient<Func<MessageServiceType, IMessageService>>(serviceProvider => key =>
{
switch (key)
{
case MessageServiceType.EmailService:
return serviceProvider.GetRequiredService<EmailService>();
case MessageServiceType.SmsService:
return serviceProvider.GetRequiredService<SmsService>();
default:
throw new NotImplementedException($"Service of type {key} is not implemented.");
}
});
2.Resolve services
private readonly Func<MessageServiceType, IMessageService> _messageServiceResolver;
public MessagesController(Func<MessageServiceType, IMessageService> messageServiceResolver)
{
_messageServiceResolver = messageServiceResolver;
}
public IActionResult Index()
{
var emailService = _messageServiceResolver(MessageServiceType.EmailService);
return View();
}

How to load appsettings.json inside a Static class based on deploy environment so that I can use in other class libraries

W'm working on a migration project. I need to use my appsettings in other class libraries. so after googling and stackoverflowing, I load my appsettings.json inside static class as follows:
public static class ReadAppConfig
{
private static readonly IConfiguration Root;
private static readonly ConfigurationBuilder ConfigurationBuilder;
static ReadAppConfig()
{
if (ConfigurationBuilder == null)
{
ConfigurationBuilder = new ConfigurationBuilder();
ConfigurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
ConfigurationBuilder.AddJsonFile("appsettings.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.QA.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.Dev.json", optional: true);
ConfigurationBuilder.AddJsonFile("appsettings.Staging.json", optional: true);
if (Root == null)
Root = ConfigurationBuilder.Build();
}
}
public static string UserManualFile => Root.GetSection("AppSettings:SomeKey").Value;
}
So now I can get UserManualFile like ReadAppConfig.UserManualFile in other libraries.
This works fine. But it always reads from appsettings.Staging.json only. How to make this read based on deploy environment.
I cannot get IHostingEnvironment here as this is static class.
Please assist / suggest me with proper way to do this.
Thanks
There's two problems here. First, don't use a static class. Configuration is designed to be dependency injected and dependency injection is fundamentally incompatible with statics. In truth, statics are almost always the wrong approach, dependency injection or not. Second, libraries should depend only on abstractions, not concrete data/implementations.
Honestly, there's three problems and the last one is the killer here: you need IHostingEnvironment for your use case, and there's absolutely know way to get that in a static class. Game over.
There's multiple ways you could go here, but I'm going to be opinionated with what I feel is the best option. Ultimately, your libraries just need UserManualFile, it seems. As such, that is all they should depend on: a string that corresponds to the location of a user manual, presumably. So, you'll do something like:
public class SomeLibraryClass
{
private readonly string _userManualFie;
public SomeLibraryClass(string userManualFile)
{
_userManualFile = userManualFile;
}
}
This requires the least amount of knowledge and provides the greatest amount of abstraction for your library. It no longer cares where or how it gets the file location, just that it gets it.
Then, in your actual app, you'll use strongly-typed config to provide this value:
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
...
services.AddScoped(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
return new SomeLibraryClass(appSettings.Value.UserManualFile);
});
Done. Now, if there's actually other stuff the library needs, you might choose to pass a custom "settings" class to the library. This class should come from the library, so that it documents what it needs. For example, in your library, you'd create a class like:
public class SomeLibrarySettings
{
public string Foo { get; set; }
public string Bar { get; set; }
// etc.
}
Then, your library class(es) would inject this:
public SomeLibraryClass(SomeLibrarySettings settings)
Finally, in your app, you can either manually compose this settings class instance or inject it. Injecting it will still require you to manually compose it, so it only makes sense to do it that way if you're going to share it between multiple classes.
Manually compose
services.AddScoped(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
var someLibrarySettings = new SomeLibrarySettings
{
Foo = appSettings.Value.Foo,
Bar = appSettings.Value.Bar,
// etc.
};
return SomeLibraryClass(someLibrarySettings);
});
Inject
services.AddSingleton(p =>
{
var appSettings = p.GetRequiredService<IOptions<AppSettings>>();
return new SomeLibrarySettings
{
Foo = appSettings.Value.Foo,
Bar = appSettings.Value.Bar,
// etc.
};
});
services.AddScoped<SomeLibraryClass1>();
services.AddScoped<SomeLibraryClass2>();
// etc.
Because SomeLibrarySettings is registered in the service collection, it will be automatically injected into the library classes that depend on it.
Finally, it's worth noting that because you're moving the configuration logic to where it actually belongs, you no longer need to even worry about the environment. ASP.NET Core is already set up to load the appropriate environment settings, so it just works.

How to create nlog loggers for various controllers and asp net mvc filters?

I need to programatically create loggers for each controller (let's say: Documents, Customers and Warehouses, to log some operations inside them) and for a filter above controllers (to log parameters sent to each action). Each controller logic should be logged to another file ex. Documents.csv, Customers.csv and Warehouses.csv.
Currently I have a wrapper for nlog logger. The wrappers are injected to controllers instances via constructor and then nlog logger inside in initialized via LogManager. Initialization creates new target and loglevel and assigns it to LogManager.Configuration.
The problem is that after a few requests each loggers logs to each file, so ex. logic from Customers is logged to Customers.csv and vice versa, same thing with logger from Filters.
How should I then configure separate blog loggers with different target for each controller and separate for filter?
I prefer programmatic configuration than via xms etc.
Adapter as requested:
public class Logger : ILogger
{
private NLog.Logger _logger;
string _deployVersion;
public Logger(string deploymentVersion)
{
_deployVersion = deploymentVersion;
}
public void Init(string loggerName)
{
_logger = NLog.LogManager.GetLogger(loggerName);
}
public void Init(string loggerName, string header, string layout, Level level)
{
LoggingConfiguration config;
if (NLog.LogManager.Configuration == null)
{
config = new LoggingConfiguration();
}
else
{
config = LogManager.Configuration;
}
if (config.FindTargetByName(loggerName) == null)
{
var target = CreateTarget(loggerName, header, layout, level); //configures target: path, archives and layout
config.AddTarget(loggerName, target);
var logLevel = GetLogLevel(level); //translates Level enum to NLog level
var rule1 = new LoggingRule("*", logLevel, target);
config.LoggingRules.Add(rule1);
LogManager.Configuration = config;
}
_logger = LogManager.GetLogger(loggerName);
}
...
//Info, Debug etc. methods
Great, you've used an interface here already which makes a pattern for this easier to produce.
Your main problem here is responsibility, as your controller code will (I assume) call Init(string loggerName, string header, string layout, Level level). This is probably not best practice as you may have to repeat this code a lot and the controller probably shouldn't care about where the log goes or what it's formatted like...just the fact that a log is used.
Instead of injecting these directly, use a Factory to obtain the correct logger type. For example, CustomerLogger : ILogger:
public class LogFactory
{
public ILogger Get<T>() where T : ILogger
{
ILogger instance = null;
if (typeof(T) == typeof(CustomerLogger))
{
instance = (T)Activator.CreateInstance(typeof(T), "CustomerLogger", "Header", "Layout", Level.Verbose);
}
else if (...)
{
...etc
}
return instance;
}
}
This means you can leave the management and creation of ILogger concretes up to the Factory.
Notice I'm passing in string loggerName, string header, string layout, Level level so you can call Init in the CustomerLogger constructor. You then have different implementations of ILogger to suit your needs.
You can then either make the LogFactory use an interface and inject that into your controller, or simply new up the factory in the controller, either way you call LogFactory.Get<T>(). Option A would be better for unit testing purposes.
Hope this helps!

Configuring MassTransit's Ninject bindings after initialisation

I want to configure MassTransit at one point in my code (using WebActivator) and configure the message handlers in another (a Ninject module). Is there a way I can achieve this? The documentation here shows how to perform what I need in one step, but to do anyhting else, it looks like I need to get an instance of a ServiceBusConfigurator, which doesn't seem to be available from the preexisting IServiceBus
Configuration and Creation of the IServiceBus cannot be separated.
That means, the only option you have is to gather the configuration information some more time before creating the bus.
As the doc you linked states, the meta data information made available by ninject is not sufficient to create the subscriptions. This basically means that you've got to create your own metadata model. Let's make an example, which can be used with single registrations but also with convention based registrations:
Hint: You should treat the following code snippets as psuedo code as i've written them from memory. It's highly likely that it won't compile.
Metadata Model
public class SubscriptionMetadata
{
public SubscriptionMetadata(Type consumer)
{
if(!typeof(IConsumer).IsAssignableFrom(consumer))
{
string message = string.Format(
"{0} does not implement {1}",
typeof(IConsumer).Name,
consumer.Name);
throw new ArgumentOutOfRangeException("consumer", message);
}
this.ConsumerType = consumer;
}
public Type ConsumerType { get; private set; }
}
Registration of Metadata
Now this can be used like this in a Ninject module:
Bind<SubscriptionMetadata>()
.ToConstant(new SubscriptionMetadata(typeof(FooConsumer));
If you're going to use it a lot i'd recommend writing an extension method:
public static class SubscriptionBindingExtensions
{
public static void BindConsumer<T>(this IBindingRoot bindingRoot)
where T : IConsumer
{
Bind<SubscriptionMetadata>()
.ToConstant(new SubscriptionMetadata(typeof(T));
}
}
and usage (#Module):
BindConsumer<FooConsumer>();
IServiceBus creation
Now you would adapt the IServiceBus creation as follows:
var kernel = new StandardKernel();
// 2nd Step left out: load all IModule`s ..
var bus = ServiceBusFactory.New(sbc =>
{
//other configuration options
foreach(var metadata in kernel.GetAll<SubscriptionMetadata>())
{
sbc.Subscribe(subs =>
{
subs.Consumer(metadata.ConsumerType, kernel)
});
}
});
Convention based binding of Consumers
It can also be used in conjunction with conventions by leveraging the IBindingCreator interface. If you wish, i can post an example.

How to substitute config file in test environment?

I am using MSTest to test an application. The test requires certain specific values, which are not normally present, to appear in the application config file.
So I need to substitute a well-known config file containing the values, at test run time, so that System.Configuration.ConfigurationManager points at the right file. (ie I am faking the real config file by substituting another one that I made earlier)
I can do all that, except that by the time my test executes, System.Configuration.ConfigurationManager has already read the config file, so that the new values are ignored.
Example code:
static TemporaryConfigFile config;
[ClassInitialize]
public static void ClassInitialise(TestContext testContext)
{
string sourceResource = "Intra_Matrix_Scheduler_Tests.Resources.test.config";
string tempConfigFileName = "test.config";
config = TemporaryConfigFile.CreateFromEmbeddedResource(Assembly.GetExecutingAssembly(), sourceResource, tempConfigFileName);
}
[ClassCleanup]
public static void ClassCleanUp()
{
config.Dispose();
}
(the above code creates a new config file with known test values, and points AppDomain.CurrentDomain("APP_CONFIG_FILE") at it. In the production code this technique of rerouting to another config file works perfectly if done at the start of the application)
The problem is that the following production line, when exercised by a test, does not retrieve the desired test values:
var dict = (System.Collections.Specialized.NameValueCollection)System.Configuration.ConfigurationManager.GetSection("ScheduledTasks");
and the reason is clearly that although the production code line and test code are by now pointing at the correct config file, the production config file has already been loaded into memory so the test config file is effectively ignored.
So the question is: how can System.Configuration.ConfigurationManager be forced to re-read the config file, or how else can the config file be faked? Alternatively how can I directly modify the in-memory config file for the duration of the test? (AFAIK I can't use dependency injection and MOQ to mock it, because System.Configuration.ConfigurationManager is static)
TIA
I suggest you to test classes in isolation from other real classes (like ConfigurationManager) and especially in isolation from environment (files, network, databases etc), because your tests could fail for some external reason not related to code you are testing (file may not exist, wrong database connection, etc). It's easy to do if you'll create your own non-static configuration manager, which will delegate all work to ConfigurationManager:
public class ConfigurationManagerWrapper : IConfigurationProvider
{
public NameValueCollection GetScheduledTasksSettings()
{
return (NameValueCollection)ConfigurationManager
.GetSection("ScheduledTasks");
}
}
Then make your sut (class under test) depend on ICoolConfigurationProvider abstraction which is easy to mock (consider also to return something more business specific than name-value collection):
public interface IConfigurationProvider
{
NameValueCollection GetScheduledTasksSettings();
}
And sut looks like:
public class SUT
{
private IConfigurationProvider _configProvider;
public SUT(IConfigurationProvider configProvider)
{
_configProvider = configProvider;
}
public void Exercise()
{
var dict = _configProvider.GetScheduledTasksSettings();
// ...
}
}
Now you can easily provide any values for your tests:
[TestMethod]
public void ShouldDoSomething()
{
var configMock = new Mock<IConfigurationProvider>();
configMock.Setup(c => c.GetScheduledTasksSettings())
.Returns(new NameValueCollection {{ "foo", "bar" }});
var sut = new SUT(configMock.Object); // inject configuration provider
sut.Exercise();
// Assertions
}

Categories

Resources