Go
evolve-logs-go is the SDK for Go 1.22 or later: net/http services, gRPC servers, and workers consuming Pub/Sub, SQS or Cloud Tasks. Standard library only — no third-party dependencies. It speaks the same wire contract as the Node.js and Python SDKs, so a trace that crosses all three is one trace on the graph.
Install and initialise
go get github.com/Pactify-Pty-Ltd/evolve-logs-go
package main
import (
"context"
logs "github.com/Pactify-Pty-Ltd/evolve-logs-go"
)
func main() {
logs.Init(logs.Options{
Key: "evk_…", // the project's ingest key
URL: "https://api.e-volv.io/api/public/v1/logs",
Service: "orders-api",
Environment: "production",
Release: "1.4.2",
})
defer logs.Flush(context.Background())
// ...
}Options: Key, URL, Service, Environment, Release, RedactKeys (merged into the backstop regex) and SampleRate (0–1, default 1). logs.New(opts) builds a non-default client; the package-level helpers delegate to the client installed by logs.Init. Without a key and URL the client is a no-op and warns once; it never panics into your code.
Logs
logs.Info(ctx, "order created", map[string]any{"orderId": "o_1", "total": 42.5})
logs.Warn(ctx, "retrying payment", map[string]any{"orderId": "o_1", "attempt": 2})
logs.Error(ctx, "payment failed", map[string]any{"orderId": "o_1"})Errors
Pass the error value. The SDK sends err.Error() as the message with exception.type (the concrete type name), exception.message and a runtime/debug stack as exception.stack — an error occurrence with a stack on the group page.
if _, err := charge(ctx, order); err != nil {
logs.Exception(ctx, err, map[string]any{"orderId": order.ID})
}Traces and spans
Trace context lives in context.Context — an explicit value, not goroutine-local — so every API takes a ctx and flows wherever you pass it.
ctx, span := logs.StartSpan(ctx, "db.query", map[string]any{"table": "orders"})
rows, err := db.QueryContext(ctx, "SELECT …")
span.End(err) // err != nil ends the span as failed; End is idempotentStartSpan starts a child span of the trace in ctx (or a new root trace) and returns the span’s context plus a handle. The span end is recorded as an event with span.name and durationMs, carrying the span’s trace and span ids, so traces assemble on the graph from ordinary log events. logs.Traceparent(ctx) returns the W3C 00-<traceId>-<spanId>-01 header, or an empty string outside a trace.
net/http
// Server: one root span per request; an inbound traceparent is continued.
http.Handle("/api/", logs.Middleware(handler))
// Client: inject traceparent into outbound requests made inside a trace,
// never overwriting an existing header.
client := &http.Client{Transport: logs.Transport(http.DefaultTransport)}A handler that panics ends the http.request span as failed and is re-panicked, so the server crashes exactly as it would without the SDK.
Queue hops: Pub/Sub, SQS and Cloud Tasks
// producer: the carrier rides with the message
carrier := logs.Inject(ctx, map[string]string{})
// publish `carrier` as Pub/Sub attributes, SQS message attributes
// or Cloud Tasks headers
// consumer: a new hop of the producer's trace
ctx := logs.Extract(context.Background(), carrier)
logs.Info(ctx, "job received", map[string]any{"jobId": job.ID})
ctx, span := logs.StartSpan(ctx, "queue.work")
handle(ctx, job)
span.End(nil)Every queue speaks string maps, so Inject and Extract cover Pub/Sub attributes, SQS message attributes and Cloud Tasks headers. For anything else, logs.RunWithTraceparent(ctx, header, fn) runs fn with the next hop of a raw W3C traceparent: same trace id, new span id, the header’s span id as parent. An absent or malformed header starts a fresh trace, so a producer that sends nothing still yields a trace of its own.
Panics in handlers and goroutines
http.HandleFunc("/work", func(w http.ResponseWriter, r *http.Request) {
logs.Recover(r.Context(), func(ctx context.Context) { handle(ctx, w, r) })
})
go logs.Recover(ctx, func(ctx context.Context) { poll(ctx) })logs.Recover reports the panic through Exception and re-panics — the process still crashes as before, but the error reaches Observer first.
Delivery
Batches flush at 200 events, every 2 seconds or at 512 KB, always gzipped. A 429 honours Retry-After, otherwise exponential backoff from 500 ms (doubling, capped at 10 s), up to three attempts; a 413 halves the batch and drops the excess half. When the pending buffer exceeds twice the batch size the oldest events are dropped and counted on client.Dropped(). A background flusher sends every 2 seconds, and logs.Flush(ctx) drains what is pending — safe to call repeatedly, also in a defer at shutdown.
Package reference: packages/logs-go. Back to SDK overview.