Requirements: Go ≥ 1.21
Installation
go get github.com/tracelit-ai/tracelit-go
Quick start
1
Initialise the SDK
main.go
package main
import (
"context"
"log"
"github.com/tracelit-ai/tracelit-go"
)
func main() {
sdk, err := tracelit.New(
tracelit.WithAPIKey(os.Getenv("TRACELIT_API_KEY")),
tracelit.WithServiceName("payments-api"),
tracelit.WithEnvironment("production"),
)
if err != nil {
log.Fatal(err)
}
defer sdk.Shutdown(context.Background())
// Your application code here
}
2
Or use environment variables (zero code changes)
export TRACELIT_API_KEY=your-api-key
export TRACELIT_SERVICE_NAME=payments-api
export TRACELIT_ENVIRONMENT=production
if err := tracelit.Configure(); err != nil {
log.Fatal(err)
}
defer tracelit.Shutdown(context.Background())
Configuration reference
| Option | Env variable | Default | Description |
|---|---|---|---|
WithAPIKey(key) | TRACELIT_API_KEY | — | Required. Your Tracelit API key |
WithServiceName(name) | TRACELIT_SERVICE_NAME | — | Required. Service name shown in Tracelit |
WithEnvironment(env) | TRACELIT_ENVIRONMENT | production | Deployment environment tag |
WithEndpoint(url) | TRACELIT_ENDPOINT | https://ingest.tracelit.app | Override only when self-hosting |
WithSampleRate(rate) | TRACELIT_SAMPLE_RATE | 1.0 | Head-based sampling ratio 0.0–1.0 |
WithEnabled(bool) | TRACELIT_ENABLED | true | Set false to disable all telemetry |
WithResourceAttributes(map) | — | — | Extra resource attributes on every signal |
Custom resource attributes
sdk, err := tracelit.New(
tracelit.WithAPIKey(os.Getenv("TRACELIT_API_KEY")),
tracelit.WithServiceName("orders-api"),
tracelit.WithResourceAttributes(map[string]string{
"deployment.region": "us-east-1",
"team": "platform",
}),
)
Tracing
Automatic instrumentation
Oncetracelit.New (or tracelit.Configure) is called, the global OTel TracerProvider is set. Any library that uses the OTel API (otelhttp, otelgrpc, otelsql, etc.) will automatically send spans to Tracelit without further configuration.
Manual spans
import "github.com/tracelit-ai/tracelit-go"
func processOrder(ctx context.Context, order Order) error {
ctx, span := tracelit.StartSpan(ctx, "process-order",
tracelit.WithSpanAttributes(map[string]any{
"order.id": order.ID,
"order.currency": order.Currency,
}),
)
defer span.End()
if err := validateOrder(ctx, order); err != nil {
span.RecordError(err) // sets codes.Error + attaches exception.stacktrace
return err
}
// Child span — automatically a child of the span above
ctx, paySpan := tracelit.StartClientSpan(ctx, "charge-payment-provider")
defer paySpan.End()
if err := chargeCard(ctx, order); err != nil {
paySpan.RecordError(err,
tracelit.WithErrorAttributes(map[string]any{
"payment.gateway": "stripe",
"payment.amount": order.TotalCents,
}),
)
return err
}
span.AddEvent("payment-captured", map[string]any{"amount": order.TotalCents})
return nil
}
Span kinds
// SpanKindInternal (default) — internal operation
ctx, span := tracelit.StartSpan(ctx, "validate-input")
// SpanKindServer — inbound request handler
ctx, span := tracelit.StartServerSpan(ctx, "POST /orders")
// SpanKindClient — outbound call (HTTP, gRPC, DB)
ctx, span := tracelit.StartClientSpan(ctx, "stripe.charge")
// SpanKindProducer / Consumer — message queues
ctx, span := tracelit.StartProducerSpan(ctx, "orders.created publish")
ctx, span := tracelit.StartConsumerSpan(ctx, "orders.created consume")
Stack traces on errors
RecordError captures a full goroutine stack trace automatically:
ctx, span := tracelit.StartSpan(ctx, "risky-op")
defer span.End()
if err := riskyOperation(); err != nil {
span.RecordError(err) // sets codes.Error + attaches exception.stacktrace
return err
}
Panic recovery
ctx, span := tracelit.StartSpan(ctx, "risky-op")
defer func() {
if r := recover(); r != nil {
span.RecordPanic(r) // records panic + re-panics by default
// span.RecordPanic(r, tracelit.WithSwallowPanic()) // absorb the panic
}
span.End()
}()
Trace and span IDs (for log correlation)
traceID := tracelit.TraceID(ctx) // "4bf92f3577b34da6a3ce929d0e0e4736"
spanID := tracelit.SpanID(ctx) // "00f067aa0ba902b7"
Passing spans across goroutines
ctx, span := tracelit.StartSpan(ctx, "fan-out")
defer span.End()
for _, item := range items {
go func(item Item) {
childCtx, child := tracelit.StartSpan(ctx, "process-item")
defer child.End()
processItem(childCtx, item)
}(item)
}
HTTP and gRPC instrumentation
HTTP server and client
import (
"github.com/tracelit-ai/tracelit-go/middleware"
"net/http"
)
// Server — wrap entire mux or individual handlers
mux := http.NewServeMux()
mux.HandleFunc("/api/orders", handleOrders)
http.ListenAndServe(":8080", middleware.NewHTTPHandler(mux))
// Client — wrap transport for outgoing requests
client := &http.Client{
Transport: middleware.NewHTTPTransport(nil),
}
resp, err := client.Get("https://api.stripe.com/v1/charges")
gRPC
import "github.com/tracelit-ai/tracelit-go/middleware"
// Server
grpcServer := grpc.NewServer(
grpc.StatsHandler(middleware.NewGRPCServerHandler()),
)
// Client
conn, err := grpc.NewClient("localhost:50051",
grpc.WithStatsHandler(middleware.NewGRPCClientHandler()),
)
Metrics
import (
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/attribute"
)
// Counter
requests, err := tracelit.Counter("http.server.requests",
metric.WithDescription("Total HTTP requests"),
metric.WithUnit("{request}"),
)
requests.Add(ctx, 1, metric.WithAttributes(
attribute.String("method", r.Method),
attribute.Int("status", code),
))
// Histogram
latency, err := tracelit.Histogram("http.server.duration",
metric.WithUnit("ms"),
)
latency.Record(ctx, durationMs)
Runtime and process metrics (goroutines, GC, memory) are exported automatically every 60 seconds — no extra code required.
Logger bridges
All bridges route log records to the Tracelit log pipeline and automatically injecttrace_id and span_id from the context.
slog (standard library)
slog (standard library)
import (
"log/slog"
"github.com/tracelit-ai/tracelit-go/bridge"
)
// Replace the default global slog logger
slog.SetDefault(slog.New(bridge.NewSlogHandler()))
// Logs are correlated with the active span when ctx is passed
slog.InfoContext(ctx, "order created", "order_id", order.ID)
zap (Uber)
zap (Uber)
import (
"github.com/tracelit-ai/tracelit-go/bridge"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Option 1: OTel-only logger
logger := bridge.NewZapLogger("payments-api")
// Option 2: tee to both stdout and OTel
stdoutLogger, _ := zap.NewProduction()
logger := zap.New(zapcore.NewTee(
stdoutLogger.Core(),
bridge.NewZapCore("payments-api"),
), zap.AddCaller())
logger.Info("order created", zap.String("order_id", order.ID))
logrus
logrus
import (
"github.com/sirupsen/logrus"
"github.com/tracelit-ai/tracelit-go/bridge"
)
logrus.AddHook(bridge.NewLogrusHook())
// Pass ctx to get trace correlation
logrus.WithContext(ctx).WithField("order_id", order.ID).Info("order created")
zerolog
zerolog
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/tracelit-ai/tracelit-go/bridge"
)
// Replace the global zerolog logger
log.Logger = zerolog.New(bridge.NewZerologWriter(os.Stderr)).
With().Timestamp().Logger()
// Use log.Ctx(ctx) to attach trace correlation
log.Ctx(ctx).Info().Str("order_id", order.ID).Msg("order created")
Sampling and error guarantee
sdk, err := tracelit.New(
tracelit.WithAPIKey(os.Getenv("TRACELIT_API_KEY")),
tracelit.WithServiceName("high-traffic-api"),
tracelit.WithSampleRate(0.1), // keep 10% of traces
)
Error spans are always exported, even when the parent trace falls outside the sample ratio. The SDK uses a custom
ErrorAlwaysOnSampler + ErrorSpanProcessor pair — no configuration required.Disabling in tests
sdk, err := tracelit.New(
tracelit.WithAPIKey(os.Getenv("TRACELIT_API_KEY")),
tracelit.WithServiceName("my-service"),
tracelit.WithEnabled(false), // or TRACELIT_ENABLED=false
)
Complete example
package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"github.com/tracelit-ai/tracelit-go"
"github.com/tracelit-ai/tracelit-go/bridge"
"github.com/tracelit-ai/tracelit-go/middleware"
)
func main() {
sdk, err := tracelit.New(
tracelit.WithAPIKey(os.Getenv("TRACELIT_API_KEY")),
tracelit.WithServiceName("my-api"),
tracelit.WithEnvironment("production"),
tracelit.WithSampleRate(0.2),
)
if err != nil {
slog.Error("tracelit init failed", "error", err)
os.Exit(1)
}
defer sdk.Shutdown(context.Background())
// Route all slog output through Tracelit
slog.SetDefault(slog.New(bridge.NewSlogHandler()))
mux := http.NewServeMux()
mux.HandleFunc("POST /orders", func(w http.ResponseWriter, r *http.Request) {
ctx, span := tracelit.StartServerSpan(r.Context(), "create-order")
defer span.End()
slog.InfoContext(ctx, "creating order")
if err := processOrder(ctx); err != nil {
span.RecordError(err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
})
srv := &http.Server{
Addr: ":8080",
Handler: middleware.NewHTTPHandler(mux),
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)
go func() { _ = srv.ListenAndServe() }()
<-quit
_ = srv.Shutdown(context.Background())
}