go
98 lines · 2 tabs
Leah Thompson
Jan 2026
2 tabs
package api
import (
"context"
"crypto/rand"
"encoding/hex"
"net/http"
"time"
)
type ctxKey string
const requestIDKey ctxKey = "request_id"
func withRequestID(ctx context.Context) (context.Context, string) {
b := make([]byte, 16)
_, _ = rand.Read(b)
id := hex.EncodeToString(b)
return context.WithValue(ctx, requestIDKey, id), id
}
func Handler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
ctx, reqID := withRequestID(ctx)
r = r.WithContext(ctx)
w.Header().Set("x-request-id", reqID)
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MiB
next.ServeHTTP(w, r)
})
}
description: "To get useful traces, you need propagation and a real exporter. I set a global `TextMapPropagator` (`TraceContext` + `Baggage`) so inbound headers connect spans across services. Then I configure an OTLP exporter and a batch span processor so tracing overhead stays low. I also explicitly set a sampler: `ParentBased(TraceIDRatioBased(0.1))` is a common starting point that respects upstream sampling and keeps costs predictable. The other key piece is resource attributes like `service.name`, which is how traces are grouped in most backends. Once this is initialized, you can start spans in handlers with `otel.Tracer("...").Start(ctx, ...)` and get end-to-end visibility without special log parsing.",
}
]
},
{
title: 'Structured logging with zap + per-request fields',
description: "When a service grows past one instance, `fmt.Println` becomes a liability. I prefer `zap` for structured logs, and I treat the request logger as a derived value: a base logger plus fields like `request_id`, `method`, and `path`. That way every log line in the request path is automatically correlated, including errors returned from lower layers. I also explicitly avoid logging secrets by keeping payload logs opt-in. The main trick is keeping the logger in context (or attaching it to a request-scoped struct) so handlers stay clean and don’t balloon with parameters. In practice this pattern makes incident response faster because you can filter by `request_id` and see the full story.",
tags: %w[go logging observability http],
code_blocks: [
{
name: 'logger.go',
hljs_language: 'go',
code: <<~GO
package observability
import (
"context"
"net/http"
"go.uber.org/zap"
)
type ctxKey string
const loggerKey ctxKey = "logger"
func FromContext(ctx context.Context) *zap.Logger {
if v := ctx.Value(loggerKey); v != nil {
if lgr, ok := v.(*zap.Logger); ok {
return lgr
}
}
return zap.NewNop()
}
func Middleware(base *zap.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqID := r.Header.Get("x-request-id")
lgr := base.With(
zap.String("request_id", reqID),
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
)
ctx := context.WithValue(r.Context(), loggerKey, lgr)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
package api
import (
"net/http"
"example.com/app/observability"
)
func healthz(w http.ResponseWriter, r *http.Request) {
log := observability.FromContext(r.Context())
log.Info("health.check")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
2 files · go
Explain with highlit
I treat context.Context as the contract between the edge and everything downstream. The pattern here starts by creating a per-request timeout using context.WithTimeout, then storing a requestID in the context so logs and traces can correlate without passing extra parameters everywhere. I also limit request body size early with http.MaxBytesReader to prevent memory blowups. The important operational detail is what happens when the deadline hits: any database call, HTTP call, or goroutine that uses ctx gets the same cancellation signal, which makes backpressure real instead of wishful. When this is wired in consistently, you see fewer stuck goroutines and far more predictable latency under load.
Related snips
go
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
go
observability
build
by Leah Thompson
1 tab
typescript
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
typescript
reliability
retry
by codesnips
2 tabs
go
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
go
postgres
transactions
by Leah Thompson
1 tab
go
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
go
aws
s3
by Leah Thompson
1 tab
go
package deps
import (
"net"
"net/http"
"time"
HTTP client tuned for production: timeouts, transport, and connection reuse
go
http
client
by Leah Thompson
1 tab
go
package api
import (
"io"
"net/http"
"os"
Safe multipart uploads using temp files (bounded memory)
go
http
uploads
by Leah Thompson
1 tab
Share this code
Here's the card — post it anywhere.