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.
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.
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?
This question is related to Steven’s answer - here. He proposed a very good logger wrapper. I will paste his code below:
public interface ILogger
{
void Log(LogEntry entry);
}
public static class LoggerExtensions
{
public static void Log(this ILogger logger, string message)
{
logger.Log(new LogEntry(LoggingEventType.Information,
message, null));
}
public static void Log(this ILogger logger, Exception exception)
{
logger.Log(new LogEntry(LoggingEventType.Error,
exception.Message, exception));
}
// More methods here.
}
So, my question is what is the proper way to create implementation that proxies to Microsoft.Extensions.Logging and what is the best way to use it later in the code?
Note: this question is a copy of this question about log4net but now specific to Microsoft.Extensions.Logging.
So, my question is what is the proper way to create implementation that proxies to Microsoft.Extensions.ILogger?
you should create something like:
public sealed class MicrosoftLoggingAdapter : ILogger
{
private readonly Microsoft.Extensions.ILogger adaptee;
public MicrosoftLoggingAdapter (Microsoft.Extensions.ILogger adaptee) =>
this.adaptee = adaptee;
public void Log(LogEntry e) =>
adaptee.Log(ToLevel(e.Severity), 0, e.Message, e.Exception, (s, _) => s);
private static LogLevel ToLevel(LoggingEventType s) =>
s == LoggingEventType.Debug ? LogLevel.Debug :
s == LoggingEventType.Information ? LogLevel.Information :
s == LoggingEventType.Warning ? LogLevel.Warning :
s == LoggingEventType.Error ? LogLevel.Error :
LogLevel.Critical;
}
what is the best way to use it later in the code?
If you are using a DI container, then just use the DI container to map ILogger to MicrosoftLoggingAdapter. You also need to register Microsoft.Extensions.ILogger, or just give an instance of MS logger to the DI container to inject it to the MicrosoftLoggingAdapter constructor.
If you don't use a DI container, i.e., you use Pure DI, then you do something like this:
var logger = loggerFactory.CreateLogger("Application");
ILogger logging_adapter = new MicrosoftLoggingAdapter(logger);
var myobject = new MyClass(other_dependencies_here, logging_adapter);
Here is my solution. Not too unlike Steven's. But not exactly like it. And mine leans toward dotNetCore, but the same thing can be accomplished in dotnetFW.
DotNetCoreLogger is the concrete of "MY" ILogger. And I inject the "microsoft" ILogger (Microsoft.Extensions.Logging.ILogger) into "My" concrete logger.
There is another SOF answer that "inspired" "my" logging abstraction....and after going from DotNetFramework (classic) to DotNotCore, I am so glad I did a "my" ILogger abstraction.
using System;
namespace MyApplication.Infrastructure.Logging.LoggingAbstractBase
{
public interface ILogger
{
void Log(LogEntry entry);
void Log(string message);
void Log(Exception exception);
}
}
namespace MyApplication.Infrastructure.Logging.LoggingAbstractBase
{
public enum LoggingEventTypeEnum
{
Debug,
Information,
Warning,
Error,
Fatal
};
}
using System;
namespace MyApplication.Infrastructure.Logging.LoggingAbstractBase
{
public class LogEntry
{
public readonly LoggingEventTypeEnum Severity;
public readonly string Message;
public readonly Exception Exception;
public LogEntry(LoggingEventTypeEnum severity, string message, Exception exception = null)
{
if (message == null) throw new ArgumentNullException("message");
if (message == string.Empty) throw new ArgumentException("empty", "message");
this.Severity = severity;
this.Message = message;
this.Exception = exception;
}
}
}
using System;
using Microsoft.Extensions.Logging;
namespace MyApplication.Infrastructure.Logging.LoggingCoreConcrete
{
public class DotNetCoreLogger<T> : MyApplication.Infrastructure.Logging.LoggingAbstractBase.ILogger
{
private readonly ILogger<T> concreteLogger;
public DotNetCoreLogger(Microsoft.Extensions.Logging.ILogger<T> concreteLgr)
{
this.concreteLogger = concreteLgr ?? throw new ArgumentNullException("Microsoft.Extensions.Logging.ILogger is null");
}
public void Log(MyApplication.Infrastructure.Logging.LoggingAbstractBase.LogEntry entry)
{
if (null == entry)
{
throw new ArgumentNullException("LogEntry is null");
}
else
{
switch (entry.Severity)
{
case LoggingAbstractBase.LoggingEventTypeEnum.Debug:
this.concreteLogger.LogDebug(entry.Message);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Information:
this.concreteLogger.LogInformation(entry.Message);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Warning:
this.concreteLogger.LogWarning(entry.Message);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Error:
this.concreteLogger.LogError(entry.Message, entry.Exception);
break;
case LoggingAbstractBase.LoggingEventTypeEnum.Fatal:
this.concreteLogger.LogCritical(entry.Message, entry.Exception);
break;
default:
throw new ArgumentOutOfRangeException(string.Format("LogEntry.Severity out of range. (Severity='{0}')", entry.Severity));
}
}
}
public void Log(string message)
{
this.concreteLogger.LogInformation(message);
}
public void Log(Exception exception)
{
/* "Always pass exception as first parameter" from https://blog.rsuter.com/logging-with-ilogger-recommendations-and-best-practices/ */
/* there is an issue with https://github.com/aspnet/Logging/blob/master/src/Microsoft.Extensions.Logging.Abstractions/LoggerExtensions.cs
* the default MessageFormatter (for the extension methods) is not doing anything with the "error". this plays out as not getting full exception information when using extension methods. :(
*
* private static string MessageFormatter(FormattedLogValues state, Exception error)
* {
* return state.ToString();
* }
*
* Below code/implementation is purposely NOT using any extension method(s) to bypass the above MessageFormatter mishap.
*
* */
this.concreteLogger.Log(LogLevel.Error, exception, exception.Message);
}
}
}
/* IoC/DI below */
private static System.IServiceProvider BuildDi(Microsoft.Extensions.Configuration.IConfiguration config)
{
//setup our DI
IServiceProvider serviceProvider = new ServiceCollection()
.AddLogging()
.AddSingleton<IConfiguration>(config)
.AddSingleton<MyApplication.Infrastructure.Logging.LoggingAbstractBase.ILogger, MyApplication.Infrastructure.Logging.LoggingCoreConcrete.DotNetCoreLogger<Program>>()
.BuildServiceProvider();
//configure console logging
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug);
return serviceProvider;
}
I'm creating a background task controller as following:
public class TaskController
{
private TaskBase task;
public TaskController(ITask task)
{
this.task = task;
}
public void DoSomething()
{
task.DoSomething();
}
}
ITask interface:
interface ITask
{
void DoSomething();
}
TaskBase abtract class:
public abtract class TaskBase : ITask
{
\\some common fields/properties/methods
public void DoSomething()
{
\\perform action here
}
}
Task implementation:
public class Task1 : TaskBase
{
public Task1(string arg, int arg1)
{
}
}
public class Task2 : TaskBase
{
public Task2(bool arg, double arg)
{
}
}
This is an example on how to use it:
public void DoTask(string arg, int arg1)
{
Task1 task = new Task1(arg, arg1);
TaskController controller = new TaskController(task);
controller.DoSomething();
}
As you can see, I'm using manual injection in this approach. Now I want to switch to using IoC like NInject but after done some research, there's 2 things still bug me.
1. How can I tell the binding which concrete task to use in particular context?
2. How to pass dynamic arguments (`arg` and `arg1` on above example) to `Bind<T>` method
Note:
Please leave some comment if you see my question deserve a downvote in order to help me avoid making mistake in the future
The problems you have are caused by your design. If you change your design, the problems will go away. There are a few things you should do:
Separate data and behavior; currently, your Tasks contain a DoSomething method, while they also contain the data they need to execute.
And related, inject runtime data into your components' constructors.
If you extract the data from the behavior, you'll get the following:
// Definition of the data of Task1
public class Task1Data
{
public string Arg;
public int Arg1;
}
// The behavior of Task1
public class Task1 : ITask<Task1Data> {
public void Handle(TTask1Data data) {
// here the behavior of this task.
}
}
Here every task implements the generic ITask<TTaskData> interface:
public interface ITask<TTaskData>
{
Handle(TTaskData data);
}
With this design in place, we can now use it as follows:
private ITask<Task1Data> task1;
public Consumer(ITask<Task1Data> task1) {
this.task1 = task1;
}
public void DoTask(string arg, int arg1)
{
task1.Handle(new Task1Data { Arg = arg, Arg1 = arg1 });
}
And we register our tasks as follows:
kernel.Bind<ITask<Task1Data>>().To<Task1>();
kernel.Bind<ITask<Task2Data>>().To<Task2>();
kernel.Bind<ITask<Task3Data>>().To<Task3>();
Although I'm not very experienced with Ninject, I'm sure there's a way to transform these registrations to a convenient single line.
This design has many advantages. For instance, it makes adding cross-cutting concerns much easier. For instance, you can create a generic decorator that wraps each task in a transaction as follows:
public class TransactionTaskDecorator<T> : ITask<T> {
private readonly ITask<T> decoratee;
public TransactionTaskDecorator(ITask<T> decoratee) {
this.decoratee = decoratee;
}
public void Handle(T data) {
using (var scope = new TransactionScope()) {
this.decoratee.Handle(data);
scope.Complete();
}
}
}
Such decorator can be applied without the consumer having to know anything about it, since it just depends on the ITask<T> interface.
You can also add a decorator that allows executing the tasks in a background thread:
public class BackgroundTaskDecorator<T> : ITask<T> {
private readonly Func<ITask<T>> decorateeFactory;
private readonly ILogger logger;
public TransactionTaskDecorator(Func<ITask<T>> decorateeFactory, ILogger logger) {
this.decorateeFactory = decorateeFactory;
this.logger = logger;
}
public void Handle(T data) {
Task.Factory.StartNew(() =>
{
try {
// We're running on a different thread, so we must create the task here.
var decoratee = this.decorateeFactory.Invoke();
decoratee.Handle(data);
} catch (Exception ex) {
this.logger.Log(ex);
}
}
}
}
You can learn more about this design here.
1) Use Named properties like so
public TaskController([Named("MyName")] ITask task)
Then in the NinjectModule
Bind<ITask>().To<Task1>().Named("MyName");
2) Think you can use the same method as above
https://github.com/ninject/ninject/wiki/Contextual-Binding
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);
}
}