whoops

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 14 Imported by: 0

README

go-whoops

Go Reference CI codecov Go Report Card

Beautiful, interactive error pages for Go web applications. Inspired by PHP's Whoops.

Features

  • Pretty error page with stack traces and inline source code
  • Stacked handler system (LIFO) — combine HTML, JSON, plain text, and custom handlers
  • Framework-agnostic core with zero external dependencies (stdlib only)
  • Editor integration — click file paths to open in VSCode, GoLand, Cursor, Sublime, Vim, Emacs, and more
  • Error chain visualization — see how errors propagate through layers
  • Sensitive data scrubbing — auto-masks passwords, tokens, keys in headers, env vars, and query params
  • Custom data panels — add your own tabs (session, user, feature flags, etc.)
  • Content negotiation — serves HTML to browsers, JSON to API clients
  • Dark/light theme toggle
  • Search & keyboard navigation in stack frames
  • Copy-to-clipboard for stack traces and source code
  • Build info panel — Go version, module info, goroutine count
  • Framework middleware as separate installable modules (Gin, Echo, Chi, Fiber)
  • Logger integrations as separate installable modules (Zap, Zerolog, slog)

Quick Start

go get github.com/RezaKargar/go-whoops
net/http (built-in, zero extra deps)
package main

import (
    "net/http"
    "github.com/RezaKargar/go-whoops"
)

func main() {
    w := whoops.New()
    w.PushHandler(whoops.NewPrettyPageHandler(
        whoops.WithEditor(whoops.EditorVSCode),
    ))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
        panic("something went wrong!")
    })

    http.ListenAndServe(":8080", w.HTTPMiddleware(mux))
}
Gin
go get github.com/RezaKargar/go-whoops/middleware/gin
import (
    "github.com/RezaKargar/go-whoops"
    whoopsgin "github.com/RezaKargar/go-whoops/middleware/gin"
)

w := whoops.New()
w.PushHandler(whoops.NewPrettyPageHandler())

router := gin.New()
router.Use(whoopsgin.Middleware(w, nil)) // nil = always render
Echo
go get github.com/RezaKargar/go-whoops/middleware/echo
import whoopsecho "github.com/RezaKargar/go-whoops/middleware/echo"

e := echo.New()
e.Use(whoopsecho.Middleware(w, nil))
Chi
go get github.com/RezaKargar/go-whoops/middleware/chi
import whoopschi "github.com/RezaKargar/go-whoops/middleware/chi"

r := chi.NewRouter()
r.Use(whoopschi.Middleware(w))
Fiber
go get github.com/RezaKargar/go-whoops/middleware/fiber
import whoopsfiber "github.com/RezaKargar/go-whoops/middleware/fiber"

app := fiber.New()
app.Use(whoopsfiber.New(w))

Handler Stack

Handlers are processed in LIFO order (last pushed = first to handle). Each handler returns an action:

Action Behavior
Done Stop processing; no further handlers run
Continue Pass to the next handler
LastHandler Continue but don't write further responses
w := whoops.New()

// First pushed: fallback JSON for API clients
w.PushHandler(whoops.NewJsonHandler(true))

// Second pushed: pretty HTML page for browsers (runs first due to LIFO)
w.PushHandler(whoops.NewPrettyPageHandler())

Built-in Handlers

Handler Description
PrettyPageHandler Interactive HTML error page with source code, stack trace, and details
JsonHandler JSON error response with optional stack trace
PlainTextHandler Plain text output for CLI/logs
CallbackHandler Wrap any function as a handler (for logging, metrics, etc.)

Custom Error Types

go-whoops works with any error type. For rich error display, implement optional interfaces:

// Provide pre-captured stack trace
type StackProvider interface {
    StackPCs() []uintptr
}

// Provide error category
type KindProvider interface {
    ErrorKind() string
}

// Provide operation chain
type OperationProvider interface {
    Operation() string
}

// Provide metadata
type MetaProvider interface {
    ErrorMeta() map[string]any
}

// Provide HTTP status code
type StatusCodeProvider interface {
    StatusCode() int
}

See _examples/custom-unwrapper/ for a complete example.

Custom Data Panels

Add custom tabs to the error page:

type SessionProvider struct{}

func (p SessionProvider) Name() string { return "Session" }
func (p SessionProvider) Data(err error, r *http.Request) map[string]string {
    return map[string]string{
        "user_id": "123",
        "role":    "admin",
    }
}

w.PushDataProvider(SessionProvider{})

Logger Integrations

go get github.com/RezaKargar/go-whoops/integration/zap
go get github.com/RezaKargar/go-whoops/integration/zerolog
go get github.com/RezaKargar/go-whoops/integration/slog  # stdlib only
import whoopszap "github.com/RezaKargar/go-whoops/integration/zap"

logger, _ := zap.NewDevelopment()
w := whoops.New(whoops.WithLogger(whoopszap.New(logger)))

Editor Integration

Click file paths in the error page to open them directly in your editor:

whoops.NewPrettyPageHandler(
    whoops.WithEditor(whoops.EditorVSCode),   // or EditorGoLand, EditorCursor, etc.
)

Supported editors: VSCode, GoLand, Cursor, Zed, Sublime Text, Atom, Vim, Emacs, PhpStorm, IntelliJ IDEA.

Configuration

w := whoops.New(
    whoops.WithUnwrapper(myCustomUnwrapper{}),           // custom error type support
    whoops.WithScrubber(whoops.NewScrubber(myPatterns)),  // custom sensitive patterns
    whoops.WithLogger(myLogger),                          // error logging
    whoops.WithHandler(whoops.NewPrettyPageHandler()),     // add handler at construction
    whoops.WithDataProvider(myProvider),                   // add data panel
)

Architecture

See ARCHITECTURE.md for detailed design documentation.

Examples

Run the examples:

# Basic net/http example
go run ./_examples/basic/main.go

# Custom handler stack
go run ./_examples/custom-handler/main.go

# Custom error type with provider interfaces
go run ./_examples/custom-unwrapper/main.go

Then visit http://localhost:8080/error in your browser.

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT License. See LICENSE for details.

Documentation

Overview

Package whoops provides a beautiful, interactive error page for Go web applications, inspired by PHP's Whoops (https://github.com/filp/whoops).

It renders stack traces with source code, error chains, request context, environment variables, and custom data panels in a developer-friendly HTML page.

Quick Start

The simplest way to use whoops is with the built-in net/http middleware:

w := whoops.New()
w.PushHandler(whoops.NewPrettyPageHandler())
http.ListenAndServe(":8080", w.HTTPMiddleware(myHandler))

Handler Stack

Like PHP Whoops, go-whoops uses a stacked handler system. Handlers are pushed onto the stack and executed in LIFO order when an error occurs:

w := whoops.New()
w.PushHandler(whoops.NewPrettyPageHandler()) // renders HTML for browsers
w.PushHandler(whoops.NewJsonHandler())       // renders JSON for API clients

Framework Integration

Framework-specific middleware is available as separate modules:

go get github.com/RezaKargar/go-whoops/middleware/gin
go get github.com/RezaKargar/go-whoops/middleware/echo
go get github.com/RezaKargar/go-whoops/middleware/chi
go get github.com/RezaKargar/go-whoops/middleware/fiber

Custom Error Types

Register an ErrorUnwrapper to extract rich data from your custom error types:

w := whoops.New(whoops.WithUnwrapper(myCustomUnwrapper{}))

The default StdUnwrapper handles standard Go errors and the errors package.

Custom Data Panels

Add custom tabs to the error page with DataProvider:

w.PushDataProvider(mySessionProvider{})

Index

Constants

View Source
const (
	// Done stops the handler pipeline; no further handlers run.
	Done = iota
	// LastHandler lets remaining handlers inspect the error but prevents
	// further response writes.
	LastHandler
	// Continue proceeds to the next handler in the stack.
	Continue
)

Handler action constants control the handler pipeline flow.

Variables

View Source
var (
	EditorNone     = Editor{}
	EditorVSCode   = Editor{Name: "VSCode", URLFmt: "vscode://file/%s:%d"}
	EditorGoLand   = Editor{Name: "GoLand", URLFmt: "goland://open?file=%s&line=%d"}
	EditorSublime  = Editor{Name: "Sublime Text", URLFmt: "subl://open?url=file://%s&line=%d"}
	EditorAtom     = Editor{Name: "Atom", URLFmt: "atom://core/open/file?filename=%s&line=%d"}
	EditorVim      = Editor{Name: "Vim", URLFmt: "mvim://open?url=file://%s&line=%d"}
	EditorEmacs    = Editor{Name: "Emacs", URLFmt: "emacs://open?url=file://%s&line=%d"}
	EditorPhpStorm = Editor{Name: "PhpStorm", URLFmt: "phpstorm://open?file=%s&line=%d"}
	EditorIdea     = Editor{Name: "IntelliJ IDEA", URLFmt: "idea://open?file=%s&line=%d"}
	EditorCursor   = Editor{Name: "Cursor", URLFmt: "cursor://file/%s:%d"}
	EditorZed      = Editor{Name: "Zed", URLFmt: "zed://file/%s:%d"}
)

Pre-defined editors.

Functions

func CollectEnvironment

func CollectEnvironment(scrubber *Scrubber) map[string]string

CollectEnvironment gathers scrubbed environment variables.

func FormatError

func FormatError(data *ErrorData) string

FormatError formats error data as a plain text string without writing to a ResponseWriter. Useful for logging or CLI output.

func MaskedValue

func MaskedValue() string

MaskedValue returns the string used to replace sensitive data.

func ResetSourceCache

func ResetSourceCache()

ResetSourceCache clears the global source reader cache.

Types

type BuildInfo

type BuildInfo struct {
	GoVersion  string
	Module     string
	Version    string
	Goroutines int
	Settings   map[string]string
}

BuildInfo holds Go build metadata.

func CollectBuildInfo

func CollectBuildInfo() BuildInfo

CollectBuildInfo gathers Go build metadata.

type CallbackHandler

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

CallbackHandler wraps a simple callback function as a Handler. The callback receives the error and collected data; it returns an action constant.

func NewCallbackHandler

func NewCallbackHandler(fn func(err error, data *ErrorData) int) *CallbackHandler

NewCallbackHandler creates a handler from a simple callback function. The callback is invoked with the error and collected data but has no direct access to the ResponseWriter; use it for logging, metrics, or side effects.

func (*CallbackHandler) Handle

func (h *CallbackHandler) Handle(err error, data *ErrorData, _ http.ResponseWriter, _ *http.Request) int

Handle invokes the wrapped callback.

type CodeLine

type CodeLine struct {
	Number      int
	Content     string
	IsHighlight bool
}

CodeLine represents a single line of source code.

func ReadSourceLines

func ReadSourceLines(filePath string, targetLine int) []CodeLine

ReadSourceLines reads source code around a target line using the global reader.

type DataProvider

type DataProvider interface {
	Name() string
	Data(err error, r *http.Request) map[string]string
}

DataProvider supplies custom data panels for the error page. Implement this interface to add custom tabs (session data, user info, etc.).

type Editor

type Editor struct {
	Name   string
	URLFmt string // format string with %s for file and %d for line
}

Editor represents a code editor for "open in editor" links.

func EditorByName

func EditorByName(name string) Editor

EditorByName returns a pre-defined editor by name (case-insensitive). Returns EditorNone if not found.

func (Editor) URL

func (e Editor) URL(file string, line int) string

URL returns the editor URL for the given file and line.

type ErrorChainEntry

type ErrorChainEntry struct {
	Op         string
	Kind       string
	Message    string
	Underlying string
	Meta       map[string]any
}

ErrorChainEntry represents one layer in a wrapped error chain.

type ErrorData

type ErrorData struct {
	StatusCode   int
	StatusText   string
	ErrorMessage string
	ErrorKind    string
	Operation    string
	Underlying   string
	Frames       []StackFrame
	ErrorChain   []ErrorChainEntry
	Request      RequestInfo
	Environment  map[string]string
	Meta         map[string]any
	BuildInfo    BuildInfo
	Panels       []Panel
}

ErrorData holds all information collected about an error for rendering.

type ErrorUnwrapper

type ErrorUnwrapper interface {
	Unwrap(err error) *ErrorData
}

ErrorUnwrapper extracts structured error data from an error. Implement this interface to support custom error types with rich metadata (e.g., operation names, error kinds, meta maps, pre-captured stacks).

type Handler

type Handler interface {
	Handle(err error, data *ErrorData, w http.ResponseWriter, r *http.Request) int
}

Handler processes an error occurrence. Implementations decide how to present the error (HTML page, JSON, plain text, logging, etc.) and return an action constant to control pipeline flow.

type HandlerFunc

type HandlerFunc func(err error, data *ErrorData, w http.ResponseWriter, r *http.Request) int

HandlerFunc is an adapter to use ordinary functions as Handler.

func (HandlerFunc) Handle

func (f HandlerFunc) Handle(err error, data *ErrorData, w http.ResponseWriter, r *http.Request) int

Handle calls f(err, data, w, r).

type JsonHandler

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

JsonHandler returns error data as a JSON response. It only writes if the client accepts JSON.

func NewJsonHandler

func NewJsonHandler(showTrace bool) *JsonHandler

NewJsonHandler creates a JsonHandler. If showTrace is true, stack frames are included in the response.

func (*JsonHandler) Handle

func (h *JsonHandler) Handle(err error, data *ErrorData, w http.ResponseWriter, r *http.Request) int

Handle writes a JSON error response if the client accepts JSON.

type KindProvider

type KindProvider interface {
	ErrorKind() string
}

KindProvider is an optional interface for errors that carry a semantic kind/category (e.g., "NotFound", "Unauthorized").

type Logger

type Logger interface {
	LogError(err error, data *ErrorData)
}

Logger logs error information. Implement this interface to integrate with logging frameworks like zap, zerolog, or slog.

type MetaProvider

type MetaProvider interface {
	ErrorMeta() map[string]any
}

MetaProvider is an optional interface for errors that carry key-value metadata.

type OperationProvider

type OperationProvider interface {
	Operation() string
}

OperationProvider is an optional interface for errors that carry an operation chain (e.g., "handler.Get -> service.Fetch -> repo.Query").

type Option

type Option func(*Run)

Option configures a Run instance.

func WithDataProvider

func WithDataProvider(dp DataProvider) Option

WithDataProvider adds a data provider during construction.

func WithHandler

func WithHandler(h Handler) Option

WithHandler adds a handler during construction.

func WithLogger

func WithLogger(l Logger) Option

WithLogger sets the logger for error reporting.

func WithScrubber

func WithScrubber(s *Scrubber) Option

WithScrubber sets a custom Scrubber.

func WithUnwrapper

func WithUnwrapper(u ErrorUnwrapper) Option

WithUnwrapper sets a custom ErrorUnwrapper.

type Panel

type Panel struct {
	Name string
	Data map[string]string
}

Panel represents a custom data tab on the error page.

type PlainTextHandler

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

PlainTextHandler renders error information as plain text. Suitable for CLI output, log files, or non-browser clients.

func NewPlainTextHandler

func NewPlainTextHandler(showTrace bool) *PlainTextHandler

NewPlainTextHandler creates a PlainTextHandler. If showTrace is true, stack frames are included in the output.

func (*PlainTextHandler) Handle

func (h *PlainTextHandler) Handle(err error, data *ErrorData, w http.ResponseWriter, r *http.Request) int

Handle writes a plain text error response.

type PrettyPageHandler

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

PrettyPageHandler renders a beautiful interactive HTML error page, inspired by PHP Whoops. It shows stack traces with source code, error chains, request context, environment, and custom data panels.

func NewPrettyPageHandler

func NewPrettyPageHandler(opts ...PrettyPageOption) *PrettyPageHandler

NewPrettyPageHandler creates a PrettyPageHandler with default settings.

func (*PrettyPageHandler) Handle

func (h *PrettyPageHandler) Handle(err error, data *ErrorData, w http.ResponseWriter, r *http.Request) int

Handle renders the HTML error page if the client accepts HTML.

type PrettyPageOption

type PrettyPageOption func(*PrettyPageHandler)

PrettyPageOption configures a PrettyPageHandler.

func WithEditor

func WithEditor(e Editor) PrettyPageOption

WithEditor sets the editor for "open in editor" links.

func WithExtraTable

func WithExtraTable(name string, data map[string]string) PrettyPageOption

WithExtraTable adds a custom data table to the error page.

func WithPageTitle

func WithPageTitle(title string) PrettyPageOption

WithPageTitle sets a custom page title.

type RequestInfo

type RequestInfo struct {
	Method      string
	URL         string
	Headers     map[string]string
	Query       map[string]string
	RemoteAddr  string
	ContentType string
	Body        string
	RequestID   string
}

RequestInfo holds scrubbed HTTP request details.

func CollectRequest

func CollectRequest(r *http.Request, scrubber *Scrubber) RequestInfo

CollectRequest gathers scrubbed HTTP request information.

type Run

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

Run is the central error handler that manages a stack of handlers. It collects error data and dispatches to registered handlers in LIFO order.

func New

func New(opts ...Option) *Run

New creates a new Run instance with the given options.

func (*Run) ClearHandlers

func (r *Run) ClearHandlers()

ClearHandlers removes all handlers from the stack.

func (*Run) DataScrubber

func (r *Run) DataScrubber() *Scrubber

Scrubber returns the current Scrubber.

func (*Run) ErrorHandler

func (r *Run) ErrorHandler(err error) http.HandlerFunc

ErrorHandler returns an http.HandlerFunc that renders the given error through the Run handler stack. Useful for custom error routes.

func (*Run) HTTPMiddleware

func (r *Run) HTTPMiddleware(next http.Handler) http.Handler

HTTPMiddleware wraps an http.Handler and recovers panics, passing any error through the Run handler stack. This is the built-in net/http integration requiring no external dependencies.

Usage:

w := whoops.New()
w.PushHandler(whoops.NewPrettyPageHandler())
http.ListenAndServe(":8080", w.HTTPMiddleware(myHandler))

func (*Run) HTTPMiddlewareFunc

func (r *Run) HTTPMiddlewareFunc(next http.HandlerFunc) http.Handler

HTTPMiddlewareFunc wraps an http.HandlerFunc for convenience.

func (*Run) HandleError

func (r *Run) HandleError(w http.ResponseWriter, req *http.Request, err error)

HandleError processes an error through the handler stack. It collects error data, runs data providers, scrubs sensitive values, and dispatches to handlers in LIFO order.

func (*Run) Handlers

func (r *Run) Handlers() []Handler

Handlers returns a copy of the current handler stack.

func (*Run) PopHandler

func (r *Run) PopHandler() Handler

PopHandler removes and returns the top handler from the stack. Returns nil if the stack is empty.

func (*Run) PushDataProvider

func (r *Run) PushDataProvider(dp DataProvider)

PushDataProvider adds a custom data provider for the error page.

func (*Run) PushHandler

func (r *Run) PushHandler(h Handler)

PushHandler adds a handler to the top of the stack. The last handler pushed is the first to handle errors.

func (*Run) SetLogger

func (r *Run) SetLogger(l Logger)

SetLogger sets the logger for error reporting.

func (*Run) SetScrubber

func (r *Run) SetScrubber(s *Scrubber)

SetScrubber replaces the data scrubber.

func (*Run) SetUnwrapper

func (r *Run) SetUnwrapper(u ErrorUnwrapper)

SetUnwrapper replaces the error unwrapper.

func (*Run) Unwrapper

func (r *Run) Unwrapper() ErrorUnwrapper

Unwrapper returns the current ErrorUnwrapper.

type Scrubber

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

Scrubber masks sensitive values in key-value data.

func DefaultScrubber

func DefaultScrubber() *Scrubber

DefaultScrubber creates a scrubber with common sensitive key patterns.

func NewScrubber

func NewScrubber(patterns []string) *Scrubber

NewScrubber creates a scrubber with the given sensitive key patterns.

func (*Scrubber) AddPattern

func (s *Scrubber) AddPattern(pattern string)

AddPattern adds a sensitive key pattern (case-insensitive substring match).

func (*Scrubber) IsSensitive

func (s *Scrubber) IsSensitive(key string) bool

IsSensitive reports whether the key name suggests sensitive data.

func (*Scrubber) ScrubMap

func (s *Scrubber) ScrubMap(m map[string]string) map[string]string

ScrubMap returns a copy of the map with sensitive values masked.

func (*Scrubber) ScrubValue

func (s *Scrubber) ScrubValue(key, value string) string

ScrubValue returns the masked value if the key is sensitive.

type SourceReader

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

SourceReader reads source files and extracts code context around a target line. It caches file contents to avoid re-reading during a single error page render.

func NewSourceReader

func NewSourceReader() *SourceReader

NewSourceReader creates a SourceReader with the default context window (15 lines).

func NewSourceReaderWithContext

func NewSourceReaderWithContext(lines int) *SourceReader

NewSourceReaderWithContext creates a SourceReader with a custom context window.

func (*SourceReader) ContextLines

func (sr *SourceReader) ContextLines() int

ContextLines returns the number of context lines shown around the error line.

func (*SourceReader) ReadLines

func (sr *SourceReader) ReadLines(filePath string, targetLine int) []CodeLine

ReadLines reads the given file and returns the lines around targetLine (1-indexed). Returns nil if the file cannot be read.

func (*SourceReader) Reset

func (sr *SourceReader) Reset()

Reset clears the file cache. Call between requests during development so that source changes are reflected immediately.

type StackFrame

type StackFrame struct {
	Index     int
	Function  string
	File      string
	ShortFile string
	Line      int
	CodeLines []CodeLine
	IsApp     bool
}

StackFrame represents one frame in the call stack.

type StackProvider

type StackProvider interface {
	StackPCs() []uintptr
}

StackProvider is an optional interface that error types can implement to supply pre-captured program counters for stack trace rendering.

type StatusCodeProvider

type StatusCodeProvider interface {
	StatusCode() int
}

StatusCodeProvider is an optional interface for errors that map to HTTP status codes.

type StdUnwrapper

type StdUnwrapper struct{}

StdUnwrapper is the default ErrorUnwrapper that works with standard Go errors. It uses errors.Unwrap, errors.As, and optional interfaces (StackProvider, KindProvider, etc.) to extract as much data as possible.

func (StdUnwrapper) Unwrap

func (u StdUnwrapper) Unwrap(err error) *ErrorData

Unwrap extracts ErrorData from any error using standard library conventions and optional provider interfaces.

Directories

Path Synopsis
_examples
basic command
Example: basic net/http usage with go-whoops.
Example: basic net/http usage with go-whoops.
custom-handler command
Example: custom handler that logs errors to stdout before rendering.
Example: custom handler that logs errors to stdout before rendering.
custom-unwrapper command
Example: custom error type with ErrorUnwrapper integration.
Example: custom error type with ErrorUnwrapper integration.
middleware
gin module

Jump to

Keyboard shortcuts

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