Static scanner for observability gaps in application code: high-cardinality metric labels, unstructured logs, and Kafka produce/consume without trace context. Runs on a checkout. Does not need your telemetry backend.
Prerequisites: Go 1.27+ and ast-grep 0.45.x on PATH.
# macOS
brew install ast-grep
# or: npm i -g @ast-grep/cli@0.45.1
go install github.com/Pieczasz/tarsier/cmd/tarsier@v0.2.0Or from source:
git clone https://github.com/Pieczasz/tarsier.git
cd tarsier
make build
./bin/tarsier scan fixtures/_badshoptarsier scan path/to/repo
tarsier --output json scan path/to/repo
tarsier --output html scan path/to/repo > tarsier-report.html
tarsier scan --engine=all path/to/go/module # pattern + Go deep tier--engine pattern|go|all (default pattern). Medium-confidence findings never trip --fail-on.
CI gate without a policy file:
tarsier scan --fail-on warning .Policy gate (opt-in blocking classes only - TAR-47):
# observability-policy.yaml
# version: 1
# block:
# - unbounded-metric-labels
tarsier check --policy observability-policy.yaml .Exit codes for check: 0 when no opted-in blocking findings; 1 when a blocked class matches (or flags/policy are invalid). Layer 3/4 rules cannot be listed under block (rejected at parse time). --baseline is honoured.
Adopt on an existing repo without failing the first build:
tarsier scan . --baseline-write tarsier-baseline.json
tarsier scan . --baseline tarsier-baseline.json --fail-on warningQuiet one finding in source (reason required):
// tarsier:ignore metrics/high-cardinality-label tenant label is bounded hereExample workflow: examples/github-actions/scan.yml.
| Rule | Languages | Notes |
|---|---|---|
metrics/high-cardinality-label |
Go, TS, Java, Rust, Python | Literal unbounded label names at declaration |
metrics/unbounded-label-value |
Go, TS, Java, Python | err.Error() / uuid-shaped values at the call site |
logs/unstructured-logging |
Go, TS | log.Printf/console.* where slog/zap/pino/winston is imported |
msgtrace/kafka-produce-no-inject |
Go, TS, Java | Headerless produce (medium on TS/Java when inject is invisible) |
msgtrace/kafka-consume-no-extract |
Go, Java | Read without .Headers/Extract |
httpctx/outbound-call-without-context |
Go | http.Get/Post/Head/PostForm |
otel/sdk-missing-resource-attrs |
Go, TS | Provider/SDK without service.name |
otel/sdk-missing-shutdown |
Go, TS | Provider without Shutdown/forceFlush (medium) |
otel/semconv-drift |
Go, TS | Deprecated HTTP attribute names (medium; TAR-77 precision gate) |
errors/swallowed-on-critical-path |
Go, Java | _ = err / empty catch (medium) |
traces/error-path-not-recorded-on-span |
Go | return err after .Start( without RecordError/SetStatus (medium) |
logs/missing-trace-correlation |
Go, TS | slog/pino alongside OTel without a bridge (medium) |
logs/slog-sprintf-message |
Go | slog.*(fmt.Sprintf(...)) - prefer structured attrs (medium; TAR-77) |
Precision process for high-confidence rules: docs/PRECISION.md.
fixtures/_badshop is a deliberately broken multi-language app. Every interesting line is marked want: / notwant: / gap:.
make build
./bin/tarsier scan fixtures/_badshop
./bin/tarsier --output html scan fixtures/_badshop > /tmp/tarsier-badshop.html
./bin/tarsier scan --fail-on warning fixtures/_badshop; echo exit:$?See DEMO.md for a five-minute walkthrough.
Apache-2.0. See LICENSE.
Shared packages live at the module root (not under internal/) so product
code can require github.com/Pieczasz/tarsier and import e.g.
github.com/Pieczasz/tarsier/finding, .../engine/pattern, .../rules.