Pure Go, zero C dependencies. Module path github.com/RelayOne/coderadar/sdks/go/coderadar. Goroutine-safe, defer-friendly. Retries 5xx with exponential backoff.
go get github.com/RelayOne/coderadar/sdks/go/coderadar@latest
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))
}
| Option | Default | Notes |
|---|---|---|
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 timeout | Override the underlying HTTP client (useful for tests). |
WithRetry(n, delay) | 3, 200ms | Max retries and base backoff for 5xx / network errors. Exponential. |
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 ..."},
})
}
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)
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))
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
}
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}),
)
NewClient 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.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.CODERADAR_API_KEY is set and non-empty.replace directive in your go.mod during local development:replace github.com/RelayOne/coderadar/sdks/go/coderadar => ../path/to/coderadar/sdks/go/coderadar