Skip to Content
Course content

227: Debug and Trace Classes

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've all been there. You're staring at a bug that only happens every tenth single time a user hits a specific button, and your first instinct is to pepper your code with Console.WriteLine("I am here 1"); and Console.WriteLine("Now I am here 2");. It's the fastest way to get a pulse on what's happening in your classes, and in a tiny console app, it works fine. But once you start building real-world systems—especially those with multiple classes interacting—that habit becomes a liability.

The "Print-Line" Trap

Imagine you're building a PaymentProcessor class. To track down why some transactions are failing, you add Console.WriteLine calls inside your ProcessPayment method and your ValidateAccount helper. It feels productive until you realize a few things. First, these messages are now hard-coded into your logic. If you ship this to a client, they're going to see your internal debugging chatter in their console or logs. Second, you can't easily turn them off. You have to go back through every class and manually delete or comment out those lines before you commit your code.

// The naive way: Hard-coded, noisy, and permanent
public class PaymentProcessor 
{
    public void ProcessPayment(PaymentRequest request) 
    {
        Console.WriteLine($"DEBUG: Processing payment for {request.UserId}"); 
        if (ValidateAccount(request)) 
        {
            Console.WriteLine("DEBUG: Account validated successfully");
            // process payment...
        }
    }
}


This approach mixes your business logic (processing a payment) with your diagnostic logic (reporting that the payment is processing). It's a violation of concerns that makes your code cluttered and harder to maintain.

Separating Intent from Output

The professional way to handle this is by using the System.Diagnostics namespace, specifically the Debug and Trace classes. To the naked eye, Trace.WriteLine() looks almost exactly like Console.WriteLine(), but the plumbing underneath is entirely different. When you use Trace, you aren't telling the program to "print this to the screen right now"; you're telling the program to "emit a diagnostic message."

Where that message actually goes is decided by Listeners. By default, it goes to the output window of your IDE, but you can attach a TextWriterTraceListener to send those messages to a log file, or a custom listener to send them to a database, all without touching the code inside your PaymentProcessor class. I love this because it means I can leave the trace calls in the code permanently, and simply toggle whether they are recorded via a configuration file or a single line of setup in Program.cs.

using System.Diagnostics;

public class PaymentProcessor 
{
    public void ProcessPayment(PaymentRequest request) 
    {
        // This intent stays, but the output is configurable
        Trace.WriteLine($"Processing payment for {request.UserId}"); 
        if (ValidateAccount(request)) 
        {
            Trace.WriteLine("Account validated successfully");
            // process payment...
        }
    }
}


Knowing when to Debug vs. Trace

You'll notice there's both a Debug class and a Trace class. The distinction is subtle but critical for your build pipeline. Debug.WriteLine calls are only compiled into your assembly when the DEBUG symbol is defined (typically only in the "Debug" build configuration). When you switch to "Release" mode for production, the compiler literally strips those lines out of the binary. They don't exist in the final product, meaning zero performance hit.

Trace.WriteLine, however, stays in the binary regardless of the build configuration. I use Debug for "developer-only" noise—things like verifying a loop ran the correct number of times. I use Trace for high-level operational milestones—like "Payment Gateway Connection Established"—that I might actually need to see in a production log file when a customer reports an error. If you use Debug for everything, you'll find yourself blind the moment you deploy to a production server.




📋 Practical Task

Refactoring the OrderManager Audit Log

You have been handed a OrderManager class that is currently using Console.WriteLine to track order status changes. This is polluting the production console and cannot be turned off. Your task is to refactor this into a professional tracing system.

Requirements:

  • Replace all Console.WriteLine calls in the OrderManager class with Trace.WriteLine.
  • In the Main method of the program, add a TextWriterTraceListener that redirects all trace output to a file named order_log.txt.
  • Add one Debug.WriteLine call that only triggers when an order is "Internal" (a developer-only check) to ensure it doesn't appear in the final release build.
  • Call Trace.Flush() at the end of the program to ensure the file is written correctly.
using System;
using System.Diagnostics;
using System.IO;

public class OrderManager 
{
    public void UpdateOrderStatus(int orderId, string status, bool isInternal) 
    {
        // TODO: Refactor this line
        Console.WriteLine($"Order {orderId} changed status to {status}");

        if (isInternal) 
        {
            // TODO: Add a Debug trace here for internal orders
        }
    }
}

public class Program 
{
    public static void Main() 
    {
        // TODO: Configure the TraceListener to write to "order_log.txt"
        
        OrderManager manager = new OrderManager();
        manager.UpdateOrderStatus(101, "Shipped", false);
        manager.UpdateOrderStatus(102, "Pending", true);

        // TODO: Flush the trace
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.