xrayhq

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 30, 2026 License: MIT Imports: 26 Imported by: 0

README

xrayhq

Go Reference Go Report Card

Lightweight, drop-in observability library for Go web applications. Request tracing, DB/Redis/MongoDB instrumentation, and a real-time dashboard — no external infrastructure required.

Dashboard Go

Features

  • Request tracing — latency, TTFB, status codes, request/response bodies, headers, goroutine and memory deltas
  • Database instrumentationdatabase/sql, GORM, Redis (go-redis), MongoDB (mongo-driver)
  • External call tracking — wraps http.Client to record outbound requests
  • Real-time dashboard — routes overview, per-route detail with latency histograms, request waterfall view, live tail with SSE, system stats
  • Automatic alerting — N+1 queries, slow queries, slow routes (P95), high error rates, memory spikes, panics
  • Framework middleware — net/http, Chi, Echo, Gin, Fiber
  • Data export — JSON and CSV export of captured traces
  • Zero infrastructure — everything runs in-process with a ring buffer, no databases or agents needed

Installation

go get github.com/Bhavyyadav25/xrayhq

Quick Start

package main

import (
    "net/http"
    "github.com/Bhavyyadav25/xrayhq"
)

func main() {
    // Initialize — dashboard starts at localhost:9090
    xrayhq.Init(
        xrayhq.WithPort(":9090"),
        xrayhq.WithMode(xrayhq.ModeDev),
    )

    mux := http.NewServeMux()
    mux.HandleFunc("/api/hello", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("hello"))
    })

    // Wrap your handler
    http.ListenAndServe(":8080", xrayhq.Wrap(mux))
}

Open http://localhost:9090 to see the dashboard.

Configuration

xrayhq.Init(
    xrayhq.WithPort(":9090"),               // Dashboard port
    xrayhq.WithBufferSize(1000),             // Ring buffer size
    xrayhq.WithMode(xrayhq.ModeDev),         // ModeDev or ModeProd
    xrayhq.WithSamplingRate(1.0),             // 1.0 = capture all, 0.5 = 50%
    xrayhq.WithCaptureBody(true),             // Capture request/response bodies
    xrayhq.WithCaptureHeaders(true),          // Capture headers
    xrayhq.WithBasicAuth("admin", "secret"),  // Protect dashboard
    xrayhq.WithSlowQueryThreshold(500*time.Millisecond),
    xrayhq.WithSlowRouteThreshold(2*time.Second),
    xrayhq.WithHighErrorRate(10.0),           // Alert above 10% error rate
    xrayhq.WithNPlusOneThreshold(5),          // Alert on 5+ repeated queries
    xrayhq.WithMemorySpikeThreshold(10*1024*1024), // 10MB
    xrayhq.WithLatencyCap(10000),             // Max latencies stored per route
)

Framework Integration

Chi
r := chi.NewRouter()
r.Use(xrayhq.ChiMiddleware)
Echo
e := echo.New()
e.Use(xrayhq.EchoMiddleware())
Gin
r := gin.Default()
r.Use(xrayhq.GinMiddleware())
Fiber
app := fiber.New()
app.Use(xrayhq.FiberMiddleware())

// Access trace in Fiber handlers
app.Get("/api/data", func(c *fiber.Ctx) error {
    xrayhq.FiberAddDBQuery(c, xrayhq.DBQuery{
        Query:    "SELECT * FROM users",
        Duration: 5 * time.Millisecond,
    })
    return c.SendString("ok")
})

Database Instrumentation

database/sql
sqlDB, _ := sql.Open("postgres", dsn)
db := xrayhq.WrapDB(sqlDB)

// Use db.QueryContext, db.ExecContext, db.QueryRowContext
// All queries are automatically traced
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1", 1)
GORM
db, _ := gorm.Open(sqlite.Open("app.db"), &gorm.Config{})
db.Use(xrayhq.NewGORMPlugin())

// All GORM operations are automatically traced
db.WithContext(ctx).Find(&users)

Redis Instrumentation

rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
rdb.AddHook(xrayhq.RedisHook())

// All Redis commands are automatically traced
rdb.Get(ctx, "user:123")

MongoDB Instrumentation

opts := options.Client().
    ApplyURI("mongodb://localhost:27017").
    SetMonitor(xrayhq.MongoMonitor())

client, _ := mongo.Connect(ctx, opts)

// All MongoDB operations are automatically traced
collection.FindOne(ctx, bson.M{"name": "Alice"})

External HTTP Call Tracking

client := xrayhq.WrapHTTPClient(&http.Client{Timeout: 5 * time.Second})

// Outbound calls are traced when the request carries a traced context
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)
resp, err := client.Do(req)

Manual Query Instrumentation

Add queries manually within any handler:

func handler(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    // ... run your query ...

    xrayhq.AddDBQuery(r.Context(), xrayhq.DBQuery{
        Query:        "SELECT * FROM orders WHERE user_id = ?",
        Duration:     time.Since(start),
        RowsAffected: 42,
        Timestamp:    start,
    })
}

Dashboard Pages

Page URL Description
Routes / All routes with hit counts, avg/P95/P99 latency, error rates
Route Detail /route/GET/api/users Per-route latency histogram, status distribution, slowest requests
Request Detail /request/{id} Full request waterfall: DB queries, external calls, Redis/Mongo ops
Live Tail /live Real-time request stream via Server-Sent Events
Alerts /alerts All triggered alerts (N+1, slow query, error rate, panics)
System /system Goroutines, memory, GC stats, uptime

Data Export

Export captured traces for offline analysis:

GET /xrayhq/export               → JSON
GET /xrayhq/export?format=csv    → CSV

Alert Types

Alert Trigger Severity
N+1 Query Same query pattern repeated > threshold times in one request Warning
Slow Query Individual query exceeds threshold Warning
Slow Route Route P95 exceeds threshold (after 10+ requests) Warning
High Error Rate Route 5xx rate exceeds threshold (after 10+ requests) Critical
Memory Spike Request allocates more than threshold bytes Warning
Panic Handler panics (recovered automatically) Critical

Architecture

xrayhq runs entirely in-process:

┌─────────────────────────────────────────────┐
│  Your Application                           │
│                                             │
│  ┌───────────┐    ┌──────────────────────┐  │
│  │ Middleware │───>│     Collector        │  │
│  │ (per req) │    │  (ring buffer + map) │  │
│  └───────────┘    └──────────┬───────────┘  │
│                              │              │
│                   ┌──────────┴───────────┐  │
│                   │    Alert Engine       │  │
│                   └──────────┬───────────┘  │
│                              │              │
│                   ┌──────────┴───────────┐  │
│                   │  Dashboard Server    │  │
│                   │  (SSE + HTML + API)  │  │
│                   └──────────────────────┘  │
└─────────────────────────────────────────────┘
  • Ring buffer stores the last N requests (configurable), zero GC pressure from old traces
  • Route metrics aggregate latency percentiles, error rates, and status code distributions
  • SSE streams new requests to the live tail view in real time
  • No goroutine leaks — SSE clients are tracked and cleaned up on disconnect

Production Usage

For production, reduce overhead with sampling and disable body capture:

xrayhq.Init(
    xrayhq.WithMode(xrayhq.ModeProd),
    xrayhq.WithSamplingRate(0.1),        // Capture 10% of requests
    xrayhq.WithCaptureBody(false),
    xrayhq.WithCaptureHeaders(false),
    xrayhq.WithBasicAuth("admin", "strongpassword"),
)

API Reference

Full documentation on pkg.go.dev.

License

MIT

Documentation

Overview

Package xrayhq is a lightweight, drop-in observability library for Go web applications. It provides request tracing, database query instrumentation, external call tracking, and a real-time dashboard — all with zero external dependencies for storage.

xrayhq captures detailed per-request traces including latency, TTFB, request/response bodies, headers, DB queries (database/sql, GORM), Redis commands, MongoDB operations, and outbound HTTP calls. It automatically detects N+1 queries, slow routes, high error rates, memory spikes, and panics via a built-in alert engine.

Quick Start

func main() {
    xrayhq.Init(xrayhq.WithPort(":9090"))
    handler := xrayhq.Wrap(yourMux)
    http.ListenAndServe(":8080", handler)
}

The dashboard is then available at http://localhost:9090.

Framework Support

xrayhq includes middleware for Chi, Echo, Gin, and Fiber in addition to the standard net/http middleware.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddDBQuery

func AddDBQuery(ctx context.Context, q DBQuery)

func AddExternalCall

func AddExternalCall(ctx context.Context, c ExternalCall)

func AddMongoOp

func AddMongoOp(ctx context.Context, op MongoOp)

func AddRedisOp

func AddRedisOp(ctx context.Context, op RedisOp)

func ChiMiddleware

func ChiMiddleware(next http.Handler) http.Handler

ChiMiddleware is a Chi-compatible middleware that captures request data.

func EchoMiddleware

func EchoMiddleware() echo.MiddlewareFunc

EchoMiddleware returns an Echo middleware that captures request data.

func FiberAddDBQuery

func FiberAddDBQuery(c *fiber.Ctx, q DBQuery)

FiberAddDBQuery adds a DB query to the Fiber request trace.

func FiberMiddleware

func FiberMiddleware() fiber.Handler

FiberMiddleware returns a Fiber middleware that captures request data.

func GinMiddleware

func GinMiddleware() gin.HandlerFunc

GinMiddleware returns a Gin middleware that captures request data.

func Init

func Init(opts ...Option)

Init initializes xrayhq with the given options and starts the dashboard server.

func MongoMonitor

func MongoMonitor() *event.CommandMonitor

MongoMonitor returns a *event.CommandMonitor that instruments MongoDB operations.

func NewDashboardServer

func NewDashboardServer(collector *Collector, config *Config) *http.Server

func RedisHook

func RedisHook() redis.Hook

RedisHook returns a go-redis Hook that instruments Redis commands.

func SetRoutePattern

func SetRoutePattern(r *http.Request, pattern string)

SetRoutePattern sets the route pattern on the trace in context. Framework adapters call this to record the matched route pattern.

func Wrap

func Wrap(handler http.Handler) http.Handler

Wrap wraps an http.Handler with xrayhq middleware.

func WrapFunc

func WrapFunc(handler http.HandlerFunc) http.Handler

WrapFunc wraps an http.HandlerFunc with xrayhq middleware.

func WrapHTTPClient

func WrapHTTPClient(client *http.Client) *http.Client

WrapHTTPClient returns a new http.Client with an instrumented transport that records external calls to the request trace.

Types

type Alert

type Alert struct {
	ID           string
	Type         string
	Message      string
	Severity     Severity
	RoutePattern string
	RequestID    string
	Timestamp    time.Time
	Details      map[string]interface{}
}

type AlertEngine

type AlertEngine struct {
	// contains filtered or unexported fields
}

func NewAlertEngine

func NewAlertEngine(collector *Collector, config *Config) *AlertEngine

func (*AlertEngine) Evaluate

func (e *AlertEngine) Evaluate(trace *RequestTrace)

type Collector

type Collector struct {
	// contains filtered or unexported fields
}

func GetCollector

func GetCollector() *Collector

GetCollector returns the default collector (for advanced usage).

func NewCollector

func NewCollector(cfg *Config) *Collector

func (*Collector) AddAlert

func (c *Collector) AddAlert(a Alert)

func (*Collector) GetAlerts

func (c *Collector) GetAlerts() []Alert

func (*Collector) GetAllRequests

func (c *Collector) GetAllRequests() []*RequestTrace

func (*Collector) GetRecentRequests

func (c *Collector) GetRecentRequests(limit int) []*RequestTrace

func (*Collector) GetRequestByID

func (c *Collector) GetRequestByID(id string) *RequestTrace

func (*Collector) GetRequestsForRoute

func (c *Collector) GetRequestsForRoute(method, pattern string, limit int) []*RequestTrace

func (*Collector) GetRoute

func (c *Collector) GetRoute(method, pattern string) *RouteMetrics

func (*Collector) GetRoutes

func (c *Collector) GetRoutes() []*RouteMetrics

func (*Collector) Record

func (c *Collector) Record(trace *RequestTrace)

func (*Collector) RequestCount

func (c *Collector) RequestCount() int

func (*Collector) SubscribeSSE

func (c *Collector) SubscribeSSE() chan *RequestTrace

func (*Collector) UnsubscribeSSE

func (c *Collector) UnsubscribeSSE(ch chan *RequestTrace)

func (*Collector) Uptime

func (c *Collector) Uptime() time.Duration

type Config

type Config struct {
	Port           string
	BufferSize     int
	Mode           Mode
	SamplingRate   float64
	CaptureBody    bool
	CaptureHeaders bool
	BasicAuthUser  string
	BasicAuthPass  string

	SlowQueryThreshold    time.Duration
	SlowRouteP95Threshold time.Duration
	HighErrorRatePercent  float64
	NPlusOneThreshold     int
	MemorySpikeBytes      uint64
	LatencyCap            int
}

func DefaultConfig

func DefaultConfig() *Config

func GetConfig

func GetConfig() *Config

GetConfig returns the default config (for advanced usage).

type DBPoolStats

type DBPoolStats struct {
	OpenConnections  int
	IdleConnections  int
	InUseConnections int
	WaitCount        int64
	WaitDuration     time.Duration
}

type DBQuery

type DBQuery struct {
	Query        string
	Duration     time.Duration
	RowsAffected int64
	Error        string
	Timestamp    time.Time
}

type DashboardServer

type DashboardServer struct {
	// contains filtered or unexported fields
}

type ExternalCall

type ExternalCall struct {
	URL        string
	Method     string
	StatusCode int
	Duration   time.Duration
	Error      string
	Timestamp  time.Time
}

type GORMPlugin

type GORMPlugin struct{}

GORMPlugin implements gorm.Plugin for query instrumentation.

func NewGORMPlugin

func NewGORMPlugin() *GORMPlugin

func (*GORMPlugin) Initialize

func (p *GORMPlugin) Initialize(db *gorm.DB) error

func (*GORMPlugin) Name

func (p *GORMPlugin) Name() string

type Mode

type Mode string
const (
	ModeDev  Mode = "dev"
	ModeProd Mode = "prod"
)

type MongoOp

type MongoOp struct {
	Collection string
	Operation  string
	Filter     string
	Duration   time.Duration
	Error      string
	Timestamp  time.Time
}

type Option

type Option func(*Config)

func WithBasicAuth

func WithBasicAuth(user, pass string) Option

func WithBufferSize

func WithBufferSize(size int) Option

func WithCaptureBody

func WithCaptureBody(capture bool) Option

func WithCaptureHeaders

func WithCaptureHeaders(capture bool) Option

func WithHighErrorRate

func WithHighErrorRate(pct float64) Option

func WithLatencyCap

func WithLatencyCap(n int) Option

func WithMemorySpikeThreshold

func WithMemorySpikeThreshold(bytes uint64) Option

func WithMode

func WithMode(mode Mode) Option

func WithNPlusOneThreshold

func WithNPlusOneThreshold(n int) Option

func WithPort

func WithPort(port string) Option

func WithSamplingRate

func WithSamplingRate(rate float64) Option

func WithSlowQueryThreshold

func WithSlowQueryThreshold(d time.Duration) Option

func WithSlowRouteThreshold

func WithSlowRouteThreshold(d time.Duration) Option

type RedisOp

type RedisOp struct {
	Command   string
	Key       string
	Duration  time.Duration
	Error     string
	Timestamp time.Time
}

type RequestTrace

type RequestTrace struct {
	ID              string
	Method          string
	Path            string
	RoutePattern    string
	QueryParams     string
	RequestHeaders  map[string]string
	RequestBody     []byte
	ResponseStatus  int
	ResponseHeaders map[string]string
	ResponseBody    []byte
	RequestSize     int64
	ResponseSize    int64
	ClientIP        string
	UserAgent       string

	StartTime   time.Time
	EndTime     time.Time
	Latency     time.Duration
	TTFB        time.Duration
	HandlerTime time.Duration

	GoroutinesBefore int
	GoroutinesAfter  int
	MemAllocBefore   uint64
	MemAllocAfter    uint64

	DBQueries      []DBQuery
	TotalDBTime    time.Duration
	ExternalCalls  []ExternalCall
	TotalExtTime   time.Duration
	RedisOps       []RedisOp
	TotalRedisTime time.Duration
	MongoOps       []MongoOp
	TotalMongoTime time.Duration

	Panicked   bool
	PanicValue interface{}
	PanicStack string

	Alerts []Alert
}

func FiberTraceFromContext

func FiberTraceFromContext(c *fiber.Ctx) *RequestTrace

FiberTraceFromContext retrieves the trace from Fiber locals.

func TraceFromContext

func TraceFromContext(ctx context.Context) *RequestTrace

type RouteMetrics

type RouteMetrics struct {
	Pattern       string
	Method        string
	TotalRequests int64
	ErrorCount    int64 // 5xx responses
	TotalLatency  time.Duration
	Latencies     []time.Duration // for percentile calculation - capped at 10000

	StatusCodes  map[int]int64
	AvgDBQueries float64

	MinLatency time.Duration
	MaxLatency time.Duration

	LastRequestTime time.Time
	// contains filtered or unexported fields
}

func NewRouteMetrics

func NewRouteMetrics(pattern, method string, latencyCap int) *RouteMetrics

func (*RouteMetrics) AvgLatency

func (rm *RouteMetrics) AvgLatency() time.Duration

func (*RouteMetrics) ErrorRate

func (rm *RouteMetrics) ErrorRate() float64

func (*RouteMetrics) P50

func (rm *RouteMetrics) P50() time.Duration

func (*RouteMetrics) P95

func (rm *RouteMetrics) P95() time.Duration

func (*RouteMetrics) P99

func (rm *RouteMetrics) P99() time.Duration

func (*RouteMetrics) Percentile

func (rm *RouteMetrics) Percentile(p float64) time.Duration

func (*RouteMetrics) Record

func (rm *RouteMetrics) Record(trace *RequestTrace)

func (*RouteMetrics) Snapshot

func (rm *RouteMetrics) Snapshot() *RouteMetrics

func (*RouteMetrics) Status

func (rm *RouteMetrics) Status() string

type Severity

type Severity string
const (
	SeverityInfo     Severity = "info"
	SeverityWarning  Severity = "warning"
	SeverityCritical Severity = "critical"
)

type WrappedDB

type WrappedDB struct {
	*sql.DB
}

WrappedDB wraps a *sql.DB to capture query metrics.

func WrapDB

func WrapDB(db *sql.DB) *WrappedDB

WrapDB wraps a *sql.DB for query instrumentation.

func (*WrappedDB) ExecContext

func (w *WrappedDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)

func (*WrappedDB) PoolStats

func (w *WrappedDB) PoolStats() DBPoolStats

PoolStats returns the current DB pool statistics.

func (*WrappedDB) QueryContext

func (w *WrappedDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)

func (*WrappedDB) QueryRowContext

func (w *WrappedDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL