Created
July 22, 2026 04:24
-
-
Save stojg/4fb4c741012da8561d7d1bee1fe35848 to your computer and use it in GitHub Desktop.
rollbar-item
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # rollbar-item — download a Rollbar item by its per-project counter. | |
| # | |
| # The counter is the number shown in the Rollbar UI and in item URLs | |
| # (e.g. #13968). The API resolves it to an internal item id and then | |
| # returns the full item. | |
| # | |
| # https://docs.rollbar.com/reference/get-an-item-by-project-counter | |
| # | |
| # By default it also fetches the latest occurrence (which carries the stack | |
| # trace, request, and telemetry) and emits { "item": ..., "occurrence": ... }. | |
| # Use --item-only for just the item metadata. | |
| # | |
| # Usage: | |
| # rollbar-item <counter> [-o output.json] [--item-only] | |
| # | |
| # Token (project access token, 'read' scope) — first found wins: | |
| # ROLLBAR_ACCESS_TOKEN environment variable, or | |
| # ~/.config/rollbar/token (chmod 600; override with ROLLBAR_TOKEN_FILE) | |
| # | |
| # Examples: | |
| # rollbar-item 13968 | |
| # rollbar-item 13968 -o err.json | |
| # rollbar-item 13968 | jq '.occurrence.body.trace.frames' | |
| set -euo pipefail | |
| api="https://api.rollbar.com/api/1" | |
| out="-" # stdout by default | |
| item_only=0 | |
| die() { printf 'rollbar-item: %s\n' "$1" >&2; exit 1; } | |
| counter="" | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| -o|--output) out="${2:-}"; [ -n "$out" ] || die "-o needs a filename"; shift 2;; | |
| --item-only) item_only=1; shift;; | |
| -h|--help) sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'; exit 0;; | |
| -*) die "unknown option: $1";; | |
| *) counter="$1"; shift;; | |
| esac | |
| done | |
| [ -n "$counter" ] || die "missing item counter (try -h)" | |
| case "$counter" in *[!0-9]*) die "counter must be an integer, got: $counter";; esac | |
| # Token: prefer the environment, else read the per-user config file | |
| # (~/.config/rollbar/token, chmod 600). Keeps the secret out of the shell | |
| # environment and out of any repo. | |
| token_file="${ROLLBAR_TOKEN_FILE:-$HOME/.config/rollbar/token}" | |
| if [ -z "${ROLLBAR_ACCESS_TOKEN:-}" ] && [ -r "$token_file" ]; then | |
| ROLLBAR_ACCESS_TOKEN="$(tr -d '[:space:]' < "$token_file")" | |
| fi | |
| [ -n "${ROLLBAR_ACCESS_TOKEN:-}" ] \ | |
| || die "no token: set ROLLBAR_ACCESS_TOKEN or write it to $token_file (project token, 'read' scope)" | |
| hdr="X-Rollbar-Access-Token: $ROLLBAR_ACCESS_TOKEN" | |
| # 1. Resolve counter -> item id. This endpoint replies with a 301 whose body | |
| # carries result.itemId; we parse it rather than blindly following, so we | |
| # can give a clear error if auth/counter is wrong. | |
| resolve=$(curl -fsS -H "$hdr" "$api/item_by_counter/$counter") \ | |
| || die "lookup failed for counter $counter (bad token, or no such item?)" | |
| item_id=$(printf '%s' "$resolve" \ | |
| | grep -o '"itemId"[[:space:]]*:[[:space:]]*[0-9]*' \ | |
| | grep -o '[0-9]*$' \ | |
| | head -1) | |
| [ -n "$item_id" ] || die "could not find itemId in response: $resolve" | |
| # 2. Fetch the item metadata. | |
| item=$(curl -fsS -H "$hdr" "$api/item/$item_id") \ | |
| || die "failed to fetch item $item_id" | |
| have_jq=0; command -v jq >/dev/null 2>&1 && have_jq=1 | |
| # Redaction: strip PII before anything touches disk. Integer ids stay | |
| # (person.id, userId, accountId are useful and not identifying on their own). | |
| # Two layers: (a) delete the known identifying string fields, and (b) mask any | |
| # email or IPv4 wherever it appears — including free-form telemetry/DOM/log | |
| # breadcrumbs, request URLs, and log messages we don't control the shape of. | |
| redact=' | |
| def mask: | |
| gsub("[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"; "[redacted-email]") | |
| | gsub("\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b"; "[redacted-ip]"); | |
| walk( | |
| if type=="object" then del(.email, .username, .user_ip, .query_string) | |
| elif type=="string" then mask | |
| else . end | |
| ) | |
| ' | |
| # 3. Fetch the latest occurrence (the item metadata has no stack trace; the | |
| # trace/request/telemetry live on an occurrence). Needs jq to locate the id | |
| # and to combine the two documents, so skip gracefully without it. | |
| occ_note="" | |
| if [ "$item_only" -eq 0 ] && [ "$have_jq" -eq 1 ]; then | |
| occ_id=$(printf '%s' "$item" | jq -r '.result.last_occurrence_id // empty') | |
| if [ -n "$occ_id" ]; then | |
| occ=$(curl -fsS -H "$hdr" "$api/instance/$occ_id") \ | |
| || die "failed to fetch occurrence $occ_id" | |
| # { item: <item.result>, occurrence: <instance.result.data> }, then redact | |
| output=$(jq -n --argjson item "$item" --argjson occ "$occ" \ | |
| "{item: \$item.result, occurrence: \$occ.result.data} | $redact") | |
| occ_note=" + occurrence $occ_id" | |
| else | |
| output=$(printf '%s' "$item" | jq "{item: .result} | $redact") | |
| occ_note=" (no occurrences)" | |
| fi | |
| elif [ "$have_jq" -eq 1 ]; then | |
| output=$(printf '%s' "$item" | jq "{item: .result} | $redact") | |
| else | |
| output="$item" # raw item, no jq available | |
| printf 'rollbar-item: jq not found; cannot redact — emitting raw item metadata only\n' >&2 | |
| fi | |
| if [ "$out" = "-" ]; then | |
| printf '%s\n' "$output" | |
| else | |
| printf '%s\n' "$output" > "$out" | |
| printf 'wrote item %s (counter %s)%s -> %s\n' "$item_id" "$counter" "$occ_note" "$out" >&2 | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment