Documentation
¶
Overview ¶
Package devtools is Vukem's development dashboard, served at /_vukem: recent requests with timings, SQL queries (with N+1 detection), jobs, events, sent mail previews, log records, per-request traces and the route table. Everything lives in bounded in-memory ring buffers.
Apps enable it with vukem.WithDevtools() or DEVTOOLS=true; the app refuses to boot when it is enabled with APP_ENV=production, and the dashboard answers only loopback requests for a loopback Host unless DEVTOOLS_INSECURE_ALLOW_REMOTE=true. Collection hooks are observers set on the database, queue and event bus, a mail transport wrapper and a slog handler, so an app without devtools pays nothing.
New returns the Devtools collector; Devtools.Middleware records requests, Devtools.Register mounts the dashboard at Path, and the Observe methods, Devtools.MailTransport and Devtools.LogHandler are the hooks the app wires for you.
See https://github.com/AlexGalhardo/vukem/blob/main/docs/devtools.md.
Index ¶
- Constants
- func NormalizeSQL(query string) string
- func RedactArgs(args []any) []string
- type Config
- type Devtools
- func (d *Devtools) Allowed(r *http.Request) bool
- func (d *Devtools) Events() []Event
- func (d *Devtools) Jobs() []Job
- func (d *Devtools) LogHandler() slog.Handler
- func (d *Devtools) Logs() []Log
- func (d *Devtools) MailTransport(next mail.Transport) mail.Transport
- func (d *Devtools) Mails() []Mail
- func (d *Devtools) Middleware() router.Middleware
- func (d *Devtools) ObserveEvent(ctx context.Context, e events.Event)
- func (d *Devtools) ObserveJob(ctx context.Context, e queue.JobEvent)
- func (d *Devtools) ObserveQuery(ctx context.Context, q database.Query)
- func (d *Devtools) Queries() []Query
- func (d *Devtools) Register(r *router.Router)
- func (d *Devtools) Requests() []Request
- func (d *Devtools) RouteMiddleware() router.Middleware
- func (d *Devtools) Trace(id string) (Trace, bool)
- type Event
- type Job
- type Log
- type Mail
- type Query
- type Repeat
- type Request
- type TimelineItem
- type Trace
Examples ¶
Constants ¶
const ( DefaultCapacity = 200 DefaultNPlusOneThreshold = 3 )
Defaults for Config.
const Path = "/_vukem"
Path is the dashboard prefix.
Variables ¶
This section is empty.
Functions ¶
func NormalizeSQL ¶
NormalizeSQL returns the statement shape used for display and N+1 detection: literals become "?", $n placeholders become "?", lists of placeholders collapse to "(?...)" and whitespace is collapsed. Literal values never reach the dashboard.
Example ¶
package main
import (
"fmt"
"github.com/AlexGalhardo/vukem/devtools"
)
func main() {
// Literals never reach the dashboard; the shape also groups N+1 queries.
fmt.Println(devtools.NormalizeSQL("SELECT * FROM posts WHERE user_id = 42 AND title = 'secret'"))
fmt.Println(devtools.NormalizeSQL("SELECT * FROM users WHERE id IN ($1, $2, $3)"))
}
Output: SELECT * FROM posts WHERE user_id = ? AND title = ? SELECT * FROM users WHERE id IN (?...)
func RedactArgs ¶
RedactArgs describes bound values by type (and length for strings and bytes), never by value: arguments hold user data, tokens and passwords.
Example ¶
package main
import (
"fmt"
"github.com/AlexGalhardo/vukem/devtools"
)
func main() {
fmt.Println(devtools.RedactArgs([]any{42, "hunter2", []byte("raw")}))
}
Output: [int string(len=7) bytes(len=3)]
Types ¶
type Config ¶
type Config struct {
// Capacity is the number of records kept per kind (default 200).
Capacity int
// NPlusOneThreshold flags a SELECT whose normalized statement runs at
// least this many times within one request (default 3).
NPlusOneThreshold int
// InsecureAllowRemote serves the dashboard to any client. By default
// only loopback clients with a loopback Host (no proxy headers) get it.
InsecureAllowRemote bool
// Level is the minimum level of collected log records (default Info).
Level slog.Leveler
// Routes lists the app routes for the routes page (Router.Routes).
Routes func() []router.RouteInfo
// Now is the clock (default time.Now).
Now func() time.Time
}
Config tunes the collector.
type Devtools ¶
type Devtools struct {
// contains filtered or unexported fields
}
Devtools collects records and serves the dashboard. It implements database.QueryObserver, queue.Observer and events.Observer.
func (*Devtools) Allowed ¶
Allowed reports whether r may see the dashboard: with InsecureAllowRemote always; otherwise only when the direct peer is a loopback address, no proxy header is present, and Host is a loopback name (which defeats DNS rebinding from a page the developer visits).
func (*Devtools) LogHandler ¶
LogHandler returns a slog.Handler that keeps records at Config.Level or above; tee it next to the app handler with slog.NewMultiHandler.
func (*Devtools) MailTransport ¶
MailTransport wraps next so every message it sends is kept for preview.
func (*Devtools) Middleware ¶
func (d *Devtools) Middleware() router.Middleware
Middleware records every request except the dashboard's own. Install it first with Router.UseServer so its timing covers the whole stack.
func (*Devtools) ObserveEvent ¶
ObserveEvent implements events.Observer.
func (*Devtools) ObserveJob ¶
ObserveJob implements queue.Observer.
func (*Devtools) ObserveQuery ¶
ObserveQuery implements database.QueryObserver.
func (*Devtools) RouteMiddleware ¶
func (d *Devtools) RouteMiddleware() router.Middleware
RouteMiddleware captures the matched route pattern; install it with Router.Use.
type Event ¶
type Event struct {
TraceID string
Time time.Time
Type string
Listeners int
Duration time.Duration
Err string
}
Event is a recorded event dispatch.
type Job ¶
type Job struct {
TraceID string
Time time.Time
ID string
Kind string
Queue string
Status string
Attempt int
Duration time.Duration
Err string
}
Job is a recorded queue step.
type Mail ¶
type Mail struct {
ID string
TraceID string
Time time.Time
From string
To []string
Cc []string
Subject string
HTML string
Text string
Attachments []string
Err string
}
Mail is a recorded outgoing message.
type Query ¶
type Query struct {
TraceID string
Time time.Time
SQL string
Args []string
Duration time.Duration
Caller string
Err string
NPlusOne bool
}
Query is a recorded SQL statement; Args are redacted to types.
type Request ¶
type Request struct {
// ID is the devtools trace id ("r12").
ID string
// RequestID is the X-Request-ID response header, when set.
RequestID string
Method string
Path string
// Pattern is the matched ServeMux pattern ("GET /users/{id}").
Pattern string
// Route is the route name, when the route has one.
Route string
Status int
Start time.Time
Duration time.Duration
Queries int
QueryTime time.Duration
// NPlusOne lists the SELECT shapes repeated at least the threshold.
NPlusOne []Repeat
}
Request is a recorded HTTP request.
type TimelineItem ¶
type TimelineItem struct {
Offset time.Duration
Kind string
Text string
Detail string
Link string
Flag bool
}
TimelineItem is one entry of a request trace.
type Trace ¶
type Trace struct {
Request Request
Items []TimelineItem
}
Trace is a request with everything recorded during it, in time order.