Debugging Mastery: Advanced Techniques for .NET, JavaScript, and Containers

Debugging Mastery: Advanced Techniques for .NET, JavaScript, and Containers

Introduction

Effective debugging separates proficient developers from experts. This guide covers advanced debugging techniques across .NET applications, JavaScript frontends, and containerized environments, including breakpoints strategies, memory profiling, remote debugging, and production diagnostics using Visual Studio, VS Code, Chrome DevTools, and specialized tools.

.NET Debugging in Visual Studio

Advanced Breakpoints

Conditional Breakpoints:

// Set breakpoint condition in Visual Studio:
// Right-click breakpoint → Conditions → Conditional Expression
// Example: orderId == 12345 || customer.IsVIP

public async Task<Order> ProcessOrder(int orderId)
{
    var order = await _orderRepository.GetById(orderId); // Breakpoint here
    // Condition: orderId == 12345
    
    if (order == null)
        throw new OrderNotFoundException(orderId);
    
    return order;
}

Hit Count Breakpoints:

// Break only when hit count condition is met
// Condition: Hit count is a multiple of 100

foreach (var item in largeCollection)
{
    ProcessItem(item); // Breaks every 100th iteration
}

Tracepoints (Logging Without Code Changes):

// Right-click breakpoint → Actions → Log message to Output
// Message: Order {orderId} processing started at {DateTime.Now}

public void ProcessOrder(int orderId)
{
    // Tracepoint logs without stopping execution
    var result = PerformOperation(orderId);
}

Data Visualization

DataTips and Pinned Variables:

// Hover over variable → Pin to source
// Pinned variables persist across debugging sessions

var customer = new Customer
{
    Id = 123,
    Name = "John Doe",
    Orders = new List<Order>() // Pin this to watch changes
};

// Use $ prefix for JSON serialization in Watch window
// $customer  → Shows JSON representation

Immediate Window:

// Immediate Window (Ctrl+Alt+I)

// Execute methods
$customer.GetTotalSpent()

// Modify variables
$customer.IsVIP = true

// Evaluate expressions
$customer.Orders.Where(o => o.Total > 1000).Sum(o => o.Total)

Memory Profiling

Memory Usage Tool:

// Debug → Performance Profiler → Memory Usage
// Take snapshots before/after operation to find leaks

public class PotentialLeakExample
{
    private static List<byte[]> _cache = new List<byte[]>();
    
    public void LoadData()
    {
        // Memory leak: large arrays never released
        _cache.Add(new byte[1024 * 1024]); // 1 MB
    }
}

Analyze with dotMemory or PerfView:

# Collect memory dump
dotnet-dump collect --process-id <pid>

# Analyze with dotnet-dump
dotnet-dump analyze dump_20250714.dmp

> dumpheap -stat
> dumpheap -mt <MethodTable>
> gcroot <ObjectAddress>

Exception Settings

Break on Exceptions:

// Debug → Windows → Exception Settings
// Configure to break on specific exceptions

try
{
    var result = await _apiClient.GetDataAsync();
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
    // Break here only for NotFound
    _logger.LogWarning("Resource not found");
}

Remote Debugging

Attach to Azure App Service:

# Enable remote debugging in Azure Portal
az webapp config set --resource-group myRG --name myApp --remote-debugging-enabled true

# Attach from Visual Studio:
# Debug → Attach to Process → Connection Type: Microsoft Azure App Services
# Select subscription → Select app → Attach

SSH Debugging on Linux:

# Install vsdbg on remote Linux server
curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg

# VS Code launch.json
{
  "type": "coreclr",
  "request": "attach",
  "name": "Attach to Remote",
  "processId": "${command:pickRemoteProcess}",
  "pipeTransport": {
    "pipeProgram": "ssh",
    "pipeArgs": ["-T", "user@remote-server"],
    "debuggerPath": "~/vsdbg/vsdbg",
    "pipeCwd": "${workspaceFolder}"
  }
}

JavaScript Debugging

Chrome DevTools Advanced

Breakpoint Types:

// Conditional breakpoint
// Right-click line number → Add conditional breakpoint
// Condition: userId === 12345

function fetchUserData(userId) {
  return fetch(`/api/users/${userId}`)  // Breakpoint here
    .then(response => response.json());
}

// Logpoint (Console message without stopping)
// Right-click line number → Add logpoint
// Message: User ${userId} data fetched

// XHR/Fetch breakpoints
// Sources → XHR/fetch Breakpoints → Add → URL contains "api/orders"

Call Stack Navigation:

// Async stack traces enabled by default in Chrome DevTools
async function processOrder(orderId) {
  const order = await fetchOrder(orderId);  // Pause here
  const validated = await validateOrder(order);  // Stack shows full async chain
  return validated;
}

// Blackbox scripts to skip framework code
// Settings → Blackboxing → Add pattern: node_modules

Scope and Closure Inspection:

function createCounter() {
  let count = 0;  // Inspect closure variables in Scope panel
  
  return {
    increment: () => ++count,
    getCount: () => count  // Breakpoint here shows closure scope
  };
}

const counter = createCounter();
counter.increment();
console.log(counter.getCount());  // Inspect count in closure

VS Code JavaScript Debugging

launch.json Configuration:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Node.js App",
      "program": "${workspaceFolder}/app.js",
      "skipFiles": ["<node_internals>/**", "node_modules/**"],
      "env": {
        "NODE_ENV": "development"
      },
      "console": "integratedTerminal"
    },
    {
      "type": "chrome",
      "request": "launch",
      "name": "Debug React App",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceFolder}/src",
      "sourceMapPathOverrides": {
        "webpack:///src/*": "${webRoot}/*"
      }
    }
  ]
}

Debugging TypeScript:

{
  "compilerOptions": {
    "sourceMap": true,  // Enable source maps
    "inlineSourceMap": false,
    "inlineSources": false
  }
}

React DevTools

Component Debugging:

// Install React DevTools browser extension

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    // Inspect in Components tab → UserProfile → Hooks
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  // Right-click component in DevTools → "Inspect the matching DOM element"
  return <div>{user?.name}</div>;
}

// Profiler for performance
// DevTools → Profiler → Record → Interact → Stop
// Analyze render times and unnecessary re-renders

Network Debugging

Fetch/XHR Inspection:

// Chrome DevTools → Network → Filter: Fetch/XHR

// Override responses for testing
// Sources → Overrides → Enable Local Overrides
// Right-click response → Override content

// Replay requests
// Network → Right-click request → Replay XHR

// Copy as cURL/fetch
// Right-click request → Copy → Copy as fetch/cURL

Container Debugging

Docker Container Debugging

Attach Debugger to Running Container:

# Start container with debugger port exposed
docker run -d -p 5000:5000 -p 5001:5001 \
  -e ASPNETCORE_ENVIRONMENT=Development \
  myapp:latest

# VS Code launch.json for Docker attach
{
  "type": "coreclr",
  "request": "attach",
  "name": "Attach to Docker",
  "processId": "${command:pickRemoteProcess}",
  "pipeTransport": {
    "pipeProgram": "docker",
    "pipeArgs": ["exec", "-i", "myapp-container"],
    "debuggerPath": "/vsdbg/vsdbg",
    "pipeCwd": "/app"
  }
}

Install Debugger in Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 5000

# Install debugger for Development stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
RUN apt-get update && apt-get install -y unzip && \
    curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg

WORKDIR /src
COPY ["MyApp.csproj", "./"]
RUN dotnet restore

COPY . .
RUN dotnet build -c Debug -o /app/build

FROM build AS publish
RUN dotnet publish -c Debug -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
COPY --from=build /vsdbg /vsdbg
ENTRYPOINT ["dotnet", "MyApp.dll"]

Debug Node.js in Container:

FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .

# Expose debug port
EXPOSE 9229

# Start with --inspect flag
CMD ["node", "--inspect=0.0.0.0:9229", "app.js"]

VS Code Configuration:

{
  "type": "node",
  "request": "attach",
  "name": "Attach to Docker Node",
  "address": "localhost",
  "port": 9229,
  "localRoot": "${workspaceFolder}",
  "remoteRoot": "/app",
  "skipFiles": ["<node_internals>/**"]
}

Kubernetes Pod Debugging

Port Forward for Debugging:

# Forward debugger port from pod
kubectl port-forward pod/myapp-pod-12345 5001:5001

# Attach with VS Code using localhost:5001

Debug Ephemeral Container:

# Create debug container in pod (Kubernetes 1.23+)
kubectl debug -it myapp-pod-12345 \
  --image=mcr.microsoft.com/dotnet/sdk:8.0 \
  --target=myapp-container \
  -- bash

# Install debugger in ephemeral container
curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l /vsdbg

Production Diagnostics

Application Insights

Track Custom Events:

using Microsoft.ApplicationInsights;

public class OrderService
{
    private readonly TelemetryClient _telemetry;
    
    public async Task ProcessOrder(Order order)
    {
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            await _orderRepository.Save(order);
            
            _telemetry.TrackEvent("OrderProcessed", 
                properties: new Dictionary<string, string>
                {
                    ["OrderId"] = order.Id.ToString(),
                    ["CustomerId"] = order.CustomerId.ToString()
                },
                metrics: new Dictionary<string, double>
                {
                    ["ProcessingTime"] = stopwatch.ElapsedMilliseconds
                });
        }
        catch (Exception ex)
        {
            _telemetry.TrackException(ex, 
                properties: new Dictionary<string, string>
                {
                    ["OrderId"] = order.Id.ToString()
                });
            throw;
        }
    }
}

Live Metrics Stream:

# Real-time debugging in Azure Portal
# Application Insights → Live Metrics

# Filter by custom dimensions
# Dimension: operation_Name = ProcessOrder
# Shows live exceptions, requests, dependencies

Snapshot Debugger

Enable Snapshot Debugger:

// Install NuGet: Microsoft.ApplicationInsights.SnapshotCollector

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry();
    services.AddSnapshotCollector();
}

// Snapshots captured automatically on exceptions
// View in Application Insights → Failures → Exception → Open Snapshot

Log Aggregation

Structured Logging with Serilog:

Log.Logger = new LoggerConfiguration()
    .Enrich.WithProperty("Application", "MyApp")
    .Enrich.WithProperty("Environment", "Production")
    .WriteTo.Console()
    .WriteTo.ApplicationInsights(telemetryConfiguration, TelemetryConverter.Traces)
    .CreateLogger();

// Query in Log Analytics
LogsTable
| where customDimensions.Application == "MyApp"
| where severityLevel >= 3  // Warning and above
| project timestamp, message, customDimensions

Performance Profiling

CPU Profiling

Visual Studio Diagnostic Tools:

// Debug → Performance Profiler → CPU Usage
// Record session while reproducing slow scenario

public async Task<List<Product>> GetProductsAsync()
{
    // Hot path identified by profiler
    var products = await _dbContext.Products
        .Include(p => p.Category)  // N+1 query issue
        .ToListAsync();
    
    // Optimized version
    var optimized = await _dbContext.Products
        .AsNoTracking()
        .Select(p => new ProductDto 
        { 
            Id = p.Id, 
            Name = p.Name, 
            CategoryName = p.Category.Name 
        })
        .ToListAsync();
    
    return products;
}

Chrome DevTools Performance

Record Performance Profile:

// DevTools → Performance → Record
// Interact with app → Stop recording

// Identify:
// - Long tasks (> 50ms)
// - Forced reflows/layouts
// - Excessive JS execution
// - Memory leaks (increasing heap size)

// Example: Optimize React rendering
const MemoizedComponent = React.memo(({ data }) => {
  // Prevents unnecessary re-renders
  return <ExpensiveComponent data={data} />;
});

Best Practices

  1. Reproduce Consistently: Create minimal reproduction steps before debugging
  2. Understand the Stack: Read error messages and stack traces carefully
  3. Binary Search: Comment out half the code to isolate the issue
  4. Logging Over Breakpoints: Use structured logging for asynchronous/distributed issues
  5. Source Maps: Always generate source maps for production deployments
  6. Remote Debugging: Only enable in isolated environments, not public production
  7. Memory Dumps: Collect dumps when issues are intermittent or production-only
  8. Version Control: Commit before debugging sessions to easily revert changes

Troubleshooting Common Issues

Breakpoint Not Hitting:

  • Verify source maps are generated and loaded
  • Check if code is optimized (Release build skips breakpoints)
  • Ensure debugger is attached to correct process
  • Clear browser cache for web apps

Slow Debugging:

  • Disable "Just My Code" (Visual Studio)
  • Reduce number of watched variables
  • Use conditional breakpoints instead of stepping through loops

Can't Attach to Process:

  • Run IDE with administrator privileges
  • Check firewall rules for remote debugging
  • Verify process is not already attached by another debugger

Key Takeaways

  • Conditional and hit-count breakpoints reduce debugging noise
  • Memory profiling identifies leaks before they impact production
  • Remote debugging enables troubleshooting in realistic environments
  • Production diagnostics (Application Insights, Snapshot Debugger) capture real user issues
  • Container debugging requires debugger installation in images

Next Steps

  • Explore Time Travel Debugging with WinDbg or Visual Studio Enterprise
  • Learn Flame Graphs for visualizing performance bottlenecks
  • Investigate Distributed Tracing with OpenTelemetry for microservices debugging
  • Master Reverse Engineering with dnSpy for .NET or Chrome DevTools for JavaScript

Additional Resources


Debug smarter, not harder.