pagerless

package module
v0.2.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, deploys and scheduled-job runs from a Go service to Pagerless. The core package is standard library only; each framework adapter is its own module, so you only pull in the framework you already use.

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

The package name is pagerless. The fastest setup is npx pagerless connect in your repo: it finds your frameworks and Temporal schedules and adds the lines below for you.

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)
	if err := http.ListenAndServe(":8080", pagerless.Middleware(mux)); err != nil {
		pagerless.Fatal(err) // reports why it exited, then exits 1
	}
}

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.

Frameworks

Every adapter reports panics before your framework's recovery swallows them (it sits inside the recovery, reports, and re-panics, so the recovery still writes the 500), and names routes by their template, not the raw path. Each takes WithClient(c) to use a client other than the default one.

Framework Module Wiring
Gin github.com/lcbasu/pagerless-go/gin r.Use(pagerlessgin.Middleware()) after your recovery, before routes
Echo v4 github.com/lcbasu/pagerless-go/echo e.Use(pagerlessecho.Middleware()) after middleware.Recover()
Echo v5 github.com/lcbasu/pagerless-go/echov5 e.Use(pagerlessechov5.Middleware())
Fiber v2 github.com/lcbasu/pagerless-go/fiber app.Use(pagerlessfiber.Middleware()) after recover.New(), before routes
Fiber v3 github.com/lcbasu/pagerless-go/fiberv3 app.Use(pagerlessfiberv3.Middleware())
chi github.com/lcbasu/pagerless-go/chi r.Use(pagerlesschi.Middleware) after middleware.Recoverer
gorilla/mux github.com/lcbasu/pagerless-go/gorillamux r.Use(pagerlessmux.Middleware)
gRPC github.com/lcbasu/pagerless-go/grpc grpc.NewServer(pagerlessgrpc.ServerOptions()...), clients: pagerlessgrpc.DialOptions()...
Temporal github.com/lcbasu/pagerless-go/temporal worker.Options{Interceptors: []interceptor.WorkerInterceptor{pagerlesstemporal.New(c)}}, or on the client
go get github.com/lcbasu/pagerless-go/gin@v0.2.0
r := gin.New()
r.Use(gin.Logger(), myRecovery())
r.Use(pagerlessgin.Middleware()) // route "GET /orders/:id"
r.GET("/orders/:id", getOrder)

Gin and Fiber apply middleware only to routes registered after Use, so add it before your routes. Errors attached with c.Error(err) (Gin) or returned by a handler (Echo, Fiber) are reported when the response is a 5xx.

gRPC. Each method is a route (gRPC /shop.Orders/Get); status codes are recorded as their HTTP equivalents (Internal → 500, Unavailable → 503), so failure rates work as for HTTP. Server errors that mean a bug (Unknown, Internal, DataLoss) are reported. The client interceptors report outbound calls that failed on the far side or the network (Unavailable, DeadlineExceeded, Internal…), not answers like NotFound.

Temporal. The worker interceptor reports activity and workflow failures (named by an ApplicationError's type, e.g. CardDeclined) and panics. Every Temporal Schedule becomes a check-in: each scheduled run pings /v1/checkins/<schedule-id> when it ends, ok or failed, so a schedule that stops firing or keeps failing pages you once. With the client passed to New, each schedule's timing is read once and sent along, so Pagerless knows when the next run is due. Skip schedules your app creates per customer with pagerlesstemporal.SkipSchedules("agent-run-").

c, _ := client.Dial(client.Options{})
w := worker.New(c, "orders", worker.Options{
	Interceptors: []interceptor.WorkerInterceptor{pagerlesstemporal.New(c)},
})

Or install it once on the client: it then covers every worker made from that client, and learns each schedule's timing from the CreateSchedule calls the process makes. Use one place, not both.

c, err := client.Dial(client.Options{
	Interceptors: []interceptor.ClientInterceptor{pagerlesstemporal.New(nil)},
})

Another framework? Report panics from its recovery hook with pagerless.Recovered(v, r, route), or build on pagerless.CapturePanic, RecordRequest and MiddlewareRoutes.

Logs and exits

// Every slog record at Error level is also captured (its error attr names the problem).
slog.SetDefault(slog.New(pagerless.SlogHandler(slog.NewJSONHandler(os.Stderr, nil))))

// Where you'd call log.Fatal: reports why, delivers what's buffered, exits 1.
// Exits right after start count toward a crash loop in Pagerless.
pagerless.Fatal(err)

Wrap a handler you build: setting a wrapper of Go's built-in default handler as the default makes the log package loop into itself.

Workers and jobs

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

// A scheduled job outside Temporal: report each run.
err := pagerless.Monitor(ctx, "nightly-backup", pagerless.Schedule{Cron: "30 3 * * *"}, backup)

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.

Testing

github.com/lcbasu/pagerless-go/pagerlesstest is a fake ingest: point a client at it with pagerless.WithEndpoint(srv.URL()), run your code, close the client and read srv.Errors(), srv.Requests() or srv.CheckIns().

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.

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 CapturePanic added in v0.2.0

func CapturePanic(v any, opts ...CaptureOption)

CapturePanic reports a recovered panic 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 CheckInSlug added in v0.2.0

func CheckInSlug(name string) string

CheckInSlug turns a job or schedule name into a check-in name the way `npx pagerless connect` does ("Nightly Report" → "nightly-report"), so code and connect agree on the name.

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 Fatal added in v0.2.0

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

Fatal reports err as the reason the process is exiting, delivers what's buffered (up to 3 s) and exits with status 1. Use it where you'd call log.Fatal: an exit right after start counts toward a crash loop in Pagerless, which a plain os.Exit never reports.

if err := srv.ListenAndServe(); err != nil {
	pagerless.Fatal(err)
}

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 MiddlewareRoutes added in v0.2.0

func MiddlewareRoutes(h http.Handler, route RouteFunc) http.Handler

MiddlewareRoutes is Middleware for routers that keep the matched route themselves: route names it. The adapters (github.com/lcbasu/pagerless-go/chi, .../gorillamux) are built on it.

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 RecordRequest added in v0.2.0

func RecordRequest(route string, status int, latency time.Duration)

RecordRequest records a request 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 RouteFor added in v0.2.0

func RouteFor(method, path string, status int) string

RouteFor names a request no router template matched: the method and the path with ID-like segments collapsed ("GET /files/:id"), or "GET (unmatched)" for a 404, so routes stay few.

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.

func SlogHandler added in v0.2.0

func SlogHandler(next slog.Handler) slog.Handler

SlogHandler wraps next so every record at Error level or above is also captured: the record's error attribute (any attr holding an error) gives the type, the message says what failed. All records still go to next unchanged. Uses the default client at log time.

slog.SetDefault(slog.New(pagerless.SlogHandler(slog.NewJSONHandler(os.Stderr, nil))))

Don't wrap slog.Default().Handler() while it is still Go's built-in default handler: setting that back as the default makes the log package loop into itself. Wrap the handler you build.

Types

type CaptureOption

type CaptureOption func(*wireEvent)

CaptureOption adds context to Capture.

func ErrorType added in v0.2.0

func ErrorType(name string) CaptureOption

ErrorType overrides the error's type name, which groups problems: use it when the type of err says less than a name the app already has (a Temporal ApplicationError's Type, say).

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). Several Tags options add up; a later key wins.

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 Default added in v0.2.0

func Default() *Client

Default returns the client Init started (nil before Init; every method is a no-op on nil). Adapters call it per request, so they can be installed before Init.

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) CapturePanic added in v0.2.0

func (c *Client) CapturePanic(v any, opts ...CaptureOption)

CapturePanic reports a value recovered from a panic. Call it inside the deferred function that recovered it, so the stack starts at the panic site. http.ErrAbortHandler (net/http's deliberate abort) is ignored. It only reports: re-panic or write the error yourself.

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) Fatal added in v0.2.0

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

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

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) MiddlewareRoutes added in v0.2.0

func (c *Client) MiddlewareRoutes(h http.Handler, route RouteFunc) http.Handler

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

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) RecordRequest added in v0.2.0

func (c *Client) RecordRequest(route string, status int, latency time.Duration)

RecordRequest feeds one finished request into the current window: route is the matched template ("GET /orders/:id"), status the response code. 5xx count as failures.

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.

func (*Client) SlogHandler added in v0.2.0

func (c *Client) SlogHandler(next slog.Handler) slog.Handler

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

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 RouteFunc added in v0.2.0

type RouteFunc func(r *http.Request) string

RouteFunc names the route template a request matched ("/orders/{id}"), read after the handler ran; "" when there is none. Routers that don't set r.Pattern (chi, gorilla/mux) supply one.

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
Package pagerlesstest is a fake Pagerless ingest for tests: point a client at it, run the code under test, close the client, and read what would have been sent.
Package pagerlesstest is a fake Pagerless ingest for tests: point a client at it, run the code under test, close the client, and read what would have been sent.
temporal module

Jump to

Keyboard shortcuts

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