autotix

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 21 Imported by: 0

README

Autotix Go SDK

Ship Go errors and panics to Autotix. Autotix turns them into Jira or GitHub tickets and, when enabled, opens AI-generated fix PRs.

  • Zero dependencies. Standard library only.
  • Never blocks, never throws. Delivery runs in the background on a bounded queue. A failed request is logged and counted, not returned.
  • Panics covered. defer client.Recover() for goroutines, client.Middleware for net/http.
  • PR previews. Errors inside a Tugboat preview are posted to the pull request instead of opening a ticket.

Install

go get github.com/HelloWorldDevs/autotix-go

Requires Go 1.22 or newer.

Quick start

import autotix "github.com/HelloWorldDevs/autotix-go"

client, err := autotix.New(os.Getenv("AUTOTIX_WEBHOOK_TOKEN"),
    autotix.WithEnvironment("production"),
    autotix.WithRelease("v1.4.2"),
    autotix.WithProject("WEB"), // route errors here; see "How errors reach a project"
)
if err != nil {
    log.Fatal(err)
}
defer client.Close(context.Background()) // deliver anything still queued

if err := doWork(); err != nil {
    client.Capture(err, autotix.WithTag("module", "billing"))
}

Capture records the stack of the calling goroutine, so call it where you handle the error.

Where do I get a token?

In the Autotix dashboard open Settings and copy your org webhook token. It is a server secret. Keep it in configuration or the environment, never in client code.

Panics

Report a panic in a goroutine and let the goroutine end normally:

go func() {
    defer client.Recover(autotix.WithTag("job", "nightly-report"))
    runNightlyReport()
}()

Recover must be the deferred call itself. To re-panic after reporting, recover yourself and call CapturePanic:

defer func() {
    if r := recover(); r != nil {
        client.CapturePanic(r)
        panic(r)
    }
}()

net/http

mux := http.NewServeMux()
mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) {
    if err := placeOrder(r); err != nil {
        client.Capture(err, autotix.WithRequest(r)) // attaches the full URL
        http.Error(w, "order failed", http.StatusInternalServerError)
        return
    }
})

srv := &http.Server{Addr: ":8080", Handler: client.Middleware(mux)}

Middleware reports panics raised by handlers, then re-panics so your existing recovery middleware (or net/http's default) runs as before. Mount it closest to your handlers so it sees the panic first. It works the same with chi, gorilla/mux, and any other router that takes an http.Handler.

Short-lived processes

CLIs, cron jobs, and Lambdas exit before background delivery finishes unless you wait:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
client.Flush(ctx) // or client.Close(ctx) on shutdown

Verify your setup

client.TriggerTestError()
client.Flush(context.Background())

This sends an AutotixTestError: … event tagged autotix_test=true, bypasses sampling, and should show up in Webhook Logs within a few seconds.

How errors reach a project

Autotix decides which project (Jira project or GitHub repo) each error belongs to in this order:

  1. A pinned project. WithProject("WEB") on the client, or on one event. Pinned events skip URL matching.
  2. URL matching. Otherwise the event's URL is tested against each project's URL patterns. WithRequest(r) attaches the full request URL; WithURL sets one by hand. A bare path matches nothing.

Server code with no request, such as workers and cron jobs, should always pin a project.

Configuration

autotix.New(token,
    autotix.WithEnvironment("production"),
    autotix.WithRelease("v1.2.3"),
    autotix.WithProject("WEB"),
    autotix.WithTags(map[string]string{"service": "api"}),
    autotix.WithServerName("web-1"),            // default: os.Hostname()
    autotix.WithSampleRate(1.0),                // 0..1, fraction of events to ship
    autotix.WithBeforeSend(func(e *autotix.Event) *autotix.Event {
        if strings.Contains(e.Message, "context canceled") {
            return nil // drop
        }
        return e
    }),
    autotix.WithTimeout(15*time.Second),        // per request
    autotix.WithQueueSize(100),                 // events waiting for delivery
    autotix.WithWorkers(2),                     // concurrent deliveries
    autotix.WithHTTPClient(&http.Client{}),     // proxies, custom transports
    autotix.WithLogger(slog.Default()),         // failures log at Warn
    autotix.WithEndpoint("https://app.autotix.io/api/webhook/error"), // self-hosted
    autotix.WithHMACSecret("shared-secret"),    // sign bodies instead of sending the token
)

Per-event options for Capture, CaptureMessage, CapturePanic, and Recover:

Option Effect
WithLevel(autotix.LevelFatal) fatal, error, warning, info, debug
WithTag(k, v) / WithTags(m) Searchable tags. Per-event tags override client defaults.
WithExtra(k, v) Non-searchable detail, such as a request id.
WithUser(autotix.User{ID: "u_42"}) The affected user.
WithURL(u) Full URL for project routing.
WithRequest(r) URL, method, and user agent from an *http.Request.
WithProject(key) Route this one event to a project.
WithStackTrace(s) Replace the captured stack.

Behavior and limits

  • Failures never reach your code. Network errors and non-2xx responses are logged at Warn and counted in client.Stats().
  • Bounded queue. When the queue is full new events are dropped, so an error storm cannot grow memory. Autotix dedupes repeats server-side anyway.
  • A nil *Client is safe. Every method is a no-op, so you can leave the client unset where Autotix is disabled.
  • Messages dedupe by text. CaptureMessage sends no stack trace, so identical messages collapse into one ticket.
  • PR previews. AUTOTIX_GIT_PROVIDER and AUTOTIX_GIT_PR, or Tugboat's TUGBOAT_GITHUB_PR, are detected at startup and sent as details.git.

Development

go test -race -cover ./...
go vet ./...

Source

This repository is a read-only mirror, split from the Autotix monorepo. Issues and PRs are welcome here and will be applied upstream.

License

MIT

Documentation

Overview

Package autotix ships Go errors and panics to Autotix, which turns them into Jira or GitHub tickets and, when enabled, AI-generated fix PRs.

The package has no dependencies outside the standard library. Create one Client per process, keep it for the lifetime of the process, and Close it on shutdown so queued events are delivered:

client, err := autotix.New(os.Getenv("AUTOTIX_WEBHOOK_TOKEN"),
	autotix.WithEnvironment("production"),
	autotix.WithProject("WEB"),
)
if err != nil {
	log.Fatal(err)
}
defer client.Close(context.Background())

if err := doWork(); err != nil {
	client.Capture(err)
}

Delivery is asynchronous and bounded. Capture never blocks the caller and never returns an error: a full queue drops the event, a failed request is logged and counted in Stats. Error tracking that can take down the app it watches is a footgun, so the SDK fails soft everywhere.

A nil *Client is safe to use. Every method is a no-op, which lets an app leave the client unset in environments where Autotix is disabled.

Index

Examples

Constants

View Source
const (
	// DefaultEndpoint is the production server-side ingest endpoint.
	DefaultEndpoint = "https://app.autotix.io/api/webhook/error"
	// DefaultTimeout bounds each delivery request. The webhook endpoint
	// creates the ticket before it answers, so it is slower than a plain
	// ingest call; this matches the PHP integrations.
	DefaultTimeout = 15 * time.Second
	// DefaultQueueSize is how many events may wait for delivery.
	DefaultQueueSize = 100
	// DefaultWorkers is how many deliveries run at once.
	DefaultWorkers = 2
)

Defaults for New. Override them with the matching With option.

View Source
const Source = "go"

Source is the webhook `source` value every event carries. The Autotix dashboard labels Go events with it and dedup keys include it.

View Source
const Version = "0.1.0"

Version is the SDK version, sent in the User-Agent and in each event.

Variables

View Source
var ErrMissingCredentials = errors.New("autotix: a webhook token or HMAC secret is required")

ErrMissingCredentials is returned by New when neither a token nor an HMAC secret was supplied.

Functions

func CaptureStack

func CaptureStack(skip int) string

CaptureStack returns the calling goroutine's stack, formatted like Go's own panic output: a function name line followed by a tab-indented file:line. Frames inside the SDK are left out, so the first frame is the code that called into Autotix. skip drops that many additional caller frames; 0 starts at the caller of CaptureStack.

Capture and CapturePanic call this for you. Use it directly when you build an event elsewhere and want to attach the stack with WithStackTrace.

Types

type Client

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

Client delivers events to Autotix. Create one with New and share it across goroutines; all methods are safe for concurrent use.

func New

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

New creates a client authenticated with the org's webhook token (from Settings in the Autotix dashboard). The token is a server secret; keep it in configuration, never in client code. New returns an error when no credentials are given or an option is invalid.

Example
package main

import (
	"context"
	"errors"
	"log"
	"os"

	autotix "github.com/HelloWorldDevs/autotix-go"
)

func main() {
	client, err := autotix.New(os.Getenv("AUTOTIX_WEBHOOK_TOKEN"),
		autotix.WithEnvironment("production"),
		autotix.WithRelease("v1.4.2"),
		autotix.WithProject("WEB"),
	)
	if err != nil {
		log.Fatal(err)
	}
	// Drain the queue before the process exits so nothing is lost.
	defer client.Close(context.Background())

	if err := doWork(); err != nil {
		client.Capture(err, autotix.WithTag("module", "billing"))
	}
}

func doWork() error { return errors.New("example") }

func (*Client) Capture

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

Capture reports an error with the stack of the calling goroutine. A nil error is ignored. The call returns immediately; delivery happens in the background.

func (*Client) CaptureMessage

func (c *Client) CaptureMessage(message string, opts ...EventOption)

CaptureMessage reports a message that is not an error, at LevelInfo unless WithLevel says otherwise. No stack trace is attached, so identical messages dedupe together on the server.

func (*Client) CapturePanic

func (c *Client) CapturePanic(recovered any, opts ...EventOption)

CapturePanic reports a recovered panic value at LevelFatal. Call it from your own deferred recover when you want to decide what happens next, for example to re-panic:

defer func() {
	if r := recover(); r != nil {
		client.CapturePanic(r)
		panic(r)
	}
}()

func (*Client) Close

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

Close stops accepting events, delivers what is already queued, and stops the workers. It returns false when ctx ends before the queue drains. Calling Close more than once is safe.

func (*Client) Flush

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

Flush waits until every queued and in-flight event has been delivered or has failed. It returns false when ctx ends first. Call it before a short-lived process exits.

Example
package main

import (
	"context"
	"log"
	"time"

	autotix "github.com/HelloWorldDevs/autotix-go"
)

func main() {
	client, _ := autotix.New("your-org-webhook-token")
	client.CaptureMessage("deploy finished", autotix.WithLevel(autotix.LevelInfo))

	// In short-lived processes (CLIs, Lambdas, cron jobs) wait for delivery.
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if !client.Flush(ctx) {
		log.Println("autotix: some events were still in flight")
	}
}

func (*Client) Middleware

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

Middleware reports panics raised by next, then re-panics so the recovery you already have (your own middleware or net/http's default) keeps working. Mount it closest to your handlers, inside any recovery middleware, so it sees the panic first. http.ErrAbortHandler is passed through without being reported.

Example
package main

import (
	"context"
	"net/http"

	autotix "github.com/HelloWorldDevs/autotix-go"
)

func main() {
	client, _ := autotix.New("your-org-webhook-token", autotix.WithProject("API"))
	defer client.Close(context.Background())

	mux := http.NewServeMux()
	mux.HandleFunc("/orders", func(w http.ResponseWriter, r *http.Request) {
		if err := placeOrder(r); err != nil {
			// Attach the request so the event carries the full URL.
			client.Capture(err, autotix.WithRequest(r))
			http.Error(w, "order failed", http.StatusInternalServerError)
			return
		}
	})

	// Panics in handlers are reported, then re-raised for your own recovery
	// middleware (or net/http's default) to handle.
	srv := &http.Server{Addr: ":8080", Handler: client.Middleware(mux)}
	_ = srv
}

func placeOrder(*http.Request) error { return nil }

func (*Client) Recover

func (c *Client) Recover(opts ...EventOption)

Recover is meant to be deferred directly: it recovers a panic in the current goroutine, reports it, and lets the goroutine end normally. It must be the deferred call itself (defer client.Recover()), not called from inside another deferred function, or recover has no effect.

The panic is swallowed even on a nil client, so disabling Autotix never changes how your program behaves. Use CapturePanic to re-panic instead.

Example
package main

import (
	"context"

	autotix "github.com/HelloWorldDevs/autotix-go"
)

func main() {
	client, _ := autotix.New("your-org-webhook-token")
	defer client.Close(context.Background())

	go func() {
		// Report a panic in this goroutine and keep the process alive.
		defer client.Recover(autotix.WithTag("job", "nightly-report"))
		runNightlyReport()
	}()
}

func runNightlyReport() {}

func (*Client) Stats

func (c *Client) Stats() Stats

Stats returns delivery counters.

func (*Client) TriggerTestError

func (c *Client) TriggerTestError(opts ...EventOption)

TriggerTestError sends one clearly labeled event through the normal pipeline to verify the setup end to end. It bypasses sampling and tags the event autotix_test=true so it is easy to filter out later.

type DualOption

type DualOption interface {
	Option
	EventOption
}

DualOption is accepted by both New (as a default for every event) and the capture methods (for one event). WithProject, WithTag, and WithTags are dual options.

func WithProject

func WithProject(key string) DualOption

WithProject routes events to a project key (a Jira key or owner/repo) instead of matching on the event URL. Passed to New it applies to every event; passed to a capture method it applies to that event. Background jobs, queue consumers, and CLIs should always pin a project because their errors carry no URL to match.

func WithTag

func WithTag(key, value string) DualOption

WithTag attaches one searchable tag. Per-event tags override client defaults with the same key.

func WithTags

func WithTags(tags map[string]string) DualOption

WithTags attaches several searchable tags at once.

type Event

type Event struct {
	// Message is the error text. Required.
	Message string
	// Level defaults to LevelError for errors, LevelInfo for messages, and
	// LevelFatal for panics.
	Level Level
	// StackTrace is formatted like a Go panic trace: a function name line
	// followed by a tab-indented file:line.
	StackTrace string
	// ErrorType is the Go type of the captured value, such as "*fs.PathError".
	ErrorType string
	// URL is the full request URL the error belongs to. Autotix matches it
	// against each project's URL patterns when no Project is pinned.
	URL string
	// RequestMethod and UserAgent are filled by WithRequest.
	RequestMethod string
	UserAgent     string
	// Project pins the event to a project key (a Jira key or owner/repo),
	// skipping URL matching. Background jobs should always set one.
	Project     string
	Environment string
	Release     string
	// ServerName is the reporting host, os.Hostname by default.
	ServerName string
	Timestamp  time.Time
	// Tags are searchable key/value pairs.
	Tags map[string]string
	// Extra holds any other detail worth attaching.
	Extra map[string]any
	User  *User
	Git   *GitContext
}

Event is one error as the SDK sees it before encoding. A WithBeforeSend hook receives a pointer to it and may change any field or drop it.

type EventOption

type EventOption interface {
	// contains filtered or unexported methods
}

EventOption adjusts a single event. Pass options to Capture, CaptureMessage, CapturePanic, Recover, or TriggerTestError.

func WithExtra

func WithExtra(key string, value any) EventOption

WithExtra attaches one non-searchable detail, such as a request id.

func WithLevel

func WithLevel(l Level) EventOption

WithLevel sets the event severity.

func WithRequest

func WithRequest(r *http.Request) EventOption

WithRequest fills the event's URL, method, and user agent from an incoming request so Autotix can route it by URL. A nil request is ignored.

func WithStackTrace

func WithStackTrace(stack string) EventOption

WithStackTrace replaces the stack the SDK captured, for errors that carry their own trace.

func WithURL

func WithURL(url string) EventOption

WithURL sets the full URL the error belongs to. Send the whole URL including scheme and host; a bare path matches no project.

func WithUser

func WithUser(u User) EventOption

WithUser attaches the affected user.

type GitContext

type GitContext struct {
	// Provider is the lowercase host name: "github", "gitlab", "bitbucket".
	// Autotix acts on GitHub today and ignores the rest.
	Provider string `json:"provider"`
	// PR is the pull or merge request number.
	PR int `json:"pr"`
	// Ref is the branch name, informational only.
	Ref string `json:"ref,omitempty"`
	// Preview is a label for the preview environment, such as a Tugboat
	// preview name.
	Preview string `json:"preview,omitempty"`
}

GitContext names the pull request an error was observed inside, so Autotix can comment on the PR instead of opening a ticket. It is detected from the environment at startup (see DetectGitContext) or set with WithGitContext.

func DetectGitContext

func DetectGitContext() *GitContext

DetectGitContext reads the pull request a process runs inside from the environment. It returns nil outside a recognized PR preview.

A generic AUTOTIX_GIT_PROVIDER / AUTOTIX_GIT_PR pair (plus optional AUTOTIX_GIT_REF and AUTOTIX_GIT_PREVIEW) wins, so any CI can opt in. Tugboat previews are recognized out of the box through their per-provider variables (TUGBOAT_GITHUB_PR, TUGBOAT_GITLAB_MR, TUGBOAT_BITBUCKET_PR, TUGBOAT_STASH_PR) together with TUGBOAT_PREVIEW_REF and TUGBOAT_PREVIEW_NAME.

New calls this once at construction. Override the result with WithGitContext.

type Level

type Level string

Level is the severity of an event.

const (
	LevelFatal   Level = "fatal"
	LevelError   Level = "error"
	LevelWarning Level = "warning"
	LevelInfo    Level = "info"
	LevelDebug   Level = "debug"
)

Severity levels, from most to least severe.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures a Client. Pass options to New.

func WithBeforeSend

func WithBeforeSend(fn func(*Event) *Event) Option

WithBeforeSend runs fn on every event right before it is queued. Return nil to drop the event. A panic inside fn is recovered and the event is sent as it was.

func WithEndpoint

func WithEndpoint(url string) Option

WithEndpoint overrides the ingest URL. Use it for a self-hosted Autotix or a staging environment. Defaults to DefaultEndpoint.

func WithEnvironment

func WithEnvironment(env string) Option

WithEnvironment tags every event with a deployment environment such as "production" or "staging".

func WithGitContext

func WithGitContext(g GitContext) Option

WithGitContext pins the pull request context attached to every event, replacing what DetectGitContext would find in the environment.

func WithHMACSecret

func WithHMACSecret(secret string) Option

WithHMACSecret signs each request body with HMAC-SHA256 and sends the hex digest in X-Webhook-Signature instead of sending the token. When set, the token passed to New may be empty.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient sends requests through the given client, for custom transports, proxies, or tests.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger routes the SDK's own log lines. Failed deliveries log at Warn, successful ones at Debug. Defaults to slog.Default. Pass a logger backed by io.Discard to silence the SDK.

func WithQueueSize

func WithQueueSize(n int) Option

WithQueueSize bounds how many events can wait for delivery. When the queue is full new events are dropped and counted in Stats. Defaults to DefaultQueueSize.

func WithRelease

func WithRelease(release string) Option

WithRelease tags every event with an app version or commit SHA.

func WithSampleRate

func WithSampleRate(rate float64) Option

WithSampleRate ships only the given fraction of events, from 0 (none) to 1 (all, the default). Sampling runs before any network work, so dropped events cost nothing. TriggerTestError always bypasses it.

func WithServerName

func WithServerName(name string) Option

WithServerName overrides the hostname reported with each event. Defaults to os.Hostname.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout bounds each delivery request. Defaults to DefaultTimeout.

func WithWorkers

func WithWorkers(n int) Option

WithWorkers sets how many deliveries run concurrently. Defaults to DefaultWorkers.

type Stats

type Stats struct {
	// Sent is the number of events the server accepted with a 2xx.
	Sent int64
	// Failed is the number of events that got a non-2xx or a network error.
	Failed int64
	// Dropped is the number of events discarded because the queue was full
	// or the client was already closed.
	Dropped int64
}

Stats counts delivery outcomes since the client was created.

type User

type User struct {
	ID       string `json:"id,omitempty"`
	Email    string `json:"email,omitempty"`
	Username string `json:"username,omitempty"`
}

User identifies the person affected by an event. Send only what you need.

Jump to

Keyboard shortcuts

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