In the previous guide, we created a solution with two projects:
- Banana: The project calling the logger
- Banana.Toolbox: The project library containing the static logger
We currently only have one logging method (.Log()) which is fine for little projects but what if I tell you that we can get more information than only the datetime, the project and the log message?
This is easy to add and we’re going to also write some more useful methods!
Adding log level
At the moment, the only log level that we have is “DEBUG”. This is the lowest level and should not be use in a production environment. We’re going to use 5 different log levels:
- Debug: Everything that is useful only when we need to debug the application without using a debugger
- Info: Some information that are useful even when we’re not debugging
- Warn: Something happened, maybe something weird or something we will maybe need to take care later
- Error: An error happened but the application can continue to live, we need to take care of this
- Fatal: A fatal error and the application needs to stop its execution. We need to avoid this situation as much as possible.
This is how I use these different log levels but feel free to adapt it at your taste.
We now need to differentiate the log levels when the .Log() is called, let’s first create a new file in the toolbox project, logger folder: LogType.cs. This will be an enum containing the log levels:
1 | namespace Banana.Toolbox.Logger |
Then, let’s say that the default logging level is “Debug” thus we won’t have to specify everytime the log level. And as the “Debug” level should be the more used, it seems better for me like this.
In the Log method from Logger.cs, add a new parameter: LogType type
with the default value LogType.Debug
:
1 | public static void Log(string message, LogType type = LogType.Debug) |
Now we need to tell Log4Net that we want to use its different level. Do a switch on type and call the rights methods. Don’t forget to remove the old message test:
1 | public static void Log(string message, LogType type = LogType.Debug) |
Then, in the main program, let’s call all these log type to see what we get in the output file:
1 | using System; |
Here is what you should get:
Adding file name, line number and method name
This is something I find really useful when I read my log files: where does exactly come this log line from!
As C# is absolutely fantastic, we can get the “Caller” information through method attributes and this will give us what we need to add to the log. It uses C# Reflection but I never seen any slowdown even with huge amount of logging.
Let’s modify our beloved Log method to add:
- [CallerFilePath] string path = @“C:\Unknown.cs”: The full path to the caller filename
- [CallerLineNumber] int line = 0: The line number from the caller file
- [CallerMemberName] string method = “” The method from the caller file
This will be optional parameters with unused default value, these should never be set when calling the Log method or this will override the real information that we need.
Don’t forget to also add these info with the message. You should get something like this:
1 | public static void Log( |
Let’s try this again, run the application and check the log!
This is better but… We don’t need the caller file full path. To keep everything clean, I suggest to create a new private method “PrefixLog” that will format the log message by adding it the relevant information:
1 | private static string PrefixLog(string path, int line, string method) |
And call it in the Log method:
1 | // Add the caller file information |
Run again and see the result in the log file:
Better! We now have everything we need to do some beautiful log files.
Bonus methods!
If like me, you have some common log lines that you want to call easily without retyping them all the time, you can add more methods to the Logger class.
First, we move the EnsureLogger() call and the switch block to another private method. If we don’t do that, in each new logging method we will need to write these lines again and… This would be really bad.
I called this method ProcessLog, it will need the log message (already prefixed and formatted) and the log level. Here is what you should have:
1 | public static void Log( |
1 | private static void ProcessLog(string message, LogType type) |
If you try it now, you shouldn’t see any difference in the output file.
Now, we can add extra methods to the Logger class that will help us to gain some precious time. Here is what methods I use in every project:
- LogIn: Called in every function, this is the first line I write everytime and I use it to know that we’ve entered a new method or function
- LogOut: Same as above, except that this is for when we exit a method or a function.
- LogException: When I want to log an exception message
And the code I use for these methods:
1 | public static void LogIn( |
Adapt the main program file to use the new methods:
1 | static void Main(string[] args) |
And the output log file will be…
You can now have a nice log file with some handful methods! Feel free to improve the Logger class with your proper code 😊
Download the example solution here: https://github.com/MayakoLyyn/Log4Net-dotnetcore