Technical Resources
Educational Resources
Connect with Us
Logging is essential for a software development project, but it’s often disregarded until the program crashes. Logging serves numerous purposes including root cause analysis, bug analysis, and even performance reviews for the application.
With .NET, the programmer can log to several locations and not just a flat file. Both the C# and VB.Net languages offer internal libraries to help you get started. They are limited, but you can find several free frameworks that are easy to use with some extended capabilities (we will discuss some of them). With .NET, you can log to the Event Viewer on the Windows server, custom flat files, or a database.
The most common way to log errors is in Windows Event Viewer. Every Windows desktop and server has the utility, so you don’t need to install anything extra on the user’s machine.
Even if you don’t log in your application, Windows will still detect the crash and create an entry in Event Viewer. For this reason, you might ask yourself why even bother with logging? Logging serves several purposes.
First, you can customize your error messages that you store. This makes it much easier to identify bugs in your application. This might not seem necessary if you create small applications, but think of a multi-layered application with several coders working on it. You might need to identify an error from a piece of code that you didn’t write. Instead of spending hours stepping through code with different input scenarios, the right logging can point to the exact piece of code and the input that caused the problem.
Take a look at a simple error logged in Event Viewer.
The fourth line in this image shows the location and line of code that caused the error. With this, we know the module, project, method, and page causing the issue. What we don’t know is what caused the error. If we had a custom error logged in Event Viewer, we could include the user’s input and any parameters that might cause the error.
Before you get started, remember that logging requires server resources. Most .NET programs log in a database or in the Event Viewer. There are security issues with flat files, but they are also common. You’ll need extra storage space for logging regardless of the method you use.
Before you begin logging, you should know that .NET has three main logging levels:
For our examples, we’re going to use a .NET solution in C#. The setup will be a web application using MVC. We named the solution LogglyGuide
. This will be important when you need to look up errors in Event Viewer and other logging utilities.
Let’s start with the easiest logging library included in the .NET framework. When you create an MVC solution, Visual Studio doesn’t automatically add the necessary logging libraries. Add these two lines to your using statements in every page that requires logging.
using System;
using System.Diagnostics;
You need three items when you log to Event Viewer. You need the application name (in our example, the name is “LogglyGuide
”), the message you want to post to the Event Viewer, and the name of the log. By default, any errors that aren’t trapped go to the Application log in Event Viewer.
We’re going to write an event to the Event Viewer, so open your HomeController in your application and go to the Index method. If you receive this error when working with Event Viewer, you need to run Visual Studio as an Administrator (right-click the icon and select “Run as Administrator”).
Now, let’s take a look at the code that writes an event to Event Viewer.
string source; string log; string eventname; source = "LogglyGuide"; log = "Application"; eventname = "LogglyError"; if (!EventLog.SourceExists(source)) EventLog.CreateEventSource(source, log); EventLog.WriteEntry(source, eventname); EventLog.WriteEntry(source, eventname, EventLogEntryType.Error);
Place this code in the Index
method for the HomeController
and run the application. We define the source as our solution and keep the event in the default Application
log. We’ve named the error “LogglyError
” to distinguish it from the rest of the events.
The “if” statement is there in case the event source isn’t found. Since this is our first event, the LogglyGuide
source will be created.
The last two lines are what write the error to the Event Viewer. After you run this code, you’ll notice a new Error event in the Event Viewer.
Our error just says “LogglyError” for now, but we’ll add to this error in the next sections.
If you’ve ever looked at the Event Viewer, you’ll notice that logs include Warning
and Information
. The type of event you log depends on this enumerator:
EventLogEntryType.Error
In this example, we used an Error
, which displays as a red symbol in the Event Viewer. These events are critical and usually mean the software crashed.
You also have the option of “EventLogEntryType.Warning
” and “EventLogEntryType.Information
”. A Warning
event is usually the sign of a possible future critical situation. For instance, you would log a warning if a user logged in five times unsuccessfully.
With the Information
event, you just want to log some kind of information in Event Viewer. For instance, you could log when an application starts and stops such as a Windows Service. The Information
event helps you with root causes when investigating software behavior and errors.
When you write log entries, you will have different levels to indicate the critical nature of the error. For instance, an “info” log level isn’t critical. It just logs some information to the log. You would use this level to provide extra information in case the logs must be reviewed. You could log when a user logs in to indicate that the user’s session was initialized. This could later be used to identify issues regarding user login methods.
The most critical is a fatal error. A fatal error usually means that the application crashed. This type of error can have severe repercussions for your users. This type of error should be handled quickly. You might even want to email an administrator should an error like this occur.
Most logging libraries have the following error levels:
You don’t have to stick with the Event Viewer for logging. Some developers prefer third-party libraries. You can download several open-source libraries that cost nothing. Here are a few of them:
All of these libraries are quick to set up and easy to use. You might want to try a couple of them before you determine which one is right for your application. Let’s see an example with NLog, which is available from NuGet in Visual Studio.
Open NuGet and type the following into the command line:
Install-Package NLog.Config
Go back to your .NET project and add the NLog library in the using statements.
using NLog;
Place the following code in the Index
method again to take a look at the way NLog logs your events.
Logger logger = LogManager.GetCurrentClassLogger();
logger.Error("Loggly Error");
That’s all it takes to use NLog. NLog is a bit different than the Event Viewer code. NLog logs your events to a flat file. When you set up logging for your application, where you want to record the logs is one consideration you’ll need to make. You can configure NLog to log in a different location than the default directory for the application, but remember that flat files should have high security on them if they are stored on a public-facing web server.
We showed you how to log errors, but you would only use this code for informational purposes if no error is triggered. In the previous section, we logged an event to the Event Viewer regardless of an error. This is useful when you want to log information such as the time the application started. Most logging, however, occurs when there are errors. This is called “error trapping” and it’s done with a try-catch block within your .NET application.
An error in .NET is referred to as an "Exception
.” The “System.SystemException
” library is where you’ll find these exceptions. Below are the Exception
classes found in this library.
Exception Class |
Description |
System.IO.IOException | Handles I/O errors such as reading and writing to files. |
System.IndexOutOfRangeException | Handles errors when code points to an index not defined in an array. |
System.ArrayTypeMismatchException | Handles the errors created from mismatched array data. |
System.NullReferenceException | Handles errors created when a null object is accessed. |
System.DivideByZeroException | Handles errors created when you attempt to divide by 0. |
System.InvalidCastException | Handles errors created when an invalid typecast is used. |
System.OutOfMemoryException | Handles errors when the system runs out of memory for the application. |
System.StackOverflowException | Handles errors created from stack overflow. |
The type of error thrown depends on the compiler. You can’t control when the error is thrown if you don’t know that you have a bug. With a try-catch statement, you “catch” these errors and gracefully exit the program or stop execution. With logging, you then take a snapshot of the error and keep a log for further review. Error trapping not only helps you find root cause issues with your program, but it also lets you display a custom error to the user.
Take a look at the following code.
string source; string log; string eventname; source = "LogglyGuide"; log = "Application"; eventname = string.Empty; decimal result = 0; int num1 = 1; int num2 = 0; try { result = num1 / num2; } catch (DivideByZeroException e) { eventname = e.Message; if (!EventLog.SourceExists(source)) EventLog.CreateEventSource(source, log); EventLog.WriteEntry(source, eventname); EventLog.WriteEntry(source, eventname, EventLogEntryType.Error); return View(); }
In the above code, we’ve set up num1
and num2
and then we divide them. Unfortunately, we set num2
equal to 0, so the division will cause a “DivideByZeroException
” error.
We have a try-catch to handle these errors, so the application won’t fail. We’ve used the logging code from the previous section and placed it in the catch section.
You can step through the code to see the try-catch in action, but we’ll explain it here. Anything in the “try” section will run. Should any of your statements cause an error, code execution jumps directly to the “catch” section. Since we define the DivideByZeroException
, the code jumps to this catch section. If you suspect there could be other types of exceptions, you must have multiple catch statements.
Within the catch statement, we define our logging procedure. We write the error to the Event Viewer. Copy and paste this code to the Index method in your MVC HomeController
and you’ll find this in the Event Viewer after it runs.
The “e.Message
” property is written to the general log message, but you can log any error and message you want in this section. In some cases, you want to dump the entire error message into the log. Let’s take a look at the code and output.
string source; string log; string eventname; source = "LogglyGuide"; log = "Application"; eventname = string.Empty; decimal result = 0; int num1 = 1; int num2 = 0; try { result = num1 / num2; } catch (DivideByZeroException e) { eventname = eventname = e.ToString(); if (!EventLog.SourceExists(source)) EventLog.CreateEventSource(source, log); EventLog.WriteEntry(source, eventname); EventLog.WriteEntry(source, eventname, EventLogEntryType.Error); return View(); }
And here are the results in Event Viewer:
With the entire Exception
logged, we can see exactly where the error occurred including the HomeController
page and the line number.
You aren’t limited to logging with just Event Viewer, although it’s probably the easiest to set up and use. Some companies prefer third-party custom libraries to write logs to a file or send to a centrally aggregated server. This is beneficial for internal applications where the server is behind a firewall. You wouldn’t want hackers to gain access to error logs, so use flat file logs on public-facing servers with a high sense of security. We’ll use NLog since it was used in the previous sections. We’ll use it to log an error to a file. We’ll use a null Exception
error this time to show you how to work with different errors. Let’s use the following code:
string source; string log; string eventname = null; source = "LogglyGuide"; log = "Application"; string result = string.Empty; try { result = eventname.ToString(); } catch (NullReferenceException e) { Logger logger = LogManager.GetCurrentClassLogger(); logger.Error("Index method in HomeController threw a null error."); return View(); }
In the code above, “eventname
” is null, so invoking ToString()
will result in the NullReferenceException
thrown. We have NLog logging the error and providing exact details of the error and where it’s found. This could be later used to find bugs in the application. The text file from NLog is shown below.
Whether you use a third-party logging system or the internal Event Viewer application, you should always log your errors. Imagine you have a multi-layer application that spans several departments. It could take you weeks to determine the root cause if you aren’t able to trap the errors and log them to a location. With this type of logging, you can provide detailed errors for easier troubleshooting and debugging.
Aggregated logging is a huge benefit to larger enterprises. Imagine having five web servers behind a load balancer. To identify issues with any one server, you would need to log in to each machine individually. You would then need to review logs on each machine individually, match up errors, and then take the right server out of rotation. You then need to trace requests across these multiple servers to identify which one is causing issues with your application.
With aggregate logging, you have a snapshot of every server and its logs in one location. Instead of switching connections across multiple servers, you have the advantage of reading log entries in one location. This speeds up root cause analysis and reduces the time your server could be out of service.
Even with error trapping, you don’t know if your logs are working. This is one of the more dangerous things to happen when you release your software. Most users won’t report issues if they have other options. They will simply choose to use a competitor product. It could be weeks before you realize that your logs aren’t working. If your software crashes, you won’t be able to troubleshoot or find the root cause using your logging code.
You also must monitor your logs to take preemptive steps to avoid more critical issues. Just logging entries and using them for root cause analysis isn’t fully taking advantage of what logging can do for you. Watching your logs and checking for any errors can give you insight to an issue that is progressively getting worse. For instance, you might have a malformed SQL query that is either running too slowly or causing memory leaks. Logs can detect SQL errors that could be causing data corruption on your server. If you let these queries run for too long, they could cause major data issues with your internal applications. When you monitor your logs, you could find these issues and repair them before they became a critical issue.
Fortunately, in .NET you can use the debugging tool included with Visual Studio to throw errors and ensure that error trapping is working. This is especially useful when you have predefined Exception
types and don’t catch them all.
To walk through your logging code, you set a breakpoint. You can set a breakpoint by clicking the left side of the Visual Studio IDE next to the line of code that starts the error trapping and logging.
Now, run your code. The compiler stops at the catch statement when an error is found. To step through your code, press the F10 key. Each time you press the F10 key, another line of code executes.
In the example code, you can step through logging an entry in NLog.