go-observability — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-observability (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Production Go services need at least two always-on signals to be debuggable: metrics (aggregated measurements for alerting and SLOs) and traces (per-request flow showing where time went). The third deliverable is correlation: a P99 metric spike must lead, in one click, to the trace that caused it.
Logging is not in this skill. Structured logging with log/slog, log levels, and zap/logrus/zerolog migration belong to the go-logging skill. This skill only references logs in the context of correlating them with traces.histogram_quantile() server-side.ctx context.Context as its first argument. No context = no trace propagation = no correlation.span.RecordError(err) and span.SetStatus(codes.Error, ...). A green span hides a real failure.trace_id into logs, attach exemplars to histograms. Otherwise the three signals are three different products.Pick the signal that matches the question. Do not log what should be a metric.
| Question | Signal | Tool |
|---|---|---|
| How often does X happen? Error rate? Rate-per-second? | Metric (Counter) | Prometheus |
| What is the P99 latency of endpoint /orders? | Metric (Histogram) | Prometheus + histogram_quantile |
| Where did this one slow request spend its time? | Trace | OpenTelemetry |
| Why does latency spike at 14:32? | Metric → exemplar → trace | Prometheus + OTel + exemplars |
| What concrete error message did this request hit? | Log (see go-logging) | log/slog |
Read references/metrics.md for Counter/Gauge/Histogram patterns, naming, and PromQL-as-comments. Read references/tracing.md for TracerProvider setup, span attributes, and otelhttp middleware.import "github.com/prometheus/client_golang/prometheus"
// rate(http_requests_total{code=~"5.."}[5m]) / rate(http_requests_total[5m])
var httpRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests by method, route, status class.",
},
[]string{"method", "route", "code"}, // ALL bounded
)
// histogram_quantile(0.99, sum by (le, route) (rate(http_request_duration_seconds_bucket[5m])))
var httpLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request latency in seconds.",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "route"},
)The comment above each metric is the PromQL it is designed to answer. This makes the metric discoverable and grep-able from a dashboard.
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/codes"
)
func (s *OrderService) Create(ctx context.Context, in CreateOrderInput) (*Order, error) {
ctx, span := otel.Tracer("order-service").Start(ctx, "OrderService.Create")
defer span.End()
span.SetAttributes(attribute.String("order.user_id", in.UserID))
order, err := s.repo.Insert(ctx, in)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "insert failed")
return nil, fmt.Errorf("creating order: %w", err)
}
return order, nil
}Every service method, every DB query, every external HTTP call gets a span. Context must flow into s.repo.Insert(ctx, ...) so the DB span is a child of the service span.
An exemplar attaches a single trace_id to a histogram observation. In Grafana, click the dot on a P99 spike and you land on the trace.
obs := httpLatency.WithLabelValues(r.Method, routePattern)
sc := trace.SpanContextFromContext(ctx)
if eo, ok := obs.(prometheus.ExemplarObserver); ok && sc.IsValid() {
eo.ObserveWithExemplar(elapsed.Seconds(),
prometheus.Labels{"trace_id": sc.TraceID().String()})
} else {
obs.Observe(elapsed.Seconds())
}Use the otelslog bridge (see go-logging skill) so every slog.InfoContext(ctx, ...) call automatically emits trace_id and span_id. You can then grep logs by trace_id when starting from a trace, or jump from a log line to the trace.
Read references/correlation.md for exemplar wiring details, request-id propagation, and the end-to-end "metric spike → trace → log" workflow.
// Bad — breaks trace propagation; the DB call starts a new root trace.
result, err := db.Query("SELECT ...")
// Good — the DB span is a child of the HTTP request span.
result, err := db.QueryContext(ctx, "SELECT ...")Every I/O boundary takes ctx: HTTP client, database, gRPC client, message queue. Use otelhttp.NewTransport to instrument outbound HTTP and otelhttp.NewHandler for inbound.
| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
Summary for latency | Cannot aggregate across replicas; no quantile flexibility | Histogram + histogram_quantile() |
userID as a Prometheus label | Unbounded cardinality → Prometheus OOM | Hash to a bucketed user_tier or drop |
| Logging the same error you return | Duplicate lines, no single source of truth | Return wrapped, log once at the boundary |
No RecordError on failed spans | Trace shows green, alert fires red | Always pair error returns with span.RecordError + SetStatus |
| Trace without context propagation | Each layer starts a new root trace | First argument of every I/O method is ctx context.Context |
| Global metric defined inside a handler | Re-registers on every call → panic | Declare metrics at package level, register once |
| Histogram with 200 buckets | High storage cost, slow queries | Start with DefBuckets, tune from real data |
Before marking the change done:
ctx first; downstream calls pass it throughspan.RecordError(err) and span.SetStatus(codes.Error, ...)ObserveWithExemplar for trace correlationotelslog bridge)otelhttp, sampling~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.