I'm using more NLog instances within single project (see my previous question Nlog config file priority). However, it doesn't work as expected.
If I call method that logs in second project, it's logged properly, but even after returning to previous project, items are being logged at wrong place.
So, for example Project1 has set to log in Project1.log, same way for second one. I can do method that simply calls:
Project1.Log.Write("1");
Project2.Log.Write("2");
Project1.Log.Write("3");
When I check logs, project1 contains "1", project 2 contains "2" and "3".
Exact (bit simplified) logger classes looks like:
public static class Log
{
private static readonly Lazy<Logger> Logger = new Lazy<Logger>(CreateLogger);
private static Logger CreateLogger()
{
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
LogManager.Configuration = new XmlLoggingConfiguration(assemblyFolder + "\\ProjectX.exe.nlog", true); //X means project id
return LogManager.GetCurrentClassLogger();
}
public static void Write(object log)
{
Logger.Value.Debug(log);
}
}
What am I doing wrong?
Your previous question talks about the application should have priority in loading a single configuration for the entire application:
Application-specific exe.nlog
Fallback to global nlog.config
Now you are talking about having multiple assemblies in the same application, that wants to load their individual NLog-configuration side-by-side.
When using the static LogManager.Configuration then you are modifying the global configuration for the entire application. If two project-assemblies are changing the global configuration, then it will of course have side-effects for others.
Maybe your CreateLogger could look like this:
private static Logger CreateLogger()
{
// Check for global NLog-configuration (Maybe your don't want this at all?)
var configuration = LogManager.Configuration;
if (configuration?.AllTarget.Count > 0)
return LogManager.GetCurrentClassLogger();
// Create assembly-specific NLog-configuration
string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
LogFactory logFactory = new LogFactory();
logFactory.Configuration = new XmlLoggingConfiguration(assemblyFolder + "\\ProjectX.exe.nlog", true, logFactory); //X means project id
return logFactory.GetCurrentClassLogger();
}
See also https://github.com/NLog/NLog/wiki/Configure-component-logging
Related
I am trying to output some log messages from within .NET Core 3.1-based xUnit test cases by means of the NLog logging library. Logging by means of NLog works fine in my main application, but the test cases just won't output any messages.
I think I am doing everything suggested in this related question: NLog works in ASP.NET Core App but not in .NET Core xUnit Test Project
Somehow, though, I cannot figure out what is missing. I have reduced my code into a minimal sample that seems very straightforward, but still does not output anything:
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using Xunit;
using Xunit.Abstractions;
namespace UnitTests
{
public class LoggingTest
{
public LoggingTest(ITestOutputHelper output)
{
this.output = output;
}
private readonly ITestOutputHelper output;
[Fact]
public void TestLog()
{
var target = new MemoryTarget {Layout = "${message}"};
LogManager.Configuration ??= new LoggingConfiguration();
LogManager.Configuration.AddRuleForAllLevels(target);
LogManager.GetCurrentClassLogger().Info("Hello, World!");
output.WriteLine("{0} line(s) logged:\n{1}", target.Logs.Count, String.Join("\n", target.Logs));
}
}
}
Expected output:
1 line(s) logged:
Hello, World!
Actual output:
0 line(s) logged:
As one further trace, I have read in various places that NLog will only write something in .NET Core 3.1 projects if certain settings are present in a Logging section of the appsettings.json file. I think this section also had to be added to our main application's appsettings.json file.
I am not sure how to transfer this knowledge to the unit tests, though, as they do not appear to come with an appsettings.json file. I have tried copying the main appsettings.json file to the output directory of the unit tests (which is, I think, their execution directory when run from within ReSharper), but to no avail.
What am I missing?
To apply the config, you need to assign LogManager.Configuration, like
LogManager.Configuration = config;
Working example:
[Fact]
public void TestLog()
{
var target = new MemoryTarget { Layout = "${message}" };
var config = new LoggingConfiguration();
config.AddRuleForAllLevels(target);
LogManager.Configuration = config; // <-- assign here
LogManager.GetCurrentClassLogger().Info("Hello, World!");
output.WriteLine("{0} line(s) logged:\n{1}", target.Logs.Count, String.Join("\n", target.Logs));
Assert.Equal(1, target.Logs.Count);
}
Bonus: tests in parallel
Bonus, if you like tests in parallel (who doesn't ;)) - create a new LogFactory instead of assigning the global LogManager.
Like this:
[Fact]
public void TestLogParallelSafe()
{
var logFactory = new LogFactory();
var target = new MemoryTarget { Layout = "${message}" };
var config = new LoggingConfiguration();
config.AddRuleForAllLevels(target);
logFactory.Configuration = config;
logFactory.GetCurrentClassLogger().Info("Hello, World!");
output.WriteLine("{0} line(s) logged:\n{1}", target.Logs.Count, String.Join("\n", target.Logs));
Assert.Equal(1, target.Logs.Count);
}
Of course if other code is using the LogManager, you can't assert those logs.
.NET Core integration
As one further trace, I have read in various places that NLog will only write something in .NET Core 3.1 projects if certain settings are present in a Logging section of the appsettings.json file. I think this section also had to be added to our main application's appsettings.json file.
This is only needed when integrating with ASP.NET Core - e.g. when injection the ILogger<T> from Microsoft. That's is not needed here. For further reference, see Getting started with ASP.NET Core 3 ยท NLog/NLog Wiki
The approach proposed by Julian can be improved by implementing a specific target which writes the log entries to the output directly, without delay and without buffering all log entries in memory.
internal class TestOutputTarget: TargetWithLayout {
private readonly ITestOutputHelper output;
public TestOutputTarget(ITestOutputHelper output) {
this.output = output;
}
protected override void Write(LogEventInfo logEvent) {
output.WriteLine(RenderLogEvent(Layout, logEvent));
}
}
...
// In the test code
var target = new TestOutputTarget(output) { Layout = "${message}" };
I am writing C# code that runs against an Azure cloud. My application is an ASP.NET Core web service that exposes methods but no UI.
Sometimes I want to run my code locally using Microsoft Azure Storage Emulator. When my code starts up, one of the first things that happens is this:
var container = new BlobContainerClient(_connectionString, s);
bool exists = await container.ExistsAsync(ct);
if (!exists)
await container.CreateAsync(cancellationToken: ct);
When running locally, I sometimes forget to start Azure Storage Emulator. When that happens, it takes my code like a minute to time out and tell me it can't reach the "cloud".
What I want to achieve is: Make the program give me good error messages quickly when running locally, but use more lenient timeout strategies when actually running in the cloud.
I can reduce the above timeout by doing something like this:
var blobClientOptions = new BlobClientOptions();
blobClientOptions.Retry.MaxRetries = 0;
var container = new BlobContainerClient(_connectionString, s, blobClientOptions);
... but when running against the real cloud I don't want that; I want it to retry. One option might be to set the retries to zero like above, but only when running locally.
I have a development-specific configuration file (appsettings.Development.json). Is it possible to configure such timeout/retry settings in the config file?
Or is there some other best-practice way to accomplish the "fail quickly in development" behaviour that I seek?
Thanks in advance!
create a class that will contain you blobstorage configuration:
public class BlobStorageConfiguration
{
public string ConnectionString {get; set;}
public int MaxRetries {get; set;}
}
in your appsettings.Development.json
{
...
"BlobStorageConfiguration": {
"ConnectionString " : "<your_connection_string>",
"MaxRetries ":0
}
...
}
in your Startup.cs in the ConfigureServices method
..
var blobConfig = new BlobStorageConfiguration ();
Configuration.Bind(nameof(BlobStorageConfiguration ), blobConfig);
services.AddSingleton(blobConfig );
..
now you can inject your config and it will take values from the appsettings.Development.json if you are running it locally:
some controller:
[Route("api/somthing")]
[ApiController]
public class SomethingController : ControllerBase
private readonly ILogger<SomethingController > logger;
public SomethingController (
ILogger<SomethingController > logger,
BlobStorageConfiguration blobConfig)
{
this.logger = logger;
// use your blobConfig (connectionstring and maxRetries)
}
Here's the situation.
I have an application which for all intents and purposes I have to treat like a black box.
I need to be able to open multiple instances of this application each with a set of files. The syntax for opening this is executable.exe file1.ext file2.ext.
If I run executable.exe x amount of times with no arguments, new instances open fine.
If I run executable.exe file1.ext followed by executable.exe file2.ext then the second call opens file 2 in the existing window rather than creating a new instance. This interferes with the rest of my solution and is the problem.
My solution wraps this application and performs various management operations on it, here's one of my wrapper classes:
public class myWrapper
{
public event EventHandler<IntPtr> SplashFinished;
public event EventHandler ProcessExited;
private const string aaTrendLocation = #"redacted";
//private const string aaTrendLocation = "notepad";
private readonly Process _process;
private readonly Logger _logger;
public myWrapper(string[] args, Logger logger =null)
{
_logger = logger;
_logger?.WriteLine("Intiialising new wrapper object...");
if (args == null || args.Length < 1) args = new[] {""};
ProcessStartInfo info = new ProcessStartInfo(aaTrendLocation,args.Aggregate((s,c)=>$"{s} {c}"));
_process = new Process{StartInfo = info};
}
public void Start()
{
_logger?.WriteLine("Starting process...");
_logger?.WriteLine($"Process: {_process.StartInfo.FileName} || Args: {_process.StartInfo.Arguments}");
_process.Start();
Task.Run(()=>MonitorSplash());
Task.Run(() => MonitorLifeTime());
}
private void MonitorLifeTime()
{
_logger?.WriteLine("Monitoring lifetime...");
while (!_process.HasExited)
{
_process.Refresh();
Thread.Sleep(50);
}
_logger?.WriteLine("Process exited!");
_logger?.WriteLine("Invoking!");
ProcessExited?.BeginInvoke(this, null, null, null);
}
private void MonitorSplash()
{
_logger?.WriteLine("Monitoring Splash...");
while (!_process.MainWindowTitle.Contains("Trend"))
{
_process.Refresh();
Thread.Sleep(500);
}
_logger?.WriteLine("Splash finished!");
_logger?.WriteLine("Invoking...");
SplashFinished?.BeginInvoke(this,_process.MainWindowHandle,null,null);
}
public void Stop()
{
_logger?.WriteLine("Killing trend...");
_process.Kill();
}
public IntPtr GetHandle()
{
_logger?.WriteLine("Fetching handle...");
_process.Refresh();
return _process.MainWindowHandle;
}
public string GetMainTitle()
{
_logger?.WriteLine("Fetching Title...");
_process.Refresh();
return _process.MainWindowTitle;
}
}
My wrapper class all works fine until I start providing file arguments and this unexpected instancing behaviour kicks in.
I can't modify the target application and nor do I have access to its source to determine whether this instancing is managed with Mutexes or through some other feature. Consequently, I need to determine if there is a way to prevent the new instance seeing the existing one. Would anyone have any suggestions?
TLDR: How do I prevent an application that is limited to a single instance determining that there is already an instance running
To clarify (following suspicious comments), my company's R&D team wrote executable.exe but I don't have time to wait for their help in this matter (I have days not months) and have permission to do whatever required to deliver the required functionality (there's a lot more to my solution than this question mentions) swiftly.
With some decompiling work I can see that the following is being used to find the existing instance.
Process[] processesByName = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
Is there any way to mess with this short of creating multiple copies of the application with different names? I looked into renaming the Process on the fly but apparently this isn't possible short of writing kernel exploits...
I have solved this problem in the past by creating copies of the source executable. In your case, you could:
Save the 'original.exe' in a specific location.
Each time you need to call it, create a copy of original.exe and name it 'instance_xxxx.exe', where xxxx is a unique number.
Execute your new instance exe as required, and when it completes you can delete it
You could possibly even re-use the instances by creating a pool of them
Building on Dave Lucre's answer I solved it by creating new instances of the executable bound to my wrapper class. Initially, I inherited IDisposable and removed the temporary files in the Disposer but for some reason that was causing issues where the cleanup would block the application, so now my main program performs cleanup at the end.
My constructor now looks like:
public AaTrend(string[] args, ILogger logger = null)
{
_logger = logger;
_logger?.WriteLine("Initialising new aaTrend object...");
if (args == null || args.Length < 1) args = new[] { "" };
_tempFilePath = GenerateTempFileName();
CreateTempCopy(); //Needed to bypass lazy single instance checks
HideTempFile(); //Stops users worrying
ProcessStartInfo info = new ProcessStartInfo(_tempFilePath, args.Aggregate((s, c) => $"{s} {c}"));
_process = new Process { StartInfo = info };
}
With the two new methods:
private void CreateTempCopy()
{
_logger?.WriteLine("Creating temporary file...");
_logger?.WriteLine(_tempFilePath);
File.Copy(AaTrendLocation, _tempFilePath);
}
private string GenerateTempFileName(int increment = 0)
{
string directory = Path.GetDirectoryName(AaTrendLocation); //Obtain pass components.
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(AaTrendLocation);
string extension = Path.GetExtension(AaTrendLocation);
string tempName = $"{directory}\\{fileNameWithoutExtension}-{increment}{extension}"; //Re-assemble path with increment inserted.
return File.Exists(tempName) ? GenerateTempFileName(++increment) : tempName; //If this name is already used, increment an recurse otherwise return new path.
}
Then in my main program:
private static void DeleteTempFiles()
{
string dir = Path.GetDirectoryName(AaTrend.AaTrendLocation);
foreach (string file in Directory.GetFiles(dir, "aaTrend-*.exe", SearchOption.TopDirectoryOnly))
{
File.Delete(file);
}
}
As a side-note, this approach will only work for applications with (lazy) methods of determining instancing that rely on Process.GetProcessByName(); it won't work if a Mutex is used or if the executable name is explicitly set in the manifests.
I am learning to use NLog. My situation is I want to use it inside DLL probably wrap it inside a log class. Basically my goals are:
1) I want this configuration to occur only ONCE:
var config = new LoggingConfiguration();
var fileTarget = new FileTarget();
config.AddTarget("file", fileTarget);
// Step 3. Set target properties
fileTarget.Layout = #"${date:format=HH\:mm\:ss} - ${message}";
fileTarget.FileName = "c:/myFolder/" + "${date:format=yyyy-MM-dd}.log";
var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget);
config.LoggingRules.Add(rule2);
// Step 5. Activate the configuration
LogManager.Configuration = config;
// Example usage
_logger = LogManager.GetLogger("Example");
If it will be relevant I also want to be able to specify say as the parameter the log file path to the initialization routine.
2) Any other class should be able to call a method like LogWrapper.Log("message"), which should log messages using my configured NLog object - I know which method of NLog writes the entry to log file, that is not a problem, say it is called _logger.write.
How can I achieve this in a (thread) safe way?
I have been struggling with this for a while already and would appreciate much help! This should not be that hard right, basically I am asking how to use NLog.
you deleted your previous question which answer this one so let me go at it again,
for #1, you need to put this code in a static constructor
ex;
static class test
{
static test()
{ /* ... code */}
}
for #2 to be safe you need a locker ex;
static class test
{
private static object _locker = new object();
static test()
{ /* ... code */}
public static void log(string msg)
{
lock(_locker )
{ /* ... code */}
}
}
In my application I want to do something like:
SomeApiClient apiClient = new SomeApiClient();
List<User> apiClient.getUsers();
In my web.config, I will a few configuration key/value pairs.
How can I write the constructor of SomeApiClient in such a way that it loads the values from the web.config, but not each time, only once when the application starts or first request?
Here ya go.
namespace dm2
{
using System.Collections.Specialized;
using System.Configuration;
public class SomeApiClient
{
internal static NameValueCollection Config
{
get
{
if (config == null) config = ConfigurationManager.AppSettings;
return config;
}
}
internal static NameValueCollection config;
}
}
Basically you just use a static property in a non static class...so in order to get your config settings,
public void DoFunConfigStuff()
{
for (var i = 0; i < Config.Count;i++ )
{
Console.WriteLine("[{0}]: {1}",Config.Keys[i] ,Config[i]);
}
}
Since you mentioned web.config, I'm assuming this is a web app. So I'd like to point out that you should expect that your app pool could be recycled at any time, at which point this would cause the static getter to reevaluate and load new settings. It's best not to reply on this.
One thing you could do is serialize this info to some medium, be it disk or database, and then have some kind of db switch, or webpage that will force a reload.
So in that getter it would check for the serialized data, if it doesn't exist, check web.config, and then save that data somewhere. Next time it gets recycled it will then pick up the old data. Really depends on your setup I suppose.