Route Inspection & Debugging
Learn how to inspect routes, manage metadata, and debug your Forge router applications
The Forge router provides comprehensive inspection and debugging capabilities to help you understand, monitor, and troubleshoot your application's routing behavior.
Route Inspection
Basic Route Information
The router provides several methods to inspect registered routes:
// Get all registered routes
routes := router.Routes()
for _, route := range routes {
fmt.Printf("Route: %s %s\n", route.Method, route.Path)
fmt.Printf(" Name: %s\n", route.Name)
fmt.Printf(" Tags: %v\n", route.Tags)
fmt.Printf(" Summary: %s\n", route.Summary)
fmt.Printf(" Description: %s\n", route.Description)
fmt.Printf(" Middleware: %d handlers\n", len(route.Middleware))
fmt.Printf(" Metadata: %v\n", route.Metadata)
}// Find route by name
route, found := router.RouteByName("user-profile")
if found {
fmt.Printf("Found route: %s %s\n", route.Method, route.Path)
fmt.Printf("Handler: %T\n", route.Handler)
}// Find routes by tag
apiRoutes := router.RoutesByTag("api")
for _, route := range apiRoutes {
fmt.Printf("API Route: %s %s\n", route.Method, route.Path)
}
// Find admin routes
adminRoutes := router.RoutesByTag("admin")
for _, route := range adminRoutes {
fmt.Printf("Admin Route: %s %s\n", route.Method, route.Path)
}// Find routes by metadata
authRoutes := router.RoutesByMetadata("auth", "required")
for _, route := range authRoutes {
fmt.Printf("Auth Required: %s %s\n", route.Method, route.Path)
}
// Find routes by version
v1Routes := router.RoutesByMetadata("version", "v1")
for _, route := range v1Routes {
fmt.Printf("V1 Route: %s %s\n", route.Method, route.Path)
}RouteInfo Structure
The RouteInfo structure provides comprehensive information about each route:
type RouteInfo struct {
Name string // Route name
Method string // HTTP method
Path string // Registered path
Pattern string // Path pattern with parameters
Handler any // Handler function/controller
Middleware []Middleware // Applied middleware
Tags []string // Route tags
Metadata map[string]any // Custom metadata
Extensions map[string]Extension // Route extensions
Summary string // Short description
Description string // Detailed description
}Route Metadata
Setting Metadata
Add custom metadata to routes for organization and inspection:
// Add metadata to individual routes
router.GET("/api/users", getUsersHandler,
forge.WithName("list-users"),
forge.WithTags("api", "users"),
forge.WithMetadata(map[string]any{
"auth": "required",
"version": "v1",
"rate_limit": 100,
"cache_ttl": 300,
"public": false,
"deprecated": false,
}),
)
// Add metadata to groups
apiGroup := router.Group("/api",
forge.WithGroupTags("api"),
forge.WithGroupMetadata(map[string]any{
"version": "v1",
"auth": "required",
"rate_limit": 1000,
}),
)// Define metadata structures
type RouteMetadata struct {
Auth AuthConfig `json:"auth"`
RateLimit RateLimitConfig `json:"rate_limit"`
Cache CacheConfig `json:"cache"`
Monitoring MonitoringConfig `json:"monitoring"`
}
type AuthConfig struct {
Required bool `json:"required"`
Roles []string `json:"roles"`
Scopes []string `json:"scopes"`
}
// Use structured metadata
metadata := RouteMetadata{
Auth: AuthConfig{
Required: true,
Roles: []string{"user", "admin"},
Scopes: []string{"read:users"},
},
RateLimit: RateLimitConfig{
RequestsPerMinute: 60,
BurstSize: 10,
},
}
router.GET("/api/users", getUsersHandler,
forge.WithMetadata(map[string]any{
"config": metadata,
}),
)// Add metadata dynamically based on conditions
func addConditionalMetadata(route string, handler any) []forge.RouteOption {
opts := []forge.RouteOption{
forge.WithName(route),
}
// Add auth metadata for protected routes
if strings.HasPrefix(route, "/admin") {
opts = append(opts,
forge.WithTags("admin", "protected"),
forge.WithMetadata(map[string]any{
"auth": "admin",
"audit_log": true,
"rate_limit": 10,
}),
)
}
// Add public metadata for public routes
if strings.HasPrefix(route, "/public") {
opts = append(opts,
forge.WithTags("public"),
forge.WithMetadata(map[string]any{
"auth": "none",
"cache_ttl": 3600,
"rate_limit": 1000,
}),
)
}
return opts
}
// Use dynamic metadata
router.GET("/admin/users", adminUsersHandler,
addConditionalMetadata("/admin/users", adminUsersHandler)...)Querying Metadata
Use metadata for runtime decisions and inspection:
// Middleware that uses metadata
func AuthMiddleware() forge.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get current route info (implementation specific)
route := getCurrentRoute(r)
// Check auth requirement from metadata
if authRequired, ok := route.Metadata["auth"].(string); ok {
if authRequired == "required" || authRequired == "admin" {
// Perform authentication
if !isAuthenticated(r) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Check admin role for admin routes
if authRequired == "admin" && !isAdmin(r) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
}
}
next.ServeHTTP(w, r)
})
}
}Debugging Features
Route Registration Debugging
Debug route registration issues:
// Enable debug logging during route registration
logger := forge.NewLogger(forge.LogConfig{
Level: forge.LogLevelDebug,
})
app := forge.NewApp(forge.AppConfig{
Logger: logger,
})
// Routes will be logged as they're registered
router := app.Router()
router.GET("/debug", debugHandler) // Logs: "Registered route: GET /debug"// Check for duplicate routes
func checkDuplicateRoutes(router forge.Router) {
routes := router.Routes()
seen := make(map[string]bool)
for _, route := range routes {
key := route.Method + " " + route.Path
if seen[key] {
fmt.Printf("WARNING: Duplicate route detected: %s\n", key)
}
seen[key] = true
}
}
// Call after all routes are registered
checkDuplicateRoutes(router)// Detect potential route conflicts
func detectRouteConflicts(router forge.Router) {
routes := router.Routes()
for i, route1 := range routes {
for j, route2 := range routes[i+1:] {
if route1.Method == route2.Method {
if couldConflict(route1.Pattern, route2.Pattern) {
fmt.Printf("POTENTIAL CONFLICT:\n")
fmt.Printf(" Route 1: %s %s\n", route1.Method, route1.Pattern)
fmt.Printf(" Route 2: %s %s\n", route2.Method, route2.Pattern)
}
}
}
}
}
func couldConflict(pattern1, pattern2 string) bool {
// Simple conflict detection logic
// In practice, this would be more sophisticated
return strings.Contains(pattern1, ":") || strings.Contains(pattern2, ":")
}Runtime Debugging
Debug runtime routing behavior:
// Middleware to trace request routing
func RequestTracingMiddleware() forge.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Log request start
fmt.Printf("[TRACE] %s %s - Started\n", r.Method, r.URL.Path)
// Wrap response writer to capture status
wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
// Process request
next.ServeHTTP(wrapped, r)
// Log request completion
duration := time.Since(start)
fmt.Printf("[TRACE] %s %s - Completed in %v (Status: %d)\n",
r.Method, r.URL.Path, duration, wrapped.statusCode)
})
}
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}// Debug middleware execution order
func DebugMiddleware(name string) forge.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("[MIDDLEWARE] %s - Before\n", name)
next.ServeHTTP(w, r)
fmt.Printf("[MIDDLEWARE] %s - After\n", name)
})
}
}
// Apply debug middleware
router.Use(DebugMiddleware("Global"))
apiGroup := router.Group("/api")
apiGroup.Use(DebugMiddleware("API Group"))
apiGroup.GET("/users", usersHandler, forge.WithMiddleware(DebugMiddleware("Route Specific")))
// Output for GET /api/users:
// [MIDDLEWARE] Global - Before
// [MIDDLEWARE] API Group - Before
// [MIDDLEWARE] Route Specific - Before
// [MIDDLEWARE] Route Specific - After
// [MIDDLEWARE] API Group - After
// [MIDDLEWARE] Global - After// Debug route parameter extraction
func ParameterDebugHandler(ctx forge.Context) error {
fmt.Printf("[PARAMS] Route: %s\n", ctx.Request().URL.Path)
// Log all parameters
params := ctx.Params()
for key, value := range params {
fmt.Printf("[PARAMS] %s = %s\n", key, value)
}
// Log query parameters
query := ctx.Request().URL.Query()
for key, values := range query {
fmt.Printf("[QUERY] %s = %v\n", key, values)
}
return ctx.JSON(200, map[string]any{
"path_params": params,
"query_params": query,
})
}
router.GET("/debug/:id/items/:item", ParameterDebugHandler)Monitoring & Observability
Built-in Metrics
The router provides built-in metrics for monitoring:
// Enable metrics collection
app := forge.NewApp(forge.AppConfig{
MetricsConfig: forge.MetricsConfig{
Enabled: true,
MetricsPath: "/_/metrics",
Type: "prometheus",
},
})
// Metrics are automatically collected for:
// - Request count by route, method, status
// - Request duration by route
// - Active requests gauge
// - Error rates// Add custom metrics to routes
func MetricsMiddleware() forge.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get metrics from context
metrics := forge.GetMetrics(r.Context())
// Increment request counter
counter := metrics.Counter("http_requests_total")
counter.WithLabels(map[string]string{
"method": r.Method,
"path": r.URL.Path,
}).Inc()
// Track request duration
histogram := metrics.Histogram("http_request_duration_seconds")
start := time.Now()
defer func() {
histogram.WithLabels(map[string]string{
"method": r.Method,
"path": r.URL.Path,
}).ObserveDuration(start)
}()
// Track active requests
gauge := metrics.Gauge("http_active_requests")
gauge.Inc()
defer gauge.Dec()
next.ServeHTTP(w, r)
})
}
}// Get route statistics
func getRouteStatistics(router forge.Router) {
routes := router.Routes()
for _, route := range routes {
// Access route metadata for stats
if stats, ok := route.Metadata["stats"]; ok {
fmt.Printf("Route: %s %s\n", route.Method, route.Path)
fmt.Printf(" Stats: %+v\n", stats)
}
}
}
// Custom statistics middleware
func StatsMiddleware() forge.Middleware {
stats := make(map[string]*RouteStats)
mu := sync.RWMutex{}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
key := r.Method + " " + r.URL.Path
// Update stats
mu.Lock()
if stats[key] == nil {
stats[key] = &RouteStats{}
}
stats[key].CallCount++
mu.Unlock()
// Process request
wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
next.ServeHTTP(wrapped, r)
// Update duration stats
duration := time.Since(start)
mu.Lock()
stats[key].TotalLatency += duration
stats[key].AverageLatency = stats[key].TotalLatency / time.Duration(stats[key].CallCount)
if wrapped.statusCode >= 400 {
stats[key].ErrorCount++
}
mu.Unlock()
})
}
}
type RouteStats struct {
CallCount int64
ErrorCount int64
TotalLatency time.Duration
AverageLatency time.Duration
}Health Checks
Monitor router health and readiness:
// Enable health checks
app := forge.NewApp(forge.AppConfig{
HealthConfig: forge.HealthConfig{
Enabled: true,
HealthPath: "/_/health",
LivenessPath: "/_/health/live",
ReadinessPath: "/_/health/ready",
},
})
// Built-in endpoints:
// GET /_/health - Overall health status
// GET /_/health/live - Liveness probe (always 200 if server up)
// GET /_/health/ready - Readiness probe (200 if all checks pass)// Add custom health checks
app.AddHealthCheck("database", func(ctx context.Context) forge.HealthResult {
// Check database connectivity
if err := db.Ping(); err != nil {
return forge.HealthResult{
Status: forge.HealthStatusUnhealthy,
Message: "Database unreachable",
Error: err,
}
}
return forge.HealthResult{
Status: forge.HealthStatusHealthy,
Message: "Database connected",
}
})
app.AddHealthCheck("external-api", func(ctx context.Context) forge.HealthResult {
// Check external API
resp, err := http.Get("https://api.example.com/health")
if err != nil || resp.StatusCode != 200 {
return forge.HealthResult{
Status: forge.HealthStatusUnhealthy,
Message: "External API unreachable",
}
}
return forge.HealthResult{
Status: forge.HealthStatusHealthy,
Message: "External API healthy",
}
})// Middleware to track route health
func RouteHealthMiddleware() forge.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Track route health
healthManager := forge.GetHealthManager(r.Context())
start := time.Now()
wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
next.ServeHTTP(wrapped, r)
// Report route health
duration := time.Since(start)
routeKey := r.Method + " " + r.URL.Path
if wrapped.statusCode >= 500 {
healthManager.ReportUnhealthy(routeKey, "Server error")
} else if duration > 5*time.Second {
healthManager.ReportDegraded(routeKey, "Slow response")
} else {
healthManager.ReportHealthy(routeKey)
}
})
}
}Debugging Tools
Route Inspector Tool
Create a debugging endpoint to inspect routes:
// Debug endpoint to inspect all routes
router.GET("/_/debug/routes", func(ctx forge.Context) error {
routes := router.Routes()
type RouteDebugInfo struct {
Name string `json:"name"`
Method string `json:"method"`
Path string `json:"path"`
Pattern string `json:"pattern"`
HandlerType string `json:"handler_type"`
Middleware int `json:"middleware_count"`
Tags []string `json:"tags"`
Metadata map[string]any `json:"metadata"`
Summary string `json:"summary"`
Description string `json:"description"`
}
debugInfo := make([]RouteDebugInfo, len(routes))
for i, route := range routes {
debugInfo[i] = RouteDebugInfo{
Name: route.Name,
Method: route.Method,
Path: route.Path,
Pattern: route.Pattern,
HandlerType: fmt.Sprintf("%T", route.Handler),
Middleware: len(route.Middleware),
Tags: route.Tags,
Metadata: route.Metadata,
Summary: route.Summary,
Description: route.Description,
}
}
return ctx.JSON(200, map[string]any{
"total_routes": len(routes),
"routes": debugInfo,
})
})Performance Profiling
Enable performance profiling for debugging:
// Enable profiling
app := forge.NewApp(forge.AppConfig{
ProfilingConfig: forge.ProfilingConfig{
Enabled: true,
ProfilePath: "/_/profile",
CPUProfile: true,
MemProfile: true,
BlockProfile: true,
MutexProfile: true,
},
})
// Available profiling endpoints:
// GET /_/profile/cpu - CPU profile
// GET /_/profile/heap - Memory heap profile
// GET /_/profile/block - Block profile
// GET /_/profile/mutex - Mutex profile
// GET /_/profile/goroutine - Goroutine profileBest Practices
Consistent Naming
// Use consistent naming conventions
router.GET("/api/users", getUsersHandler,
forge.WithName("list-users"),
forge.WithTags("api", "users"),
)
router.GET("/api/users/:id", getUserHandler,
forge.WithName("get-user"),
forge.WithTags("api", "users"),
)Meaningful Metadata
// Add meaningful metadata for debugging
router.POST("/api/users", createUserHandler,
forge.WithName("create-user"),
forge.WithTags("api", "users", "write"),
forge.WithMetadata(map[string]any{
"auth": "required",
"rate_limit": 10,
"audit_log": true,
"cache_invalidate": []string{"users", "user-count"},
}),
)Debug Mode
// Enable debug mode in development
if os.Getenv("DEBUG") == "true" {
router.Use(RequestTracingMiddleware())
router.Use(DebugMiddleware("Global"))
}Monitoring Integration
// Integrate with monitoring systems
router.Use(MetricsMiddleware())
router.Use(TracingMiddleware())
router.Use(LoggingMiddleware())The inspection and debugging features help you understand your application's routing behavior, troubleshoot issues, and monitor performance in production environments.
Related Documentation
- Learn about Basic Routing for fundamental concepts
- Explore Middleware for request processing
- Check out Advanced Features for OpenAPI and monitoring
- See Examples for complete implementation patterns
How is this guide?
Last updated on