log4net: different logs on different file appenders at runtime - c#

Good morning guys.
I wrote a single istance C# 2.0 app (call it myapp).
Myapp is called many times, and at every call generates a sort of "task" that will be executed in a separated thread.
If you call myapp several times in a short time, task are executed in parallel.
Generally I use log4net for logging purposes; I configure it loading an xml file by XmlConfigurator.Configure(<config>.xml) at startup, then I use static LogManager.GetLogger(name) in every class I need a logger, quite simple.
This scenario is challenging, instead.
I need to do is this: based on one of the args received on every call (call it arg), I need to get a different RollingFileAppender that logs in a different file, e. g. .log.
Just to make an example:
1st call: myapp.exe -arg:01
- myapp creates thread1
- set a new RollingFileAppender to 01.log file, if not exists
- objects used in this thread must log in 01.log file
2nd call: myapp.exe -arg:02
- create thread2
- set a new RollingFileAppender to 02.log file, if not exists
- objects used in this thread must log in 02.log file, but not in log.01
3rd call: myapp.exe -arg:01
- create thread03
- get the RollingFileAppender to 01.log file (it already exists!)
- objects used in this thread must log in 01.log file, but not in log.02
And so on.
I don't need to leave the configuration of RollingAppender in xml file, I can create it programmatically; my idea is to use a static wrapper class, call it LogHelper, that creates appenders if they do not exist based on arg, and that dispatch right ILog istances when needed by objects (in classes I would use something like ILog log = LogHelper.GetLogger(name, arg) to get a logger to use instead o default log4net method LogManager.GetLogger(name)).
So if I have 2 istances of the same class in 2 different threads, when I log messages goes one per file, depending or arg (I will inject arg in every object, if needed).
I browse many threads here in StackOverflow, but I can't find a solution.
Can someone point me to the right direction?
Thanks in advance.

I ended up with a slightly different solution.
I created a LogMaster static class (sorry for poor name) that work similar to the default log4net LogManager class.
The difference is that you can get different ILog istances based on arg: LogMaster will create a new ILoggerRepository for each different arg you will use.
Here the code:
#region Usings
using System;
using System.IO;
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Core;
using log4net.Filter;
using log4net.Layout;
using log4net.Repository;
using log4net.Repository.Hierarchy;
#endregion
namespace Common.Voyager
{
/// <summary>
/// A static class that emulates defualt log4net LogManager static class.
/// The difference is that you can get various loggers istances based from an args.
/// LogMaster will create a different logger repository for each new arg it will receive.
/// </summary>
public static class LogMaster
{
#region Const
private const string RollingFileAppenderNameDefault = "Rolling";
private const string MemoryAppenderNameDefault = "Memory";
#endregion
#region Constructors
static LogMaster()
{
}
#endregion
#region Public Methods
public static ILog GetLogger(string arg, string name)
{
//It will create a repository for each different arg it will receive
var repositoryName = arg;
ILoggerRepository repository = null;
var repositories = LogManager.GetAllRepositories();
foreach (var loggerRepository in repositories)
{
if (loggerRepository.Name.Equals(repositoryName))
{
repository = loggerRepository;
break;
}
}
Hierarchy hierarchy = null;
if (repository == null)
{
//Create a new repository
repository = LogManager.CreateRepository(repositoryName);
hierarchy = (Hierarchy)repository;
hierarchy.Root.Additivity = false;
//Add appenders you need: here I need a rolling file and a memoryappender
var rollingAppender = GetRollingAppender(repositoryName);
hierarchy.Root.AddAppender(rollingAppender);
var memoryAppender = GetMemoryAppender(repositoryName);
hierarchy.Root.AddAppender(memoryAppender);
BasicConfigurator.Configure(repository);
}
//Returns a logger from a particular repository;
//Logger with same name but different repository will log using different appenders
return LogManager.GetLogger(repositoryName, name);
}
#endregion
#region Private Methods
private static IAppender GetRollingAppender(string arg)
{
var level = Level.All;
var rollingFileAppenderLayout = new PatternLayout("%date{HH:mm:ss,fff}|T%2thread|%25.25logger|%5.5level| %message%newline");
rollingFileAppenderLayout.ActivateOptions();
var rollingFileAppenderName = string.Format("{0}{1}", RollingFileAppenderNameDefault, arg);
var rollingFileAppender = new RollingFileAppender();
rollingFileAppender.Name = rollingFileAppenderName;
rollingFileAppender.Threshold = level;
rollingFileAppender.CountDirection = 0;
rollingFileAppender.AppendToFile = true;
rollingFileAppender.LockingModel = new FileAppender.MinimalLock();
rollingFileAppender.StaticLogFileName = true;
rollingFileAppender.RollingStyle = RollingFileAppender.RollingMode.Date;
rollingFileAppender.DatePattern = ".yyyy-MM-dd'.log'";
rollingFileAppender.Layout = rollingFileAppenderLayout;
rollingFileAppender.File = string.Format("{0}.{1}", "log", arg);
rollingFileAppender.ActivateOptions();
return rollingFileAppender;
}
private static IAppender GetMemoryAppender(string station)
{
//MemoryAppender
var memoryAppenderLayout = new PatternLayout("%date{HH:MM:ss} | %message%newline");
memoryAppenderLayout.ActivateOptions();
var memoryAppenderWithEventsName = string.Format("{0}{1}", MemoryAppenderNameDefault, station);
var levelRangeFilter = new LevelRangeFilter();
levelRangeFilter.LevelMax = Level.Fatal;
levelRangeFilter.LevelMin = Level.Info;
var memoryAppenderWithEvents = new MemoryAppenderWithEvents();
memoryAppenderWithEvents.Name = memoryAppenderWithEventsName;
memoryAppenderWithEvents.AddFilter(levelRangeFilter);
memoryAppenderWithEvents.Layout = memoryAppenderLayout;
memoryAppenderWithEvents.ActivateOptions();
return memoryAppenderWithEvents;
}
#endregion
}
}
Usage:
var arg = "myArg";
var loggerName = "MyLogger";
var log = LogMaster.GetLogger(arg, loggerName);
Using this solution you can benefit from default LogManager behavior retrieving ILog loggers: if a logger with the same name already exists in a repository, you will get back that istance (recycling behavior).
Thank you #making3 for your suggestions!

I've done something similar, where I needed a different log for every instance of a class. You can create logs dynamically with a few steps.
It looks like a default Logger (Line 97) is already defined, but it's internal to their assembly, so it will need to be inherited (as far as I know).
public sealed class DynamicLogger : Logger
{
internal DynamicLogger(string name) : base(name)
{
base.Hierarchy = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
}
}
Sample method to retrieve an ILog:
public static ILog GetSample(string arg)
{
var logger = new DynamicLogger(arg);
logger.Level = Level.All;
var consoleAppender = new ConsoleAppender();
consoleAppender.Name = arg;
consoleAppender.Layout = new PatternLayout(arg + ": %m%newline");
logger.AddAppender(consoleAppender);
var newLog = new LogImpl(logger);
if (_logs.Any(log => log.Logger.Name == newLog.Logger.Name) == false)
_logs.Add(newLog);
return newLog;
}
Basic usage:
var foo = DynamicLog.GetSample("foo");
var bar = DynamicLog.GetSample("bar");
foo.Error("Test");
bar.Error("Test");
For your scenario, look at creating a RollingFileAppender, and look at the available properties on the object, since this was just an example.
EDIT: Added the below for storing ILog's, and modified the original GetSample method above.
Add an array of ILog's and a GetLogger method:
private static List<ILog> _logs = new List<ILog>();
public static ILog GetLogger(string name)
{
return _logs.SingleOrDefault(a => a.Logger.Name == name);
}
Sample usage:
DynamicLog.GetSample("foo");
var foo = DynamicLog.GetLogger("foo");

Related

Mock log file for unit test in .NET Core application

In my C# application I have a getLogFile which is set by calling the GetLogFile() method:
private static string getLogFile = GetLogFile();
private static string GetLogFile()
{
var fileTarget = (FileTarget)LogManager.Configuration.FindTargetByName("file-target");
var logEventInfo = new LogEventInfo();
string fileName = fileTarget.FileName.Render(logEventInfo);
if (!File.Exists(fileName))
throw new Exception("Log file does not exist.");
return fileName;
}
I'm now trying to unit test some code which will require the getLogFile variable to be set. I'd like to mock this in some way as I'd like to use specific log data for my test but am not sure how to go about it. How can I do this?
"Mocking" a private static field or method is not really possible.
To have this kind of method as a private member of another class smells like a violation of Single Responsibility Principle.
You should probably refactor this behavior to a separate class and hide it behind an interface. I'm not entirely sure what the NLog Code does, but what your method really seem to do is not providing the log file, but the name of the log file (you return fileName) So this is how it could look like:
public interface ILogFileNameProvider
{
string GetLogFileName();
}
public class DefaultLogFileNameProvider : ILogFileNameProvider
{
public string GetLogFileName()
{
// your code ommitted
}
}
It's just an example on how you can handle it. The naming/structure and how you use it is up to you.
This interface can then be injected in the using class that currently has the private methods. This dependency/call can be mocked.
Usage with constructor injection:
public class LogFileNameUsingService
{
private readonly ILogFileNameProvider _logFileNameProvider;
public LogFileNameUsingService(ILogFileNameProvider logFileNameProvider)
{
_logFileNameProvider = logFileNameProvider;
}
}
Test with xUnit and AutoMocker as example:
[Theory]
public void TestWithMockedLogFileName()
{
//Arrange
var mocker = new AutoMocker();
var logFileNameProviderMock = mocker.GetMock<ILogFileNameProvider>();
logFileNameProviderMock.Setup(x => x.GetLogFileName()).Returns("your mocked file name.log");
var sut = mocker.CreateInstance<LogFileNameUsingService>();
//Act
var result = sut.TestedMethodWhichUsesLogFileName();
//Assert whatever you want to test
}
This also allows you swap out the current logic to get a log file later without changing the logic of the existing class.

How to dynamically invoke a class based on a string?

Can you give me some guidance to dynamically load class based on name. The name is provided as a argument at runtime.
static void Main(string[] args)
{
...
}
Let's say my argument is "parrot" then I would like to load my parrotProcessor class. If my argument name is "snake" then I load my To make snakeProcessor. Of course this mean I have a parrot and snake processor class that inherit an interface IProcessor. I don't know what could be the list of all processor. this list is maintained by other developers and they can create what they want.
Example of my processor interface:
public interface IProcessor
{
void Process();
}
And in my Program.cs
static void Main(string[] args)
{
var processor = GetProcessor(args[0]);
processor.Process();
}
My question is what should I do in GetProcessor() method?
Here is what I have at this moment:
private IProcessor GetProcessor(string name)
{
switch (name)
{
case "ant":
return new AntProcessor();
case "parrot":
return new ParrotProcessor();
case "snake":
return new SnakeProcessor();
default:
throw new ArgumentException("Case not found");
}
}
But this mean I must updated this ugly switch each time I create a new processor specialized class. This is not possible. I can do it now for development but not for long term.
What are the solutions? Should I use something similar to DI with NInject or am I mixing everything? Can I simply use the Invoke method? And why Invoke or DI? I know this question is kind of open question but it is something that happen often in code of many people so I suppose there is unique best practice about it.
You can use something like the following
var type = Type.GetType("MyFullyQualifiedTypeName");
var myObject = (MyAbstractClass)Activator.CreateInstance(type);
You need to do some string modifications like taking the word, adding the processor string and making sure that all the processors are at the same place.
If you are certain that the type is in the current assembly, simply fully qualified name by this
Activator.CreateInstance(Type.GetType("SomeProcessor"));
First of all, there is absolutely nothing wrong with using a switch block as you are doing. It gets the job done, and it serves as an explicit source of truth with regards to the interpretation of the command-line parameter. It also acts as a whitelist, preventing users from passing a class name that maybe you don't want them to instantiate. Yes, you have to update it every time you add a new class, but you're already adding the class itself, so you are already in a situation where a new version and deployment will be needed.
That being said, there are various reasons why you'd rather have the ability to look up a command line parameter and automatically know which type to instantiate. That is trivial to do with a Dictionary<string,Func<IProcessor>>:
this.Map = new Dictionary<string,Func<IProcessor>>
{
{ "ant", () => new AntProcessor() },
{ "snake", () => new SnakeProcessor() },
{ "parrot", () => new ParrotProcessor() },
{ "lizard", () => new LizardProcessor() }
};
Then you would handle a command-line parameter like this:
//Use the string to look up a delegate
var ok = this.Map.TryGetValue(textFromCommandline, out var func);
//If not found, user entered a bad string
if (!ok) throw new ArgumentException();
//Invoke the delegate to obtain a new instance
IProcessor processor = func();
return processor;
Once you understand the concept of using a map in this way, you can come up with a scheme to populate it automatically, e.g
this.Map = assembly.GetTypes()
.Where( t => typeof(IProcessor).IsAssignableFrom( t ))
.ToDictionary
(
t => t.Name,
t => new Func<IProcessor>( () => Activator.CreateInstance(t) as IProcessor );
);
And you will have met your goal of not having to maintain a hardcoded list anywhere.
You can rewrite private IProcessor GetProcessor(string name) method using a little mix of reflection and LINQ like this:
private IProcessor GetProcessor<Tinterface>(string name) where Tinterface : IProcessor
{
var type = typeof(Tinterface).Assembly.GetTypes()
.First(x => x.FullName.Contains("name"));
return (Tinterface)Activator.CreateInstance(type);
}
Usage:
static void Main(string[] args)
{
var processor = GetProcessor<IProcessor>(args[0]);
processor.Process();
}
This route saves you the stress of typing the fully qualified name of the class
You can try iterate over all exported types in specific assembly:
using System;
using System.Linq;
using System.Reflection;
namespace ConsoleApp
{
public class Program
{
public static void Main (string[] args)
{
Assembly assembly = typeof (Program).Assembly; // use current assembly...
var types = assembly.GetExportedTypes() // public types only
.Where (type => type.GetInterfaces().Contains (typeof (IProcessor))) // interface must be implemented
.Where (type => type.Name.EndsWith ("Processor")) // and maybe use some naming convention?
.ToList();
//string name = args[0];
string name = "Parrot";
Type parrotType = types.Where (x => x.Name.StartsWith (name)).FirstOrDefault();
if (parrotType != null)
{
// it will work only when we implement parameterless constructor for this type
IProcessor parrotInstance = (IProcessor) Activator.CreateInstance (parrotType);
parrotInstance.Process();
}
}
}
public interface IProcessor
{
void Process();
}
public class SnakeProcessor : IProcessor
{
public void Process()
{
}
}
public class ParrotProcessor : IProcessor
{
public void Process()
{
Console.WriteLine ("Parrot Process");
}
}
}
I will add to the answers that suggest using reflection : EX: IProcessor parrotInstance = (IProcessor) Activator.CreateInstance(parrotType); is to make this part of code in a separate Factory class for Processor this way you can reuse the factory class in other places in your code, also even if you decide to keep the switch statement, the changes on the factory class will only affect it without affecting any dependent code.
public interface IProcessorFactory
{
IProcessor GetProcessor(string processorTypeName);
}
Good read:
SOLID Design Principles

Ninject Constructor argument check parameter existence

I was wondering if there is anyway, how to do a parameter existence check with Ninject?
What I am referring to: let's have a theoretical class and an interface:
public interface IFileExistenceCheck
{
bool FileExist();
}
public class FileExistenceChecker : IFileExistenceCheck
{
private readonly string filePath;
private readonly IFileSystem fileSystem;
public FileExistenceChecker(IFileSystem fileSystem, string filePath)
{
this.fileSystem = fileSystem;
this.filePath = filePath;
}
public bool FileExist()
{
return this.fileSystem.File.Exists(this.filePath);
}
}
Then somewhere in the code, I will be getting an instance of IFIleExistenceCheck interface via kernel like so:
public class Foo()
{
public void Bar()
{
// do something
var filePath = SomeMagicString;
var filePathArgument = new ConstructorArgument("filePath", filePath); // <- This part I do not really like
var checker = Kernel.Get<IFileExistenceCheck>(filePathArgument);
var fileExist = checker.FileExist();
// do the rest of code
}
}
This will work just fine, problem is, that it will only work as long as the name of file path argument stays the same. Let's say one day somebody will decide, that filePath is unnecessary and renames it just to path. The code itself will still compile, but it will not cause any error until somebody will actually do the call to the Bar() method.
Is there any way how to prevent this from happening?
I do not really want to expose the filePath. I still want it to be passed as an argument of a constructor. I do not want to change signature of FileCheck() to accept filePath as an argument and I do not even want to change filePath to publicly accessible field.
This is abusing the DI container as a service locator, and yet another reason why it service locator is considered to be anti-pattern.
Your contrived example would not happen if you were using the dependency injection pattern. Refactoring your example to use dependency injection, it would look like this:
public interface IFileExistanceCheck
{
bool FileExist(string filePath);
}
public class FileExistanceChecker : IFileExistanceCheck
{
private readonly IFileSystem fileSystem;
public FileExistanceChecker(IFileSystem fileSystem)
{
if (fileSystem == null)
throw new ArgumentNullException(nameof(fileSystem));
this.fileSystem = fileSystem;
}
// Pass runtime data through the method parameters!
public bool FileExist(string filePath)
{
// Prevent an empty file path from being used
if (string.IsNullOrEmpty(filePath))
throw new ArgumentNullException(nameof(filePath));
return this.fileSystem.File.Exists(filePath);
}
}
Foo Class
public class Foo
{
private readonly IFileExistanceCheck fileExistanceCheck;
public Foo(IFileExistanceCheck fileExistanceCheck)
{
if (fileExistanceCheck == null)
throw new ArgumentNullException(nameof(fileExistanceCheck));
this.fileExistanceCheck = fileExistanceCheck;
}
public void Bar()
{
// do something
var filePath = SomeMagicString;
var fileExist = fileExistanceCheck.FileExist(filePath);
// do the rest of code
}
}
At the composition root, Ninject would tie it all together and get Foo running.
class Program
{
static void Main(string[] args)
{
// Begin composition root
var kernel = new StandardKernel();
kernel.Bind<IFileSystem>().To<FileSystem>();
kernel.Bind<IFileExistanceCheck>().To<FileExistanceChecker>();
var app = kernel.Get<Foo>();
// End composition root
app.Bar();
}
}
If there is a need to check for the existence of the filePath parameter, this check can be done using a guard clause. You only need to use the dependency injection pattern, and pass the runtime data (the file path) through a method parameter.
If you want the filePath to be a configuration value that is passed through the constructor at application startup, then having a service to check for file existence seems rather pointless. In that case, you should check whether the file exists before allowing your application to run.
class Program
{
static void Main(string[] args)
{
var filePath = "SomeFileThatShouldExist.txt";
// Check configuration
if (!File.Exists(filePath))
throw new InvalidOperationException("Invalid configuration");
// Begin composition root
var kernel = new StandardKernel();
kernel.Bind<Foo>().To(new Bar(filePath));
// Register other services...
var app = kernel.Get<Foo>();
// End composition root
app.Bar();
}
}
If your parameter is known only in runtime you can inject factory and create your FileExistenceChecker with it:
public interface IFileExistenceCheckerFactory
{
IFileExistenceCheck Create(string path);
}
...
var kernel = new StandardKernel();
kernel.Bind<IFileExistenceCheck>().To<FileExistenceChecker>();
kernel.Bind<IFileSystem>().To<FileSystem>();
kernel.Bind<IFileExistenceCheckerFactory>().ToFactory(() => new TypeMatchingArgumentInheritanceInstanceProvider());
var factory = kernel.Get<IFileExistenceCheckerFactory>();
var checker = factory.Create("SomeMagicString");
var fileExist = checker.FileExist();
Then even if your parameter names don't match, TypeMatchingArgumentInheritanceInstanceProvider ensure that parameters will be matched by its type.

Can we assign TextContext.TestDeploymentDir value to a static class data member?

I am very new to CodedUI test automation framework. I came across TestContext which has the important information regarding test result outputs and directories.
Actually i have created a static Logger class which writes output data to a .txt file. Now i want to create it under TestResults folder. Everytime i am running the test method it is creating a folder followed by some timestamp. I want to create my Results.txt file in that location.
Below is the code i am using :
public static class Logger
{
string logLocation = TestContext.TestDeploymentDir + "\\Results.txt";
static Logger() {
File.Create(logLocation);
using (var fs = new FileStream(logLocation, FileMode.Truncate))
{
}
}
public static void ResultLog(int testcasenum,String Expected,String Actual, String textResult)
{
FileInfo outtxt = new FileInfo(logLocation);
StreamWriter logline = outtxt.AppendText();
logline.WriteLine("Test Case : " + testcasenum);
logline.WriteLine("{0},{1},{2}", "Expected - "+Expected, "Actual - "+Actual, "Result - "+textResult);
// flush and close file.
logline.Flush(); logline.Close();
}
}
Now i am getting a compile time error saying A field initializer cannot reference the non-static field, method, or property TestContext.TestDeploymentDir. Not sure how to resolve this error or whether it is possible or not ?
You're going to need to mark logLocation as static since it is contained in a static class. It's kind of silly but all members of a static class do also need to be marked static. I believe this is to prevent confusion when reading larger classes where you cannot see the class declaration. Next your current error message also says TestContext.TestDeploymentDir is not marked static and you'll need to modify that as well if possible. If not you'll need to implement a singleton pattern to provide a static copy of an instance of that class. Depending on the way that class works it may or may not be possible.
Finally figured out a way to get the out path of Coded UI framework. Below is the code i have written :
public static class Logger
{
static string uripath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) + "\\Results.txt";
public static string logLocation = new Uri(uripath).LocalPath;
static Logger() {
using (File.Create(logLocation))
{ }
using (var fs = new FileStream(logLocation, FileMode.Truncate)){}
}
public static void ResultLog(int testcasenum,String Expected,String Actual, String textResult)
{
FileInfo outtxt = new FileInfo(logLocation);
StreamWriter logline = outtxt.AppendText();
logline.WriteLine("Test Case : " + testcasenum);
logline.WriteLine("{0},{1},{2}", "Expected - "+Expected, "Actual - "+Actual, "Result - "+textResult);
// flush and close file.
logline.Flush(); logline.Close();
}
}
uripath will contain the same path as that of TestContext.TestDeploymentDir. Now Results.txt will come as an attachment in Test Explorer and we can see the output.

MEF Ignores CreationPolicy

I want to create an delegate based export for an interface which is called upon every compose call to return every consumer a new instance. But apparently I do get always the same instance back. Is this a limitation of MEF (I am using .NET 4.0)?
Here is the sample code how I tried to create fresh instances for every compose call:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
namespace TestApp
{
class Program
{
[Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
ITest Instance { get; set; }
static void Main(string[] args)
{
CompositionContainer container = new CompositionContainer();
CompositionBatch batch = new CompositionBatch();
AddExportedValue<ITest>(batch, () =>
{
Console.WriteLine("Create new");
return new Haha();
}, CreationPolicy.NonShared);
container.Compose(batch);
Program p1 = new Program();
container.SatisfyImportsOnce(p1);
// Why do I see only one "Create new" print although CreationPolicy on import and export level is NonShared?
container.SatisfyImportsOnce(p1);
}
interface ITest
{}
class Haha : ITest
{}
static ComposablePart AddExportedValue<iT>(CompositionBatch batch, Func<iT> factory, CreationPolicy policy)
{
string contractNameAndTypeIdentity = AttributedModelServices.GetContractName(typeof(iT));
IDictionary<string, object> metadata = new Dictionary<string, object>();
metadata.Add(CompositionConstants.ExportTypeIdentityMetadataName, contractNameAndTypeIdentity);
metadata.Add(CompositionConstants.PartCreationPolicyMetadataName, policy); // <--- the policy seems to be ignored by MEF although incompatible policies are errored out with an exception.
return batch.AddExport(new Export(contractNameAndTypeIdentity, metadata, () => factory()));
}
}
}
Expected Output:
Create new
Create new
Actual Output
Create new
you can use ExportFactory with .NET4.0 too. Glenn Block posted a version a while ago. i use it in my projects too.

Categories

Resources