Docs · SDK · Go

Go SDK.

Pure Go, zero C dependencies. Module path github.com/RelayOne/coderadar/sdks/go/coderadar. Goroutine-safe, defer-friendly. Retries 5xx with exponential backoff.

Install

go get github.com/RelayOne/coderadar/sdks/go/coderadar@latest

Init

Construct a single *Client at startup and share it across the process. The client is goroutine-safe.

package main

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

    "github.com/RelayOne/coderadar/sdks/go/coderadar"
)

func main() {
    cr := coderadar.NewClient(
        os.Getenv("CODERADAR_API_KEY"),
        coderadar.DefaultEndpoint,
        coderadar.WithServiceName("api-gateway"),
        coderadar.WithEnvironment(os.Getenv("ENV")),
        coderadar.WithGitSHA(os.Getenv("RELEASE_SHA")),
    )

    mux := http.NewServeMux()
    // ... register handlers

    handler := cr.WrapPanics(mux) // recover panics and report them
    log.Fatal(http.ListenAndServe(":8080", handler))
}

Client options

OptionDefaultNotes
WithServiceName(s)""Tags every event with this service identifier.
WithEnvironment(e)"production"dev / staging / production / preview-*
WithGitSHA(sha)""Attached as git_sha on every event.
WithHTTPClient(c)10s timeoutOverride the underlying HTTP client (useful for tests).
WithRetry(n, delay)3, 200msMax retries and base backoff for 5xx / network errors. Exponential.

CaptureError

import "github.com/RelayOne/coderadar/sdks/go/coderadar"

if err := db.QueryRow(...).Scan(&result); err != nil {
    _ = cr.CaptureError(ctx, err, coderadar.ErrorOpts{
        Tags: map[string]string{
            "feature": "checkout",
            "region":  "us-central1",
        },
        User:  userID,
        Extra: map[string]any{"query": "SELECT ..."},
    })
}

net/http middleware — WrapPanics

WrapPanics wraps any http.Handler. It recovers panics, ships the error to CodeRadar (tagging the HTTP method and path), and writes a 500 response to the client.

handler := cr.WrapPanics(yourMux)

gin middleware

The SDK does not ship a gin-specific middleware yet. Wire it manually using gin's recovery mechanism:

import (
    "github.com/gin-gonic/gin"
    "github.com/RelayOne/coderadar/sdks/go/coderadar"
)

func CodeRadarRecovery(cr *coderadar.Client) gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if rec := recover(); rec != nil {
                var err error
                switch v := rec.(type) {
                case error:
                    err = v
                default:
                    err = fmt.Errorf("panic: %v", v)
                }
                _ = cr.CaptureError(c.Request.Context(), err, coderadar.ErrorOpts{
                    Tags: map[string]string{
                        "http.method": c.Request.Method,
                        "http.path":   c.FullPath(),
                    },
                })
                c.AbortWithStatus(500)
            }
        }()
        c.Next()
    }
}

r := gin.New()
r.Use(CodeRadarRecovery(cr))

Recover pattern (deferred)

func processJob(ctx context.Context, cr *coderadar.Client, job Job) {
    defer func() {
        if rec := recover(); rec != nil {
            err := fmt.Errorf("panic in processJob: %v", rec)
            _ = cr.CaptureError(ctx, err, coderadar.ErrorOpts{
                Tags: map[string]string{"job_id": job.ID},
            })
        }
    }()
    // ... job logic
}

OpenTelemetry exporter

Convert OTel spans with non-OK status codes into CodeRadar errors using the built-in OTelExporter:

exporter := cr.OTelExporter()

// Wrap it in a type that satisfies sdktrace.SpanExporter:
type otelBridge struct{ exp func(SpanLike) error }
func (b *otelBridge) ExportSpans(_ context.Context, spans []sdktrace.ReadOnlySpan) error {
    for _, s := range spans {
        _ = b.exp(otelSpanAdapter{s}) // otelSpanAdapter implements coderadar.SpanLike
    }
    return nil
}
func (b *otelBridge) Shutdown(_ context.Context) error { return nil }

tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(&otelBridge{exp: exporter}),
)

Common gotchas

  • Goroutine-safe — one client per processNewClient allocates an HTTP client with a connection pool. Create one instance in main() and pass it down via dependency injection or a package-level variable. Constructing a new client per request wastes connections.
  • Context cancellation vs. reporting — If the request context is cancelled (e.g. client disconnected), the SDK's HTTP POST will also be cancelled. WrapPanics uses context.WithoutCancel internally to detach from the request context so panics are always reported. Do the same in your custom handlers when calling CaptureError after a cancelled context.
  • 4xx errors are dropped silently — The SDK does not retry 4xx responses (except 429). A 401 means your API key is wrong; a 400 means the payload schema is invalid. Check that CODERADAR_API_KEY is set and non-empty.
  • Module not yet on go.pkg.dev — The module is in a private monorepo. Add a replace directive in your go.mod during local development:
    replace github.com/RelayOne/coderadar/sdks/go/coderadar => ../path/to/coderadar/sdks/go/coderadar