LogTide

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,
    })
}

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()),
)

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

1. Always defer flush/Close
Use defer flush() (global) or defer client.Close() (explicit client) immediately after initialisation to guarantee buffered entries are delivered on shutdown.
2. Clone the Hub per request
Never share a Hub across goroutines with different lifecycles. Call hub.Clone() at the start of each request or goroutine to get an isolated scope.
3. Use CaptureError for Go errors
CaptureError attaches a full stack trace automatically. Use it instead of Error whenever you have a Go error value.
4. Use the net/http middleware
nethttp.Middleware handles scope isolation, HTTP tagging, and breadcrumbs automatically — no boilerplate needed.
Esc

Type to search across all documentation pages