In my software there is a logger object (log4net) that regularly do its job: prints a log file into a folder.
However, since multiple threads are executed simultaneously (multithreading executed through multiple tasks), I get a very confusing log file.
I would like, without making too many changes to the code already written, to have a second log file that this time follows my logic before it is printed.
Until now, I was able to intercept the log4net logging events, and I'm trying to figure out what is the best way to solve my problem.
This is an example class where I use a normal logger:
class Core
{
private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType.Name); // my default logger
private static MyLogger myLogger = MyLogger.GetLogger(Thread.CurrentThread, logger); // my interceptor
public void ExecuteGenericMethodCommand(Metodo metodo)
{
logger.Info("First comment");
// ... stuff...
logger.Info("Second comment");
}
}
Here the implementation of a interceptor of the logger, with an appender:
internal class MyLogger
{
static MyLogger myLogger;
static Thread CurrentThread;
static Logger InterceptedLogger;
static MessageModifyingForwardingAppender newAppender = new MessageModifyingForwardingAppender();
private MyLogger()
{
myLogger = new MyLogger();
}
internal static MyLogger GetLogger(Thread currentThread, ILog logger)
{
CurrentThread = currentThread;
InterceptedLogger = (log4net.Repository.Hierarchy.Logger)logger.Logger;
InterceptedLogger.AddAppender(newAppender);
return myLogger;
}
class MessageModifyingForwardingAppender : ForwardingAppender
{
private static Queue<LoggingEvent> Queue;
public MessageModifyingForwardingAppender()
{
Queue = new Queue<LoggingEvent>();
}
~MessageModifyingForwardingAppender()
{
Flush();
}
protected override void Append(LoggingEvent loggingEvent)
{
Queue.Enqueue(loggingEvent);
}
internal void Flush()
{
while (Queue.Count > 0)
{
var l = Queue.Dequeue();
// my logic...
// and now I need to use the FileAppender, but with a different destination folder. How?
}
}
}
}
Some ideas?
Related
I'm trying to make an unit test for a logger in an application.
For example I need to test the method Logger.info("some message"), but this method is static and return void.
Searching on Google I understand that I have to use Moq but am unable to implement that on the UnitTest class.
The Logger constructor does not have an argument and in x.Debug I have an error that says that I can't access
from instance reference.
Is there a way to implement UnitTest without editing the production code?
[TestClass()]
public class LoggerTests
{
[TestMethod()]
public void DebugTest()
{
var mock = new Mock<Logger>();
mock.Setup(x => x.Debug(It.IsAny<string>());
new Logger(mock.Object).AddLog("testing");
mock.VerifyAll;
}
}
Program.cs
private static void ConfigureLogger()
{
Logger.AddLog(new NLogAppender());
Logger.Level = TraceLevel.Verbose;
Logger.Info("Configured Logger");
}
Logger.cs
public class Logger
{
public static readonly List<IAppender> loggings = new List<IAppender>();
public static void AddLog(IAppender appender)
{
loggings.Add(appender);
}
public static TraceLevel Level { get; set; }
static Logger()
{
Level = TraceLevel.Verbose;
}
public static void Info(string message)
{
LogMessage(message);
}
}
NlogAppender.cs
public class NLogAppender : IAppender
{
public NLog.Logger logger;
public NLogAppender()
{
logger = LogManager.GetLogger(nameof(NLogAppender));
}
public void AddLog(string str)
{
}
}
IAppender.cs
public interface IAppender
{
void AddLog(string str);
}
You can't mock a static class, and you shouldn't mock the class/system under test.
Add a mock appender to the logger:
// Arrange
var logString = "test-info"
var appenderMock = new Mock<IAppender>();
appenderMock.Setup(a => a.AddLog(logString));
Logger.AddLog(appenderMock.Object);
// Act
Logger.Info(logString);
// Assert
// TODO: exactly once
appenderMock.VerifyAll();
Note this static class may persist data between tests causing unexpected results, consult your test framework for configuring this.
Apart from that, you usually don't want to roll your own logging infrastructure, there's lots of things you can do wrong and why reinvent the wheel? Plenty of ILogger(<T>) implementations around.
I'm following a c# tutorial on udemy and finding how the code works very confusing. I'll paste the code then explain why
public interface ILogger
{
void LogError(string message);
void LogInfo(string message);
}
public class DbMigrator
{
private readonly ILogger _logger;
public DbMigrator(ILogger logger)
{
_logger = logger;
}
public void Migrate()
{
_logger.LogInfo("Migrationg started at {0}" + DateTime.Now);
// Details of migrating the database
_logger.LogInfo("Migrationg finished at {0}" + DateTime.Now);
}
}
public class FileLogger : ILogger
{
private readonly string _path;
public FileLogger(string path)
{
_path = path;
}
public void LogError(string message)
{
Log(message, "ERROR");
}
public void LogInfo(string message)
{
Log(message, "INFO");
}
private void Log(string message, string messageType)
{
using (var streamWriter = new StreamWriter(_path, true))
{
streamWriter.WriteLine(messageType + ": " + message);
}
}
}
class Program
{
static void Main(string[] args)
{
var dbMigrator = new DbMigrator(new FileLogger("C:\\Projects\\log.txt"));
dbMigrator.Migrate();
}
}
I'm getting confused about how do interfaces work in memory(which I think is called the heap)
since dbmigrator calls migrate()
which is using type of Ilogger when an instance of FileLogger is passed but appears to work without explicitly casting which I'm finding very confusing to how it works.
Hopefully my question makes sense as a lot of these concepts are new to me and I'm struggling with the correct terminology to explain my confusion
Thanks
DbMigrator can call any class that implements the ILogger interface. It just happens you have a FileLogger, but you could also implement a logger that writes to a database, as long as you implement all the functions in the interface.
class MySuperLogger : ILogger
{
void LogError(string message) { /* do something super */ }
void LogInfo(string message) { /* do something super */ }
}
class Program
{
static void Main(string[] args)
{
var dbMigrator = new DbMigrator(new MySuperLogger());
dbMigrator.Migrate();
}
}
See how there is no difference in the dbMigrator code, just a different type of ILogger was created. Everything else is the same.
Do I need to call Run() inside Main() method? So it will be call on daily basis at mentioned time in code.
public class Program
{
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main()
{
var host = new JobHost();
// The following code ensures that the WebJob will be running continuously
host.RunAndBlock();
}
// This method will be called on weekly basis
public static void Run([TimerTrigger(typeof(MyDailySchedule))] TimerInfo timerInfo, TextWriter log)
{
log4net.Config.XmlConfigurator.Configure();
try
{
MainA.Wait();
}
catch (Exception ex)
{
}
}
static async Task MainA()
{
WebJob1 Service = new WebJob1();
await Service.DeletData();
}
}
public class MyDailySchedule : DailySchedule
{
public MyDailySchedule() :
//Schedule
base("2:00:00", "14:00:00", "15:00:00")
{ }
}
You don't need to use the WebJobs SDK to achieve this. Instead:
Write a simple Console app that directly does what you need to when it's launched (i.e. don't use any JobHost).
Deploy it as a Scheduled WebJob using cron expressions (see doc for details).
I have a C# project in .NET Core with unit tests. In order to better examine the flow through the code during a test I would really like to see the output of the NLog messages that are built into the code base.
I am using XUnit and I know XUnit has moved away from Console messages because of parallel test execution. For what I want to accomplish using ITestOutputHelper won't be very helpful.
Is there a way to get NLog logging to be included in test output?
I use something like this:
namespace Tests.Builders
{
internal static class Logging
{
public static ILogger DefaultLogger()
{
return NullLogger();
}
public static ILogger NullLogger()
{
return Substitute.For<ILogger>();
}
public static ILogger XUnitLogger(ITestOutputHelper testOutputHelper)
{
// Step 1. Create configuration object
var config = new LoggingConfiguration();
// Step 2. Create targets and add them to the configuration
var consoleTarget = new XUnitTarget(testOutputHelper);
config.AddTarget("xunit", consoleTarget);
// Step 3. Set target properties
consoleTarget.Layout = #"${date:format=HH\:mm\:ss} ${logger} ${message}";
// Step 4. Define rules
var rule1 = new LoggingRule("*", LogLevel.Trace, consoleTarget);
config.LoggingRules.Add(rule1);
// Step 5. Activate the configuration
LogManager.Configuration = config;
return LogManager.GetLogger("");
}
}
[Target("XUnit")]
public sealed class XUnitTarget : TargetWithLayout
{
private readonly ITestOutputHelper _output;
public XUnitTarget(ITestOutputHelper testOutputHelper)
{
_output = testOutputHelper;
}
protected override void Write(LogEventInfo logEvent)
{
string logMessage = this.Layout.Render(logEvent);
_output.WriteLine(logMessage);
}
}
}
public class SomeTests
{
private readonly ITestOutputHelper _output;
public SomeTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void Lets_see_some_output()
{
var foo = new Foo(Logging.XUnitLogger(_output));
var result = foo.ExecuteSomething();
Assert.Equal(true, result);
}
}
I've previously used log4net, but my current employer uses Enterprise Library application blocks. I had previously developed unit tests for my core logging classes as follows and was wondering if someone knew the equivalent for the OneTimeSetup code below for the logging app block (sorry for the long code post):
public abstract class DataGathererBase
{
public readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public void CollectData()
{
this.LogDebug("Initialize started");
}
public static class Logger
{
private static LoggingSettings settings = LoggingSettings.GetLoggingSettings(new SystemConfigurationSource());
static Logger()
{
log4net.Config.XmlConfigurator.Configure();
}
public static void LogDebug(this DataGathererBase current, string message)
{
if (current.logger.IsDebugEnabled)
{
current.logger.Debug(string.Format("{0} logged: {1}", current.GetType().Name, message));
}
}
}
[TestFixture]
public class LoggerTests:DataGathererBase
{
private ListAppender appender;
private static ILog log;
[TestFixtureSetUp]
public void OneTimeSetup()
{
appender = new ListAppender();
appender.Layout = new log4net.Layout.SimpleLayout();
appender.Threshold = log4net.Core.Level.Fatal;
log4net.Config.BasicConfigurator.Configure(appender);
log = LogManager.GetLogger(typeof(ListAppender));
}
[Test]
public void TestLogging()
{
this.LogDebug("Debug");
Assert.AreEqual(0, ListAppender.logTable.Count());
}
}
Enterprise Library 5.0 introduced a fluent interface which can be used to programmatically configure the application blocks. You will probably find this to be a more comfortable option.
To give credit, this answer is based on a David Hayden article which is based on an Alois Kraus article, Programatic Configuraton - Enterprise Library (v2.0) Logging Block . Read those two articles for a good look at programmatic access to Enterprise Library logging.
I wasn't familiar with ListAppender so I created a CustomTraceListener that sticks the log messages in a List<string>:
public class ListAppender : Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.CustomTraceListener
{
private List<string> list = new List<string>();
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
list.Add(message);
}
public List<string> LogTable
{
get
{
return list;
}
}
}
Here is a modified LoggerTests class that programmatically accesses the EL logging classes to setup the tests (this does not use NUnit):
public class LoggerTests
{
private ListAppender appender;
private static LogWriter log;
public void OneTimeSetup()
{
appender = new ListAppender();
// Log all source levels
LogSource mainLogSource = new LogSource("MainLogSource", SourceLevels.All);
mainLogSource.Listeners.Add(appender);
// All messages with a category of "Error" should be distributed
// to all TraceListeners in mainLogSource.
IDictionary<string, LogSource> traceSources = new Dictionary<string, LogSource>();
traceSources.Add("Error", mainLogSource);
LogSource nonExistentLogSource = null;
log = new LogWriter(new ILogFilter[0], traceSources, nonExistentLogSource,
nonExistentLogSource, mainLogSource, "Error", false, false);
}
public void TestLogging()
{
LogEntry le = new LogEntry() { Message = "Test", Severity = TraceEventType.Information };
le.Categories.Add("Debug");
log.Write(le);
// we are not setup to log debug messages
System.Diagnostics.Debug.Assert(appender.LogTable.Count == 0);
le.Categories.Add("Error");
log.Write(le);
// we should have logged an error
System.Diagnostics.Debug.Assert(appender.LogTable.Count == 1);
}
}