Backend

Pulse (Observability)

Every Grit project ships with Pulse, a self-hosted observability and performance monitoring SDK. It provides request tracing, database monitoring, runtime metrics, error tracking, health checks, alerting, and a real-time dashboard — all with a single Mount() call.

What You Get

After starting your API, the Pulse dashboard is available at /pulse/ui/ (default credentials: admin / pulse).

FeatureDescription
Request TracingAutomatic trace IDs, slow request detection, latency tracking
Database MonitoringGORM query capture, N+1 detection, connection pool stats
Runtime MetricsHeap memory, goroutines, GC pauses, leak detection
Error TrackingPanic recovery, stack traces, request body capture, error fingerprinting
Health ChecksKubernetes-compatible /live and /ready endpoints
AlertingThreshold-based rules with Slack, Discord, email, and webhook notifications
Prometheus18+ metrics at /pulse/metrics
DashboardReact 19 UI with 8 pages: Overview, Routes, Database, Errors, Runtime, Health, Alerts, Settings

Configuration

Pulse is mounted in routes.go and configured via environment variables:

.env
# Observability — Pulse
PULSE_ENABLED=true
PULSE_USERNAME=admin
PULSE_PASSWORD=pulse

The mount call in routes.go:

internal/routes/routes.go
// Mount Pulse observability
if cfg.PulseEnabled {
p := pulse.Mount(r, db, pulse.Config{
AppName: cfg.AppName,
DevMode: cfg.IsDevelopment(),
Dashboard: pulse.DashboardConfig{
Username: cfg.PulseUsername,
Password: cfg.PulsePassword,
},
Tracing: pulse.TracingConfig{
ExcludePaths: []string{"/studio/*", "/sentinel/*", "/docs/*", "/pulse/*"},
},
Prometheus: pulse.PrometheusConfig{
Enabled: true,
},
})
// Register health checks for connected services
if svc.Cache != nil {
p.AddHealthCheck(pulse.HealthCheck{
Name: "redis",
Type: "redis",
Critical: false,
CheckFunc: func(ctx context.Context) error {
return svc.Cache.Client().Ping(ctx).Err()
},
})
}
}

Endpoints

EndpointDescription
GET /pulse/ui/Dashboard UI
GET /pulse/healthFull health status (public)
GET /pulse/health/liveKubernetes liveness probe
GET /pulse/health/readyKubernetes readiness probe
GET /pulse/metricsPrometheus metrics
POST /pulse/api/auth/loginDashboard authentication
GET /pulse/api/overviewDashboard summary (auth required)
WS /pulse/ws/liveWebSocket live updates

Custom Health Checks

The Mount() function returns a Pulse instance. Use it to register health checks for any dependency:

p := pulse.Mount(r, db, cfg)
// Redis health check
p.AddHealthCheck(pulse.HealthCheck{
Name: "redis",
Type: "redis",
Critical: false, // non-critical = won't fail /ready
CheckFunc: func(ctx context.Context) error {
return redisClient.Ping(ctx).Err()
},
})
// External API health check
p.AddHealthCheck(pulse.HealthCheck{
Name: "payment-api",
Type: "http",
Critical: true, // critical = will fail /ready if down
CheckFunc: func(ctx context.Context) error {
resp, err := http.Get("https://api.stripe.com/v1/health")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("status %d", resp.StatusCode)
}
return nil
},
})

The database health check is registered automatically when you pass a *gorm.DB to Mount().

Alerting

Pulse ships with 5 default alert rules (high latency, high error rate, high memory, goroutine leak, health check failure). You can add custom rules and notification channels:

pulse.Config{
Alerts: pulse.AlertConfig{
Rules: []pulse.AlertRule{
{
Name: "api_slow",
Metric: "p95_latency",
Operator: ">",
Threshold: 1000, // ms
Duration: 3 * time.Minute,
Severity: "warning",
},
},
// Notification channels
Slack: &pulse.SlackConfig{
WebhookURL: "https://hooks.slack.com/services/...",
Channel: "#alerts",
},
Discord: &pulse.DiscordConfig{
WebhookURL: "https://discord.com/api/webhooks/...",
},
Webhooks: []pulse.WebhookConfig{
{
URL: "https://api.example.com/webhook",
Secret: "hmac-secret", // HMAC-SHA256 signature
},
},
},
}

Built-in Default Rules

RuleMetricConditionSeverity
high_latencyP95 latency> 2000ms for 5mincritical
high_error_rateError rate> 10% for 3mincritical
high_memoryHeap alloc> 500MB for 5minwarning
goroutine_leakGoroutine growth> 100/hr for 10minwarning
health_check_failureHealth statusunhealthy for 2mincritical

Data Storage

By default, Pulse uses in-memory ring buffers (~100K request capacity). For persistence across restarts, switch to SQLite:

pulse.Config{
Storage: pulse.StorageConfig{
Driver: pulse.SQLite,
DSN: "pulse.db",
RetentionHours: 24,
},
}

Prometheus Integration

Pulse exposes 18+ metrics in Prometheus exposition format at /pulse/metrics. Connect it to Grafana or any Prometheus-compatible tool.

Key metrics include:

  • pulse_http_requests_total — Total HTTP requests by method, path, status
  • pulse_http_request_duration_seconds — Request duration histogram
  • pulse_http_error_rate — Current error rate percentage
  • pulse_runtime_goroutines — Active goroutine count
  • pulse_runtime_heap_bytes — Heap allocation in bytes
  • pulse_db_query_duration_seconds — Database query duration
  • pulse_health_check_status — Health check status (1=healthy, 0=unhealthy)

Disabling Pulse

Set PULSE_ENABLED=false in your .env to disable Pulse entirely. This skips the mount and adds zero overhead to your application.