errorgap

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 19 Imported by: 0

README

errorgap-go

Go notifier for Errorgap. Captures errors and panics, embeds application and dependency source excerpts, instruments net/http requests and background jobs for APM, and forwards standard-library slog records.

Install

go get github.com/errorgaphq/errorgap-go

Requires Go 1.22+.

Configure

package main

import (
    "context"
    "os"

    errorgap "github.com/errorgaphq/errorgap-go"
)

func main() {
    err := errorgap.Init(errorgap.Config{
        Endpoint:    os.Getenv("ERRORGAP_ENDPOINT"),
        ProjectSlug: os.Getenv("ERRORGAP_PROJECT_SLUG"),
        APIKey:      os.Getenv("ERRORGAP_API_KEY"),
        Environment: os.Getenv("APP_ENV"),
        APMEnabled:  true,
        LogsEnabled: true,
    })
    if err != nil {
        panic(err)
    }
    defer errorgap.Close(context.Background())
    // ... your app ...
}

Init reads the same values from ERRORGAP_ENDPOINT, ERRORGAP_PROJECT_SLUG, ERRORGAP_PROJECT_ID, and ERRORGAP_API_KEY if you leave them empty.

Manual notification

if err := risky(); err != nil {
    errorgap.Notify(err, errorgap.NoticeOptions{
        Context: map[string]any{"component": "billing"},
    })
    return err
}

Notify returns a Result ({Status, Body, Err, Queued}). The SDK never panics — recoverable failures are logged via the configured slog.Logger.

net/http errors and APM

mux := http.NewServeMux()
mux.Handle("GET /orders/{orderId}",
    stdhttp.Route("/orders/{orderId}", http.HandlerFunc(handler)))
http.ListenAndServe(":8080", stdhttp.Recover(mux))

The middleware catches panics, reports them, returns a 500 response, and sends request duration, status, raw path, and normalized route statistics. On Go versions that expose the matched ServeMux pattern, stdhttp.Route is optional; keeping it makes normalized paths work on Go 1.22 too.

Record database and outbound HTTP work against the request context:

started := time.Now()
rows, err := db.QueryContext(r.Context(), "SELECT * FROM orders WHERE id = ?", id)
errorgap.RecordDatabase(r.Context(), "SELECT * FROM orders WHERE id = 42", time.Since(started))

started = time.Now()
response, err := http.DefaultClient.Do(request)
errorgap.RecordExternal(r.Context(), time.Since(started))

SQL literals are normalized before delivery so equivalent queries aggregate.

Background jobs

err := errorgap.TrackJob(ctx, "ReceiptJob", "critical", func(ctx context.Context) error {
    errorgap.RecordDatabase(ctx, "SELECT 42 AS receipt", 5*time.Millisecond)
    return generateReceipt(ctx)
})

Failed jobs send both an error notice and a failed job transaction.

slog forwarding

handler := errorgap.NewSlogHandler(slog.NewJSONHandler(os.Stdout, nil), nil, slog.LevelWarn)
logger := slog.New(handler)
logger.Warn("payment gateway timeout", "order_id", orderID)

Pass a *Client instead of nil when not using the package-level client.

Recover helper

For background goroutines or workers without HTTP middleware:

func worker() {
    defer errorgap.Recover()
    // ... risky code that may panic ...
}

Recover reports the panic and re-panics so the host's normal panic handling still runs.

Graceful shutdown

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = errorgap.Flush(ctx) // wait for queued notices
_ = errorgap.Close(ctx) // shut down the worker goroutine

Configuration reference

Field Default Notes
Endpoint ERRORGAP_ENDPOINT or http://127.0.0.1:3030 Base URL, no trailing slash
ProjectSlug ERRORGAP_PROJECT_SLUG Required
ProjectID ERRORGAP_PROJECT_ID Optional, embedded in payload
APIKey ERRORGAP_API_KEY Sent as x-errorgap-project-key
Environment ERRORGAP_ENVIRONMENT or "production"
Release Embedded in context.release
RootDirectory current directory Classifies app frames and makes their paths relative
Async true (via Init) Background goroutine delivery
Logger discard logger Replace to surface SDK diagnostics
FilterKeys ["password", "token", ...] Substring, case-insensitive
HTTPClient &http.Client{Timeout: 5s} Plug in your own transport
QueueSize 100 Drops the new telemetry item when full
APMEnabled ERRORGAP_APM_ENABLED or false Sends requests, spans, and jobs
APMSampleRate ERRORGAP_APM_SAMPLE_RATE or 1 Fraction from 0 to 1
LogsEnabled ERRORGAP_LOGS_ENABLED or false Enables NotifyLog and SlogHandler delivery
MinimumLogLevel ERRORGAP_MINIMUM_LOG_LEVEL or WARN Threshold used by NewSlogHandler

Verify

curl -sS -X POST "$ERRORGAP_ENDPOINT/api/projects/$ERRORGAP_PROJECT_SLUG/notices" \
  -H "content-type: application/json" \
  -H "x-errorgap-project-key: $ERRORGAP_API_KEY" \
  -d '{"errors":[{"type":"ErrorgapInstallTest","message":"Errorgap install verification"}],"context":{"environment":"development"}}'

Then trigger a real error and confirm it appears in the Errorgap UI.

Development

go test ./...

License

MIT.

Documentation

Overview

Package errorgap is the Go notifier for the Errorgap error-tracking platform. Use Init to configure the package-level default client and Notify / Flush / Close as the simple, package-level entry points.

For libraries or apps that want isolated state (e.g. tests), instantiate a Client directly with NewClient.

Index

Constants

View Source
const Version = "0.2.0"

Version is the SDK version, embedded in every notice's User-Agent header.

Variables

View Source
var (
	// ErrMissingProjectSlug is returned from validation when ProjectSlug
	// is empty.
	ErrMissingProjectSlug = errors.New("errorgap: ProjectSlug is required")
	// ErrMissingEndpoint is returned from validation when Endpoint is
	// empty.
	ErrMissingEndpoint = errors.New("errorgap: Endpoint is required")
)
View Source
var DefaultFilterKeys = []string{
	"password",
	"password_confirmation",
	"token",
	"secret",
	"api_key",
	"authorization",
	"cookie",
}

DefaultFilterKeys are matched (case-insensitive substring) against param keys to mask sensitive values before delivery.

Functions

func Close

func Close(ctx context.Context) error

Close drains and shuts down the package-level client.

func FilterParams

func FilterParams(params map[string]any, filterKeys []string) map[string]any

FilterParams masks sensitive keys (case-insensitive substring match) in a params map. Nested maps are walked; arrays/slices are not recursed into.

func Flush

func Flush(ctx context.Context) error

Flush blocks until in-flight async deliveries finish.

func Init

func Init(cfg Config) error

Init configures the package-level default client. Subsequent calls replace the existing default. The previous client is closed in the background so in-flight deliveries still finish.

func NormalizeSQL added in v0.2.0

func NormalizeSQL(sql string) string

NormalizeSQL replaces string and numeric literals so equivalent queries aggregate into one APM row.

func RecordDatabase added in v0.2.0

func RecordDatabase(ctx context.Context, sql string, duration time.Duration)

RecordDatabase records a normalized database query against the active request or job transaction.

func RecordExternal added in v0.2.0

func RecordExternal(ctx context.Context, duration time.Duration)

RecordExternal records an outbound request span against the active request or job transaction.

func Recover

func Recover()

Recover wraps a deferred panic recovery: any non-nil recovered value is reported to Errorgap, then re-panicked so caller stacks observe it.

defer errorgap.Recover()

func TrackJob added in v0.2.0

func TrackJob(ctx context.Context, jobClass, queue string, operation func(context.Context) error) error

TrackJob runs operation as a background job using the package-level client.

Types

type Client

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

Client posts notices to an Errorgap server. Safe for concurrent use.

func NewClient

func NewClient(cfg Config) (*Client, error)

NewClient validates the config, applies defaults, and starts the async delivery goroutine. The caller should defer Close() to flush in-flight deliveries during shutdown.

func (*Client) Close

func (c *Client) Close(ctx context.Context) error

Close drains the queue and stops the background worker. Idempotent.

func (*Client) Config

func (c *Client) Config() Config

Config returns a copy of the client's effective configuration.

func (*Client) Flush

func (c *Client) Flush(ctx context.Context) error

Flush blocks until in-flight async deliveries complete.

func (*Client) Notify

func (c *Client) Notify(err error, opts ...NoticeOptions) Result

Notify queues an error for delivery and returns immediately when Async is true. When Async is false, it blocks until the HTTP call completes.

func (*Client) NotifyLog added in v0.2.0

func (c *Client) NotifyLog(message, level, source string) Result

NotifyLog sends one structured log event when log forwarding is enabled.

func (*Client) NotifyTransaction added in v0.2.0

func (c *Client) NotifyTransaction(transaction Transaction) Result

NotifyTransaction sends an APM transaction when APM is enabled and the configured sample rate accepts it.

func (*Client) TrackJob added in v0.2.0

func (c *Client) TrackJob(ctx context.Context, jobClass, queue string, operation func(context.Context) error) error

TrackJob runs operation as a background-job transaction. Returned errors are reported as notices and re-returned to the caller.

type Config

type Config struct {
	// Endpoint is the base URL of the Errorgap server (no trailing slash).
	// Defaults to $ERRORGAP_ENDPOINT or http://127.0.0.1:3030.
	Endpoint string

	// ProjectSlug is the slug used in the ingestion URL.
	// Defaults to $ERRORGAP_PROJECT_SLUG. Required.
	ProjectSlug string

	// ProjectID is optional and embedded in the notice payload.
	// Defaults to $ERRORGAP_PROJECT_ID.
	ProjectID string

	// APIKey is sent as the x-errorgap-project-key header.
	// Defaults to $ERRORGAP_API_KEY.
	APIKey string

	// Environment labels the deployment ("production", "staging").
	// Defaults to $ERRORGAP_ENVIRONMENT or "production".
	Environment string

	// Release is the application version embedded in the notice context.
	Release string

	// RootDirectory is used to classify application frames and make their
	// filenames relative. Defaults to $ERRORGAP_ROOT_DIRECTORY or the current
	// working directory.
	RootDirectory string

	// Async controls fire-and-forget delivery. Defaults to true.
	Async bool

	// Logger receives SDK warnings. Defaults to a discard logger.
	// Set to a no-op handler to silence.
	Logger *slog.Logger

	// FilterKeys overrides DefaultFilterKeys.
	FilterKeys []string

	// HTTPClient lets callers plug in a custom transport.
	// Defaults to a copy of http.DefaultClient with a 5s timeout.
	HTTPClient *http.Client

	// Timeout for the default HTTP client. Ignored if HTTPClient is set.
	Timeout time.Duration

	// QueueSize bounds the in-flight notice channel when Async is true.
	// Drops the new item when full. Defaults to 100.
	QueueSize int

	// CaptureGlobals installs a recover-and-log hook at the entry point.
	// (Go doesn't allow process-wide panic handlers; use the middleware
	// adapters instead.) Currently unused; reserved for future use.
	CaptureGlobals bool

	// APMEnabled controls transaction delivery. Defaults to
	// $ERRORGAP_APM_ENABLED or false.
	APMEnabled bool

	// APMSampleRate is the fraction of transactions to send, from 0 to 1.
	// Defaults to $ERRORGAP_APM_SAMPLE_RATE or 1.
	APMSampleRate float64

	// LogsEnabled controls structured log delivery. Defaults to
	// $ERRORGAP_LOGS_ENABLED or false.
	LogsEnabled bool

	// MinimumLogLevel is used by NewSlogHandler. Defaults to
	// $ERRORGAP_MINIMUM_LOG_LEVEL or slog.LevelWarn.
	MinimumLogLevel slog.Level
}

Config controls notifier behavior.

type ErrorEntry

type ErrorEntry struct {
	Type      string  `json:"type"`
	Message   string  `json:"message"`
	Backtrace []Frame `json:"backtrace"`
}

ErrorEntry is one entry in the notice's errors array.

type Frame

type Frame struct {
	File     string         `json:"file,omitempty"`
	Line     int            `json:"line,omitempty"`
	Function string         `json:"function,omitempty"`
	InApp    bool           `json:"in_app"`
	Index    int            `json:"index"`
	Source   *SourceExcerpt `json:"source,omitempty"`
}

Frame is a single backtrace entry in the notice envelope.

type LogEntry added in v0.2.0

type LogEntry struct {
	Message     string    `json:"message"`
	Level       string    `json:"level"`
	Source      string    `json:"source,omitempty"`
	Environment string    `json:"environment,omitempty"`
	OccurredAt  time.Time `json:"occurred_at"`
}

LogEntry is the wire payload accepted by the Errorgap logs endpoint.

type Notice

type Notice struct {
	ProjectID   string         `json:"project_id,omitempty"`
	ReceivedAt  string         `json:"received_at"`
	Errors      []ErrorEntry   `json:"errors"`
	Context     map[string]any `json:"context"`
	Environment map[string]any `json:"environment"`
	Session     map[string]any `json:"session"`
	Params      map[string]any `json:"params"`
}

Notice is the wire envelope POSTed to /api/projects/:slug/notices.

type NoticeOptions

type NoticeOptions struct {
	Context     map[string]any
	Environment map[string]any
	Session     map[string]any
	Params      map[string]any

	// Skip extra runtime.Caller frames when capturing the backtrace.
	// Useful when Notify is wrapped by another helper.
	BacktraceSkip int
}

NoticeOptions allows callers to add per-notice context.

type Result

type Result struct {
	Status int
	Body   []byte
	Err    error
	Queued bool
}

Result records the outcome of a single Notify call.

func Notify

func Notify(err error, opts ...NoticeOptions) Result

Notify sends an error via the package-level client. Returns an empty Result if Init has not been called.

func NotifyLog added in v0.2.0

func NotifyLog(message, level, source string) Result

NotifyLog sends a structured log event via the package-level client.

func NotifyTransaction added in v0.2.0

func NotifyTransaction(transaction Transaction) Result

NotifyTransaction sends an APM transaction via the package-level client.

func (Result) Success

func (r Result) Success() bool

Success reports whether delivery returned a 2xx.

type SlogHandler added in v0.2.0

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

SlogHandler forwards records at or above minimumLevel to Errorgap while preserving delivery to the wrapped handler. Pass nil for client to use the package-level client configured by Init.

func NewSlogHandler added in v0.2.0

func NewSlogHandler(next slog.Handler, client *Client, minimumLevel slog.Level) *SlogHandler

NewSlogHandler returns a standard library slog handler with Errorgap log forwarding. A nil wrapped handler discards local output.

func (*SlogHandler) Enabled added in v0.2.0

func (h *SlogHandler) Enabled(ctx context.Context, level slog.Level) bool

func (*SlogHandler) Handle added in v0.2.0

func (h *SlogHandler) Handle(ctx context.Context, record slog.Record) error

func (*SlogHandler) WithAttrs added in v0.2.0

func (h *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler

func (*SlogHandler) WithGroup added in v0.2.0

func (h *SlogHandler) WithGroup(name string) slog.Handler

type SourceExcerpt added in v0.2.0

type SourceExcerpt struct {
	StartLine int      `json:"start_line"`
	Lines     []string `json:"lines"`
}

SourceExcerpt contains source lines surrounding a backtrace frame.

type Span added in v0.2.0

type Span struct {
	Kind       string  `json:"kind"`
	SQL        string  `json:"sql,omitempty"`
	File       string  `json:"file,omitempty"`
	Line       int     `json:"line,omitempty"`
	Function   string  `json:"fn_name,omitempty"`
	DurationMS float64 `json:"duration_ms"`
}

Span is one timed operation within an APM transaction.

type SpanCollector added in v0.2.0

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

SpanCollector safely accumulates spans for a request or job.

func WithSpanCollector added in v0.2.0

func WithSpanCollector(ctx context.Context) (context.Context, *SpanCollector)

WithSpanCollector attaches a new span collector to ctx.

func (*SpanCollector) Spans added in v0.2.0

func (c *SpanCollector) Spans() []Span

Spans returns a snapshot of recorded spans.

type Transaction added in v0.2.0

type Transaction struct {
	Kind        string    `json:"kind"`
	Method      string    `json:"method,omitempty"`
	Path        string    `json:"path,omitempty"`
	PathRaw     string    `json:"path_raw,omitempty"`
	StatusCode  int       `json:"status_code,omitempty"`
	DurationMS  float64   `json:"duration_ms"`
	Environment string    `json:"environment,omitempty"`
	OccurredAt  time.Time `json:"occurred_at"`
	Spans       []Span    `json:"spans"`
	JobClass    string    `json:"job_class,omitempty"`
	Queue       string    `json:"queue,omitempty"`
}

Transaction is a web request or background job sent to the APM endpoint.

Directories

Path Synopsis
internal
testutil
Package testutil hosts test helpers shared across the package.
Package testutil hosts test helpers shared across the package.
Package stdhttp provides net/http error, request-context, and APM instrumentation for Errorgap.
Package stdhttp provides net/http error, request-context, and APM instrumentation for Errorgap.

Jump to

Keyboard shortcuts

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