Search, ripgrep & jq for Debugging
Use ripgrep to hunt symbols across Go modules and jq to slice JSON logs into answers during incidents.
Search across all documentation pages
Use ripgrep to hunt symbols across Go modules and jq to slice JSON logs into answers during incidents.
Debugging Go services means searching two haystacks: source in git and JSON lines on disk or in journal exports.
rg replaces slow recursive grep for code.
jq turns structured logs into filters, counts, and field extractions without a full log platform.
Quick-reference recipe card - copy-paste ready.
# Code: find handler registration
rg -n "HandleFunc|Handle\(" --glob '*.go' internal/ cmd/
# Code: tests referencing a flaky function
rg -n "TestProcessOrder" -g '*_test.go'
# Logs: errors in last export
jq -c 'select(.level=="error")' app.json.log | head
# Logs: count by trace_id prefix
jq -r 'select(.level=="error") | .trace_id' app.json.log | cut -c1-8 | sort | uniq -c | sort -nrWhen to reach for this:
request_id quickly.#!/usr/bin/env bash
set -euo pipefail
# --- Repository search ---
# Find where context cancellation is ignored in handlers
rg -n "context\.Background\(\)" --glob '*.go' -g '!*_test.go' internal/
# Find build tags or platform files
rg -n "//go:build" --glob '*.go'
# --- Log analysis (sample NDJSON file) ---
cat > /tmp/sample.json.log <<'EOF'
{"time":"2026-07-15T10:00:01Z","level":"info","msg":"start","service":"api","trace_id":"abc123"}
{"time":"2026-07-15T10:00:02Z","level":"error","msg":"db timeout","service":"api","trace_id":"abc123","err":"context deadline"}
{"time":"2026-07-15T10:00:03Z","level":"error","msg":"db timeout","service":"api","trace_id":"def456","err":"context deadline"}
EOF
# All errors for one trace
jq -c 'select(.trace_id=="abc123")' /tmp/sample.json.log
# Top error messages
jq -r 'select(.level=="error") | .msg' /tmp/sample.json.log | sort | uniq -c | sort -nr
# Combine rg on a journal export
journalctl -u api.service --since today --no-pager -o cat > /tmp/api.today.log
rg '"level":"error"' /tmp/api.today.log | jq -c . | headWhat this demonstrates:
jq -r extracts a field..gitignore unless you pass --no-ignore.select), projections (.field), and slurp (-s) reshape streams for shell pipes.slog, zap, zerolog) typically emits NDJSON or logfmt; NDJSON is jq-friendly when each line is valid JSON.rg on the handler named in the log msg.| Flag | Effect |
|---|---|
-n | Line numbers |
-l | Files with matches only |
-g '*.go' | Glob filter (repeatable) |
-g '!vendor/**' | Exclude paths |
-F | Fixed string (log needles) |
-C 3 | Context lines |
--type go | Built-in type filter |
| Task | jq |
|---|---|
| Filter level | select(.level=="error") |
| Field exists | select(has("trace_id")) |
| Extract field | .trace_id with -r |
| Time window | select(.time >= "2026-07-15T10:00:00Z") |
| Array stats | `jq -s 'map(select(.level=="error")) |
// Prefer stable field names for jq filters
slog.Error("db timeout",
"level", "error", // redundant if handler sets level - be consistent
"trace_id", traceID,
"err", err.Error(),
)Align key names with dashboards (trace_id vs traceId) to avoid duplicate jq recipes.
jq parsers. Fix: rg filter first, or use jq -R 'fromjson? | select(.)' to skip bad lines.--no-ignore scans vendored deps. Fix: default ignore, or -g '!vendor/**'.ERROR vs error misses rows. Fix: normalize in Go logging config; use jq ascii_downcase if legacy logs vary.jq -s - Loads entire file into memory. Fix: stream line-by-line filters for GB-scale files.rg - Dots in err.context deadline need -F or escaping. Fix: rg -F 'context deadline'.| Alternative | Use When | Don't Use When |
|---|---|---|
| IDE global search | Single-module navigation with click-to-open | Remote SSH session without IDE |
git grep | History bounded to tracked files | Untracked generated logs or large ignored dirs |
| Loki / Elasticsearch | Team-wide historical queries | You only have a 50MB file and five minutes |
ack / ag | Legacy laptops without rg | Greenfield - install ripgrep instead |
mlog / custom Go tool | Proprietary log format | Standard JSON already works with jq |
ripgrep respects gitignore by default, searches untracked trees when needed, and is faster on large monorepos.
git grep is fine for quick history-only searches.
That NDJSON shape is the common case for log files.
Multi-line pretty JSON needs different parsing or jq -s for small files.
If JSON keys match your filters, the same jq recipes apply.
For console-encoded logs, switch to JSON encoding in staging before incidents.
rg --files -g '*.go' lists paths; combine with fzf for interactive pickers.
Use find when metadata like mtime matters.
Panics are often multi-line plain text.
Use rg -U multiline mode or read journal entries as units via journalctl -o json.
rg still finds keys; jq does not apply.
Consider mlog, lnav, or converting a slice to JSON in a small Go script.
Streaming counts with jq -c 'select(...)' | wc -l stays O(n) memory.
Avoid -s on gigabyte files.
Check in a scripts/debug/ shell snippet or document one-liners in the runbook next to dashboard links.
Yes if the image includes rg, or docker exec into a debug sidecar.
Distroless runtime images often lack shell tools - copy logs out instead.
rg -n "TODO\(|FIXME" --glob '*.go' with optional -g '!vendor/**'.
jq does not parse Go's duration format natively.
Extract the string field and interpret in Go or convert at log emission time to numeric milliseconds.
Use space-prefixed commands where HISTCONTROL ignores them, or run recipes from scripts that read files instead of inline customer IDs.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026