Official Go SDK for LogTide with automatic batching, retry logic, circuit breaker pattern, Hub/Scope context isolation, and native OpenTelemetry integration.
Installation
go get github.com/logtide-dev/logtide-sdk-go Requires Go 1.23 or later.
Quick Start
Global singleton (recommended)
package main
import (
"context"
logtide "github.com/logtide-dev/logtide-sdk-go"
)
func main() {
flush := logtide.Init(logtide.ClientOptions{
DSN: "https://[email protected]",
Service: "my-service",
Environment: "production",
Release: "v1.2.3",
})
defer flush()
ctx := context.Background()
logtide.Info(ctx, "Server started", map[string]any{"port": 8080})
logtide.Error(ctx, "Connection failed", map[string]any{"host": "db.example.com"})
} Explicit client
opts := logtide.NewClientOptions()
opts.DSN = "https://[email protected]"
opts.Service = "my-service"
client, err := logtide.NewClient(opts)
if err != nil {
log.Fatal(err)
}
defer client.Close()
client.Info(context.Background(), "Hello LogTide!", nil) Configuration
opts := logtide.NewClientOptions()
// Identity
opts.DSN = "https://[email protected]" // required
opts.Service = "my-service" // required
opts.Release = "v1.2.3"
opts.Environment = "production"
opts.Tags = map[string]string{"region": "eu-west-1"}
// Batching
opts.BatchSize = 100 // entries per HTTP batch (default: 100)
opts.FlushInterval = 5 * time.Second // (default: 5s)
opts.FlushTimeout = 10 * time.Second // (default: 10s)
// Reliability
opts.MaxRetries = 3
opts.RetryMinBackoff = 1 * time.Second
opts.RetryMaxBackoff = 60 * time.Second
opts.CircuitBreakerThreshold = 5 // failures before open (default: 5)
opts.CircuitBreakerTimeout = 30 * time.Second
// Stack traces
opts.AttachStacktrace = logtide.Bool(true) // default: true Logging Methods
All methods return an EventID (empty string if the entry was dropped).
ctx := context.Background()
client.Debug(ctx, "cache miss", map[string]any{"key": "user:42"})
client.Info(ctx, "request handled", map[string]any{"status": 200, "ms": 12})
client.Warn(ctx, "rate limit approaching", nil)
client.Error(ctx, "db query failed", map[string]any{"query": "SELECT ..."})
client.Critical(ctx, "out of memory", nil) Hub & Scope
The Hub/Scope model lets you attach tags, breadcrumbs, and user info to all log entries within a logical unit of work — without passing extra parameters everywhere.
Global scope
logtide.ConfigureScope(func(s *logtide.Scope) {
s.SetTag("region", "eu-west-1")
s.SetUser(logtide.User{ID: "u123", Email: "[email protected]"})
}) Per-request isolation
logtide.PushScope()
defer logtide.PopScope()
logtide.ConfigureScope(func(s *logtide.Scope) {
s.SetTag("request_id", requestID)
s.AddBreadcrumb(&logtide.Breadcrumb{
Category: "auth",
Message: "user authenticated",
Level: logtide.LevelInfo,
Timestamp: time.Now(),
}, nil)
})
// All log calls here include request_id tag + breadcrumb
logtide.Info(ctx, "processing order", nil) Per-request with explicit Hub
hub := logtide.CurrentHub().Clone()
hub.ConfigureScope(func(s *logtide.Scope) {
s.SetTag("request_id", r.Header.Get("X-Request-ID"))
})
// Inject into context for use downstream
ctx := logtide.SetHubOnContext(r.Context(), hub)
hub.Info(ctx, "request received", nil) Error Capture
result, err := db.Query(ctx, "SELECT ...")
if err != nil {
// Captures the error with full stack trace automatically
client.CaptureError(ctx, err, map[string]any{
"query": "SELECT ...",
"user": userID,
})
} slog Handler
The logtideslog integration provides a
log/slog handler, so existing slog-based logging
flows to LogTide without code changes. Records honour the client's full pipeline — scope merge,
processors, BeforeSend, sampling, and batching.
import (
"log/slog"
logtide "github.com/logtide-dev/logtide-sdk-go"
"github.com/logtide-dev/logtide-sdk-go/integrations/logtideslog"
)
client, err := logtide.NewClient(logtide.ClientOptions{
DSN: "https://[email protected]",
Service: "my-service",
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
// opts may be nil (defaults to slog.LevelInfo).
handler := logtideslog.New(client, &logtideslog.Options{Level: slog.LevelDebug})
slog.SetDefault(slog.New(handler))
slog.Info("user signed in", "user_id", 42, "plan", "pro")
slog.Error("checkout failed", "err", err, "order_id", 1234)
Attributes become entry metadata, slog groups become nested metadata objects, and attribute
values implementing error are promoted to structured
exceptions so server-side error grouping works.
OpenTelemetry Integration
Automatic trace context
tracer := otel.Tracer("my-service")
func handleRequest(ctx context.Context) {
ctx, span := tracer.Start(ctx, "handle-request")
defer span.End()
// trace_id and span_id are included automatically
client.Info(ctx, "Processing request", map[string]any{"user_id": 123})
} Span exporter
import "github.com/logtide-dev/logtide-sdk-go/integrations/otelexport"
integration := otelexport.New()
flush := logtide.Init(logtide.ClientOptions{
DSN: "https://[email protected]",
Service: "my-service",
Integrations: func(defaults []logtide.Integration) []logtide.Integration {
return append(defaults, integration)
},
})
defer flush()
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(integration.Exporter()),
) Metric exporter
Export OpenTelemetry metrics (counters, gauges, histograms) to LogTide. Each data point becomes a log entry carrying the same service name, environment, tags, and resource attributes as your logs and traces. It is opt-in — register it only if you need metrics.
import (
"go.opentelemetry.io/otel"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"github.com/logtide-dev/logtide-sdk-go/integrations/otelmetric"
)
integration := otelmetric.New()
flush := logtide.Init(logtide.ClientOptions{
DSN: "https://[email protected]",
Service: "my-service",
Integrations: func(defaults []logtide.Integration) []logtide.Integration {
return append(defaults, integration)
},
})
defer flush()
// A PeriodicReader collects metrics on an interval and hands them to the exporter.
mp := sdkmetric.NewMeterProvider(
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(integration.Exporter())),
)
otel.SetMeterProvider(mp)
When exemplars are enabled and a sampled span is active during measurement, each linked
data point's entry inherits the exemplar's trace_id/span_id,
and the full exemplar list is recorded under metadata.metric.exemplars —
correlating metrics with the traces that produced them.
Outbound trace propagation
Wrap an http.Client transport with
WrapTransport to inject the W3C
traceparent header on outbound requests, propagating the
current trace context to downstream services. The trace context is sourced from the active
OTel span, falling back to the scope carried by the request context. Requests with no active
trace, or with a traceparent already set, pass through untouched.
// A nil base uses http.DefaultTransport.
client := &http.Client{Transport: logtide.WrapTransport(nil)}
// Pass the request-scoped context so the trace context propagates.
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.upstream.dev/users", nil)
resp, err := client.Do(req) HTTP Middleware
The built-in middleware automatically isolates scope per request, sets HTTP tags,
parses Traceparent headers, and adds request/response breadcrumbs.
import lnethttp "github.com/logtide-dev/logtide-sdk-go/integrations/nethttp"
mux := http.NewServeMux()
mux.HandleFunc("/users", usersHandler)
// Wrap with LogTide middleware
http.ListenAndServe(":8080", lnethttp.Middleware(mux)) Best Practices
defer flush() (global) or
defer client.Close() (explicit client)
immediately after initialisation to guarantee buffered entries are delivered on shutdown.
hub.Clone() at the start of each
request or goroutine to get an isolated scope.
CaptureError attaches a full stack trace automatically.
Use it instead of Error whenever you have a Go
error value.
nethttp.Middleware handles scope isolation,
HTTP tagging, and breadcrumbs automatically — no boilerplate needed.