Observability
Comprehensive observability with logging, metrics, tracing, and health checks
Observability
Forge provides comprehensive observability features including structured logging, metrics collection, distributed tracing, and health checks. These features work together to give you complete visibility into your application's behavior and performance.
Observability Stack
Forge's observability stack includes:
- Logging: Structured logging with multiple outputs and levels
- Metrics: Prometheus-compatible metrics collection
- Tracing: Distributed tracing with OpenTelemetry
- Health Checks: Comprehensive health monitoring
- Profiling: Built-in profiling capabilities
Logging
Structured Logging
Forge uses structured logging with contextual information:
// Basic logging
app.Logger().Info("user created", forge.F("user_id", userID))
// Error logging with context
app.Logger().Error("database error",
forge.F("error", err),
forge.F("query", query),
forge.F("duration", time.Since(start)),
)
// Debug logging
app.Logger().Debug("processing request",
forge.F("request_id", requestID),
forge.F("user_id", userID),
forge.F("path", path),
)Log Levels
// Different log levels
app.Logger().Debug("debug message")
app.Logger().Info("info message")
app.Logger().Warn("warning message")
app.Logger().Error("error message")
app.Logger().Fatal("fatal message")Log Configuration
app := forge.NewApp(forge.AppConfig{
Logger: forge.NewLogger(forge.LoggerConfig{
Level: "info",
Format: "json",
Output: "stdout",
Fields: map[string]interface{}{
"service": "my-app",
"version": "1.0.0",
},
}),
})Log Outputs
// Multiple outputs
logger := forge.NewLogger(forge.LoggerConfig{
Outputs: []forge.LogOutput{
forge.NewStdoutOutput(),
forge.NewFileOutput("/var/log/app.log"),
forge.NewSyslogOutput(),
},
})Metrics
Built-in Metrics
Forge automatically collects several types of metrics:
// HTTP metrics
http_requests_total{method, path, status}
http_request_duration_seconds{method, path}
http_errors_total{method, path, error_type}
// Application metrics
app_start_time_seconds
app_uptime_seconds
app_memory_usage_bytes
// System metrics (if enabled)
system_cpu_usage_percent
system_memory_usage_bytes
system_disk_usage_bytesCustom Metrics
// Business metrics
userRegistrations := app.Metrics().Counter("user_registrations_total")
activeUsers := app.Metrics().Gauge("active_users")
sessionDuration := app.Metrics().Histogram("session_duration_seconds")
// Record metrics
userRegistrations.Inc()
activeUsers.Set(float64(userCount))
sessionDuration.Observe(duration.Seconds())Metrics Configuration
app := forge.NewApp(forge.AppConfig{
MetricsConfig: forge.MetricsConfig{
Enabled: true,
MetricsPath: "/_/metrics",
Namespace: "forge",
CollectionInterval: 10 * time.Second,
EnableSystemMetrics: false,
EnableRuntimeMetrics: false,
EnableHTTPMetrics: true,
DefaultTags: map[string]string{
"environment": "production",
"service": "my-app",
},
},
})Distributed Tracing
OpenTelemetry Integration
Forge integrates with OpenTelemetry for distributed tracing:
// Configure tracing
app := forge.NewApp(forge.AppConfig{
TracingConfig: forge.TracingConfig{
Enabled: true,
ServiceName: "my-app",
ServiceVersion: "1.0.0",
Exporter: "jaeger",
Endpoint: "http://localhost:14268/api/traces",
SampleRate: 1.0,
},
})Trace Context
// Create spans
func userHandler(ctx forge.Context) error {
span := trace.SpanFromContext(ctx.Request().Context())
span.SetAttributes(
attribute.String("user.id", userID),
attribute.String("user.email", email),
)
// Add events
span.AddEvent("processing user request")
// Add child span
childCtx, childSpan := tracer.Start(ctx.Request().Context(), "database.query")
defer childSpan.End()
// Database operation
user, err := userService.GetUser(childCtx, userID)
if err != nil {
childSpan.RecordError(err)
return err
}
childSpan.SetAttributes(attribute.String("user.name", user.Name))
return ctx.JSON(200, user)
}Trace Propagation
// Propagate trace context
func httpClientMiddleware(next forge.HandlerFunc) forge.HandlerFunc {
return func(ctx forge.Context) error {
// Extract trace context
traceCtx := trace.SpanFromContext(ctx.Request().Context())
// Propagate to downstream services
headers := make(map[string]string)
propagator := otel.GetTextMapPropagator()
propagator.Inject(traceCtx.SpanContext(), otel.MapCarrier(headers))
// Add headers to request
for key, value := range headers {
ctx.SetHeader(key, value)
}
return next(ctx)
}
}Health Checks
Health Monitoring
Forge provides comprehensive health monitoring:
// Register health checks
app.HealthManager().Register("database", func(ctx context.Context) HealthResult {
if err := db.PingContext(ctx); err != nil {
return HealthResult{
Status: HealthStatusUnhealthy,
Message: "database connection failed",
Error: err,
}
}
return HealthResult{
Status: HealthStatusHealthy,
Message: "database is healthy",
}
})
// Check overall health
status := app.HealthManager().HealthStatus()
report := app.HealthManager().Check(ctx)Health Endpoints
# Overall health
GET /_/health
# Detailed health report
GET /_/health/detailed
# Specific service health
GET /_/health/service/{name}
# Health check history
GET /_/health/historyProfiling
Built-in Profiling
Forge includes built-in profiling capabilities:
// Enable profiling
app := forge.NewApp(forge.AppConfig{
ProfilingConfig: forge.ProfilingConfig{
Enabled: true,
ProfilePath: "/_/profile",
CPUProfile: true,
MemProfile: true,
BlockProfile: true,
MutexProfile: true,
},
})Profile Endpoints
# CPU profile
GET /_/profile/cpu
# Memory profile
GET /_/profile/mem
# Block profile
GET /_/profile/block
# Mutex profile
GET /_/profile/mutexObservability Middleware
Request Observability
func ObservabilityMiddleware(app App) forge.Middleware {
return func(next forge.HandlerFunc) forge.HandlerFunc {
return func(ctx forge.Context) error {
start := time.Now()
requestID := generateRequestID()
// Add request ID to context
ctx.Set("request_id", requestID)
// Create span
spanCtx, span := tracer.Start(ctx.Request().Context(), "http.request")
defer span.End()
// Set span attributes
span.SetAttributes(
attribute.String("http.method", ctx.Method()),
attribute.String("http.path", ctx.Path()),
attribute.String("request.id", requestID),
)
// Log request start
app.Logger().Info("request started",
forge.F("request_id", requestID),
forge.F("method", ctx.Method()),
forge.F("path", ctx.Path()),
)
// Increment request counter
app.Metrics().Counter("http_requests_total").
WithTags("method", ctx.Method(), "path", ctx.Path()).
Inc()
// Process request
err := next(ctx)
// Record metrics
duration := time.Since(start)
app.Metrics().Histogram("http_request_duration_seconds").
WithTags("method", ctx.Method(), "path", ctx.Path()).
Observe(duration.Seconds())
// Log request completion
app.Logger().Info("request completed",
forge.F("request_id", requestID),
forge.F("method", ctx.Method()),
forge.F("path", ctx.Path()),
forge.F("duration", duration),
forge.F("error", err),
)
// Record error if request failed
if err != nil {
span.RecordError(err)
app.Metrics().Counter("http_errors_total").
WithTags("method", ctx.Method(), "path", ctx.Path()).
Inc()
}
return err
}
}
}Database Observability
func DatabaseObservabilityMiddleware(app App) forge.Middleware {
return func(next forge.HandlerFunc) forge.HandlerFunc {
return func(ctx forge.Context) error {
// Check if this is a database operation
if isDatabaseOperation(ctx.Path()) {
start := time.Now()
// Create database span
spanCtx, span := tracer.Start(ctx.Request().Context(), "database.operation")
defer span.End()
// Set span attributes
span.SetAttributes(
attribute.String("db.operation", getDatabaseOperation(ctx.Path())),
attribute.String("db.table", getDatabaseTable(ctx.Path())),
)
// Record database metrics
dbCounter := app.Metrics().Counter("db_operations_total").
WithTags("operation", getDatabaseOperation(ctx.Path()))
dbCounter.Inc()
err := next(ctx)
// Record duration
duration := time.Since(start)
app.Metrics().Histogram("db_operation_duration_seconds").
WithTags("operation", getDatabaseOperation(ctx.Path())).
Observe(duration.Seconds())
if err != nil {
span.RecordError(err)
app.Metrics().Counter("db_errors_total").
WithTags("operation", getDatabaseOperation(ctx.Path())).
Inc()
}
return err
}
return next(ctx)
}
}
}Observability Configuration
Complete Configuration
app := forge.NewApp(forge.AppConfig{
// Logging configuration
Logger: forge.NewLogger(forge.LoggerConfig{
Level: "info",
Format: "json",
Output: "stdout",
Fields: map[string]interface{}{
"service": "my-app",
"version": "1.0.0",
},
}),
// Metrics configuration
MetricsConfig: forge.MetricsConfig{
Enabled: true,
MetricsPath: "/_/metrics",
Namespace: "forge",
CollectionInterval: 10 * time.Second,
EnableSystemMetrics: false,
EnableRuntimeMetrics: false,
EnableHTTPMetrics: true,
DefaultTags: map[string]string{
"environment": "production",
"service": "my-app",
},
},
// Health configuration
HealthConfig: forge.HealthConfig{
Enabled: true,
CheckInterval: 30 * time.Second,
ReportInterval: 60 * time.Second,
EnableAutoDiscovery: true,
MaxConcurrentChecks: 10,
DefaultTimeout: 5 * time.Second,
EnableSmartAggregation: true,
HistorySize: 100,
},
// Tracing configuration
TracingConfig: forge.TracingConfig{
Enabled: true,
ServiceName: "my-app",
ServiceVersion: "1.0.0",
Exporter: "jaeger",
Endpoint: "http://localhost:14268/api/traces",
SampleRate: 1.0,
},
// Profiling configuration
ProfilingConfig: forge.ProfilingConfig{
Enabled: true,
ProfilePath: "/_/profile",
CPUProfile: true,
MemProfile: true,
BlockProfile: true,
MutexProfile: true,
},
})Observability Tools Integration
Prometheus + Grafana
# prometheus.yml
scrape_configs:
- job_name: 'forge-app'
static_configs:
- targets: ['localhost:8080']
metrics_path: '/_/metrics'
scrape_interval: 15sJaeger Tracing
# docker-compose.yml
version: '3'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686"
- "14268:14268"
environment:
- COLLECTOR_OTLP_ENABLED=trueELK Stack
# docker-compose.yml
version: '3'
services:
elasticsearch:
image: elasticsearch:7.14.0
ports:
- "9200:9200"
logstash:
image: logstash:7.14.0
ports:
- "5044:5044"
kibana:
image: kibana:7.14.0
ports:
- "5601:5601"Best Practices
- Structured Logging: Use structured logging with consistent fields
- Meaningful Metrics: Collect metrics that provide business value
- Trace Context: Propagate trace context across service boundaries
- Health Checks: Monitor all critical dependencies
- Performance: Keep observability overhead minimal
- Security: Don't log sensitive information
- Retention: Set appropriate retention policies
- Alerting: Set up alerts for critical metrics
For more detailed information about specific observability components, see the Logging, Metrics, and Health Checks documentation.
How is this guide?
Last updated on