SDK & Framework Guides

Go Error Tracking for Microservices

Capture panics and errors across Go services with the Sentry SDK, propagate trace context, and correlate failures across your microservices.

Go's error handling is minimal by design — if err != nil is simple and explicit. But in production microservices, that simplicity breaks down fast. Panics crash goroutines silently, errors propagate across the network without context, and by the time you notice a service is flaking, you've lost hours to debugging. Go error tracking with the Sentry SDK solves this: you get automatic panic capture, full request context, and distributed tracing across your service mesh so you can spot the broken service in minutes instead of hours.

This guide walks through setting up production-grade error tracking in Go, from a single service to a full microservices architecture with trace correlation.

Install and initialize the SDK

The Sentry Go SDK is Sentry-SDK-compatible, so you point it at LightTrace by setting the DSN. Installation is one line:

go get github.com/getsentry/sentry-go

Then initialize it early in your main() function, before any goroutines start:

package main

import (
	"github.com/getsentry/sentry-go"
	"time"
)

func main() {
	err := sentry.Init(sentry.ClientOptions{
		Dsn: "https://<key>@your-lighttrace-host/1",
		Environment: "production",
		Release: "api@1.2.3",
		TracesSampleRate: 1.0,
	})
	if err != nil {
		log.Fatalf("sentry.Init failed: %v", err)
	}
	defer sentry.Flush(2 * time.Second)

	// Start your app here
	startServer()
}

That's it. The SDK is now installed and ready to capture panics and unhandled errors. The defer sentry.Flush() call ensures all events are sent before the process exits.

Capture panics automatically

In Go, a panic in a goroutine crashes only that goroutine, not the whole process — but you'll never know it happened unless you're capturing it. The Sentry SDK wraps goroutines so panics are reported automatically.

The pattern is simple: instead of go myFunc(), use sentry.GetHub().RecoverWithContext() inside the goroutine:

go func() {
	defer sentry.RecoverWithContext(ctx)
	// Your code here — if it panics, Sentry catches it
	processMessage(msg)
}()

Or wrap the handler with sentry.PanicHandler() if you're using http.Handler:

http.HandleFunc("/api/process", sentry.PanicHandler(handleProcess))

func handleProcess(w http.ResponseWriter, r *http.Request) {
	// If a panic happens here, it's captured and reported
	result := expensiveComputation(r.URL.Query())
	json.NewEncoder(w).Encode(result)
}

Always defer the recovery function at the start of the goroutine, not at the start of main. Each goroutine needs its own recovery handler so panics don't slip through the cracks.

Capture errors with full context

Not every error is a panic. Errors that are caught and logged still matter — they're just silent when there's no error tracker. Explicitly report them:

result, err := fetchUserData(userID)
if err != nil {
	sentry.CaptureException(err)
	http.Error(w, "User not found", http.StatusNotFound)
	return
}

Better yet, attach context so you know which user and which request failed:

sentry.WithScope(func(scope *sentry.Scope) {
	scope.SetUser(sentry.User{
		ID:    userID,
		Email: user.Email,
	})
	scope.SetContext("request", map[string]interface{}{
		"endpoint": r.URL.Path,
		"method":   r.Method,
		"ip":       r.RemoteAddr,
	})
	sentry.CaptureException(err)
})

Now every reported error arrives in your dashboard with the user ID, email, and request details — everything you need to reproduce the bug.

Wire it into your HTTP middleware

The cleanest way is to add Sentry as middleware once, and every request gets automatic context:

func sentryMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hub := sentry.CurrentHub().Clone()
		ctx := r.Context()

		hub.Scope().SetUser(sentry.User{
			ID: getUserID(r),
		})
		hub.Scope().SetContext("http", map[string]interface{}{
			"method":   r.Method,
			"url":      r.URL.String(),
			"status":   http.StatusOK, // You'll update this after
		})

		ctx = hub.Scope().Context(ctx)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

// Use it:
mux := http.NewServeMux()
mux.HandleFunc("/api/users", sentryMiddleware(handleUsers))

Now every error or panic in any handler automatically includes the HTTP method, URL, and user context — no boilerplate needed per handler.

Propagate trace context across services

In a microservices architecture, a user request often spans multiple services. Go error tracking only gets powerful when you can see the full path. The SDK automatically creates traces and propagates trace IDs across HTTP calls:

ctx := r.Context()
transaction := sentry.StartTransaction(ctx, "fetch-user-data")
defer transaction.Finish()

// Make a downstream call
req, _ := http.NewRequestWithContext(ctx, "GET", 
	"http://userservice:8080/user/123", nil)

// The trace ID is automatically added to the headers
resp, err := http.DefaultClient.Do(req)
if err != nil {
	sentry.CaptureException(err)
}

The userservice on the other end sees the trace ID and links its own errors back to the originating request. This is distributed tracing — one request, multiple services, one waterfall in your dashboard so you can see exactly where it slowed down or broke.

For tighter correlation across your whole microservices mesh, see the cross-project tracing guide.

Scrub sensitive data before sending

Errors can leak database connection strings, API keys, PII, and passwords. Before initializing the SDK, set up a BeforeSend hook to scrub sensitive fields:

err := sentry.Init(sentry.ClientOptions{
	Dsn: "...",
	BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
		// Remove sensitive keys from exceptions and breadcrumbs
		for _, exc := range event.Exception {
			if exc.Value != "" {
				// Remove SQL connection strings, tokens, etc.
				exc.Value = strings.ReplaceAll(exc.Value, 
					"password="+os.Getenv("DB_PASSWORD"), 
					"password=***")
			}
		}
		return event
	},
})

The default SDK scrubs HTTP headers and cookies automatically. Add custom scrubbing only for fields unique to your app.

Deploy and monitor

Once initialized, your Go services start reporting errors and panics to LightTrace. Tag every release with a version so you can correlate errors to deployments:

sentry.Init(sentry.ClientOptions{
	Dsn: "...",
	Release: "backend@" + os.Getenv("APP_VERSION"),
	Environment: os.Getenv("ENV"),
})

Now when you see a spike of errors after a deploy, group them by release to pinpoint the exact code change that broke. Combine this with faster debugging via MTTR to go from incident to rollback or fix in minutes instead of hours.

Start tracking errors in minutes

Set up Go error tracking in minutes — initialize the Sentry SDK pointed at LightTrace and get automatic panic capture across your microservices.

That's the complete setup: panics caught, errors grouped, traces correlated, and sensitive data scrubbed. For a framework-agnostic overview, see how to add error tracking to any app. For Go-specific best practices, error tracking best practices covers the strategies that work across all languages.

Fix your next production error faster

Point any Sentry SDK at LightTrace — free up to 5,000 events/month.