5

I want to write my own Logging classes (in C#) which implement a standard interface, which I can call from any part of the code.

My idea is to have multiple Log classes implement the Logger interface, each for its specific log destination, for example, a FileLogger will implement logging to a file, a TextBox logger will implement logging into a Multi Line TextBox in a Form, a DBLogger will implement logging to a database table, etc.

Further, each logger class can have a nested logger or chained logger classes, so that a single call to Log() method from the application code can log the message in multiple destinations; example log to a file and a textbox on Form in a single call.

The difficulty I am facing is this:

Usually I log to a running log file (which will contain all log messages required for debugging), a review log file (which will contain only log messages to be reviewed by the user, or which require user action), a Multi Line textbox on screen (which will replicate all log messages to give a progress indication to the user), and another Multi Line textbox (which will log only messages required for user to review).

When I call logger.Log(message), some messages may not apply to a particular log destination. For example, some message may be intended to be logged only in a running log file or progress textbox, but not in the user review textbox, and vice versa.

Since the loggers will be chained so that a single function call can log into all required destinations, how can a particular logger identify that the log message is not intended for it and hence ignore the log message?

My sample log interface is:

public interface Logger
{
    public void Log(string msg);
    public void Log(string msgType, string msg);
    public void InitLogSession();
    public void EndLogSession();
    public void AddLogger(Logger chainedLogger);
    public void RemoveLogger(Logger chainedLogger);
}

public class FileLogger : Logger
{
      //implement methods
}

public class TextBoxLogger : Logger
{
      //implement methods
}

public class DBLogger : Logger
{
      //implement methods
}

EDIT 1:

To be more precise, there could be 4 loggers: 2 file loggers and 2 textbox loggers. A particular message is suppose meant for 1 of the textbox loggers, and 1 of the file loggers; how should my design handle this?

EDIT 2: Please do not suggest existing logging frameworks. I just want to write it on my own !

EDIT 3: Ok. I have a design. Please give your feedback and probably fill the gaps.

The revised interface:

public interface Logger
{
    public void Log(string msg);
    public void Log(string msgType, string msg);
    public void Log(int loggerIds, string msg);
    public void Log(int loggerIds, string msgType, string msg);
    public void InitLogSession();
    public void EndLogSession();
    public int getLoggerId();
}

public enum LoggerType
{
    File,
    TextBox
};

public class LoggerFactory
{
    public Logger getLogger(LoggerType loggerType)
    {

    }
}

The LoggerFactory class will be the sole way to instantiate a logger. This class will assign a unique id to each instance of a logger. This unique id will be a power of 2. Example, 1st logger will get id 1, 2nd will get id 2, 3rd will get 4, and 4th will get 8, and so on.

The returned logger object can be typecast to specific class, and further values like filePath, textbox, etc. can be set by the caller, or else I can have multiple methods in LoggerFactory: one for each type of logger, which will accept specific parameters.

So, suppose we have 4 loggers with ids 1,2,4,8. A particular message which has to be processed by the 1st and 3rd logger (i.e. logger ids 1 and 4) has to be logged using the function:

    public void Log(int loggerIds, string msg);

The value to be passed to loggerIds should be "0101". Each logger will check whether its logger id bit is ON. If yes, only then it will log the message.

Now in the function signatures, I have mentioned int type, but which is the specific optimised type for performing bit manipuations and comparisons?

In this approach, there can probably be a limit on the max no. of loggers, but that is fine with me. Please give your feedback.

Note: Currently I am still on .NET 2.0. If possible, suggest solution within .NET 2.0, else fine, I can move to higher versions.

CONS of this design: Each class which needs to log, needs to know about all the available loggers instantiated by the application, and accordingly set up the bit pattern. Any ideas how to have a loosely coupled design?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
AllSolutions
  • 1,176
  • 5
  • 19
  • 40
  • 1
    well... what's the logic that _you_ use to decide what logger logs what message? – Paolo Falabella Dec 28 '12 at 09:38
  • I think right this is his question. – Andreas H. Dec 28 '12 at 09:40
  • Do you want to this as an exercise? Because there are frameworks which already do this rather well (like [log4net](http://logging.apache.org/log4net/)). These frameworks use config files to define how individual loggers will behave (if and where they will send their output). Each logger in your code then needs to have a unique name based on which the output (or multiple outputs) is determined. – vgru Dec 28 '12 at 10:11
  • I will like to do this on my own. I do not want to learn a third party library at this point. But how will unique names help the logger decide if it should log a particular message? – AllSolutions Dec 28 '12 at 10:28
  • I guess you need to try your revised idea out. If you are using unit testing then you should be able to mock up the factory and other classes and test out how they would work – ScruffyDuck Dec 28 '12 at 14:14
  • In terms of working, I am pretty sure it will work. But there are some gaps, for example, int is not the right data type. I just want a bit-pattern. So each logger will be allocated a particular bit in the bit pattern. Which data type I should use? Secondly, from design view point, is it a good design? – AllSolutions Dec 28 '12 at 14:17
  • Incidentally, I was going through the other thread suggested by you, where you have asked a question. The design which I have suggested - will it not work even for the question which you asked? If you want to accumulate the status messages and log them all at once later, you can introduce a Flush() method. On the flip side, each class which needs to log, needs to know about all the available loggers instantiated by the application, and accordingly set up the bit pattern. – AllSolutions Dec 28 '12 at 14:33
  • But even in your other solution, each class needs to be aware about the GUIMessageQueue, so I guess it is very difficult to have loosely coupled code. – AllSolutions Dec 28 '12 at 14:33
  • Instead of attaching other `ILogger` instances, check out the [Chain of Responsibility](http://sourcemaking.com/design_patterns/chain_of_responsibility/c-sharp-dot-net) pattern. Each logger could optionally consume another logger, passing the message down. I recently did similar and have a `NullLogger` class that emulates `/dev/null`. I have some decorator loggers such as `TimestampLogger` and `DebugLogger` that let me add them optionally (by consuming another `ILogger`). – Erik Apr 14 '14 at 20:46

6 Answers6

8

Why don't you look at (or indeed use) an existing logging framework such as log4net or NLog.

They have the concept of a log level (e.g. trace, info, error etc) as well as being able to filter by the name of the log (which is normally the fully qualified type name which invoked the logging call). You can then map these to one or more 'targets'.

devdigital
  • 34,151
  • 9
  • 98
  • 120
  • Dont reinvet a wheel. Go for NLog or log4net (both frameworks are quite mature, in corporate we use log4net) and AOP:) – Simon Dec 28 '12 at 10:15
  • 2
    I will like to do this on my own. I do not want to learn a third party library at this point. Log Levels will not help, as different messages of same Log Level may need to be logged in only select destinations. – AllSolutions Dec 28 '12 at 10:33
  • You'll have to differentiate each message that requires different targets if they share the same logging level and logger name. For example, you could add a 'category' type to each message. If you wanted to use an existing framework, you could construct your own logger names rather than using the type name. E.g. you could have multiple ILogger's that a class uses, with each implementation using a different logger name. – devdigital Dec 28 '12 at 10:37
  • But when a message is meant for say 2 out of 4 loggers, how will a single Category Field help the 4 loggers decide whether the message is meant for them or not? Can you give a pseudo-code example? – AllSolutions Dec 28 '12 at 10:44
  • To be more precise, there could be 4 loggers: 2 file loggers and 2 textbox loggers. A particular message is suppose meant for 1 of the textbox loggers, and 1 of the file loggers; how to handle this? – AllSolutions Dec 28 '12 at 10:48
  • If you're writing your own logging framework (which seems like a lot of work when there are popular frameworks already available) then you have complete control. You could use a configuration file (or in code via your API) to map the message categories/logger names/logger levels to particular targets. You could achieve this with log4net/NLog using different ILogger implementations which are wrappers for the log4net/NLog calls, but sets the appropriate logger name. – devdigital Dec 28 '12 at 10:56
  • Message Categories / Logger Names / Logger Levels - how can any of them handle multiple specific destinations at message level? Can you tell me if there are 4 loggers: 2 file loggers and 2 textbox loggers, and a particular message is meant for 1 of the textbox loggers, and 1 of the file loggers; how will your solution be able to handle this? – AllSolutions Dec 28 '12 at 11:14
  • E.g. you could give the message a category or logger name of 'special', and then in your configuration you map any message of the logger name/category 'special' to the textbox target called 'textbox1' and the file target called 'file1'. – devdigital Dec 28 '12 at 11:37
  • @devdigital, "You can then map these to one or more 'targets'". Can I introduce my own log levels using log4net framework, and can I map a given level to multiple targets. In the log4net features document, I could not see any appender for TextBox. – AllSolutions Dec 28 '12 at 15:29
  • 1
    You can (http://www.l4ndash.com/Log4NetMailArchive%2Ftabid%2F70%2Fforumid%2F1%2Fpostid%2F14714%2Fview%2Ftopic%2FDefault.aspx) but you may be better off using the logger name to differentiate. To output to a TextBox, you'll need to write a custom appender, e.g. http://triagile.blogspot.co.uk/2010/12/log4net-appender-for-displaying.html – devdigital Dec 28 '12 at 16:11
  • @devdigital, I have posted a followup question on configuring the TextBoxAppender via an Xml file: http://stackoverflow.com/questions/14114614/configuring-log4net-textboxappender-custom-appender-via-xml-file. Can you have a look at it? – AllSolutions Jan 01 '13 at 21:59
2

"Please do not suggest existing logging frameworks. I just want to write it on my own !"

Accepted answer: Don't reinvent the wheel! Use this existing logging framework!

facepalm

The best answer, which is my answer, goes something like this. Use interfaces if you want plug and play functionality. You can make it easily configurable. Here's the high level run down.

  1. Use a config file to indicate what type of logger you want which implements your logging interface

  2. Use reflection to instantiate the type you pull from your config file AT RUNTIME.

  3. Pass in your logger interface you just made via constructor injection inside your classes.

You're not reinventing the wheel by designing by interface. If you make your interface general enough, it is implementation non specific (ideally). This means if log4net goes to crap, or is no longer supported, you don't have to RIP OUT AND MODIFY ALL YOUR CALLING CODE. This is like hard-wiring a lamp directly into your house to turn it on. For the love of god, please don't do that. The interface defines the contract by which the components interact, not the implementation.

The only thing I can think of is, look at existing logging frameworks, find the common elements, and write your interface as the intersection of the common features. Obviously there are going to be features that you miss. It depends on how much flexibility you want. You can use Log4net, or the Microsoft Event Viewer logger, or both! No implementation details are re implemented. And it is a MUCH less coupled system than having everything in your code tied to one technology / framework.

Nathvi
  • 196
  • 1
  • 6
  • 14
1

As devdigital wrote, these Frameworks usually do this by providing designated methods for Logging like: Warn("..."), Fail("...")...

You could also look for the ILogger interface of the logging facility of the castle project. (maybe try to google the ILogger.cs sourcecode)

If you still adhere to your approach of chained loggers with common interface (for which you you would also have to implement a chaining mechanism) you would have to provide a kind of logging level to your Log() method. This may be just an integer or an enum as well.

Like this:

    public interface Logger
    {
        public void Log(LogLevel level, string msg);
        public void Log(LogLevel level, string msgType, string msg);
        public void InitLogSession();
        public void EndLogSession();
        public void AddLogger(Logger chainedLogger);
        public void RemoveLogger(Logger chainedLogger);
    }

With an logging level enum like this:

public enum LogLevel
{
    Info,
    Warn,
    Debug,
    Error,
    Fail
}

The loggers to use would then be selected within a chain of responsibility.

Andreas H.
  • 766
  • 9
  • 17
  • That's true, regarding logging levels. But OP's question seems to be more related to configuring loggers to have different targets (or "appenders" in log4net, for example). – vgru Dec 28 '12 at 10:10
  • Logging Levels or Message Type is captured in the parameter msgType in my design. But a message of a particular type also may be intended to be logged in only a few destinations. So how to handle that? Example, some message of type INFO may need to be logged into Progress Text Box as well as Review Text Box, whereas another message of type INFO may need to be logged only in Progress Text Box. – AllSolutions Dec 28 '12 at 10:32
1

I wrote my own logger some time ago. To be honest it was not as good as those available for free and I realized that I was trying to re-invent a wheel that was already round!

I see that you want to write your own code but it might still be an idea to look at open source solutions and perhaps use them or modify them for your own specific needs

I now use TracerX: http://www.codeproject.com/Articles/23424/TracerX-Logger-and-Viewer-for-NET This is an open source project so it easy to modify the source code it you need to. The other loggers mentioned are also good of course.

EDIT

This is based on the accepted answer to my question here: How to pass status information to the GUI in a loosely coupled application So I claim no originality in this. Your log messages are simple at the moment I think

My suggested answer is that you use a message type that can process (e.g.) send itself to different loggers based on either some logic passed to it at run time or by using a factory to create different message types depending on run time conditions.

So

  1. create an abstract message class or interface that has a process method.
  2. create a number of message types inheriting from the abstract class or interface that represent the different types of logging you want to carry out. The process method could determine where to send them.
  3. Consider using a factory to create the message type you need during runtime so you don't need to decide what types you will need in advance
  4. When you generate a log message use the process message to route the message to the loggers you want it to go to
Community
  • 1
  • 1
ScruffyDuck
  • 2,606
  • 3
  • 34
  • 50
  • Everybody here is advising to use an existing logging framework. But can the existing logging framework meet my stated requirement? Suppose 1 log message is meant for 2 out of 4 loggers (and not based on Logging Level) - can the existing framework handle such scenarios? – AllSolutions Dec 28 '12 at 10:45
  • I can see that you have a specific problem. I have a similar problem in that I want to send status messages to the user separately from the application log. I may well go for a separate class to handle user data. Can I just suggest that you take a look at the article for TracerX that I posted above? It does use Logging level to separate what goes where but the code for the viewer does allow a range of searches. I am intercepting log requests and using a small class to send them to the right place. Again that may not be what you want since it is rather tightly coupled to the code. – ScruffyDuck Dec 28 '12 at 11:00
  • I ran out of space. I think what we are all saying is to look at an existing framework for the basic boring plumbing work and extend that for your needs rather than start from scratch. However I can see that might not work. – ScruffyDuck Dec 28 '12 at 11:02
  • I just went through TracerX. It seems a wonderful product, but just lacks the 1 feature which I want. i.e. control logging destinations individually at each message level! Any ideas? – AllSolutions Dec 28 '12 at 11:21
  • I have no idea if this will help however what I am doing is sending my logging information to a small static wrapper class. This class then does the work of sending the information on to different loggers. One is TracerX and another is my user status reporting which reports to the GUI. It changes log levels and filters the messages depending on what they are. I have a little class that contains the logging info - log level, message etc etc. Another possible option is to open the TracerX code and add your requirement. I changed it for my use. – ScruffyDuck Dec 28 '12 at 11:31
  • Even the wrapper class - how it should decide that a message has to go to Logger 1, Logger 2, but not to Logger 3 and Logger 4?? "Another possible option is to open the TracerX code and add your requirement" - If I know what I have to do, I might as well do it in my own code, as I dont need to use the advanced features of TracerX – AllSolutions Dec 28 '12 at 11:38
  • OK - one more idea. TracerX allows you to define named loggers. You can have as many of these as you like. So you perhaps could set up a named logger for each group you want to send log to and use the filter class I suggested above to send the messages where you want them to go depending on some criteria in the message class.... – ScruffyDuck Dec 28 '12 at 11:39
  • Point is I do not know the "logger groups" in advance, else it would have been very easy. At run time, each function block is going to decide which message has to go to which all destinations! – AllSolutions Dec 28 '12 at 11:50
  • OK then I think I am stuck - sorry. – ScruffyDuck Dec 28 '12 at 11:58
  • Just happened to see your edited response now... It is helpful and am still thinking on it ..meantime, can you see the suggested design I have posted in my EDITed question and fill the missing gaps? – AllSolutions Dec 28 '12 at 14:05
  • "create a number of message types inheriting from the abstract class or interface that represent the different types of logging you want to carry out. The process method could determine where to send them." - But when a single message has to go to multiple loggers, how will it be possible to determine the destination based on the Message Type class? – AllSolutions Dec 28 '12 at 14:40
  • I am not sure that comments are the best way to Q&A. If you want to contact me then my email should be in my profile. – ScruffyDuck Dec 28 '12 at 14:59
  • Well, could not get your mail id from the profile .. it mentions only website address .. Does your id start with jon? – AllSolutions Dec 28 '12 at 15:07
  • jon AT scruffyduck DOT org DOT uk – ScruffyDuck Dec 28 '12 at 15:16
  • Thanks, Have sent you a mail, and we can have further discussion on mail. – AllSolutions Dec 28 '12 at 15:24
0

This seem like a good place to use extension methods.

Create your base class, then create the extension methods for it.

BaseLogger(LogMessage).toTextBoxLog().toFileLog().toDatabaseLog().

This way, you always call the BaseLogger and then only the extension methods where needed.

0

.NET now provides the ILogger interface which can be used with a variety of .NET or 3rd party logging tools through dependency injection.

Using this you can separate your logging functionality in code, from the concrete implementation in your architecture, and can later swap out loggers without significant modification to your business code.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger?view=dotnet-plat-ext-6.0

https://learn.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line

Greg
  • 1,549
  • 1
  • 20
  • 34