10/02/2015

Debugging .Net with System.Diagnostics

.Net

Oh debugging, you dastardly fiend! While it’s not likely to be at the top of your favorites list, proficiency with it is a requirement for any developer. It’s well known that debugging any application can not only be a big pain point, but also downright difficult to do, even if it’s on your own machine. In this post I want to go over objects within the System.Diagnostics namespace that you can use to make both debugging locally and on deployed environments less of a hassle.
I’m also going to assume here that you have a basic understanding of debugging in Visual Studio, as well as compiler symbols (via the #define directive).

The Common Stuff

There’s a bunch of goodies in the System.Diagnostics namespace and, in my experience, most people have heard of at least a couple of items (or they should have).

Debug and Trace classes

These fairly well known classes are easy ways that you can output specific programming information to dedicated debugging streams, and then output those streams to a variety of locations. Both classes use a listener pattern, where you can add text to a dedicated stream, and then elsewhere in your application have a dedicated listener to handle information added to that stream.

In this very primitive example, you can see that I’m writing specific messages to either the Trace or Debug streams. When the application runs in Visual Studio the messages will be output to the Output window, as shown here:

Why would you use this though? The benefit lies in a couple of key points:

  1. Both the Debug and Trace classes are static, so you can write to them from anywhere in your application (they also contain similar methods) without having to initialize objects or use heavy syntax.
  2. You can attach as many listeners to each stream as you’d like, from anywhere, at any time. Also note that there are some great built in .Net listeners that easily allow you to stream their output to a variety of sources, such as flat files.
  3. The statements can become inert by toggling compilation symbols. Disable the DEBUG symbol and debug messages won’t be output to the stream; disable the TRACE symbol and trace messages won’t be output to the stream.
  4. These classes have been available in every version of .Net since 2.0.

I’m not going to go into this in any more detail, but if you’re interested, check out the MSDN documentation for each that provides plenty of implementation examples: Debug (class) and Trace (class).

Working with Windows Event Log

With the improvements in the native Windows Event Log, writing to a custom Windows log has really become easier and faster. While this isn’t always a great choice for a web application, if you ever find yourself in the position of writing a Windows Service or otherwise complex long-running application in Windows, writing to the Windows Event Log can be a great choice. Let’s check out a quick code example:

In this example, I make sure my log source exists, initialize my eventlog object, and log away! Or saw away? Or some tree log pun that I can’t make happen right now.

Again, I’ll pose my favorite question: why would you use this?

  1. It’s native. You don’t need to worry about file system permissions, or whether or not the log exists, you just write the message and you’re done.
  2. If you’re building a Windows Service, for example, the ServiceBase class has properties built into it for you to write to the log.

The important objects to check out are: EventLog (class)EventLogPermission (class), and EventLogEntry.

The Not-So-Common Stuff

Alright, let’s get to the not commonly used items. There’s some real gold in here so I’m going to separate it out into different sections for different use cases and functionality. Also, note that the entire System.Diagnostics namespace has some really great things in it, so if you get a chance do a little spelunking: MSDN – System.Diagnostics Namespace.

The Last Word

There’s a ton of debugging and diagnostic power within the System.Diagnostics namespace. By adding some of the things I’ve mentioned above, you can make your current and future applications easier to debug and track across environments. And, as they say, the more you know.

Debugging Descriptions and Metadata

The debugger is just a powerful piece of source code, and it can take some cues from your source code if you know to provide them. There are a handful of metadata-related attributes in the Diagnostics namespace that allow you to manipulate how the debugger reports information about your objects, but my favorite one is:

DebuggerDisplayAttribute

The DebuggerDisplayAttribute allows you to control what appears in the value column of the Watch, Locals, or Auto windows in Visual Studio. A great example is that you create a custom class, and you add a reference to that class in the Watch window, and it will appear similar to {Namespace.CustomClass}, which isn’t really that helpful. You can decorate the object with this attribute and tell it to output something different. For example:

Now, if I add an instance of this to the Watch window, the value might look something like: Employee: Id – 1. So not only could I change the output value, I can also have it dynamically resolve a property or method that belongs to it! Of course, you need to be careful so that you don’t add anything to the attribute that takes a significant amount of time to run or it will make your debugging experience a nightmare.

Runtime Conditional Operations

There are two classes that allow you to easily setup a mechanism for controlling debugging output without using compiler variables, and do not require changing source code. Conveniently, you can define them in the web.config for easy manipulation.

BooleanSwitch

At first glance, the BooleanSwitch class might appear to be a normal boolean, and while it is that, it’s controlled via the web.config; let’s look at a quick example. First, what’s in the web.config:

Next, what’s in code:

What’s happening here, is that I have an element defined in the web.config that I’m checking from code. The BooleanSwitch class automatically converts the value of the value attribute into a boolean. This simple mechanism allows easy debugging switches to be added into code, with some basic validation, and without cluttering up the appSettings section (which frequently gets abused).

SourceSwitch

Similar to the BooleanSwitch, SourceSwitch allows you to pull a value from a dedicated section in the web.config. The only difference is instead of converting the value of the value attribute into a boolean, it get’s converted into the SourceLevelsenum. This way you can set a level to the field you’re adding, such as All, Critical, Warning, and Off. Let’s take a look at the web.config and code for this implementation:

And the code:

Compiler Conditional Operations

A couple of the classes within this namespace allow you to make conditional compiler operations. If you’re familiar with compiler declarations, you know that #define TEST defines a compiler-time variable named TEST. This is powerful because you can, based on the project configuration that you’re currently running, attach or remove specific debugging and trace operations from your code when it’s compiled.

Generally, when you want to conditionally run a method if the DEBUG variable is defined, you can do the following:

While this is great, and you can do some else blocks, if you just need the basic if variable syntax the ConditionalAttributeclass is made for you. Now you can decorate the method like so:

and just call it wherever you’d like. Now, if DEBUG is defined, the method will be called, and if it isn’t, it will be skipped. It accomplishes the same thing as above, but IMO, if the syntax is a bit more nice then I don’t need to ensure that all developers remember when they call this method they should wrap it in a #if block.

Debugger Control

There are plenty of Attributes that live within the Diagnostics namespace, and a good number of them allow you to control debugging flow. There’s really only one here that I use occasionally, while the other two are interesting to note.

DebuggerNonUserCodeAttribute

The DebuggerNonUserCodeAttribute allows you to explicitly mark methods that were generated by code generators so that while you are performing line-by-line debugging, the debugger knows to skip over the method rather than into it. This is really great for either auto-generated code, or framework code where you’re setting up things like unit tests, IoC, or object mappings. This attribute also acts as a kind of combination of the next two attributes.

DebuggerStepThroughAttribute

The DebuggerStepThroughAttribute allows you to explicitly mark a method that the debugger is not allowed to step into. Primarily, this was created so that a debugger cannot debug its own code, but can also be useful with framework and auto-generated code. Thus while you’re debugging, even if you instruct the debugger to step into the method, it will step over it.

DebuggerHiddenAttribute

The DebuggerHiddenAttribute allows you to explicitly mark a method that should be completely hidden from the debugger. Similar to the DebuggerStepThroughAttribute, it was originally created for debuggers so that they didn’t get caught attempting to debug their own source. This attribute also ensures that no breakpoints can be set within the method.

Debugger

Yup, there’s a class called Debugger and, rather unsurprisingly, allows you to manipulate the debugger if it’s attached. Again, there’s not always a huge use case for this, but sometimes it’s a really powerful tool to help with debugging. My favorite use case for this guy is to have a known set of breakpoints that are triggered in code by declaring specific compilation symbols. For instance, #define DEBUGBULKCOPY turns on breakpoints within the Sql Bulk Copy routines (which are long, lengthy, and tend to break in the same places).

The Last Word

There’s a ton of debugging and diagnostic power within the System.Diagnostics namespace. By adding some of the things I’ve mentioned above, you can make your current and future applications easier to debug and track across environments. And, as they say, the more you know.

More Thoughts

Need help with your next digital project?

Click on that button and fill in the simple form.