pagerless

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 29 Imported by: 0

README

Pagerless Go SDK

Reports errors, request health, heartbeats and deploys from a Go service to Pagerless. Standard library only.

go get github.com/lcbasu/pagerless-go@v0.1.0

The package name is pagerless.

Setup

Set PAGERLESS_KEY in the service's environment (an ECS task secret, for example), then:

import (
	"context"
	"net/http"

	pagerless "github.com/lcbasu/pagerless-go"
)

func main() {
	pagerless.Init("") // reads PAGERLESS_KEY; without it every call is a no-op
	defer pagerless.Close(context.Background())

	mux := http.NewServeMux()
	mux.HandleFunc("GET /orders/{id}", getOrder)
	http.ListenAndServe(":8080", pagerless.Middleware(mux))
}

Middleware records status and latency per route (the Go 1.22+ pattern, e.g. GET /orders/{id}) and reports panics, then re-panics so your own recovery still runs.

Gin

Gin's Recovery catches panics before they reach net/http middleware, so report them from its hook with Recovered, and wrap the engine for status and latency:

r := gin.New()
r.Use(gin.Logger(), gin.CustomRecovery(func(c *gin.Context, v any) {
	pagerless.Recovered(v, c.Request, c.FullPath()) // route "GET /orders/:id"
	c.AbortWithStatus(http.StatusInternalServerError)
}))
r.GET("/orders/:id", getOrder)
http.ListenAndServe(":8080", pagerless.Middleware(r))

The stack starts at the panic, not at the hook, so the same panic groups as one problem. Echo's and Chi's recoverers work the same way: call Recovered from their hook.

Workers and jobs

if err := syncOrders(ctx); err != nil {
	pagerless.Capture(err, pagerless.Route("job sync-orders"), pagerless.Tags(map[string]string{"shop": id}))
}

go func() {
	defer func() {
		if v := recover(); v != nil {
			pagerless.Recovered(v, nil, "job sync-orders")
			panic(v) // or log and carry on
		}
	}()
	runJob()
}()

pagerless.Event("Refund queue backed up", pagerless.Wake, nil) // Wake, Tell or Log

Call pagerless.Close(ctx) on shutdown (SIGTERM on ECS) so the last window is sent.

Settings

Variable Option Default
PAGERLESS_KEY Init(key) none: reporting is off, ErrNoKey
PAGERLESS_SERVICE WithService executable name
PAGERLESS_ENV WithEnv production
PAGERLESS_RELEASE WithRelease VCS revision from the build info
PAGERLESS_ENDPOINT WithEndpoint https://events.pagerless.dev/v1/events

Secrets and personal data are scrubbed before anything leaves the process. The SDK never blocks the caller and never panics into your code.

Releasing (maintainers)

Go sub-module tags carry the directory: sdk/go/v0.1.0, not v0.1.0. From a clean checkout of the commit to release (normally main after merge):

make tag-sdk-go VERSION=v0.1.0
git push origin sdk/go/v0.1.0

License

MIT

Documentation

Overview

Package pagerless reports errors, request health, heartbeats and deploys from a Go service to Pagerless.

func main() {
	pagerless.Init("") // reads PAGERLESS_KEY
	defer pagerless.Close(context.Background())
	mux := http.NewServeMux()
	mux.HandleFunc("GET /orders/{id}", getOrder)
	http.ListenAndServe(":8080", pagerless.Middleware(mux))
}

The SDK groups repeats in-process (a new problem is sent at once, repeats are counted per 10 s window), scrubs secrets and personal data before anything leaves the process, never blocks the caller, and never panics into user code. When the endpoint is down it backs off and drops the oldest data first once its bounded buffer is full.

Index

Constants

View Source
const DefaultEndpoint = "https://events.pagerless.dev/v1/events" // the ingest API's public address

DefaultEndpoint is the ingest event API.

Variables

View Source
var ErrBadSlug = errors.New("pagerless: check-in name must be 1-64 letters, digits, '.', '_' or '-'")

ErrBadSlug is returned for a job name that isn't 1-64 letters, digits, '.', '_' or '-'.

View Source
var ErrNoKey = errors.New("pagerless: no key (pass one to Init or set PAGERLESS_KEY); reporting is off")

ErrNoKey is returned by Init and New when no key is given and PAGERLESS_KEY is unset. The returned client is still usable; it records nothing.

Functions

func Capture

func Capture(err error, opts ...CaptureOption)

Capture reports err on the default client.

func CheckIn

func CheckIn(ctx context.Context, slug string, opts ...CheckInOption) error

CheckIn reports one run on the default client.

func Close

func Close(ctx context.Context) error

Close flushes and stops the default client.

func Deploy

func Deploy(release string)

Deploy sends a deploy marker on the default client.

func Event

func Event(title string, severity Severity, tags map[string]string)

Event reports a custom event on the default client.

func Init

func Init(key string, opts ...Option) error

Init starts the default client. key falls back to PAGERLESS_KEY and the endpoint to PAGERLESS_ENDPOINT. Without a key, reporting is off and ErrNoKey is returned; the rest of the API keeps working as a no-op. Calling Init again replaces (and closes) the previous client.

func Middleware

func Middleware(h http.Handler) http.Handler

Middleware wraps h to record status and latency per route and to report panics. Wrap the outermost handler (usually your *http.ServeMux) so the matched Go 1.22+ pattern, e.g. "GET /orders/{id}", is the route. A panic is reported and then re-panicked, so net/http (or your own recovery) still handles it as before. Uses the default client at request time, so it can be installed before Init.

func Monitor

func Monitor(ctx context.Context, slug string, s Schedule, fn func(context.Context) error) error

Monitor runs fn and reports the run on the default client.

func Recovered

func Recovered(v any, r *http.Request, route string)

Recovered reports a panic that a framework's own recovery already caught. Gin's Recovery, Echo's Recover and Chi's Recoverer stop the panic before it reaches Middleware, so call this from their hook. route is the framework's matched pattern if it has one (Gin: c.FullPath(), e.g. "/orders/:id"); "" falls back to the request path with IDs collapsed. It only reports: writing the 500 stays with the framework. Call it inside the recovery hook (while the panic is being handled) so the stack points at the panic site.

r.Use(gin.CustomRecovery(func(c *gin.Context, v any) {
	pagerless.Recovered(v, c.Request, c.FullPath())
	c.AbortWithStatus(http.StatusInternalServerError)
}))

func Scrub

func Scrub(s string) string

Scrub removes secrets and personal data from s: URL credentials, JWTs, bearer tokens, vendor API keys, secret key=value pairs, emails, card numbers (Luhn-checked) and phone numbers.

Types

type CaptureOption

type CaptureOption func(*wireEvent)

CaptureOption adds context to Capture.

func Route

func Route(route string) CaptureOption

Route sets the route the error happened on, e.g. "POST /checkout".

func Status

func Status(code int) CaptureOption

Status sets the HTTP status the error produced.

func Tags

func Tags(tags map[string]string) CaptureOption

Tags attaches tags (scrubbed; values under secret-looking keys are redacted).

type CheckInOption

type CheckInOption func(*checkInBody)

CheckInOption sets a field of one check-in.

func Failed

func Failed(reason string) CheckInOption

Failed marks the run failed, with the reason (scrubbed before sending).

func RunID

func RunID(id string) CheckInOption

RunID is an idempotency key for this run: a retry with the same id counts once.

func Took

func Took(d time.Duration) CheckInOption

Took records how long the run took.

func WithSchedule

func WithSchedule(s Schedule) CheckInOption

WithSchedule creates the check-in on first use and keeps it in step (unless edited in the app).

type CheckInStatus

type CheckInStatus string

CheckInStatus is the outcome of one run.

const (
	// CheckInOK is a run that finished fine.
	CheckInOK CheckInStatus = "ok"
	// CheckInFailed is a run that failed; Pagerless opens a problem at once.
	CheckInFailed CheckInStatus = "failed"
)

type Client

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

Client reports to one Pagerless project. Use the package-level functions unless you need more than one client. All methods are safe for concurrent use and on a nil *Client.

func New

func New(key string, opts ...Option) (*Client, error)

New builds a client. With no key it returns a disabled client and ErrNoKey.

func (*Client) Capture

func (c *Client) Capture(err error, opts ...CaptureOption)

Capture reports err. Repeats of the same problem (type + call site + route) are counted, not re-sent. Safe to call with a nil error (no-op).

func (*Client) CheckIn

func (c *Client) CheckIn(ctx context.Context, slug string, opts ...CheckInOption) (err error)

CheckIn reports one run of a scheduled job, at once (not batched: jobs often exit right after). It returns ErrNoKey when reporting is off and never panics.

func (*Client) Close

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

Close flushes the current window and tries to deliver everything buffered until ctx ends. Events recorded after Close are ignored.

func (*Client) Deploy

func (c *Client) Deploy(release string)

Deploy sends a deploy marker for release. The ID is derived from service, env and release, so every replica and restart of one release collapses to a single marker at ingest.

func (*Client) Event

func (c *Client) Event(title string, severity Severity, tags map[string]string)

Event reports a custom event. severity is wake, tell or log; an invalid severity is sent as tell (unsure means tell, never log).

func (*Client) Middleware

func (c *Client) Middleware(h http.Handler) http.Handler

Middleware is the per-client version of the package-level Middleware.

func (*Client) Monitor

func (c *Client) Monitor(ctx context.Context, slug string, s Schedule, fn func(context.Context) error) error

Monitor runs fn and reports the run: ok with its run time, or failed with fn's error (or panic, which is re-raised after reporting). It returns fn's error; a check-in that couldn't be sent is logged, never returned, so it can't hide the job's own result.

func (*Client) Recovered

func (c *Client) Recovered(v any, r *http.Request, route string)

Recovered is the per-client version of the package-level Recovered.

type Option

type Option func(*config)

Option configures Init / New.

func WithEndpoint

func WithEndpoint(url string) Option

WithEndpoint overrides the ingest URL (default PAGERLESS_ENDPOINT, then DefaultEndpoint).

func WithEnv

func WithEnv(env string) Option

WithEnv sets the environment (default PAGERLESS_ENV, then "production").

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sets the HTTP client used to send batches.

func WithHeartbeat

func WithHeartbeat(d time.Duration) Option

WithHeartbeat sets the heartbeat interval (default 60 s).

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger receives SDK diagnostics (send failures, drops). Default: discarded.

func WithMaxQueue

func WithMaxQueue(n int) Option

WithMaxQueue bounds how many unsent events are buffered (default 2000).

func WithRelease

func WithRelease(release string) Option

WithRelease sets the release (default PAGERLESS_RELEASE, then the VCS revision in the build info). A known release is sent once as a deploy marker at Init.

func WithService

func WithService(name string) Option

WithService names the service (default PAGERLESS_SERVICE, then the executable name).

func WithWindow

func WithWindow(d time.Duration) Option

WithWindow sets the grouping window (default 10 s).

type Schedule

type Schedule struct {
	// Name is the plain-words name for alerts ("Nightly backup"); default from the slug.
	Name string
	// Every is the time between runs (at least a minute).
	Every time.Duration
	// Cron is a 5-field cron expression ("30 3 * * *") or a macro (@daily).
	Cron string
	// TZ is the IANA zone for Cron; default the account's time zone, else UTC.
	TZ string
	// Grace is how late a run may be before it counts as missed; default a tenth of the gap,
	// 5 minutes to 1 hour.
	Grace time.Duration
	// Level is Tell (default) or Wake. Log is treated as Tell: a job nobody hears about isn't watched.
	Level Severity
}

Schedule says when a job should report. Set exactly one of Every or Cron.

type Severity

type Severity string

Severity is how loud a custom event is (wire Level).

const (
	// Wake pages a person now.
	Wake Severity = "wake"
	// Tell notifies without waking anyone.
	Tell Severity = "tell"
	// Log records the event only.
	Log Severity = "log"
)

Directories

Path Synopsis
gin module
temporal module

Jump to

Keyboard shortcuts

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