integration
Go Integration
Send structured logs from Go applications to LogTide using the official Go SDK.
- DSN-based setup
- log/slog handler
- net/http middleware
- OpenTelemetry support
Send structured logs from any Go application to LogTide using the official SDK. It handles batching, retries, and circuit breaking automatically. This page covers the Go-specific patterns: where the client lives, how context.Context carries request state, how to plug in log/slog, and how to shut down without losing the last batch. The Go SDK reference has the full API.
Installation
go get github.com/logtide-dev/logtide-sdk-go
Requires Go 1.23+.
Quick Start
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",
})
defer flush()
logtide.Info(context.Background(), "Application started", map[string]any{
"version": "1.2.3",
})
}
Global Init or explicit client
The SDK supports two styles, and the choice mostly depends on how your code is structured.
logtide.Init(...) | logtide.NewClient(opts) | |
|---|---|---|
| Access | Package functions (logtide.Info) | Methods on the client you pass around |
| Teardown | defer flush() | defer client.Close() |
| Good for | Small services, main packages | Libraries, tests, several clients in one process |
| Returns an error | No | Yes, so you can fail on a bad DSN |
opts := logtide.NewClientOptions()
opts.DSN = "https://[email protected]"
opts.Service = "my-service"
opts.Release = "v1.2.3"
opts.Environment = "production"
opts.Tags = map[string]string{"region": "eu-west-1"}
client, err := logtide.NewClient(opts)
if err != nil {
log.Fatal(err)
}
defer client.Close()
Passing context.Context
Every log call takes a context.Context as its first argument. That context is how the SDK finds the active OpenTelemetry span, so pass the request context (r.Context()), not context.Background(), whenever you have one. Two consequences follow:
- Trace and span IDs are attached automatically when the context carries an active span.
- The context is also where per-request state can live. The SDK offers
logtide.SetHubOnContext, which stores a cloned hub in the context so downstream functions can use it.
func ordersHandler(w http.ResponseWriter, r *http.Request) {
hub := logtide.CurrentHub().Clone()
hub.ConfigureScope(func(s *logtide.Scope) {
s.SetTag("request_id", r.Header.Get("X-Request-ID"))
})
ctx := logtide.SetHubOnContext(r.Context(), hub)
hub.Info(ctx, "request received", nil)
if err := processOrder(ctx); err != nil {
// client comes from logtide.NewClient; captures the error with a stack trace
client.CaptureError(ctx, err, map[string]any{"handler": "orders"})
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
If you use the net/http middleware below, per-request scope isolation is already done for you, so reach for a manual hub only when you run outside HTTP (a queue consumer, a cron job).
Using log/slog
If your code already logs through the standard library’s log/slog, you do not have to rewrite call sites. The logtideslog integration is a slog.Handler: attributes become entry metadata, slog groups become nested metadata objects, and attribute values that implement error are promoted to structured exceptions, which is what server-side error grouping needs.
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()
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)
A few slog habits pay off with this handler:
- Use the
...Contextvariants (slog.InfoContext(ctx, ...)) so the request context reaches the handler. - Use
logger.With("component", "billing")to attach fixed attributes once instead of repeating them at each call. - Pass errors as attributes (
"err", err), not as formatted strings, so they are promoted to structured exceptions. - Records go through the client’s normal pipeline (scope merge,
BeforeSend, sampling and batching), so the same sampling and filtering rules apply as for direct SDK calls.
The SDK documentation describes a slog handler but does not list adapters for other Go logging libraries such as zap or zerolog. If your code base is on one of those, you can call the LogTide client alongside it for the events you care about, or migrate gradually to slog, which the SDK supports directly.
HTTP Middleware
The built-in net/http middleware handles scope isolation, HTTP tagging, Traceparent parsing, and request/response breadcrumbs per request:
import lnethttp "github.com/logtide-dev/logtide-sdk-go/integrations/nethttp"
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", usersHandler)
http.ListenAndServe(":8080", lnethttp.Middleware(mux))
Because it wraps any http.Handler, it works the same way with the standard ServeMux as with routers that expose an http.Handler.
Outbound calls
To keep a trace connected across services, wrap the transport of your http.Client with logtide.WrapTransport. It injects the W3C traceparent header on outbound requests when a trace is active:
httpClient := &http.Client{Transport: logtide.WrapTransport(nil)} // nil uses http.DefaultTransport
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.upstream.dev/users", nil)
resp, err := httpClient.Do(req)
Create the request with the request-scoped ctx, otherwise there is no trace context to propagate.
OpenTelemetry
Trace and span IDs are extracted automatically from any active OTel span in the context:
ctx, span := tracer.Start(ctx, "process-order")
defer span.End()
// trace_id and span_id included automatically
client.Info(ctx, "Order processed", map[string]any{"order_id": id})
The SDK also has span and metric exporters (otelexport, otelmetric); see the Go SDK reference for setup.
Graceful Shutdown
Logs are batched, so the last few entries sit in memory until the next flush. Stop the HTTP server first so no new requests produce logs, then let the deferred flush run:
func main() {
flush := logtide.Init(opts)
defer flush() // runs last, after the server has stopped
srv := &http.Server{Addr: ":8080", Handler: lnethttp.Middleware(mux)}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logtide.Error(context.Background(), "server failed", map[string]any{"err": err.Error()})
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}
Note that os.Exit and log.Fatal skip deferred functions, so do not call them after Init for normal shutdown paths. In containers, make sure the orchestrator’s termination grace period is longer than your shutdown timeout. See the Docker and Kubernetes guides.
Next Steps
- Go SDK reference - full API documentation
- Docker Integration - container deployment
- Kubernetes Integration - cluster logging
Frequently Asked Questions
How do I send Go application logs to LogTide?
Run go get github.com/logtide-dev/logtide-sdk-go, then call logtide.Init() with your DSN and service name. Use logtide.Info(), logtide.Error(), and similar functions, and defer the returned flush function to ensure buffered logs are flushed on shutdown.
Does the Go SDK support structured log metadata?
Yes. Every log call accepts a map[string]any as its third argument, which is sent as structured JSON metadata alongside the service name, message, and log level.
Can I keep using log/slog in my Go code?
Yes. The logtideslog integration provides a slog.Handler. Register it with slog.SetDefault and existing slog calls flow to LogTide, with attributes becoming metadata and error values promoted to structured exceptions.
How do I add LogTide logging to a Go HTTP server without changing every handler?
Use the built-in net/http middleware by wrapping your mux with lnethttp.Middleware(mux). It handles scope isolation, HTTP tagging, and breadcrumbs per request automatically.
Does LogTide integrate with OpenTelemetry in Go?
Yes. If an active OpenTelemetry span is present in the context passed to a log call, the SDK automatically extracts and attaches the trace_id and span_id to the log entry.