Debugging with X-Ray
Stacks.js X-Ray provides powerful debugging, profiling, and inspection tools for development and production troubleshooting.
Configuration
X-Ray Configuration
// config/xray.ts
export default {
// Enable X-Ray (usually only in development)
enabled: process.env.NODE_ENV === 'development',
// Debug bar
debugBar: {
enabled: true,
position: 'bottom', // 'bottom' | 'top' | 'right'
collapsed: false,
},
// Request logging
requests: {
enabled: true,
logBody: true,
logHeaders: true,
maxBodySize: 10000, // chars
},
// Query logging
queries: {
enabled: true,
slowThreshold: 100, // ms
explain: true,
},
// View debugging
views: {
enabled: true,
showVariables: true,
},
// Performance profiling
profiling: {
enabled: true,
sampleRate: 0.1, // 10% of requests in production
},
// Error tracking
errors: {
enabled: true,
stackTraceLines: 20,
captureContext: true,
},
// Memory tracking
memory: {
enabled: true,
warnThreshold: 100 * 1024 * 1024, // 100MB
},
}
Debug Bar
Enable Debug Bar
<!-- layouts/app.stx -->
<html
<body
<x-slotname="
<!-- Debug bar (only shown in development) -->
@if(config'xray.debugBar.enabled')
<x-debug-bar
</body
</html
Debug Bar Panels
The debug bar shows:
- Request Info: Method, URL, status, duration
- Database Queries: All queries with timing and bindings
- Views: Rendered views and their data
- Cache: Cache hits, misses, and operations
- Session: Session data
- Logs: Log entries for this request
- Timeline: Request lifecycle events
- Memory: Memory usage
Custom Debug Bar Panels
import { DebugBar } from '@stacksjs/xray'
// Register custom panel
DebugBar.addPanel({
name: 'Custom',
icon: 'cog',
render: (data) => {
return `
<div class="panel-content">
<h3>Custom Data</h3>
<pre>${JSON.stringify(data, null, 2)}</pre>
</div>
`
},
collect: async (request) => {
return {
customField: 'custom value',
}
},
})
Dump & Die
dd() Function
import { dd, dump } from '@stacksjs/xray'
// Dump and die (stops execution)
dd(variable)
// Dump multiple variables
dd(user, request, config)
// Dump without stopping
dump(variable)
// Dump to log instead of output
dumpToLog(variable)
Dump in Views
<!-- Dump variable in template -->
<x-dump:data="user
<!-- Dump and die -->
<x-dd:data="user
<!-- Conditional dump -->
($user)
<!-- Dump with label -->
<x-dump:data="userlabel="
Pretty Dump Output
// Configure dump appearance
import { Dumper } from '@stacksjs/xray'
Dumper.configure({
maxDepth: 10,
maxItems: 100,
theme: 'dark', // 'dark' | 'light'
showMethods: false,
showPrivate: true,
})
Query Debugging
Query Logging
import { DB, QueryLog } from '@stacksjs/database'
// Enable query logging
DB.enableQueryLog()
// Run queries...
const users = await User.where('active', true).get()
// Get logged queries
const queries = DB.getQueryLog()
// [
// { sql: 'SELECT * FROM users WHERE active = ?', bindings: [true], time: 2.5 },
// ...
// ]
// Listen to queries in real-time
DB.listen((query) => {
console.log(query.sql, query.bindings, `${query.time}ms`)
})
// Log slow queries
DB.listen((query) => {
if (query.time > 100) {
Log.warning('Slow query', { sql: query.sql, time: query.time })
}
})
Query Explain
import { XRay } from '@stacksjs/xray'
// Explain query
const explanation = await XRay.explainQuery(
'SELECT * FROM users WHERE email = ?',
['test@example.com']
)
// {
// plan: '...',
// indexUsed: 'users_email_index',
// rowsExamined: 1,
// possibleKeys: ['users_email_index'],
// }
// Explain model query
const users = User.where('email', 'test@example.com')
const plan = await XRay.explain(users)
N+1 Detection
import { XRay } from '@stacksjs/xray'
// Enable N+1 detection
XRay.detectNPlusOne()
// This will trigger a warning:
const posts = await Post.all()
for (const post of posts) {
console.log(post.author.name) // N+1 query!
}
// Warning: N+1 query detected: Post -> Author (50 queries)
// Solution: Use Post.with('author').all()
Request Inspection
Request Logging
import { XRay } from '@stacksjs/xray'
// Log full request
XRay.logRequest(request)
// Log response
XRay.logResponse(response)
// Get request timeline
const timeline = XRay.getTimeline()
// [
// { event: 'request_start', time: 0 },
// { event: 'middleware_auth', time: 5 },
// { event: 'controller_start', time: 10 },
// { event: 'query_1', time: 15 },
// { event: 'query_2', time: 25 },
// { event: 'view_render', time: 50 },
// { event: 'response_sent', time: 75 },
// ]
Request Replay
// Save request for replay
await XRay.saveRequest(request, 'debug-request-1')
// Replay saved request
const response = await XRay.replayRequest('debug-request-1')
// Export request as cURL
const curl = XRay.toCurl(request)
// curl -X POST http://localhost:3000/api/users -H 'Content-Type: application/json' -d '{"name":"John"}'
Profiling
Performance Profiling
import { Profiler } from '@stacksjs/xray'
// Start profiling
Profiler.start('user-creation')
// ... code to profile ...
// Stop and get results
const result = Profiler.stop('user-creation')
// { name: 'user-creation', duration: 45.2, memory: 1024 }
// Profile a function
const result = await Profiler.profile('expensive-operation', async () => {
return await expensiveOperation()
})
// Profile with segments
Profiler.start('process')
await step1()
Profiler.segment('step1-complete')
await step2()
Profiler.segment('step2-complete')
await step3()
const profile = Profiler.stop('process')
// Includes timing for each segment
CPU Profiling
# Start CPU profile
buddy xray:profile:cpu --duration=30
# Output: cpu-profile-2024-01-15.cpuprofile
# Open in Chrome DevTools for analysis
Memory Profiling
import { MemoryProfiler } from '@stacksjs/xray'
// Take heap snapshot
await MemoryProfiler.snapshot('before-operation')
// ... operation ...
await MemoryProfiler.snapshot('after-operation')
// Compare snapshots
const diff = await MemoryProfiler.compare('before-operation', 'after-operation')
// Shows memory growth, leaked objects, etc.
// Track memory over time
MemoryProfiler.track({
interval: 1000, // Check every second
duration: 60000, // For 1 minute
onData: (stats) => {
console.log(stats.heapUsed, stats.heapTotal)
},
})
Error Tracking
Error Inspection
import { XRay } from '@stacksjs/xray'
try {
await riskyOperation()
} catch (error) {
// Enhanced error with context
const enhanced = XRay.enhanceError(error, {
request,
user: request.user,
input: request.body,
})
// Log with full context
XRay.logError(enhanced)
// Get pretty stack trace
const prettyTrace = XRay.formatStackTrace(error)
}
Error Context
// Add context to errors
XRay.addContext('user', { id: user.id, email: user.email })
XRay.addContext('request', { url: request.url, method: request.method })
// Context is automatically included in error reports
throw new Error('Something went wrong')
// Error includes user and request context
Error Replay
// Capture error state for reproduction
const errorState = await XRay.captureErrorState(error)
// Save for later debugging
await XRay.saveErrorState(errorState, 'error-123')
// Restore state in REPL for debugging
// buddy tinker
// > XRay.loadErrorState('error-123')
Logging
Structured Logging
import { Log } from '@stacksjs/xray'
// Basic logging
Log.info('User logged in', { userId: user.id })
Log.warning('Rate limit approaching', { remaining: 10 })
Log.error('Payment failed', { orderId: order.id, error: error.message })
// With context
Log.withContext({ requestId: request.id }).info('Processing request')
// Timed operations
const timer = Log.time('database-query')
await query()
timer.end() // Logs duration automatically
Log Viewer
# View logs in terminal
buddy xray:logs
# Filter by level
buddy xray:logs --level=error
# Filter by time
buddy xray:logs --since="1 hour ago"
# Follow logs
buddy xray:logs --follow
# Search logs
buddy xray:logs --search="payment"
Log Dashboard
// routes/admin.ts
router.get('/xray/logs', XRayController.logs)
// Access at /xray/logs
// Shows searchable, filterable log viewer
Tracing
Distributed Tracing
import { Tracer } from '@stacksjs/xray'
// Start trace
const trace = Tracer.start('api-request')
// Add spans
const dbSpan = trace.startSpan('database-query')
await db.query()
dbSpan.end()
const cacheSpan = trace.startSpan('cache-lookup')
await cache.get()
cacheSpan.end()
// End trace
trace.end()
// Export for visualization (Jaeger, Zipkin compatible)
const traceData = trace.export()
Request Tracing
// Automatically trace requests
import { TraceMiddleware } from '@stacksjs/xray'
router.use(TraceMiddleware)
// Traces are automatically created for each request
// Include: route, controller, queries, cache, external calls
CLI Tools
Debug Commands
# Interactive REPL with app context
buddy tinker
# Inspect a specific model
buddy xray:model User
# Show all routes
buddy xray:routes
# Show configuration
buddy xray:config
# Show environment
buddy xray:env
# Database inspection
buddy xray:tables
buddy xray:table users
# Cache inspection
buddy xray:cache
buddy xray:cache:clear
Debugging Sessions
# Start debug session
buddy xray:debug
# Opens interactive debugger with:
# - Breakpoint support
# - Variable inspection
# - Step through code
# - REPL at any point
Production Debugging
Safe Production Debugging
// Enable temporarily for specific request
router.get('/debug/:token', async (request) => {
const { token } = request.params
// Verify debug token
if (token !== process.env.DEBUG_TOKEN) {
return response(403)
}
// Enable debugging for this request
XRay.enable()
// ... handle request ...
return {
queries: DB.getQueryLog(),
timeline: XRay.getTimeline(),
memory: process.memoryUsage(),
}
})
Health Checks
import { HealthCheck } from '@stacksjs/xray'
// Define health checks
HealthCheck.register('database', async () => {
await DB.raw('SELECT 1')
return { healthy: true }
})
HealthCheck.register('cache', async () => {
await cache.ping()
return { healthy: true }
})
HealthCheck.register('queue', async () => {
const pending = await Queue.pending()
return {
healthy: pending < 10000,
pending,
}
})
// Health endpoint
router.get('/health', async () => {
return await HealthCheck.run()
})
// {
// status: 'healthy',
// checks: {
// database: { healthy: true, time: 5 },
// cache: { healthy: true, time: 2 },
// queue: { healthy: true, pending: 50 },
// },
// }
VS Code Integration
Launch Configuration
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Stacks App",
"type": "bun",
"request": "launch",
"program": "${workspaceFolder}/app/server.ts",
"cwd": "${workspaceFolder}",
"stopOnEntry": false
}
]
}
Breakpoints
// Set breakpoint in code
debugger // Execution stops here when debugging
// Conditional breakpoint (in VS Code)
// Right-click line number -> Add Conditional Breakpoint
// Condition: user.role === 'admin'
Best Practices
Development Debugging
// Only in development
if (process.env.NODE_ENV === 'development') {
XRay.enable()
DB.enableQueryLog()
}
Performance Impact
// Minimal production overhead
export default {
enabled: process.env.NODE_ENV !== 'production',
// Or sample in production
profiling: {
enabled: true,
sampleRate: 0.01, // 1% of requests
},
}
Security
// Never expose debug endpoints in production
if (process.env.NODE_ENV === 'production') {
// Disable debug bar
// Require authentication for any debug endpoints
// Use time-limited debug tokens
}