Breakups with Breakpoints: Creating configurable debug traces with C# and .NET

Breakpoints: Secretly evil

There. I said it. You can get mad and leave a nasty comment now, or you can hear me out. I’ve been writing .NET for years using breakpoints because that’s just how they train you to do it. It’s how things are done in Visual Studio because… why not? It’s there to use isn’t it? But is it always the best way to solve a problem, especially a very complex one?

A spotlight on the problem

Don’t get me wrong – breakpoints are an invaluable tool and not to be underestimated. For quick and dirty debugs, they’re almost always the way to go. However, for complex problems they can be limiting and cloud your broader vision. Set a breakpoint here, set a breakpoint there, hit F10 twenty times, hit F5 and continue… read a variable here, read a variable there, continue again, read another variable… wait, what happened back there a minute ago? Where am I now? What is going on? Suddenly I am super lost in my own code.

Call me attention deficit but I seriously don’t have the attention span or cognitive capacity to track all of these numbers and values and process flow all at once… and why should I? This is why computers were created in the first place. What we need is a way to trace the process AND the values at the same time, in a readable format that I can comprehend and process in my own way.

A floodlight on the problem

It’s time to say hello to our new best fiends: the Trace class and Visual Studio’s Output window. Using these we can pretty much write out anything we want in string format to the Output window and watch the program go to work.

Trace.WriteLine(string.Format("[{0:HH:mm:ss.fff}] The current value is: {1}", DateTime.Now, myVariable));

This is our basic trace. With these you can go to the moon and back, printing everything you want. The Trace class requires the TRACE compiler constant to be defined in your build settings. You can see if this is set by right clicking on the project (not the solution!) and going to Properties. They will be under the Build tab.

Defining the TRACE constant

You will pretty much always only want this defined in your Debug or other debugging-related build configurations. Leaving tracing enabled on your production builds can seriously hurt performance, so watch out. I’ll mention a bit more about this later on.

Comment in! Comment out! Don’t commit that! Delete those lines!

Possibly the worst part about tracing lines is that most people have to comment them in and out of their code when they want to do debugging. Other times they delete them and then end up needing them later. Sometimes they’ll get committed into source control in the wrong state. Other headaches, problems, etc… What a pain. How about we avoid all of that using a little compiler magic that .NET provides for us?

As we just saw above we can define the TRACE constant to enable the Trace class. If this isn’t defined then our calls into the Trace class are actually compiled all the way out of the build! It’s like those lines don’t even exist in our source. Amazing, right? The problem here is, sometimes I want to trace things in debug mode and sometimes I don’t.

“What’s the harm in tracing if I’m already debugging?” you might be asking – here is why. Tracing is super useful, but it is also super slow. If you have a large loop with some tracing inside you will really feel the difference as the Output window struggles to capture everything you are dumping out into it. Sometimes I want to run debug mode, but I don’t have all day to wait for the Output window to fill with more traces than I can even consume.

What I’d really love is the ability to do tracing on my own terms, in my own consistent format, while being able to completely compile them out of my assembly with the switch of a build configuration.

What do you think? Can we build it?

YES WE CAN! (Please don’t sue me)

Absolutely we can, thanks to the power of the Conditional attribute. Since TRACE is already taken as a compiler constant, I am going to use my own. If that’s not your style, you can just repurpose TRACE to do what you need – I promise it won’t hurt my feelings… much 🙁

Creating our new build configuration

First things first we’re going to create a new build configuration for our project. Here’s the step by step:

Go to the configuration manager for your solution

Select to create a new configuration for your project

Enter the config’s name and choose to copy from something (I like Debug for this). If this is the first project in your solution you are doing this for then also choose to create a new solution configuration.

If you have more than one project in your solution (like I do here) you will need to do this for each one you would like to trace on. I would recommend using the same name, and after the first time you do this in a solution don’t select to create a new configuration – it will throw an error if you have already use that name.

Now you can select the Trace configuration from your list and debug with it

There’s just one more thing we need to do now to turn it on. We’ll set our own compiler constant so that we can control it outside of the standard TRACE constant. Go ahead and right click on your project (not solution!) again and hit Properties. Go to the Build tab. Here we will add the OUTPUT compiler constant as shown.

Setting the OUTPUT compiler constant for our Trace build configuration

Nice! Now we’re ready to to put it into action.

Writing compiler conditional code

For my project, I’m going to have a static class that I’ll use to write my helper methods for tracing. You can do this however you like, but here’s my example class:

using System.Diagnostics;
 
// You should probably put this in a namespace so it's not dangling
 
public static class CoreHelpers
{
	[Conditional("OUTPUT")]
	public static void WriteOut(string str)
	{
		Trace.WriteLine(string.Format("[{0:HH:mm:ss.fff}] {1}", DateTime.Now, str));
	}
}

 

Great! Now that I have my helper method setup I can start tracing with it. Here’s an example from my project:

//Draw the moisture
// ... code code code code here
CoreHelpers.WriteOut("Moisture Value: " + moisture);
CoreHelpers.WriteOut("Moisture Density: " + moistureDensity);
// ... more code code code code here

 

Putting it to work

Now when I debug my project with Visual Studio and my new Trace build configuration I will see these messages in the Output window:

Visual Studio’s Output window shows our tracing lines

From the Output window you can copy results, hit the clear button, run them again, paste them, save them, look for points where things go wrong in the process, difference them to look for variations (check out my favorite difftool KDiff3), send them to customers/clients for verification, or whatever else you need to do.

Now instead of getting a single look at a variable or two, with no scope on the overall process, and no way to document what in the world just happened you get a nice, neat output complete with timestamps to look for things that take too long.

Cleaning up some trace trash

By default, Visual Studio has a bunch of other things writing into the Output window. You’ll see traces about bypassed code, loaded/unloaded modules and assemblies, etc… This might be useful to you, but it’s not useful to me. It’s annoying and clutters up my output. So I turned them off. Go to Tools, Options, and find Output Window under Debugging. These are the options I have set, but you can adjust them however you like.

Changing your Output window options in Visual Studio

Final thoughts and other ideas

To wrap up, I just wanted to list other thoughts and ideas I had around this. Feel free to leave comments if you have other thoughts!

  • This is how I have done this on one project and it has worked out really well. However, in the future I may use the existing TRACE variable instead of defining my own. I’ll probably disable it in my base Debug build and enable it in my copied Trace build.
  • I think there is a real benefit to creating your own WriteOut method or whatever you want to call it. Doing it this way you could very easily switch to log to another source (such as a text file) or control the Trace output on a more complex level (Normal, Fine, Superfine… like Java does). It’s your call. In fact you could even write the same method twice and have it compile differently using the #if compiler directive. Imagine tracing to a different source with the switch of a build configuration! Amazing.
  • I think that it could be very possible to replace some of your comments with detailed tracing messages. Since they are removed at compile time I don’t see the harm in leaving them there. If you think they are cluttering your code, use them like comments instead of writing more comments.
    They should help explain your code and you can turn them back on (or off) with the flip of a build config. Save yourself the headache of remembering to comment them in or out, remove them, avoid committing them to source control, etc…
  • Know when to call it – writing extensive tracing is not always a good or useful way to spend your time. If you think it’s quicker or easier to use breakpoints then do it! There’s nothing wrong with them. You can even create conditional breakpoints to search for specific conditions without hitting F5 hundreds of time and watching variables like a hawk.

About James

I own this site. I love code. The end?
This entry was posted in Hacks, Programming. Bookmark the permalink.

One Response to Breakups with Breakpoints: Creating configurable debug traces with C# and .NET

  1. Jason Goemaat says:

    I like it. Even smoother if you let WriteOut take arguments to format like normal WriteLine():

    [Conditional("OUTPUT")]
    public static void WriteOut(string str, params object[] args)
    {
    Trace.WriteLine(string.Format("[{0:HH:mm:ss.fff}] {1}", DateTime.Now, string.Format(str, args)));
    }

Leave a Reply

Your email address will not be published. Required fields are marked *