sleuth

sleuth registers deployments, manual changes, and custom incident or metric impacts with the Sleuth.io API, validating each payload before sending.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/sleuth"

Write-side client for the Sleuth.io API, covering four registration workflows.

c, err := sleuth.New(addr, org, apiKey)

err = c.SendDeployRegistration(ctx, &sleuth.DeployRegistrationRequest{...})
err = c.SendManualChange(ctx, &sleuth.ManualChangeRequest{...})
err = c.SendCustomIncidentImpactRegistration(ctx, &sleuth.CustomIncidentImpactRegistrationRequest{...})
err = c.SendCustomMetricImpactRegistration(ctx, &sleuth.CustomMetricImpactRegistrationRequest{...})

Four workflows, and what separates them

Deploy registration reports a code deployment: the ordinary case, where a pipeline ships a build.

Manual change reports something that changed production without going through a deployment. A feature flag flipped, a configuration value edited, a database migration run by hand. These cause incidents at a rate comparable to deployments and are invisible to a tool that only watches your pipeline, which makes change failure rate look better than it is.

Custom incident impact reports an incident from a source Sleuth does not integrate with directly.

Custom metric impact feeds an arbitrary metric, for tracking something specific to your system against deployments.

The org slug is a constructor argument because Sleuth’s URLs are organisation-scoped. New builds the endpoint templates once from the base URL and slug, so each send is a substitution rather than string assembly per call.

Validation before the network call

Every request struct is validated through validator before anything is sent.

A metrics ingestion endpoint accepts a payload and returns success; whether the payload was usable surfaces later as a gap in a dashboard. Local validation turns that into an error at the call site, where you can see which request was malformed.

API keys stay out of error text

Like slack, this client wraps its errors so the configured address is replaced with REDACTED in the message, while Unwrap returns the original error so errors.Is and errors.As keep working.

Go’s net/http puts the request URL into transport error messages. Logging a raw transport error from an authenticated client is a routine way for credentials and internal endpoints to reach a log aggregator.

Options

OptionEffect
WithTimeoutrequest timeout
WithPingTimeouthealth-check timeout
WithPingURLhealth-check target
WithHTTPClientinject a transport or a mock
WithRetryAttemptsretries per send

Wiring

Each send is a synchronous call to an external service from the path that just deployed or just detected an incident. Register deployments from the pipeline rather than from service startup, and register incidents after your own system of record has the incident, so a Sleuth outage costs a metric rather than the event itself.

WithTimeout and WithRetryAttempts bound how long a failing send holds the caller.

Example

c, err := sleuth.New("https://app.sleuth.io", "my-org", "api-key")
if err != nil {
    log.Fatal(err)
}

err = c.HealthCheck(context.Background())

fmt.Println(err)

Full source is in example_sleuth_test.go. More runnable examples are on pkg.go.dev.

Dependencies

Reaches the validation stack through validator.