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
- Reproduce Consistently: Create minimal reproduction steps before debugging
- Understand the Stack: Read error messages and stack traces carefully
- Binary Search: Comment out half the code to isolate the issue
- Logging Over Breakpoints: Use structured logging for asynchronous/distributed issues
- Source Maps: Always generate source maps for production deployments
- Remote Debugging: Only enable in isolated environments, not public production
- Memory Dumps: Collect dumps when issues are intermittent or production-only
- 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
- Visual Studio Debugging Documentation
- Chrome DevTools Documentation
- VS Code Debugging Guide
- Application Insights Documentation
Debug smarter, not harder.