Go SDK

Full observability for Go services — HTTP, databases, gRPC, and logging captured automatically via OpenTelemetry

Built on OpenTelemetry

The Go SDK is a thin wrapper that configures OpenTelemetry with the Obtrace ingest endpoint. It uses OTel contrib packages (otelhttp, otelsql, otelgrpc) to instrument your application. You get automatic spans for HTTP, database, and gRPC operations out of the box.

Installation

go get github.com/obtraceai/obtrace-sdk-go

Minimum Setup

main.go
package main
 
import (
	"context"
	"os"
 
	ob "github.com/obtraceai/obtrace-sdk-go/pkg/obtrace"
)
 
func main() {
	client := ob.NewClient(ob.Config{
		APIKey:        os.Getenv("OBTRACE_API_KEY"),
		ServiceName:   "checkout-api",
	})
	defer client.Shutdown(context.Background())
}

Set OBTRACE_API_KEY in your environment. ServiceName should be a stable, lowercase identifier that matches what your team calls the service (e.g., checkout-api, billing-worker).

What's Captured Automatically

After ob.NewClient(), the SDK configures OpenTelemetry and instruments Go's standard library and common packages. You don't need to write any extra code for this:

WhatHowNeeds code?
All stdlib log outputlog.Println(), log.Printf(), log.Fatalf() etc. are intercepted and sent as structured logsNo
Outbound HTTP callshttp.DefaultTransport is wrapped via otelhttp to emit spans with method, URL, status, and duration, and inject traceparent headersNo
Inbound HTTPUse otelhttp.NewHandler() or framework-specific contrib middleware (gin, echo, chi, mux, fiber)Wrap handler
database/sqlUse otelsql to wrap your database driver for automatic query spansWrap driver
gRPCUse otelgrpc interceptors for automatic client and server spansAdd interceptor

Every http.Get(), http.Post(), or request made through http.DefaultClient is automatically traced. Trace context is propagated to downstream services via the traceparent header.

OTel contrib packages for Go require explicit wiring (unlike dynamic languages), but the SDK pre-configures the OTel exporter and resource so you only need to add the middleware or wrapper.

Opting Out of Auto HTTP Instrumentation

client := ob.NewClient(ob.Config{
	APIKey:          os.Getenv("OBTRACE_API_KEY"),
	ServiceName:     "checkout-api",
	DisableAutoHTTP: true,
})

Optional: Custom Telemetry

The automatic log and HTTP capture covers basic observability. For business-specific events, use the SDK methods directly.

Logging

Use client.Log(level, message, ctx) to record events you need to search for later. Good candidates:

  • Errors and panics recovered in middleware
  • Business events (order placed, payment failed, user signed up)
  • Audit trail entries (permission changed, config updated)
  • State transitions (circuit breaker opened, cache flushed)
client.Log("ERROR", "payment.declined", &ob.Context{
	Attrs: map[string]any{
		"order_id":    orderID,
		"provider":    "stripe",
		"decline_code": resp.DeclineCode,
	},
})
 
client.Log("INFO", "user.signup", &ob.Context{
	Attrs: map[string]any{
		"plan": "startup",
	},
})

The level parameter accepts standard syslog levels: DEBUG, INFO, WARN, ERROR, FATAL. Messages are truncated at 32KB.

The ctx parameter is optional. When provided, it attaches structured attributes to the log entry. Use Attrs for custom key-value pairs, and the built-in fields (TraceID, SpanID, Method, Endpoint, StatusCode) to correlate logs with traces.

Metrics

Use client.Metric(name, value, unit, ctx) to record measurements you want to graph or alert on. Good candidates:

  • Latency measurements (how long did this take?)
  • Counters (how many orders per minute?)
  • Gauges (current queue depth, active connections)
  • Business KPIs (revenue per checkout, items per cart)
client.Metric("http.server.duration", 142.5, "ms", &ob.Context{
	Method:   "POST",
	Endpoint: "/api/checkout",
})
 
client.Metric("checkout.revenue", 49.99, "USD", nil)
 
client.Metric("queue.depth", float64(len(workQueue)), "1", nil)

The unit parameter follows OTLP conventions: "ms" for milliseconds, "By" for bytes, "1" for dimensionless values (counts, ratios), or a currency code.

Tracing / Spans

Use client.Span(...) to track a unit of work and its duration. Returns (traceID, spanID) so you can propagate context to downstream services. Good candidates:

  • HTTP handler execution (request in, response out)
  • Database queries
  • External API calls
  • Background job processing
  • Any operation where you need to see where time was spent
traceID, spanID := client.Span("db.query", "", "", 0, "", map[string]any{
	"db.system":    "postgresql",
	"db.statement": "SELECT * FROM orders WHERE id = $1",
})
 
client.Span("http.client POST /payments", traceID, "", 0, "", map[string]any{
	"http.method": "POST",
	"http.url":    "https://api.stripe.com/v1/charges",
})

Pass an empty string for traceID and spanID to auto-generate them. Pass an existing traceID to group spans into the same trace. The statusCode uses OTLP conventions: 0 = unset, 1 = OK, 2 = error.

Propagating Trace Context

Trace context is injected automatically on outbound HTTP calls made through http.DefaultTransport. For custom transports or non-HTTP protocols, inject manually:

traceID, spanID := client.Span("http.client GET /inventory", "", "", 0, "", nil)
 
req, _ := http.NewRequest("GET", "https://inventory-api.internal/stock", nil)
client.InjectPropagation(req.Header, traceID, spanID, "")
resp, err := customClient.Do(req)

Framework Integration

net/http Middleware

The SDK includes middleware for net/http that uses otelhttp under the hood to create a span and log entry for each inbound request:

main.go
package main
 
import (
	"context"
	"net/http"
	"os"
 
	ob "github.com/obtraceai/obtrace-sdk-go/pkg/obtrace"
	mw "github.com/obtraceai/obtrace-sdk-go/middleware/nethttp"
)
 
func main() {
	client := ob.NewClient(ob.Config{
		APIKey:        os.Getenv("OBTRACE_API_KEY"),
		ServiceName:   "checkout-api",
	})
	defer client.Shutdown(context.Background())
 
	mux := http.NewServeMux()
	mux.HandleFunc("/checkout", handleCheckout)
 
	http.ListenAndServe(":8080", mw.Middleware(client, mux))
}
 
func handleCheckout(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"ok":true}`))
}

The middleware emits one span (http.server METHOD) and one log (http request done) per request, with method, route, status code, and duration attached.

Gin, Echo, Chi, Gorilla Mux, and Fiber middleware are also available under middleware/gin, middleware/echo, middleware/chi, middleware/mux, and middleware/fiber. These use the corresponding OTel contrib packages internally.

Configuration Reference

FieldTypeDefaultDescription
APIKeystring""Required. Your Obtrace API key.
ServiceNamestring""Required. Stable name for this service.
ServiceVersionstring""Deployment version (git SHA, semver, date).
TenantIDstring""Scoped ingest identity.
ProjectIDstring""Scoped ingest identity.
AppIDstring""Scoped ingest identity.
Envstring""Environment name (prod, staging, dev).
DisableAutoHTTPboolfalseDisable automatic http.DefaultTransport instrumentation.
RequestTimeoutMSint5000HTTP timeout per OTLP request in milliseconds.
MaxQueueSizeint1000Max queued telemetry items before oldest are dropped.
ValidateSemanticMetricsboolfalseWhen true + Debug, warns on non-canonical metric names.
DebugboolfalseEnables SDK diagnostic output to stdout.
DefaultHeadersmap[string]stringnilExtra headers sent with every OTLP request.
TraceHeaderNamestring""Custom trace propagation header name.
SessionHeaderNamestring""Custom session propagation header name.

Validation Checklist

After integrating the SDK, verify these before shipping to production:

  • ServiceName, Env, and ServiceVersion are set and survive restarts and deploys
  • At least one request path emits both a log and a span
  • client.Flush() or client.Shutdown() is called before process exit (use defer)
  • No 401 or 403 errors appear in logs during OTLP submission (check with Debug: true)
  • Metrics use correct units (ms, By, 1) not free-form strings
  • Outbound HTTP calls via http.DefaultTransport show auto-generated spans (powered by otelhttp)
  • Custom HTTP clients with non-default transports use InjectPropagation manually
  • Database queries via database/sql show spans when using otelsql driver wrapper
  • gRPC calls show spans when using otelgrpc interceptors

See also: Semantic Metrics

On this page