tripswitch

package module
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

README

tripswitch-go

Go Reference Go Report Card Version

Official Go client SDK for Tripswitch - a circuit breaker management service.

v0.9.0 Breaking Changes: NewClient now accepts a context.Context and returns (*Client, error). The Ready method has been removed — initialization blocking is handled by NewClient. See Migration for details.

This SDK conforms to the Tripswitch SDK Contract v0.2.

Features

  • Real-time state sync via Server-Sent Events (SSE)
  • Automatic sample reporting with buffered, batched uploads
  • Fail-open by default - your app stays available even if Tripswitch is unreachable
  • Goroutine-safe - one client per project, safe for concurrent use
  • Graceful shutdown with context-aware close and sample flushing

Installation

go get github.com/tripswitch-dev/tripswitch-go

Requires Go 1.22+ (uses math/rand/v2 for thread-safe random number generation)

Authentication

Tripswitch uses a two-tier authentication model introduced in v0.3.0:

Runtime Credentials (SDK)

For SDK initialization, you need two credentials from Project Settings → SDK Keys:

Credential Prefix Purpose
Project Key eb_pk_ SSE connection and state reads
Ingest Secret ik_ HMAC-signed sample ingestion
ts, err := tripswitch.NewClient(ctx, "proj_abc123",
    tripswitch.WithAPIKey("eb_pk_..."),    // Project key
    tripswitch.WithIngestKey("ik_..."),    // Ingest secret
)
Admin Credentials (Management API)

For management and automation tasks, use an Admin Key from Organization Settings → Admin Keys:

Credential Prefix Purpose
Admin Key eb_admin_ Organization-scoped management operations

Admin keys are used with the Admin Client for creating projects, managing breakers, and other administrative tasks—not for runtime SDK usage.

Quick Start

package main

import (
    "context"
    "log"
    "net/http"
    "time"

    "github.com/tripswitch-dev/tripswitch-go"
)

func main() {
    // Create client (blocks until SSE state sync completes)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    ts, err := tripswitch.NewClient(ctx, "proj_abc123",
        tripswitch.WithAPIKey("eb_pk_..."),
        tripswitch.WithIngestKey("ik_..."),
    )
    if err != nil {
        log.Fatal("tripswitch failed to initialize:", err)
    }
    defer ts.Close(context.Background())

    // Wrap operations with circuit breaker
    resp, err := tripswitch.Execute(ts, ctx, func() (*http.Response, error) {
        return http.Get("https://api.example.com/data")
    },
        tripswitch.WithBreakers("external-api"),              // Gate on breaker state
        tripswitch.WithRouter("my-router-id"),                // Route samples to this router
        tripswitch.WithMetrics(map[string]any{"latency": tripswitch.Latency}), // Report latency
    )
    if err != nil {
        if tripswitch.IsBreakerError(err) {
            // Circuit is open - return cached/fallback response
            log.Println("circuit open, using fallback")
            return
        }
        log.Println("request failed:", err)
        return
    }
    defer resp.Body.Close()
    // Process response...
}

Configuration Options

Client Options
Option Description Default
WithAPIKey(key) Project key (eb_pk_) for SSE authentication Required
WithIngestKey(key) Ingest secret (ik_) for HMAC-signed sample reporting Required
WithFailOpen(bool) Allow traffic when Tripswitch is unreachable true
WithBaseURL(url) Override API endpoint https://api.tripswitch.dev
WithLogger(logger) Custom logger (compatible with slog.Logger) slog.Default()
WithOnStateChange(fn) Callback on breaker state transitions nil
WithTraceIDExtractor(fn) Extract trace ID from context for each sample nil
WithGlobalTags(tags) Tags applied to all samples nil
WithMetadataSyncInterval(d) Interval for refreshing breaker/router metadata from the API. Set ≤ 0 to disable. 30s
Execute Options
Option Description
WithBreakers(names...) Breaker names to check before executing (any open → ErrOpen). If omitted, no gating is performed.
WithSelectedBreakers(fn) Dynamically select breakers based on cached metadata. Mutually exclusive with WithBreakers.
WithRouter(routerID) Router ID for sample routing. If omitted, no samples are emitted.
WithSelectedRouter(fn) Dynamically select a router based on cached metadata. Mutually exclusive with WithRouter.
WithMetrics(map[string]any) Metrics to report (Latency sentinel, func() float64, or numeric values)
WithDeferredMetrics(fn) Extract metrics from the task's return value (e.g., token counts from API responses)
WithTag(key, value) Add a single diagnostic tag
WithTags(tags) Diagnostic tags for this specific call (merged with global tags)
WithIgnoreErrors(errs...) Errors that should not count as failures
WithErrorEvaluator(fn) Custom function to determine if error is a failure (takes precedence over WithIgnoreErrors)
WithTraceID(id) Explicit trace ID (takes precedence over WithTraceIDExtractor)
Error Classification

Every sample includes an ok field indicating whether the task succeeded or failed. This is determined by the following evaluation order:

  1. WithErrorEvaluator(fn) — if set, takes precedence over everything else. The function signature is func(error) bool. Return true if the error is a failure; return false if it should be treated as success.

    // Only count 5xx as failures; 4xx are "expected" errors
    tripswitch.WithErrorEvaluator(func(err error) bool {
        var httpErr *HTTPError
        if errors.As(err, &httpErr) {
            return httpErr.StatusCode >= 500
        }
        return true // non-HTTP errors are failures
    })
    
  2. WithIgnoreErrors(errs...) — if the task error matches any of these (via errors.Is, so wrapped errors work), it is not counted as a failure.

    // sql.ErrNoRows is expected, don't count it
    tripswitch.WithIgnoreErrors(sql.ErrNoRows)
    
  3. Default — any non-nil error is a failure; nil error is success.

Trace IDs

Trace IDs associate samples with distributed traces. Two ways to set them:

  • WithTraceID(id) — explicit per-call trace ID. Takes precedence over the extractor.

  • WithTraceIDExtractor(fn) (client option) — automatically extracts a trace ID from the context for every Execute call. Useful for OpenTelemetry integration:

    tripswitch.WithTraceIDExtractor(func(ctx context.Context) string {
        span := trace.SpanFromContext(ctx)
        if span.SpanContext().IsValid() {
            return span.SpanContext().TraceID().String()
        }
        return ""
    })
    

If both are set, WithTraceID wins.

API Reference

NewClient
func NewClient(ctx context.Context, projectID string, opts ...Option) (*Client, error)

Creates a new Tripswitch client. Starts background goroutines for SSE state sync and sample flushing, and blocks until the initial SSE sync completes (when an API key is configured). The context controls how long to wait for initialization. Returns an error if the context expires before sync completes.

Execute
func Execute[T any](c *Client, ctx context.Context, task func() (T, error), opts ...ExecuteOption) (T, error)

Runs a task end-to-end: checks breaker state, executes the task, and reports samples — all in one call.

  • Use WithBreakers() to gate execution on breaker state (omit for pass-through)
  • Use WithRouter() to specify where samples go (omit for no sample emission)
  • Use WithMetrics() to specify what values to report

Returns ErrOpen if any specified breaker is open.

Note: This is a package-level generic function (not a method) because Go does not support generic methods.

Latency
var Latency = &latencyMarker{}

Sentinel value for WithMetrics that instructs the SDK to automatically compute and report task duration in milliseconds.

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

Gracefully shuts down the client. The context controls how long to wait for buffered samples to flush.

Stats
func (c *Client) Stats() SDKStats

Returns a snapshot of SDK health metrics:

type SDKStats struct {
    DroppedSamples      uint64    // Samples dropped due to buffer overflow
    BufferSize          int       // Current buffer occupancy
    SSEConnected        bool      // SSE connection status
    SSEReconnects       uint64    // Count of SSE reconnections
    LastSuccessfulFlush time.Time // Timestamp of last successful flush
    LastSSEEvent        time.Time // Timestamp of last SSE event (zero if none received)
    FlushFailures       uint64    // Batches dropped after retry exhaustion
    CachedBreakers      int       // Number of breakers in local state cache
}
Breaker State Inspection

These methods expose the SDK's local breaker cache for debugging, logging, and health checks. For gating traffic on breaker state, use Execute with WithBreakers — it handles state checks, throttling, and sample reporting together.

type BreakerStatus struct {
    Name      string
    State     string  // "open", "closed", "half_open"
    AllowRate float64 // 0.0 to 1.0
}

func (c *Client) GetState(name string) *BreakerStatus
func (c *Client) GetAllStates() map[string]BreakerStatus

GetState returns the cached state of a single breaker, or nil if not found. GetAllStates returns a copy of all cached breaker states. Both return copies safe to hold without affecting internal state.

// Debug: why is checkout rejecting requests?
if status := ts.GetState("checkout"); status != nil {
    log.Printf("checkout breaker: state=%s allow_rate=%.2f", status.State, status.AllowRate)
}

// Health endpoint: expose all breaker states to monitoring
for name, status := range ts.GetAllStates() {
    log.Printf("breaker %s: %s", name, status.State)
}
Error Handling
var (
    ErrOpen                = errors.New("tripswitch: breaker is open")
    ErrConflictingOptions  = errors.New("tripswitch: conflicting execute options")
    ErrMetadataUnavailable = errors.New("tripswitch: metadata cache unavailable")
)

func IsBreakerError(err error) bool
Error Cause
ErrOpen A specified breaker is open or request was throttled in half-open state
ErrConflictingOptions Mutually exclusive options used (e.g. WithBreakers + WithSelectedBreakers)
ErrMetadataUnavailable Selector used but metadata cache hasn't been populated yet

Use IsBreakerError to check if an error is circuit breaker related:

result, err := tripswitch.Execute(ts, ctx, task,
    tripswitch.WithBreakers("my-breaker"),
)
if tripswitch.IsBreakerError(err) {
    // Breaker is open or request was throttled
    return fallbackValue, nil
}

Custom Metric Values

Latency is a convenience sentinel that auto-computes task duration in milliseconds. You can report any metric with any value:

tripswitch.WithMetrics(map[string]any{
    // Auto-computed latency (convenience)
    "latency": tripswitch.Latency,

    // Static numeric values
    "response_bytes": 4096,
    "queue_depth":    42.5,

    // Dynamic values via closure (called after task completes)
    "memory_mb": func() float64 {
        var m runtime.MemStats
        runtime.ReadMemStats(&m)
        return float64(m.Alloc / 1024 / 1024)
    },
})
Deferred Metrics

Use WithDeferredMetrics to extract metrics from the task's return value — useful when the interesting values are in the response (e.g., token counts from LLM APIs):

result, err := tripswitch.Execute(ts, ctx, func() (*anthropic.Response, error) {
    return anthropic.Complete(ctx, req)
},
    tripswitch.WithBreakers("anthropic-spend"),
    tripswitch.WithRouter("llm-router"),
    tripswitch.WithMetrics(map[string]any{"latency": tripswitch.Latency}),
    tripswitch.WithDeferredMetrics(func(res *anthropic.Response, err error) map[string]float64 {
        if res == nil {
            return nil
        }
        return map[string]float64{
            "prompt_tokens":     float64(res.Usage.PromptTokens),
            "completion_tokens": float64(res.Usage.CompletionTokens),
            "total_tokens":      float64(res.Usage.TotalTokens),
        }
    }),
)

Deferred metrics are resolved after the task completes and merged with eager metrics into the same sample batch. If the function panics, it is recovered and a warning is logged — eager metrics are still emitted.

Dynamic Selection

Use WithSelectedBreakers and WithSelectedRouter to choose breakers or routers at runtime based on cached metadata. The SDK periodically syncs metadata from the API (default 30s), and your selector receives the current snapshot.

// Gate on breakers matching a metadata property
result, err := tripswitch.Execute(ts, ctx, task,
    tripswitch.WithSelectedBreakers(func(breakers []tripswitch.BreakerMeta) []string {
        var names []string
        for _, b := range breakers {
            if b.Metadata["region"] == "us-east-1" {
                names = append(names, b.Name)
            }
        }
        return names
    }),
)

// Route samples to a router matching a metadata property
result, err := tripswitch.Execute(ts, ctx, task,
    tripswitch.WithSelectedRouter(func(routers []tripswitch.RouterMeta) string {
        for _, r := range routers {
            if r.Metadata["env"] == "production" {
                return r.ID
            }
        }
        return ""
    }),
    tripswitch.WithMetrics(map[string]any{"latency": tripswitch.Latency}),
)

Constraints:

  • WithBreakers and WithSelectedBreakers are mutually exclusive — using both returns ErrConflictingOptions
  • WithRouter and WithSelectedRouter are mutually exclusive — using both returns ErrConflictingOptions
  • If the metadata cache hasn't been populated yet, returns ErrMetadataUnavailable
  • If the selector returns an empty list/string, no gating or sample emission occurs

You can also access the metadata cache directly:

breakers := ts.GetBreakersMetadata() // []BreakerMeta
routers := ts.GetRoutersMetadata()   // []RouterMeta
Report
func (c *Client) Report(input ReportInput)

Send a sample independently of Execute. Use this for async workflows, result-derived metrics, or fire-and-forget reporting:

// Report token usage from an LLM API response
ts.Report(tripswitch.ReportInput{
    RouterID: "llm-router",
    Metric:   "total_tokens",
    Value:    float64(resp.Usage.TotalTokens),
    OK:       true,
})

// Background process metrics
ts.Report(tripswitch.ReportInput{
    RouterID: "worker-metrics",
    Metric:   "queue_depth",
    Value:    float64(queueLen),
    OK:       true,
    Tags:     map[string]string{"worker": "processor-1"},
})

Samples are buffered and batched the same way as Execute samples. Global tags are merged automatically.

Examples

See the examples directory for complete, runnable examples:

Circuit Breaker States

State Behavior
closed All requests allowed, results reported
open All requests rejected with ErrOpen
half_open Requests throttled based on allow_rate (e.g., 20% allowed)

How It Works

  1. State Sync: The client maintains a local cache of breaker states, updated in real-time via SSE
  2. Execute Check: Each Execute call checks the local cache (no network call)
  3. Sample Reporting: Results are buffered and batched (500 samples or 15s, whichever comes first)
  4. Graceful Degradation: If Tripswitch is unreachable, the client fails open by default

Admin Client

The admin package provides a client for management and automation tasks. This is separate from the runtime SDK and uses organization-scoped admin keys.

import "github.com/tripswitch-dev/tripswitch-go/admin"

client := admin.NewClient(
    admin.WithAPIKey("eb_admin_..."), // From Organization Settings → Admin Keys
)

// List all projects
projects, err := client.ListProjects(ctx)

// Create a project
project, err := client.CreateProject(ctx, admin.CreateProjectInput{
    Name: "prod-payments",
})

// Get project details
project, err := client.GetProject(ctx, "proj_abc123")

// Delete a project (requires name confirmation as a safety guard)
err = client.DeleteProject(ctx, "proj_abc123",
    admin.WithConfirmDeleteProjectName("prod-payments"),
)

// List breakers
page, err := client.ListBreakers(ctx, "proj_abc123", admin.ListParams{Limit: 100})

// Create a breaker
breaker, err := client.CreateBreaker(ctx, "proj_abc123", admin.CreateBreakerInput{
    Name:      "api-latency",
    Metric:    "latency_ms",
    Kind:      admin.BreakerKindP95,
    Op:        admin.BreakerOpGt,
    Threshold: 500,
})

Note: Admin keys (eb_admin_) are for management operations only. For runtime SDK usage, use project keys (eb_pk_) as shown in Quick Start.

Migration

v0.9.0: Synchronous NewClient

NewClient now takes a context.Context, blocks until SSE state sync completes, and returns (*Client, error). The Ready method has been removed.

// Before (v0.8.x)
ts := tripswitch.NewClient("proj_abc123",
    tripswitch.WithAPIKey("eb_pk_..."),
    tripswitch.WithIngestKey("ik_..."),
)
defer ts.Close(context.Background())
if err := ts.Ready(ctx); err != nil {
    log.Fatal(err)
}

// After (v0.9.0)
ts, err := tripswitch.NewClient(ctx, "proj_abc123",
    tripswitch.WithAPIKey("eb_pk_..."),
    tripswitch.WithIngestKey("ik_..."),
)
if err != nil {
    log.Fatal(err)
}
defer ts.Close(context.Background())
v0.7.0: WithMetrics map API

WithMetric was removed in favor of WithMetrics with a map:

// Before (v0.6.0)
tripswitch.WithMetric("latency", tripswitch.Latency),
tripswitch.WithMetric("count", 1),

// After (v0.7.0)
tripswitch.WithMetrics(map[string]any{
    "latency": tripswitch.Latency,
    "count":   1,
})

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

License

Apache License 2.0

Documentation

Overview

Package tripswitch provides the official Go client SDK for Tripswitch, a circuit breaker management service.

The client maintains real-time circuit breaker state via Server-Sent Events (SSE) and automatically reports execution samples to the Tripswitch API. It is goroutine-safe and designed for high-throughput applications.

Authentication

The runtime client uses two credentials:

  • Project API key (eb_pk_...): For SSE subscriptions and state reads
  • Ingest secret: For HMAC-signed sample ingestion

Create project keys via the admin API or Tripswitch dashboard.

Quick Start

ts, err := tripswitch.NewClient(ctx, "proj_abc123",
    tripswitch.WithAPIKey("eb_pk_..."),   // project key for SSE
    tripswitch.WithIngestSecret("..."),   // 64-char hex string for HMAC
)
if err != nil {
    log.Fatal(err)
}
defer ts.Close(context.Background())

// Wrap operations with circuit breaker
resp, err := tripswitch.Execute(ts, ctx, func() (*http.Response, error) {
    return client.Do(req)
},
    tripswitch.WithBreakers("external-api"),               // Gating: block if breaker is open
    tripswitch.WithRouter("router-id"),                    // Route samples to this router
    tripswitch.WithMetrics(map[string]any{"latency": tripswitch.Latency}),
)

Circuit Breaker States

The SDK handles three breaker states:

  • closed: All requests allowed, results reported
  • open: All requests rejected with ErrOpen
  • half_open: Requests throttled based on allow_rate (probabilistic)

Error Handling

Use IsBreakerError to check if an error is circuit breaker related:

if tripswitch.IsBreakerError(err) {
    // Return cached/fallback response
}

Graceful Shutdown

Always call Client.Close to flush buffered samples:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ts.Close(ctx)

Index

Constants

View Source
const ContractVersion = "0.2"

ContractVersion declares the SDK Contract version this implementation conforms to. See https://app.tripswitch.dev/docs/sdk-contract.md

Variables

View Source
var (
	// ErrOpen is returned by Execute when the circuit breaker is open.
	ErrOpen = errors.New("tripswitch: breaker is open")
	// ErrConflictingOptions is returned when mutually exclusive Execute options are used.
	ErrConflictingOptions = errors.New("tripswitch: conflicting execute options")
	// ErrMetadataUnavailable is returned when a selector is used but metadata cache is empty.
	ErrMetadataUnavailable = errors.New("tripswitch: metadata cache unavailable")
)
View Source
var ErrNotModified = errors.New("tripswitch: not modified")

ErrNotModified is returned when the server responds with 304 Not Modified.

View Source
var ErrUnauthorized = errors.New("tripswitch: unauthorized")

ErrUnauthorized is returned when the server responds with 401 or 403.

View Source
var Latency = &latencyMarker{}

Latency is a sentinel value for WithMetrics that instructs the SDK to automatically compute and report task duration in milliseconds.

Example:

tripswitch.Execute(c, ctx, task,
    tripswitch.WithRouter("router-id"),
    tripswitch.WithMetrics(map[string]any{"latency": tripswitch.Latency}),
)

Functions

func Execute

func Execute[T any](c *Client, ctx context.Context, task func() (T, error), opts ...ExecuteOption) (T, error)

Execute wraps a task with circuit breaker logic. It runs the task and optionally reports samples to a router. This is a package-level generic function because Go does not support generic methods.

Use WithBreakers to optionally gate execution on breaker state. Use WithRouter to specify where samples go (required for metrics to be emitted). Use WithMetrics to specify what values to report. Use WithDeferredMetrics to extract metrics from the task's return value.

Example:

result, err := tripswitch.Execute(c, ctx, task,
    tripswitch.WithBreakers("checkout-error-rate"),
    tripswitch.WithRouter("checkout-router"),
    tripswitch.WithMetrics(map[string]any{"latency": tripswitch.Latency}),
    tripswitch.WithTag("endpoint", "/checkout"),
)

func IsBreakerError

func IsBreakerError(err error) bool

IsBreakerError returns true if the error is a circuit breaker error.

Types

type BreakerMeta added in v0.8.0

type BreakerMeta struct {
	ID       string            `json:"id"`
	Name     string            `json:"name"`
	Metadata map[string]string `json:"metadata,omitempty"`
}

BreakerMeta contains a breaker's identity and metadata.

type BreakerStatus added in v0.8.0

type BreakerStatus struct {
	Name      string
	State     string  // "open", "closed", "half_open"
	AllowRate float64 // 0.0 to 1.0
}

BreakerStatus represents the cached state of a circuit breaker. Returned by GetState and GetAllStates for debugging and observability.

type Client

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

Client is the main tripswitch client.

func NewClient

func NewClient(ctx context.Context, projectID string, opts ...Option) (*Client, error)

NewClient creates a new tripswitch client. The provided context controls how long to wait for the initial SSE state sync. If an API key is configured, the client blocks until the first SSE event is received or the context expires. If no API key is set (or SSE is disabled), the client returns immediately.

The context governs initialization only — the client's internal lifetime is managed separately and is terminated by Client.Close.

func (*Client) Close

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

Close gracefully shuts down the client, flushing any buffered samples. The provided context controls how long to wait for the flush to complete.

func (*Client) GetAllStates added in v0.8.0

func (c *Client) GetAllStates() map[string]BreakerStatus

GetAllStates returns a copy of all cached breaker states. Useful for admin dashboards, health checks, and debugging.

func (*Client) GetBreakersMetadata added in v0.8.0

func (c *Client) GetBreakersMetadata() []BreakerMeta

GetBreakersMetadata returns a deep copy of the cached breaker metadata.

func (*Client) GetRoutersMetadata added in v0.8.0

func (c *Client) GetRoutersMetadata() []RouterMeta

GetRoutersMetadata returns a deep copy of the cached router metadata.

func (*Client) GetState added in v0.8.0

func (c *Client) GetState(name string) *BreakerStatus

GetState returns the cached state for a single breaker. Returns nil if the breaker is not in the cache. Useful for debugging to verify the SDK's local state matches expected state.

func (*Client) GetStatus added in v0.3.0

func (c *Client) GetStatus(ctx context.Context) (*Status, error)

GetStatus retrieves the project health status from the API. This returns the count of open and closed breakers for the project. Requires a project API key (eb_pk_).

func (*Client) ListBreakersMetadata added in v0.8.0

func (c *Client) ListBreakersMetadata(ctx context.Context, etag string) ([]BreakerMeta, string, error)

ListBreakersMetadata retrieves metadata for all breakers in the project. If etag is non-empty it is sent as If-None-Match; a 304 response returns (nil, etag, ErrNotModified).

func (*Client) ListRoutersMetadata added in v0.8.0

func (c *Client) ListRoutersMetadata(ctx context.Context, etag string) ([]RouterMeta, string, error)

ListRoutersMetadata retrieves metadata for all routers in the project. If etag is non-empty it is sent as If-None-Match; a 304 response returns (nil, etag, ErrNotModified).

func (*Client) Report added in v0.7.0

func (c *Client) Report(input ReportInput)

Report sends a sample to the report buffer. Use this for async workflows, result-derived metrics, or fire-and-forget reporting where Execute's synchronous wrap-and-report model doesn't fit.

ts.Report(tripswitch.ReportInput{
    RouterID: "llm-router",
    Metric:   "total_tokens",
    Value:    float64(resp.Usage.TotalTokens),
    OK:       true,
})

func (*Client) Stats

func (c *Client) Stats() SDKStats

Stats returns a snapshot of the SDK's health metrics.

type ExecuteOption

type ExecuteOption func(*executeOptions)

ExecuteOption configures a single Execute call.

func WithBreakers added in v0.5.0

func WithBreakers(names ...string) ExecuteOption

WithBreakers specifies breaker names to check before executing the task. If ANY breaker is open, Execute returns ErrOpen without running the task. This makes gating opt-in - if not specified, no breaker check is performed.

func WithDeferredMetrics added in v0.7.1

func WithDeferredMetrics[T any](fn func(T, error) map[string]float64) ExecuteOption

WithDeferredMetrics registers a function that extracts metrics from the task's return value after execution. The function receives the result and error from the task and returns a map of metric names to values. Only one deferred function is supported per Execute call; if called multiple times, the last one wins.

This is useful for reporting values that are only available in the response, such as token counts from LLM APIs:

result, err := tripswitch.Execute(ts, ctx, func() (*Response, error) {
    return anthropic.Complete(ctx, req)
},
    tripswitch.WithRouter("llm-router"),
    tripswitch.WithMetrics(map[string]any{"latency": tripswitch.Latency}),
    tripswitch.WithDeferredMetrics(func(res *Response, err error) map[string]float64 {
        if res == nil {
            return nil
        }
        return map[string]float64{
            "prompt_tokens":     float64(res.Usage.PromptTokens),
            "completion_tokens": float64(res.Usage.CompletionTokens),
            "total_tokens":      float64(res.Usage.TotalTokens),
        }
    }),
)

func WithErrorEvaluator

func WithErrorEvaluator(f func(error) bool) ExecuteOption

WithErrorEvaluator sets a custom function to determine if an error is a failure. If set, this takes precedence over WithIgnoreErrors. Return true if the error should count as a failure.

func WithIgnoreErrors

func WithIgnoreErrors(errs ...error) ExecuteOption

WithIgnoreErrors specifies errors that should not count as failures.

func WithMetrics added in v0.5.0

func WithMetrics(metrics map[string]any) ExecuteOption

WithMetrics sets the metrics to be reported with this Execute call. The map keys are metric names and values can be:

  • Latency: SDK computes task duration in milliseconds
  • func() float64: User closure called after task completes
  • int or float64: Static numeric value

Empty keys are ignored.

func WithRouter added in v0.6.0

func WithRouter(routerID string) ExecuteOption

WithRouter specifies the router ID for sample routing. If not specified, no samples will be emitted (metrics will be silently ignored). If metrics are specified but no router, a warning will be logged.

func WithSelectedBreakers added in v0.8.0

func WithSelectedBreakers(fn func([]BreakerMeta) []string) ExecuteOption

WithSelectedBreakers dynamically selects breakers based on cached metadata. The selector function receives the current breaker metadata cache and returns the names of breakers to use for gating.

Cannot be combined with WithBreakers - returns ErrConflictingOptions if both are used. Returns ErrMetadataUnavailable if the metadata cache is empty. If the selector returns an empty list, no breaker gating is performed.

Example:

tripswitch.Execute(ts, ctx, task,
    tripswitch.WithSelectedBreakers(func(breakers []tripswitch.BreakerMeta) []string {
        var names []string
        for _, b := range breakers {
            if b.Metadata["region"] == "us-east-1" {
                names = append(names, b.Name)
            }
        }
        return names
    }),
)

func WithSelectedRouter added in v0.8.0

func WithSelectedRouter(fn func([]RouterMeta) string) ExecuteOption

WithSelectedRouter dynamically selects a router based on cached metadata. The selector function receives the current router metadata cache and returns the ID of the router to use for sample routing.

Cannot be combined with WithRouter - returns ErrConflictingOptions if both are used. Returns ErrMetadataUnavailable if the metadata cache is empty. If the selector returns an empty string, no samples will be emitted.

Example:

tripswitch.Execute(ts, ctx, task,
    tripswitch.WithSelectedRouter(func(routers []tripswitch.RouterMeta) string {
        for _, r := range routers {
            if r.Metadata["env"] == "production" {
                return r.ID
            }
        }
        return ""
    }),
)

func WithTag added in v0.5.0

func WithTag(key, value string) ExecuteOption

WithTag adds a single diagnostic tag for this Execute call. Tags are merged with global tags, with call-site tags taking precedence.

func WithTags

func WithTags(tags map[string]string) ExecuteOption

WithTags sets diagnostic tags for this specific Execute call. These are merged with global tags, with call-site tags taking precedence.

func WithTraceID

func WithTraceID(traceID string) ExecuteOption

WithTraceID sets a specific trace ID for this Execute call. This takes precedence over the client's TraceIDExtractor.

type Logger

type Logger interface {
	Debug(msg string, args ...any)
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger interface - compatible with slog.Logger

type Option

type Option func(*Client)

Option is a functional option for configuring the client.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the project API key for SSE subscriptions and state reads. Project keys have the prefix "eb_pk_" and are project-scoped.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the default base URL for the Tripswitch API.

func WithFailOpen

func WithFailOpen(failOpen bool) Option

WithFailOpen sets the fail-open behavior of the client. If true, the client will allow traffic when tripswitch is unreachable. Defaults to true.

func WithGlobalTags

func WithGlobalTags(tags map[string]string) Option

WithGlobalTags sets tags that will be applied to all samples reported by the client.

func WithIngestKey

func WithIngestKey(key string) Option

WithIngestKey is deprecated: use WithIngestSecret instead. This function exists for backwards compatibility.

func WithIngestSecret added in v0.2.0

func WithIngestSecret(secret string) Option

WithIngestSecret sets the ingest secret for HMAC-signed sample ingestion. The secret is a 64-character hex string used to sign requests to the metrics endpoint.

func WithLogger

func WithLogger(logger Logger) Option

WithLogger sets a custom logger for the client.

func WithMetadataSyncInterval added in v0.8.0

func WithMetadataSyncInterval(d time.Duration) Option

WithMetadataSyncInterval sets the interval for refreshing breaker and router metadata from the API. Defaults to 30 seconds. A value <= 0 disables sync.

func WithOnStateChange

func WithOnStateChange(f func(name, from, to string)) Option

WithOnStateChange sets a callback that is invoked whenever a breaker's state changes.

func WithTraceIDExtractor

func WithTraceIDExtractor(f func(ctx context.Context) string) Option

WithTraceIDExtractor sets a function to extract a trace ID from a context. This is used to associate samples with a specific trace.

type ReportInput added in v0.7.0

type ReportInput struct {
	RouterID string            // Required: router to associate the sample with
	Metric   string            // Required: metric name
	Value    float64           // Metric value
	OK       bool              // Whether this sample represents a successful outcome
	TraceID  string            // Optional: trace ID for correlation
	Tags     map[string]string // Optional: per-sample tags (merged with global tags)
}

ReportInput contains fields for reporting a sample independently of Execute.

type RouterMeta added in v0.8.0

type RouterMeta struct {
	ID       string            `json:"id"`
	Name     string            `json:"name"`
	Metadata map[string]string `json:"metadata,omitempty"`
}

RouterMeta contains a router's identity and metadata.

type SDKStats

type SDKStats struct {
	DroppedSamples      uint64
	BufferSize          int
	SSEConnected        bool
	SSEReconnects       uint64
	LastSuccessfulFlush time.Time
	LastSSEEvent        time.Time // Zero if no events received yet
	FlushFailures       uint64    // Batches dropped after retry exhaustion
	CachedBreakers      int       // Number of breakers in local state cache
}

SDKStats holds health metrics about the SDK.

type Status added in v0.3.0

type Status struct {
	OpenCount   int   `json:"open_count"`
	ClosedCount int   `json:"closed_count"`
	LastEvalMs  int64 `json:"last_eval_ms,omitempty"`
}

Status represents the project health status.

Directories

Path Synopsis
Package admin provides a client for the Tripswitch management API.
Package admin provides a client for the Tripswitch management API.
examples
database command
Example: Database Query with Ignored Errors
Example: Database Query with Ignored Errors
evaluator command
Example: Custom Error Evaluator
Example: Custom Error Evaluator
http command
Example: HTTP Client Wrap
Example: HTTP Client Wrap
shutdown command
Example: Graceful Shutdown
Example: Graceful Shutdown
tags command
Example: Per-Request Tags
Example: Per-Request Tags

Jump to

Keyboard shortcuts

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