A Guide to Debugging .NET Software Applications

Introduction

This guide is intended to help developers think about debugging .NET applications.

Target Audience

This post intends to serve two audiences:

  1. Beginner .NET developers. This post will help beginners understand what tools are available to debug .NET software applications.
  2. Intermediate to Advanced Developers. This post can serve as a quick-reference for debugging tools.

I expect this post will serve as a personal quick-reference I will use myself in the future. When I need to review the available tools in the .NET developers debugging toolbox, I can refer back to this post for ideas. Especially when debugging complex, systemic problems.

A Debugging Anti-Pattern

A common application debugging anti-pattern that I see often when debugging code goes like this:

  1. I think code x is the problem.
  2. Make a change to code x.
  3. Bug still exists.
  4. Maybe it’s y.
  5. Make a change to code y.
  6. Bug still exists.
  7. Maybe it’s…

Don’t do this. It is usually a complete waste of time. I have found that the actual bug is often something completely unrelated to my best guess. Additionally, even in those cases where I was correct about the cause of the bug, running the debugger still often provides context that I hadn’t thought of regarding the bug and how to fix it. So stop guessing and just run the debugging tools right away. Even if you think you are certain what’s causing the bug. You can still make the assumption, but that assumption then needs to be validated with actual data from the available tools.

This is a habit I have had to break myself from as well. I still have the urge to make that small change to the code and run it again to see if my change fixed the bug. I resist this now and just go directly to the debugger, or profiler, or read the logs, or whatever other tools are available to me to use that match the debugging use case.

Debugging Checklist

This section is intended to help get developers thinking about possible ways to resolve a bug.

Understand the Bug

The first step in debugging is to understand the problem. This requires an understanding of the code base and underlying systems. In this step the following types of questions should be asked:

Can the Bug be Reproduced?

Bugs that can be consistently reproduced are usually relatively easy to resolve. In the best case scenario, the steps to reproduce a bug will be provided by the user or team that is reporting the bug to the code owners. Reporting the steps to reproduce a bug is often requested when submitting a bug ticket.

What If the Bug Can’t be Reproduced?

Bugs that can’t be consistently reproduced are much harder to resolve. These types of bugs often occur intermittently under a set of unknown conditions. In these cases, it can be helpful to ask the following questions:

What is the Application Code Doing?

Think about the code and what it’s doing. What does the system look like. What other systems is the code interacting with? Even code that runs on a single PC may be interacting with several subsystems. CPU, GPU, Memory, Database, File System, etc.

The principle is the same for larger systems. Where does the code run? What does it interact with? Network, File System, Databases, Caching Servers, Firewalls or Firewall-like components like AWS Security Groups, NACLs, etc. Does the code connect to an external, third-party server through a Web API, SFTP?

Does the Application Code Write to Logs? What is Reported in the Logs?

Hopefully, the application is writing to logs. Logs are usually the first thing I check if a bug can’t be reproduced. Is there a message in the logs around the time the bug occurred? Are there any exceptions or stack traces captured in the logs? Are there any messages before or after the bug occurred that might provide clues as to what is causing the bug?

Do Logs Need to be Added?

In some cases, the application code may not be writing to the logs. In this case, logging may need to be added to help capture more information. If the application already uses a logging framework, then it’s relatively easy to add code to capture logging in the area of code where the bug is suspected to occur. If the application does not use a logging framework, then adding one is recommended. Serilog and Log4Net are popular choices for .NET applications. With a logging framework, you can add Debug calls and set the logging level to Debug to capture the logs. Then switch the logging level back to a higher level to stop the logging once the log data has been captured.

Debugging Tools Index

In this section, I’ll briefly discuss a variety of tools available for assisting with debugging .NET applications. I’ll cover what they are, what they can be used for, and high-level instructions on how to use them. I will link to more in-depth tutorials from this page.

  • Visual Studio Debugger
  • Visual Studio Profiler
  • Debugging in Visual Studio Code
  • Tracing
  • Logging (include popular frameworks)
  • Windows Event Viewer
  • SQL Server Activity Monitor
  • SQL Server Profiler
  • Estimated Execution Plan
  • Actual Execution Plan

Debugging Tools

Visual Studio Debugger

While modern .NET development is shifting to Visual Studio Code, Visual Studio is still widely used. The Visual Studio debugger is often the .NET developer’s primary tool for debugging issues in code. As the name implies, it is built into Visual Studio.

Visual Studio Profiler

The Visual Studio profiler is a tool that examines performance metrics for various aspects of your code. It can often be an essential and valuable tool when debugging performance issues.

Debugging in Visual Studio Code

Visual Studio Code (VS Code) is slightly different than Visual Studio. VS Code supports modern .NET and many other languages, either out of the box or through extensions available through the VS Code Marketplace. Legacy .NET Framework development is not supported. Many teams are on the latest version of .NET, but teams still using .NET Framework or .NET Core will have a better experience with Visual Studio.

Tracing

Tracing is when an application is configured to write output to the console when running in debugging mode. For .NET applications, it’s typically enabled in the web.config, app.config or application bootstrap code. Examples of what tracing is often used for include monitoring database queries generated by ORM frameworks like Entity Framework or HTTP requests/responses for things like SOAP web services or REST APIs.

Logging

Logging is adding points in your code where the application writes to a log. Typically a file or a database table. Logging is usually done with a logging framework. Most logging frameworks support what is known as “deferred writes”. Meaning the framework will queue the log entries and write them at specific times during application execution. This is to prevent the logging itself from creating a bottleneck in the application. Logging frameworks are also designed to fail quietly to keep issues with the logging itself from disrupting the application. This can make setting up logging in a new application challenging, but the results are worth the effort.

Some popular logging frameworks for .NET include:

  • Microsoft.Extensions.Logging
  • Serilog
  • NLog
  • Log4Net

Microsoft.Extensions.Logging is probably the easiest to start with, if you’re using a version of .NET that supports it. For third party libraries, I personally have used log4net and Serilog and found them both to be satisfactory. However, log4net is older and not as actively maintained, so between the two, I would recommend Serilog for new development.

Temporary Logging

When working in a more restricted environment, it can sometimes be helpful to add temporary logging for debugging things like bugs that only occur in production. This can be done through adding the logging through the companies established SDLC process and then removing it when done, or using the capabilities of the logging framework to only write to the log if the application configuration is defined to allow the minimum level where those logs will be written.

Visualization Tools

For highly complex and distributed environments, visualization tools like Kibana can be used to store logs in a central location. This is typically done by setting up log forwarding jobs that take application logs and periodically ingest them into Kibana. This can be extremely helpful for seeing the “big picture” for complex systems and diagnosing complicated bugs.

Windows Event Viewer

Windows Event Viewer is a built in application where the Windows operating system logs system events. I have found that the Application log in Event Viewer is typically where I find the most useful information for application debugging. The Event Viewer is helpful for these types of problems:

  • An application is throwing an exception that is not being caught or logged by the application. For example, a web site or API is throwing a 500 error on startup, before the application logging has been initialized.

  • Issues that occur due to system related issues. Things like the correct version of .NET Framework not being installed, a DLL the application depends on not being found, etc.

Using the Windows Event Viewer to Debug .NET Applications running on Windows explains the Event Viewer in more detail and includes some specific examples.

SQL Server Debugging

If you’re developing .NET applications, especially in an enterprise environment, the chances are very high that you are using a SQL Server database. So, I’ve included some SQL Server debugging tools on this list as well.

SQL Server Management Studio (SSMS)

SQL Server Management Studio (SSMS) is a tool that can be used for things like database administration, create database projects, run ad-hoc queries, view database execution plans, etc.

SQL Server Activity Monitor

SQL Server Activity Monitor is a tool found in SSMS. It can be used to view high-level statistics about current database activity and to monitor recent and active expensive queries. It can also be used to get execution plans for currently running queries.

While SQL Server Activity Monitor is a good tool for general information, most DBAs tend to favor other products. However, it’s useful when SSMS is the only tool you have at your disposal.

SQL Server Profiler (Deprecated)

SQL Server Profiler is a GUI tool for examining detailed information about SQL Server activity. Traces can be paused, started and stopped, saved to the file system, etc. It is typically installed with SSMS. I am most used to accessing it through SSMS using the Tools > SQL Server Profiler menu option.

Extended Events (XEvents)

Extended Events is the modern replacement for SQL Server Profiler. It is directly integrated into SSMS. You can read more about how to use it in the article Use the SSMS XEvent Profiler from Microsoft.

Estimated Execution Plan

SSMS has the ability to show the estimated execution plan for a query. This is helpful when debugging long-running, performance intensive queries, as it generates the estimated plan without running the query. The estimated plan can be accessed by clicking the “Display Estimated Execution Plan” or clicking Ctrl + L.

Actual Execution Plan

While SQL Server does a pretty good job estimating the execution plan, viewing the actual execution plan is often more helpful. This is the plan that SQL actually uses when the query runs to retrieve data. To view the actual execution plan, click the “Include Actual Execution Plan” button and run the query or press Ctrl + M to toggle the feature on and off. Unlike the estimated execution plan, Ctrl + M does not automatically execute the query to return the plan. This is likely by design, as the actual execution plan requires that the query run, retrieve actual data and complete.

Include Client Statistics

I don’t use this as much as other items on the list, but Include Client Statistics can sometimes be helpful in diagnosing problems with database performance that may be occurring due to factors other than the database itself, such as network latency, processing time on the client side, etc.

Final Thoughts

I hope this list of tools helps modern .NET developers understand what is available for debugging .NET applications. You can refer to this list to help you find the right tool for your specific debugging scenario. I plan to refer to it when I debug problems, simply as a tool to remember things that I may not have thought to try yet, such as looking at the Windows Event Viewer on a production server, or running traces in the SQL Server Profiler. As I expand and link to more in-depth posts, this will create the hub page for more in-depth .NET debugging content.


The postings on this site are my own and do not necessarily reflect the views of my employer.

The content on this blog is for informational and educational purposes only and represents my personal opinions and experience. While I strive to provide accurate and up-to-date information, I make no guarantees regarding the completeness, reliability, or accuracy of the information provided.

By using this website, you acknowledge that any actions you take based on the information provided here are at your own risk. I am not liable for any losses, damages, or issues arising from the use or misuse of the content on this blog.

Please consult a qualified professional or conduct your own research before implementing any solutions or advice mentioned here.