Documentation
¶
Overview ¶
Package groundcover is the groundcover runtime SDK for Go. Its v1 scope is error tracking: it captures application errors and panics and ships them to groundcover without ever affecting the host application.
Safety guarantees ¶
- Every public entry point and background task is panic-guarded; internal faults are swallowed (recorded as a self-metric and a throttled log).
- Memory is strictly bounded by a ring buffer with both an item-count and a byte budget; on overflow the oldest events are dropped.
- Capturing an error never blocks on I/O: the caller enriches the event and performs a single non-blocking hand-off to a background worker that owns all network traffic.
Usage ¶
The package exposes a package-level default client configured with Init, plus an explicit Client (via New) for tests and multi-config setups.
if err := groundcover.Init(groundcover.Config{
DSN: "https://<ingestion-origin>",
IngestionKey: "<key>",
}); err != nil {
log.Fatal(err)
}
defer groundcover.Close(context.Background())
groundcover.CaptureError(ctx, err)
Errors are submitted as events; that this happens over the RUM ingestion endpoint in v1 is an implementation detail that may change without affecting callers (the SDK owns the path).
Instrumenting an existing service ¶
A step-by-step playbook (for humans and AI coding agents) lives in docs/llm-instrumentation-guide.md in the repository. The essentials:
- Call Init exactly once at startup; defer CloseTimeout (or Close) on shutdown.
- Capture at boundaries with CaptureError(ctx, err); keep returning the error as before — the SDK observes, it never alters control flow.
- Attach identity/attributes to the request context with SetUser / WithScope.
- Wrap HTTP servers with the nethttp middleware, a contrib middleware (gin, echo, fiber, fasthttp, iris, negroni), or the contrib/grpc interceptors, to capture panics and seed a per-request scope automatically.
- Pass the real error value (not a formatted string) so the type is extracted and grouping works; always thread the request context.
- Scrub PII/secrets in BeforeSend; pseudonymize identity with an IdentityHasher.
Index ¶
- Variables
- func CaptureError(ctx context.Context, err error, opts ...Option)
- func CaptureMessage(ctx context.Context, msg string, level Level, opts ...Option)
- func CaptureRecovered(ctx context.Context, recovered any, opts ...Option)
- func Close(ctx context.Context) error
- func CloseTimeout(d time.Duration) error
- func Flush(ctx context.Context) error
- func FlushTimeout(d time.Duration) error
- func Init(cfg Config) error
- func Recover(ctx context.Context)
- func SetUser(ctx context.Context, u User) context.Context
- func Version() string
- func WithIsolatedScope(ctx context.Context) context.Context
- func WithScope(ctx context.Context, fn func(*Scope)) context.Context
- type Attributes
- type Client
- func (c *Client) CaptureError(ctx context.Context, err error, opts ...Option)
- func (c *Client) CaptureMessage(ctx context.Context, msg string, level Level, opts ...Option)
- func (c *Client) CaptureRecovered(ctx context.Context, recovered any, opts ...Option)
- func (c *Client) Close(ctx context.Context) error
- func (c *Client) CloseTimeout(d time.Duration) error
- func (c *Client) Flush(ctx context.Context) error
- func (c *Client) FlushTimeout(d time.Duration) error
- func (c *Client) Recover(ctx context.Context)
- func (c *Client) SetUser(ctx context.Context, u User) context.Context
- func (c *Client) Stats() Stats
- func (c *Client) WithIsolatedScope(ctx context.Context) context.Context
- func (c *Client) WithScope(ctx context.Context, fn func(*Scope)) context.Context
- type Config
- type Event
- type Frame
- type HMACHasher
- type IdentityHasher
- type Level
- type Logger
- type LoggerFunc
- type Option
- type Scope
- type Service
- type Stats
- type User
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrMissingDSN = errors.New("groundcover: DSN is required (set Config.DSN) unless Disabled is true")
ErrMissingDSN is returned by Init/New when no DSN is configured on an enabled client.
Functions ¶
func CaptureError ¶
CaptureError captures err using the package-level client.
Example ¶
package main
import (
"context"
"errors"
"fmt"
groundcover "github.com/groundcover-com/groundcover-go"
)
func main() {
_ = groundcover.Init(groundcover.Config{Disabled: true})
if err := errors.New("charge failed"); err != nil {
groundcover.CaptureError(context.Background(), err)
}
fmt.Println("captured")
}
Output: captured
func CaptureMessage ¶
CaptureMessage captures a non-error notice using the package-level client.
Example ¶
package main
import (
"context"
"fmt"
groundcover "github.com/groundcover-com/groundcover-go"
)
func main() {
_ = groundcover.Init(groundcover.Config{Disabled: true})
groundcover.CaptureMessage(context.Background(), "falling back to stale cache", groundcover.LevelWarning)
fmt.Println("noticed")
}
Output: noticed
func CaptureRecovered ¶
CaptureRecovered captures an already-recovered panic value without re-raising, using the package-level client.
func CloseTimeout ¶
CloseTimeout closes the package-level client with a context bounded by d.
func Flush ¶
Flush flushes the package-level client.
Example ¶
package main
import (
"context"
"fmt"
"time"
groundcover "github.com/groundcover-com/groundcover-go"
)
func main() {
_ = groundcover.Init(groundcover.Config{Disabled: true})
// Short-lived job: bound the flush before exit.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = groundcover.Flush(ctx)
fmt.Println("flushed")
}
Output: flushed
func FlushTimeout ¶
FlushTimeout flushes the package-level client with a context bounded by d.
func Init ¶
Init configures the package-level default client. Calling it again replaces the previous default and tears the old one down in the background (a bounded, best-effort Close) so its worker goroutine does not leak. Init never blocks on that teardown.
Example ¶
package main
import (
"context"
"fmt"
groundcover "github.com/groundcover-com/groundcover-go"
)
func main() {
// Zero-config in-cluster: workload/env/release/pod come from the Downward
// API environment. Here we disable the client for a hermetic example.
if err := groundcover.Init(groundcover.Config{Disabled: true}); err != nil {
panic(err)
}
defer func() { _ = groundcover.Close(context.Background()) }()
fmt.Println("initialized")
}
Output: initialized
func Recover ¶
Recover captures a panic (then re-raises it) using the package-level client. Use it deferred: defer groundcover.Recover(ctx).
func SetUser ¶
SetUser returns a context with the identity set, using the package-level client.
Example ¶
package main
import (
"context"
"errors"
"fmt"
groundcover "github.com/groundcover-com/groundcover-go"
)
func main() {
_ = groundcover.Init(groundcover.Config{Disabled: true})
ctx := groundcover.SetUser(context.Background(), groundcover.User{ID: "u-123", Organization: "acme"})
groundcover.CaptureError(ctx, errors.New("boom"), groundcover.WithAttributes(groundcover.Attributes{
"order_id": "o-9",
"amount": 42.5,
"is_retry": true,
}))
fmt.Println("captured with user and attributes")
}
Output: captured with user and attributes
func Version ¶
func Version() string
Version returns the SDK version reported in telemetry (telemetry.sdk.version) and the User-Agent header. It is resolved from the module's build metadata so it matches the published git tag without a manual bump on each release.
When the SDK is consumed as a tagged module dependency, Version returns the tag (for example "0.1.1"). When running directly from source (go test / go run inside this repo) build metadata has no tag and Version returns "dev". The resolution is cheap and safe to call from hot paths; callers that want to avoid repeated work may cache the result themselves.
func WithIsolatedScope ¶
WithIsolatedScope returns a context with a fresh, isolated copy of the current scope, using the package-level client. Middleware uses it at request boundaries.
Types ¶
type Attributes ¶
Attributes is a bag of custom, caller-supplied data attached to an event. Nested maps and slices are allowed. Values are kept with their natural JSON type so the backend can route them (strings/bools into string columns, numbers into numeric columns). The gc.* key namespace is reserved for the SDK.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is an explicit SDK client. Most callers use the package-level API (Init + CaptureError); New is for tests and multi-config setups.
Example ¶
package main
import (
"context"
"errors"
"fmt"
groundcover "github.com/groundcover-com/groundcover-go"
)
func main() {
// An explicit client is useful for tests and multi-config setups.
client, err := groundcover.New(groundcover.Config{Disabled: true})
if err != nil {
panic(err)
}
defer func() { _ = client.Close(context.Background()) }()
client.CaptureError(context.Background(), errors.New("explicit client error"))
fmt.Println(client.Stats().Captured)
}
Output: 0
func (*Client) CaptureError ¶
CaptureError captures err (handled by default) and enqueues it for delivery. It never blocks on I/O and never affects control flow.
func (*Client) CaptureMessage ¶
CaptureMessage captures a non-error notice at the given level.
func (*Client) CaptureRecovered ¶
CaptureRecovered captures an already-recovered panic value without re-raising. It is used by middleware that owns the response lifecycle.
func (*Client) CloseTimeout ¶
CloseTimeout is convenience sugar for Close with a fresh context bounded by d. The context-based Close remains the primitive.
func (*Client) FlushTimeout ¶
FlushTimeout is convenience sugar for Flush with a fresh context bounded by d. The context-based Flush remains the primitive for callers that need cancellation or composition with an existing context.
func (*Client) Recover ¶
Recover captures a panic in the current goroutine (as an unhandled error), performs a short best-effort flush, and re-raises the panic. Use it deferred:
defer client.Recover(ctx)
func (*Client) SetUser ¶
SetUser sets the identity on the request scope and returns the context. If the context already carries a scope (e.g. one installed by middleware) it is mutated in place, so the change is visible to whoever else holds that context.
func (*Client) WithIsolatedScope ¶
WithIsolatedScope returns a context carrying a fresh, isolated copy of the current scope. Middleware uses it at the start of each request so per-request identity/attributes set by handlers never leak across requests.
type Config ¶
type Config struct {
// DSN is the base ingestion origin (e.g. https://<tenant>.platform.grcv.io
// for BYOC). The SDK owns the path and appends /json/rum. A missing scheme
// defaults to https. Find it in the groundcover UI under
// Settings -> Access -> Ingestion Keys.
DSN string
// IngestionKey is the write key sent as "Authorization: Bearer <key>".
// It is REQUIRED when posting directly to a cloud/BYOC ingestion origin
// (omitting it yields silent 401s, since capture never errors at the call
// site). It is optional ONLY when DSN points at a local in-cluster sensor,
// which needs no auth. Use a RUM-type ingestion key.
IngestionKey string
// ServiceName sets the service identity (OpenTelemetry service.name; the
// "service" in Datadog/OTel terms). Auto-detected from OTEL_SERVICE_NAME /
// GC_SERVICE_NAME when unset. In Kubernetes you can usually leave this empty
// and let the groundcover sensor enrich pod -> workload server-side.
ServiceName string
// Env overrides deployment.environment.name (env: GC_ENV / DEPLOYMENT_ENVIRONMENT).
Env string
// Release overrides service.version (env: GC_RELEASE).
Release string
// MaxQueue bounds the pending buffer by item count (default 10000).
MaxQueue int
// MaxBytes bounds the pending buffer by estimated bytes (default 32 MiB).
MaxBytes int
// BatchSize is the maximum number of events per request (default 250).
BatchSize int
// FlushInterval is the periodic flush cadence (default 5s).
FlushInterval time.Duration
// MaxBatchBytes is the maximum estimated request size (default 512 KiB).
MaxBatchBytes int
// MaxRetries is the retry attempt cap after the first try (default 3).
MaxRetries int
// RetryMax caps exponential backoff (default 30s).
RetryMax time.Duration
// RateLimitBackoff is the minimum 429 backoff (default 30s).
RateLimitBackoff time.Duration
// StackDepthMax caps captured stack frames (default 128).
StackDepthMax int
// OnDrop observes dropped events (also recorded as a self-metric).
OnDrop func(n int)
// BeforeSend can scrub or sample an event; returning nil drops it.
BeforeSend func(*Event) *Event
// Hasher optionally pseudonymizes user.id / user.email via keyed HMAC.
Hasher IdentityHasher
// Logger receives throttled SDK-internal logs.
Logger Logger
// Disabled makes the client a no-op with near-zero overhead.
Disabled bool
// Debug prints each captured event to stderr in a compact, readable form
// (after scrubbing/hashing), for local development. It does not affect
// delivery. Leave off in production.
Debug bool
// HTTPClient overrides the HTTP client used for delivery. Primarily a test
// seam; nil uses a client with sensible timeouts.
HTTPClient *http.Client
}
Config configures a Client. The zero value is only valid when Disabled is true.
Most callers set only DSN and IngestionKey; every other field has a sensible default and is a tuning knob you can ignore until you need it.
type Event ¶
type Event struct {
// ID is a per-occurrence identifier used for de-duplication.
ID string
// Timestamp is when the event was captured.
Timestamp time.Time
// Type is the event type (always "exception" in v1).
Type string
// Level is the severity.
Level Level
// User is the associated identity, if any.
User User
// SessionID is an optional session identifier (usually empty for backends).
SessionID string
// AnonymousID is a caller-supplied pre-auth identifier (no PII by construction).
AnonymousID string
// Service identifies the instrumented service.
Service Service
// ErrorType is the innermost meaningful error type.
ErrorType string
// ErrorMessage is the error message.
ErrorMessage string
// ErrorHandled reports whether the error was handled (vs. an unrecovered panic).
ErrorHandled bool
// Stacktrace is the resolved frames, innermost first.
Stacktrace []Frame
// Fingerprint is the client-computed grouping key (opaque hash).
Fingerprint string
// Title is the human-readable display label (e.g. "*net.OpError: connection
// refused"). It is derived from ErrorType and ErrorMessage when left empty.
// Unlike Fingerprint, it is for display, not grouping.
Title string
// Attributes is the custom data bag.
Attributes Attributes
// Resource is the detected resource/spine attributes (telemetry.sdk.*, k8s.*, ...).
Resource map[string]string
// contains filtered or unexported fields
}
Event is the internal representation of a captured occurrence. Public callers never build an Event directly; Options mutate it before enqueue. It is exported only so that Option and BeforeSend can operate on it.
type Frame ¶
type Frame struct {
// Function is the fully-qualified function name (code.function.name).
Function string
// File is the source file path (code.file.path).
File string
// Line is the source line number (code.line.number).
Line int
// InApp reports whether the frame belongs to the application (under the main
// module path, excluding vendored code).
InApp bool
}
Frame is a single resolved stack frame. Field names follow OTel code.* semantics internally; they are mapped to the wire representation at encode time.
type HMACHasher ¶
type HMACHasher struct {
// contains filtered or unexported fields
}
HMACHasher is a keyed HMAC-SHA256 IdentityHasher. The zero value hashes with an empty key; prefer NewHMACHasher.
func NewHMACHasher ¶
func NewHMACHasher(key []byte) *HMACHasher
NewHMACHasher returns an HMACHasher keyed with the given secret.
func (*HMACHasher) HashIdentity ¶
func (h *HMACHasher) HashIdentity(value string) string
HashIdentity returns the hex-encoded HMAC-SHA256 of value, or "" for "".
type IdentityHasher ¶
type IdentityHasher interface {
// HashIdentity returns the pseudonymized form of value. An empty input must
// map to an empty output.
HashIdentity(value string) string
}
IdentityHasher pseudonymizes identity fields (user.id / user.email) at the SDK boundary. Implementations should use a keyed function (e.g. HMAC), not a plain hash, so values cannot be trivially reversed via a dictionary.
type Level ¶
type Level string
Level is the severity of a captured event. It follows OTel SeverityText conventions and maps to a numeric SeverityNumber on the wire.
const ( // LevelDebug is fine-grained diagnostic information. LevelDebug Level = "debug" // LevelInfo is informational. LevelInfo Level = "info" // LevelWarning indicates a recoverable problem or notable condition. LevelWarning Level = "warning" // LevelError indicates an error; the default for CaptureError. LevelError Level = "error" // LevelFatal indicates an unrecoverable error. LevelFatal Level = "fatal" )
Supported severity levels.
type Logger ¶
Logger is the pluggable sink for SDK-internal logs. Implementations must never panic; a panicking logger is contained by the SDK. suppressed reports how many identical lines were throttled since the last emitted line.
type LoggerFunc ¶
LoggerFunc adapts a function to a Logger.
type Option ¶
type Option func(*Event)
Option mutates an Event before it is enqueued. Options are applied last and therefore take precedence over global defaults and the request scope.
func WithAttributes ¶
func WithAttributes(a Attributes) Option
WithAttributes merges the given attributes into the event (per-call override).
func WithFingerprint ¶
WithFingerprint overrides the client-computed grouping fingerprint.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope holds request-level data merged into every event captured with the owning context. It sits between global defaults and per-call options in the merge precedence.
A Scope is mutable and safe for concurrent use. Middleware installs one fresh, isolated Scope per request (see WithIsolatedScope); handlers then mutate that same Scope through SetUser / WithScope, and the captured event observes those changes without the handler having to thread a new context back.
func (*Scope) SetAnonymousID ¶
SetAnonymousID sets the pre-auth anonymous identifier for events in this scope.
func (*Scope) SetAttribute ¶
SetAttribute sets a single attribute on the scope.
func (*Scope) SetAttributes ¶
func (s *Scope) SetAttributes(a Attributes)
SetAttributes merges attributes into the scope.
func (*Scope) SetFingerprint ¶
SetFingerprint overrides the grouping fingerprint for events in this scope.
func (*Scope) SetLevel ¶
SetLevel sets the default severity for events in this scope. Note that the scope level never downgrades an intrinsically-fatal event (a recovered panic).
func (*Scope) SetSessionID ¶
SetSessionID sets the session identifier for events in this scope.
type Service ¶
type Service struct {
// Name is service.name.
Name string
// Version is service.version.
Version string
}
Service identifies the instrumented service on the wire.
type Stats ¶
type Stats struct {
// Captured is the number of events accepted into the pipeline.
Captured int64
// Sent is the number of events successfully delivered.
Sent int64
// DroppedOverflow is the number of events evicted by buffer overflow.
DroppedOverflow int64
// DroppedSendExhausted is the number of events dropped after delivery failed.
DroppedSendExhausted int64
// DroppedBeforeSend is the number of events dropped by BeforeSend.
DroppedBeforeSend int64
// Retries is the number of delivery retry attempts.
Retries int64
// RateLimited is the number of 429 responses observed.
RateLimited int64
// PanicsRecovered is the number of recovered SDK-internal panics.
PanicsRecovered int64
// ConfigReloads is the number of configuration swaps.
ConfigReloads int64
// QueuePendingItems is the current number of buffered events.
QueuePendingItems int64
// QueuePendingBytes is the current estimated size of buffered events.
QueuePendingBytes int64
// SubsystemsDisabled is the number of background subsystems self-disabled
// after a panic.
SubsystemsDisabled int64
}
Stats is a point-in-time snapshot of the SDK's self-observability counters. Field names mirror the exported Prometheus metric names.
func GlobalStats ¶
func GlobalStats() Stats
GlobalStats returns the package-level client's self-metrics snapshot. (The per-client accessor is the Client.Stats method; this avoids colliding with the Stats type at package scope.)
type User ¶
type User struct {
// ID is a stable user identifier.
ID string
// Email is the user's email address.
Email string
// Name is a human-readable user name.
Name string
// Organization is the B2B group/tenant key.
Organization string
}
User identifies the principal associated with an event. Organization is the B2B group key used for attribution; it has no OTel convention and is a groundcover extension.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
contrib
|
|
|
gin
module
|
|
|
internal
|
|
|
logthrottle
Package logthrottle implements a self-throttling log front-end.
|
Package logthrottle implements a self-throttling log front-end. |
|
ringbuf
Package ringbuf implements the SDK's bounded pending buffer.
|
Package ringbuf implements the SDK's bounded pending buffer. |
|
safeguard
Package safeguard provides panic guards used at every SDK boundary and around every spawned goroutine.
|
Package safeguard provides panic guards used at every SDK boundary and around every spawned goroutine. |
|
selfmetrics
Package selfmetrics holds the SDK's self-observability counters.
|
Package selfmetrics holds the SDK's self-observability counters. |
|
testutil
Package testutil provides shared test seams: an injectable mock sender and a controllable clock.
|
Package testutil provides shared test seams: an injectable mock sender and a controllable clock. |
|
transport
Package transport owns all network I/O for the SDK: a single HTTP sender that POSTs gzipped JSON batches, and a single background worker that batches, retries, and flushes from the bounded buffer.
|
Package transport owns all network I/O for the SDK: a single HTTP sender that POSTs gzipped JSON batches, and a single background worker that batches, retries, and flushes from the bounded buffer. |
|
Package nethttp provides net/http middleware that recovers panics, captures them through groundcover, and seeds a fresh request scope into the context.
|
Package nethttp provides net/http middleware that recovers panics, captures them through groundcover, and seeds a fresh request scope into the context. |
|
prometheus
module
|