I'd like to write a logger that can be easily appended to any class in my current project. For development, it will be convenient to log messages to the console, whereas in the final release I'd like to log to a file or something. One should be able to change the behavior by editing just a few lines of code, or ideally a settings file.
What I have so far is this class structure:
public interface ILogger {
void LogMessage(String message);
// ... other logging functions (exceptions, time etc.) don't matter here
};
public interface ILoggable { };
public static class LoggingProvider {
private static ILogger logger = ConsoleLogger.handle;
public static void LogMessage(this ILoggable obj, String message) {
logger.LogMessage(message);
}
}
public sealed class NullLogger : ILogger {
// A logger that does nothing..
};
public sealed class FileLogger : ILogger {
// A logger that writes to a file..
};
public sealed class ConsoleLogger : ILogger {
#region Console Allocation
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool FreeConsole();
#endregion
#region Singleton implementation
private static Object cs = new Object();
private static ILogger instance = null;
private ConsoleLogger() {
AllocConsole();
}
~ConsoleLogger() {
FreeConsole();
}
public static ILogger handle {
get {
lock ( cs ) { if ( instance == null ) instance = new ConsoleLogger(); }
return instance;
}
}
#endregion
#region ILogger Member
public void LogMessage(string message) {
lock ( cs ) {
String logString = getTimeString();
logString += ( " -> \t " + message );
Console.WriteLine(logString);
}
}
#endregion
#region Helper functions
// ...
#endregion
};
Now, I can have any class I'd like to implement ILoggable, and via the Extension Method LogingProvider.LogMessage I can call this.LogMessage("...") within these classes. If it was C++, I'd just use private inheritance instead.
Is this now good or bad design? Anything I can improve? Is there a way to provide classes with logging functionality without the extension method, but with similarly few changes?
Your design seems a bit over-engineered at first sight. In this method:
public static void LogMessage(this ILoggable obj, String message) {
logger.LogMessage(message);
}
the ILoggable obj is not used at all. Why do you need it then? Why not just have:
public static void LogMessage(String message) {
logger.LogMessage(message);
}
and call it LoggingProvider.LogMessage(...)?
On a side note, take a look at Log4Net, this is the industry standard implementation of logging functionality.
Related
I Know
IoC is a design principle which recommends the inversion of different kinds of controls in object-oriented design to achieve loose coupling between application classes.
But I have confilct with the following code:
static void Main(string[] args)
{
ProductService ProductService = new ProductService(new LogInDB());
ProductService.Log();
Console.ReadKey();
}
public class ProductService
{
private readonly Ilog log;
public ProductService(Ilog _log)
{
log = _log;
}
public void Log()
{
log.Log();
}
}
public interface Ilog
{
void Log();
}
public class LogInFile : Ilog
{
public void Log()
{
Console.WriteLine("Log Into File");
}
}
public class LogInDB : Ilog
{
public void Log()
{
Console.WriteLine("Log Into Data Base");
}
}
What is difference between previous and next code
In the first code I used IOC (and added product service) but next I'm using just late binding
but i see IOC not added any value
static void Main(string[] args)
{
Ilog logObj = new new LogInDB();
logObj.Log();
//I still able to using LogInDB
//Ilog logObj = new new LogInDB();
//logObj.Log();
Console.ReadKey();
}
public interface Ilog
{
void Log();
}
public class LogInFile : Ilog
{
public void Log()
{
Console.WriteLine("Log Into File");
}
}
public class LogInDB : Ilog
{
public void Log()
{
Console.WriteLine("Log Into Data Base");
}
}
This depends on your defintion of value. One advantage of IoC would be a better testability of your code, which many would argue adds a lot of value. You can easily inject mocked classes into your test code and only test the class you want to test.
By the way your example is not compileable because of the line Ilog logObj = new new LogInDB();
I'd like to pass a value when calling a function but want to omit to actually add it as a parameter.
To be more precise I'd like to write a logger that also prints which class called the logging function but don't want to always pass a "this" as a parameter.
Example code:
class static Logger{
public static void LogMsg(string msg, object objectCalling){
Print(objectCalling.GetType().Name + ": " + msg);
}
private void Print(string msg){
// print it
}
}
class SomeClass{
private void WriteTestLog() {
Logger.LogMsg("Testing!");
}
}
This should then create an output like: "SomeClass: Testing!"
I am not sure how to tackle this maybe I am just missing sth.
There are a few attributes which might be helpful:
CallerMemberNameAttribute: the name of the calling method or property;
CallerFilePathAttribute: the file path where the calling member is in;
CallerLineNumberAttribute: the line number within the file.
As you see, there is no attribute for the class name, but with the file path you might achieve the same level of information.
How to use this? Decorate an argument in your logging method with the attribute (of course, using the correct type and default).
public static void LogMsg(string msg, [CallerMemberName] string callingMember = null)
{
Print($"{callingMember}: {msg}");
}
And just call:
LogMsg("hello!");
You can use System.Runtime.CompilerServices with it's CallerMemberNameAttribute
Here is example:
using System;
using System.Runtime.CompilerServices;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Logger.WriteLog("Hello");
}
}
public class Logger
{
public static void WriteLog(string msg, [CallerMemberName] string methodName="")
{
Console.WriteLine("Method:{0}, Message: {1}",methodName,msg);
}
}
}
You could create an extension method to do that as well as the answers above :
void Main()
{
SomeClass x = new SomeClass();
x.WriteTestLog();
int i = 1;
i.LogMsg("abc");
}
public static class Logger
{
public static void LogMsg(this object objectCalling, string msg)
{
Print(objectCalling.GetType().Name + ": " + msg);
}
private static void Print(string msg)
{
Console.WriteLine(msg); // print it
}
}
public class SomeClass
{
public void WriteTestLog()
{
this.LogMsg("Testing!");
}
}
If you really wish to extract the caller's type you can play with the stack trace:
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogMsg(string msg)
{
var caller = new StackTrace().GetFrames()[1].GetMethod();
Console.WriteLine($"{caller.DeclaringType}.{caller.Name}: {msg}");
}
But keep in mind that extracting the caller from the stack trace is a very expensive operation. Besides despite of the NoInlining it is not guaranteed that in an optimized build the caller itself is not inlined. I do not recommend to use it in a release build or if performance matters.
I have a winforms application with lot's of classes, and in every class I need to write to a log if something goes wrong.
Today I made a logger function that I initialize in every class object for using it inside.
For example I have a main logic class that have log and one more class that's running different logic that should have a log.
Today I am using:
Initialize log object in class contractor for working with it.
passing log object to the contractor.
what will be the best architecture for initialize it one time and use it in every class (Not doing it static).
My logger class:
namespace MyLogger
{
public class Logger : IMessageLogger
{
IMessageLogger _messageLogger;
public Logger(IMessageLogger messageLogger)
{
_messageLogger = messageLogger;
}
public void Log(string message)
{
_messageLogger.Log(message);
}
}
public interface IMessageLogger
{
void Log(string message);
}
public class FileLogger : IMessageLogger
{
string _filePath = Environment.CurrentDirectory;
public string filePath
{
get { return _filePath; }
set { _filePath = value; }
}
public FileLogger(string filePath)
{
_filePath = filePath;
}
public void Log(string message)
{
string strFileName = Path.Combine(_filePath, String.Format("{0}{1}.log", _filePath, DateTime.Now.ToString("yyyyMMdd")));
using (StreamWriter writer = new StreamWriter(strFileName, true))
{
writer.WriteLine(DateTime.Now.ToString("[dd/MM/yyyy hh:mm:ss]") + " -> " + message);
};
}
}
public class ConsoleLogger : IMessageLogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
}
I believe the best way is to implement it via DependencyInjection, you should read about it online.
But if you want a quick and easy solution, simply implement a Singleton pattern for your logger, like such -
public class Logger : IMessageLogger
{
private IMessageLogger _messageLogger;
private static Logger _instance;
public static Logger Instance
{
get
{
if (_instance == null)
{
// Pick one:
_instance = new Logger(new FileLogger("SomePath"));
_instance = new Logger(new ConsoleLogger());
}
return _instance;
}
}
private Logger(IMessageLogger messageLogger)
{
_messageLogger = messageLogger;
}
public void Log(string message)
{
_messageLogger.Log(message);
}
}
And to write a log use this line -
Logger.Instance.Log("This is a log message!");
There are two sides to my answer.
The first, to get the behavior you want, make the class and methods static. This creates one instance for the lifetime of your application and you can just use FileLogger.Log wherever you need it without having to new a new FileLogger up.I am not sure why you are adverse to using a static though, so you could get the same behavior with a singleton.
The second is that you should not be writing your own logger. There are so many good, open source options available. Look at log4net, nLog or even the built in TraceSource to save yourself the effort of re-inventing the wheel.
I know parameters cant pass to static constructor. But I am using this static class with parameters hence I initialize a variable I have to duplicate lines for each useage of this class functions
here is my logger class(it uses log4net, but its not in case):
public static class Logger
{
private static ILog log {get; set;}
static Logger()
{
log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
}
public static void Error(object msg, string userip)
{
log4net.LogicalThreadContext.Properties["ClientIp"] = userip;
if (log.IsErrorEnabled)
{
log.Error(msg);
}
}
public static void Error(object msg, string userip, Exception ex)
{
log4net.LogicalThreadContext.Properties["ClientIp"] = userip;
if (log.IsErrorEnabled)
{
log.Error(msg, ex);
as you think I have to add custom "ClientIp" field for all kind of log methods. but I dont want to repeat
log4net.LogicalThreadContext.Properties["ClientIp"] = userip;
line for each method
I wish I can edit the constructor like this:
static Logger()
{
log4net.LogicalThreadContext.Properties["ClientIp"] = userip;
log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
}
but I cant pass parameter to static constructor.. any advice ?
Create an Initialize method which you will call only once: at startup. Pass in the variables you need to set only once. In all subsequent calls, you can reuse those values.
Im writing my custom logger and injecting a log storage. I need to log requests to this storage. The problem is that this will be a circular reference between logger and storage. Is there a way to provide loose coupling in this case?
Smth like this:
public interface ILogger{
void Log(String msg);
}
public interface IStorage{
void Save(String msg);
}
public class MyLogger:ILogger{
private IStorage _storage;
public MyLogger(IStorage storage){
_storage = storage;
}
public void Log(String msg){
_storage.Save(msg);
}
}
public class MyStorage:IStorage{
private ILogger _log;
public MyStorage(ILogger logger){
_log = logger
}
public void Save(String msg){
_log.Log(msg);
//....
//send msg to a database like SQL, MongoDB, Cassandra or anything
}
}
public class MyNinjectModule : NinjectModule
{
public override void Load()
{
Kernel.Bind<ILogger>().To<MyLogger>();
Kernel.Bind<IStorage>().To<MyStorage>();
}
}