Golang Application Monitoring: The Complete Guide to Go APM, Tracing & Profiling
A goroutine leak took down a payments service at 2:14 a.m. Memory climbed for six hours before anyone noticed, because nothing was watching goroutine count, only CPU and uptime. That's the story behind most Go production incidents: the language's biggest strengths like cheap concurrency, automatic memory management are also exactly what generic monitoring tools fail to see.
This guide covers everything needed to actually monitor a Go application in production: the runtime signals that matter, how to instrument with pprof and OpenTelemetry, what to track in Kubernetes, and how to choose a tool that understands Go instead of treating it like any other backend.
In this Blog Post,
- Why Monitoring Go Applications Is Different?
- Common Production Problems in Go Applications
- Golden Signals for Go Services
- Go Runtime Metrics Every Team Should Track
- Distributed Tracing & OpenTelemetry for Go
- Database Performance & Slow API Detection
- Monitoring Go Workloads in Kubernetes
- Continuous Profiling
- Incident Response Workflow
- Choosing the Right Go Monitoring Tool
- Go Monitoring Best Practices
- Common Mistakes
- Production Readiness Checklist
- Get Started with Atatus
Why Monitoring Go Applications Is Different?
Go's runtime is fundamentally different from a JVM or Node.js event loop, and that difference shows up directly in what "healthy" looks like. Instead of OS threads, Go schedules lightweight goroutines, you can run hundreds of thousands of them on a handful of OS threads. Instead of manual memory management or a JVM-style generational GC, Go uses a concurrent, tri-color mark-and-sweep collector tuned for low pause times.
Generic APM agents built primarily for request/response languages track latency, throughput, and error rate well, but they were not designed around Go's scheduler and memory model. That means goroutine leaks, channel deadlocks, and GC pressure - the failure modes most specific to Go, often go unmonitored until they cause an outage.
What is Golang application monitoring?
Golang application monitoring is the practice of collecting metrics, logs, and traces from a Go program to understand its runtime behavior in production, including request-level performance data and Go-specific runtime signals such as goroutine counts, GC pauses, and heap allocation, so teams can detect degradation before it affects users.
Common Production Problems in Go Applications
- Goroutine leaks - goroutines blocked forever on a channel or waiting on a mutex that never releases, slowly consuming memory.
- GC pressure - excessive short-lived allocations in hot paths driving up GC CPU usage and pause frequency.
- Channel deadlocks - two or more goroutines waiting on each other's channel sends/receives, freezing part of the system.
- Connection pool exhaustion -
database/sqlpools with too-lowMaxOpenConns, causing request queuing under load. - Lock contention - heavy
sync.Mutexusage on shared state serializing what should be concurrent work. - CPU throttling in containers - GOMAXPROCS not matching the container's actual CPU quota.
- Silent panics in goroutines - an unrecovered panic in a spawned goroutine crashing the whole process.
Slow request buried in five microservices? Atatus traces it end-to-end in one click
Golden Signals for Go Services
Google's SRE book defines four golden signals such as latency, traffic, errors, and saturation as the minimum viable monitoring set for any service. For Go specifically, saturation needs to include runtime saturation, not just CPU and memory:
| Signal | What to Measure | Go-Specific Addition |
|---|---|---|
| Latency | p50 / p95 / p99 request duration | Watch tail latency (p99). GC pauses often appear here first. |
| Traffic | Requests per second | Correlate with goroutine count growth. |
| Errors | Error rate, panic count | Include recovered panics from goroutines, not just HTTP 5xx. |
| Saturation | CPU and memory utilization | Add goroutine count, GC CPU fraction, and scheduler run queue length. |
Go Runtime Metrics Every Team Should Track
Beyond the golden signals, the Go runtime exposes internal metrics through the runtime and runtime/metrics packages that give early warning of exactly the problems generic monitoring misses.
Goroutine Monitoring
How do you monitor goroutines in production? Expose runtime.NumGoroutine() as a gauge metric scraped on an interval, and enable net/http/pprof so you can pull a full goroutine dump on demand. A healthy service holds a roughly stable goroutine count under steady load; a count that climbs continuously, especially one that doesn't drop back down after traffic subsides is the clearest signal of a leak.
import (
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// your application code
}With this endpoint running, pull a live goroutine dump:
go tool pprof http://localhost:6060/debug/pprof/goroutine
Developer scenario: An engineering team notices p99 latency creeping up over three days with no traffic increase. A goroutine count graph shows a steady climb from 400 to 40,000 goroutines. The dump reveals thousands of goroutines blocked on a channel receive inside a retry loop that never got a context cancellation - the fix is a five-line change, but finding it without goroutine tracking would have taken hours of guesswork.
Memory Leak Detection
How do you detect a memory leak in a Go application? Track heap allocation and process RSS over time, then capture two heap profiles a few minutes apart under load and diff them to see which allocations keep growing.
# Capture a heap profile
go tool pprof http://localhost:6060/debug/pprof/heap
# Inside the pprof shell, list the biggest allocators
(pprof) top10
(pprof) list <suspect_function>- Common causes: unbounded in-memory caches, slices appended to without ever being trimmed or reset, goroutine leaks holding references alive, and forgotten
time.Ticker/time.Timerinstances that are never stopped. - Best practice: always pair
defer cancel()with everycontext.WithCancel/WithTimeout, and always callticker.Stop()when a ticker goes out of scope.
GC Performance Monitoring
How does garbage collection affect Go application performance? Go's GC runs concurrently with your application and modern versions keep stop-the-world pauses sub-millisecond, but GC cycles still consume CPU, and under high allocation pressure, frequent cycles raise CPU usage and can add latency to request handling.
Key metrics to track: GC pause duration (GCCPUFraction), heap size before/after GC, and allocation rate. Reduce GC pressure by minimizing allocations in hot paths (reuse buffers with sync.Pool, avoid unnecessary string concatenation, pre-allocate slices with known capacity) and by tuning GOGC or, on Go 1.19+, setting a GOMEMLIMIT to give the collector a soft memory ceiling.
Catch goroutine leaks before they page you at 2am
Distributed Tracing & OpenTelemetry for Go
Traces show how a single request moves through every service, database call, and downstream dependency it touches, essential once a monolith becomes a handful of Go microservices.
How do you trace requests across Go microservices? Instrument each service with the OpenTelemetry Go SDK, propagate trace context (trace ID and span ID) through HTTP headers or gRPC metadata, and export spans to a tracing backend that stitches them into one end-to-end view.
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func initTracer() (*sdktrace.TracerProvider, error) {
exporter, err := otlptracehttp.New(context.Background())
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
return tp, nil
}How does OpenTelemetry work with Go? The SDK provides a TracerProvider and MeterProvider that generate traces and metrics in a vendor-neutral format. Instrumentation libraries exist for net/http, gRPC, and popular database drivers, so spans are created automatically around those calls. Everything exports over OTLP to any compatible backend including Atatus without rewriting instrumentation if you change vendors later.
Already on OpenTelemetry? Plug it into Atatus in minutes, no rip and replace
Database Performance & Slow API Detection
How do you monitor database performance in Go applications? Track query latency and error rate per query type, watch connection pool utilization (open, idle, and in-use connections from database/sql), and wrap database calls with OpenTelemetry instrumentation so database spans appear inside the same trace as the HTTP request that triggered them.
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)For slow API detection, alert on p95/p99 latency per endpoint rather than an average, which hides the tail latency that actually frustrates users, and Go services under GC pressure or lock contention typically show that pressure first in the p99.
Monitoring Go Workloads in Kubernetes
How do you monitor Go applications running in Kubernetes? Combine application-level telemetry (metrics, traces, and logs from the Go binary itself) with cluster-level signals such as pod restarts, CPU throttling, memory limits, and OOMKilled events. Run the OpenTelemetry Collector as a DaemonSet or sidecar to gather both and correlate them.
The single most common Kubernetes-specific Go issue is GOMAXPROCS misconfiguration. By default, Go sets GOMAXPROCS to the number of logical CPUs it detects on the host but in a container with a CPU limit (say, 0.5 vCPU) enforced by a cgroup quota, Go may still see all of the node's CPUs and over-schedule, leading to throttling that shows up as latency spikes with no obvious cause in application code.
import _ "go.uber.org/automaxprocs"
func main() {
// automaxprocs sets GOMAXPROCS to match
// the container's CFS CPU quota automatically
}- Correlate pod CPU throttling (
container_cpu_cfs_throttled_periods_total) with in-app GC CPU fraction - throttling makes GC pauses worse. - Watch memory limits against Go's heap target; without
GOMEMLIMITset, Go doesn't know about the container's memory ceiling and can be OOMKilled before GC kicks in aggressively enough. - Correlate pod restarts with goroutine/heap graphs from just before the crash to catch leaks that build up over hours.
Continuous Profiling
What is continuous profiling and why does it matter for Go? Continuous profiling automatically and periodically captures CPU, memory, and goroutine profiles from a running application, instead of requiring someone to manually trigger pprof during an incident. For Go services this means an engineer can open a flame graph, a visualization where bar width represents time or resource spent in a function from the exact moment latency spiked, without reproducing the issue.
This is the difference between "we think it might be JSON marshaling in the checkout handler" and "here's the exact line consuming 40% of CPU at 2:14 a.m., confirmed by the flame graph."
Incident Response Workflow
- Alert fires on an SLO burn rate or latency threshold.
- Check golden signals is it latency, error rate, or saturation (including goroutine count and GC CPU fraction)?
- Pull the trace for a slow/failed request to identify which service or downstream call is responsible.
- Cross-reference the flame graph from that time window if the bottleneck is CPU or memory rather than a downstream dependency.
- Check goroutine and heap graphs for signs of a leak if the issue built up gradually rather than spiking suddenly.
- Mitigate (scale, restart, roll back), then root-cause with the profile/trace data captured during the incident.
Choosing the Right Go Monitoring Tool
Selecting an APM tool for Go means evaluating real-time visibility, alerting, dashboarding, scalability, and specifically for Go whether the tool understands runtime signals like goroutines and GC, not just HTTP-level metrics.
| Capability | Atatus | Datadog | New Relic | Prometheus + Grafana + Jaeger |
|---|---|---|---|---|
| Metrics, logs, traces unified | ✓ Single platform | ✓ (separate pricing per pillar) | ✓ | Requires 3 separate tools |
| Go-specific runtime metrics (goroutines, GC) | ✓ Built-in | Partial, via custom metrics | Partial, via custom metrics | ✓ via client_golang, manual setup |
| Continuous profiling for Go | ✓ Included | Add-on, separately priced | Add-on, separately priced | Not included, needs Pyroscope/Parca |
| OpenTelemetry native ingestion | ✓ | ✓ | ✓ | ✓ (native OTel target) |
| Kubernetes correlation | ✓ | ✓ | ✓ | Requires manual dashboard building |
| Setup & maintenance overhead | Low — managed SaaS | Low — managed SaaS | Low — managed SaaS | High — self-hosted, multiple systems to run |
| Pricing model | Transparent, per-host | Complex, per-pillar | Complex, usage-based | Free tools, but engineering time isn't. |
Teams standardized on open-source observability often run Prometheus for metrics and Jaeger for tracing, which works but requires stitching multiple systems together and building your own correlation between them. A unified platform like Atatus keeps metrics, logs, traces, and continuous profiling in one place, cutting the time it takes to go from alert to root cause.
One platform. Metrics, traces, logs, profiling. No add-on fees
Go Monitoring Best Practices
Define Clear Objectives
- Set SLOs using the SMART framework, tied to user-facing latency and error rate, not vanity metrics.
- Focus monitoring effort on the request paths that matter most to revenue or user experience.
Target the Right Metrics
- Track p95/p99 latency, error rate, throughput, goroutine count, GC CPU fraction, and heap allocation — not dozens of low-value metrics.
- Combine real user monitoring with synthetic checks for a full picture.
Implement Proactive Alerting
- Alert on SLO burn rate, not just static thresholds, to reduce noise.
- Alert on goroutine count trend (rate of change), not just an absolute number, to catch leaks early.
Ensure End-to-End Visibility
- Use distributed tracing across every service boundary, including internal gRPC calls.
- Correlate application metrics with infrastructure and Kubernetes-level data.
Common Mistakes
- Monitoring only HTTP-level metrics and ignoring goroutine count, GC pauses, and heap allocation entirely.
- Alerting on averages instead of percentiles, which hides tail latency caused by GC pauses.
- Never calling
cancel()on contexts, quietly leaking goroutines over weeks. - Leaving GOMAXPROCS unset in containers, causing CPU throttling that looks like "random" latency spikes.
- Only profiling reactively during an incident instead of running continuous profiling that captures the moment automatically.
- Treating
pprofas a debugging tool only, rather than wiring it into ongoing production monitoring.
Production Readiness Checklist
| # | Production Readiness Checklist |
|---|---|
| ✓ | Goroutine count exposed as a metric and alerted on rate-of-change. |
| ✓ | net/http/pprof enabled behind an internal-only port. |
| ✓ | GC pause time, GC CPU fraction, and heap allocation tracked. |
| ✓ | OpenTelemetry SDK instrumented across all services with context propagation. |
| ✓ | database/sql pool limits set and connection pool utilization monitored. |
| ✓ | GOMAXPROCS explicitly aligned to container CPU limits (for example, via automaxprocs). |
| ✓ | GOMEMLIMIT set relative to the container memory limit. |
| ✓ | SLOs defined for p95/p99 latency and error rate on critical request paths. |
| ✓ | Continuous profiling enabled in production, not just during local debugging. |
| ✓ | Alerts tied to SLO burn rate, with on-call runbooks linked for faster incident response. |
Frequently Asked Questions
1) What is Golang application monitoring?
Golang application monitoring is the practice of collecting metrics, logs, and traces from a Go program to understand its runtime behavior in production. It covers standard application performance data (latency, throughput, error rate) plus Go-specific runtime signals such as goroutine counts, garbage collection pauses, heap allocation, and scheduler activity. The goal is to detect performance degradation, concurrency bugs, and memory issues before they cause an outage.
2) How do you monitor goroutines in production?
You can monitor goroutines by exposing runtime.NumGoroutine() as a metric, enabling the net/http/pprof endpoint to capture goroutine dumps, and shipping that data to an APM tool that graphs goroutine count over time. A healthy service has a stable or bounded goroutine count; a steadily climbing count under constant load usually indicates a leak from an unclosed channel, a missing context cancellation, or a goroutine blocked on I/O.
3) How do you detect a memory leak in a Go application?
Detect Go memory leaks by tracking heap allocation and RSS over time, capturing heap profiles with pprof (go tool pprof http://localhost:6060/debug/pprof/heap), and comparing two profiles taken minutes apart to see which allocations keep growing. Common causes are goroutine leaks holding references, unbounded caches, growing slices appended to without trimming, and forgotten timers or tickers that are never stopped.
4) What is the best tool to monitor Go applications?
The best Go monitoring tool depends on your stack, but for teams that want unified metrics, logs, traces, and continuous profiling without stitching together multiple open-source tools, a full-stack APM platform like Atatus is a strong fit. Teams already standardized on OpenTelemetry often pair Prometheus and Grafana for metrics with Jaeger for tracing, though this requires more setup and maintenance than a managed APM solution.
5) How do you monitor Go applications running in Kubernetes?
Monitoring Go workloads in Kubernetes combines application-level telemetry (metrics, traces, logs from the Go binary) with cluster-level signals (pod restarts, CPU throttling, memory limits, OOMKilled events). Run the OpenTelemetry Collector as a DaemonSet or sidecar to gather both, correlate pod-level resource pressure with in-app GC and goroutine metrics, and watch for GOMAXPROCS misconfiguration, which is a common cause of throttling in containerized Go apps.
Get Started with Atatus
Monitoring Go applications with Atatus gives you full-stack visibility including goroutines, GC, distributed tracing, continuous profiling, logs, and errors without hand-rolling multiple open-source tools together. Start free and see your first trace, goroutine graph, and flame graph in minutes.