Last active
March 9, 2020 16:10
-
-
Save gburgett/0dd50fc8a0caabebae656069d88db487 to your computer and use it in GitHub Desktop.
A utility script to download error instances from the Bugsnag API. Automatically paginates and handles rate limits.
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
#! /bin/bash | |
COLOR_NC='\033[0m' # No Color | |
COLOR_LGREEN='\033[1;32m' | |
COLOR_GRAY='\033[1;30m' | |
COLOR_LGRAY='\033[0;37m' | |
COLOR_YELLOW='\033[1;33m' | |
COLOR_RED='\033[0;31m' | |
DIR="$( cd "$( dirname "$0" )" && pwd )" | |
## *** Utility functions *** | |
logv() { | |
[[ -z "$VERBOSE" ]] && return 0; | |
local msg=$@ | |
[[ -z $BUGSNAG_AUTH_TOKEN ]] || msg=$(echo "$@" | sed "s/$BUGSNAG_AUTH_TOKEN/\*\*\*\*\*/" ) | |
>&2 echo -e "${COLOR_GRAY}$msg${COLOR_NC}" || true | |
} | |
logerr() { | |
>&2 echo -e "${COLOR_RED}$@${COLOR_NC}" | |
} | |
warn() { | |
>&2 echo -e "${COLOR_YELLOW}$@${COLOR_NC}" | |
} | |
log() { | |
>&2 echo -e "${COLOR_LGREEN}$@${COLOR_NC}" | |
} | |
panic() { | |
logerr "$@" | |
exit -1; | |
} | |
curlv() { | |
logv "curl" "$@" | |
curl "$@" | |
} | |
teev() { | |
[[ "${VERBOSE}" ]] && tee >(cat 1>&2) || cat | |
} | |
execv() { | |
logv "$@" | |
"$@" | |
} | |
confirm() { | |
while true; do | |
if [[ -z "$2" ]]; then | |
read -p $'\033[1;36m'"$1"' (exec/skip): '$'\033[0m' yn | |
else | |
# double confirm - extra dangerous. | |
read -p $'\033[0;31m'"$1"' (exec/skip): '$'\033[0m' yn | |
fi | |
case $yn in | |
[Ee]* ) return 0;; | |
[Ss]* ) return 1;; | |
* ) echo "Please answer 'execute' or 'skip'.";; | |
esac | |
done | |
} | |
HEADERS_FILE=$(mktemp /tmp/bugsnag-headers.XXXXXX) | |
api_curl() { | |
local url=${@: -1}; | |
if [[ ! -z "$url" ]]; then set -- "${@:1:${#}-1}"; fi | |
resp=$(curlv -s -H "Authorization: token $BUGSNAG_AUTH_TOKEN" -D "$HEADERS_FILE" "$@" \ | |
"$url") | |
if grep -q 'HTTP/2 429' "$HEADERS_FILE"; then | |
logv "rate limited! Sleeping for 1 minute" | |
# rate limit resets every 60 seconds | |
sleep 60 | |
api_curl "$url" "$@" | |
else | |
echo "$resp" | |
fi | |
} | |
paginate() { | |
local url="$1" | |
while | |
resp=$(api_curl "$url") | |
echo "$resp" | jq -c '.[]' | |
url=$(grep -Eoi 'link: <[^>]+>' "$HEADERS_FILE" | grep -Eo '(http|https)://[^>]+' | head -n1) | |
[[ ! -z "$url" ]] | |
do | |
continue | |
done | |
} | |
require_environment() { | |
[[ -z "$BUGSNAG_AUTH_TOKEN" ]] && panic "Please set BUGSNAG_AUTH_TOKEN environment variable or use '-a' flag." | |
export PROJECT_FOLDER="bugsnag/${BUGSNAG_PROJECT_ID}" | |
mkdir -p "$PROJECT_FOLDER" | |
command -v jq >/dev/null 2>&1 || panic "Please install jq" | |
true | |
} | |
# Write your usage | |
usage() { | |
echo "$0 | |
Utilities for querying Bugsnag via the API | |
curl | |
Curls the API with bugsnag auth and handling rate limit errors. | |
organizations | |
Lists all the organizations the current user belongs to. | |
projects [organization ID]? | |
Lists all projects and project IDs for the given organization or current user. | |
events [error ID] | |
Downloads all events for the given error. | |
requests [error ID] | |
Downloads all events and prints the requests in JSON format | |
Flags:" && \ | |
grep " .)\ #" $0 | |
echo " | |
Examples:" && \ | |
grep -i "#\ example:" $0 | awk '{$1=""; $2=""; print " "$0}' | |
} | |
parse_args() { | |
OPTIND=1 | |
subcommand='' | |
local s=$(echo "$1" | tr '[:upper:]' '[:lower:]') | |
case "$s" in | |
curl|requests|events|organizations|projects|help|h|\?) | |
export subcommand=$s | |
OPTIND=2 | |
;; | |
esac | |
# Parse flags | |
while getopts ":hvtja:p:C" arg; do | |
case $arg in | |
v) # verbose mode - debug output | |
VERBOSE=true | |
FLAGS="$FLAGS -v" | |
;; | |
t) # table output | |
TABLE=true | |
FLAGS="$FLAGS -t" | |
;; | |
j) # JSON output | |
TABLE='' | |
FLAGS="$FLAGS -j" | |
;; | |
a) # Bugsnag API Token - overrides env var BUGSNAG_AUTH_TOKEN | |
export BUGSNAG_AUTH_TOKEN=$OPTARG | |
;; | |
p) # Bugsnag project ID - overrides env var BUGSNAG_PROJECT_ID | |
export BUGSNAG_PROJECT_ID=$OPTARG | |
;; | |
C) # Skip cache | |
export SKIP_CACHE=true | |
FLAGS="$FLAGS -C" | |
;; | |
h | *) # Display help. | |
usage | |
exit 0 | |
;; | |
esac | |
done | |
export OPTIND | |
} | |
parse_args "$@" && shift $(($OPTIND - 1)) | |
# If they put args before the command like 'bin/bugsnag -s 1xab migrate -y', try parsing again | |
[[ -z "$subcommand" ]] && parse_args "$@" && shift $(($OPTIND - 1)) | |
logv "$0 '$FLAGS' '$subcommand' $@" | |
# Example: bugsnag events 5c5a18cd11fa7800181b38bb > events.json | |
get-events() { | |
local id="$1" | |
[[ ! -z "$id" ]] || panic "Please provide error ID" | |
[[ -z "$BUGSNAG_PROJECT_ID" ]] && panic "Please set BUGSNAG_PROJECT_ID environment variable or use '-p' flag." | |
found=$(api_curl "https://api.bugsnag.com/projects/$BUGSNAG_PROJECT_ID/errors/$id") | |
if grep -q 'HTTP/2 404' "$HEADERS_FILE"; then | |
panic "Cannot find error ID ${id}" | |
fi | |
events_count=$(echo "$found" | jq -r ".events") | |
events_url=$(echo "$found" | jq -r ".events_url") | |
paginate "$events_url?per_page=100" | \ | |
jq -r '.url' | \ | |
xargs -I{} bash -c "$0 curl $FLAGS \"{}\"" | \ | |
pv -l -s "$events_count" | \ | |
tee "${PROJECT_FOLDER}/events_${id}_$(date '+%Y-%m-%d').json" | |
} | |
cached-get-events() { | |
local id="$1" | |
if [[ -z "$SKIP_CACHE" && -f "${PROJECT_FOLDER}/events_${id}_$(date '+%Y-%m-%d').json" ]]; then | |
cat "${PROJECT_FOLDER}/events_${id}_$(date '+%Y-%m-%d').json" | |
return 0 | |
fi | |
get-events "$id" | |
} | |
get-requests() { | |
cached-get-events "$@" | \ | |
jq -c '{ id: .id, date: .received_at, error_id: .error_id, httpMethod: .request.httpMethod, url: .request.url, referer: .request.referer, params: .request.params }' | |
} | |
list-organizations() { | |
local table="$TABLE" | |
if [[ "$1" == '-j' ]]; then table=''; fi | |
api_curl "https://api.bugsnag.com/user/organizations" | | |
( [[ "${table}" ]] && jq -r ".[] | \"\\(.id)\t\\(.name)\"" || cat ) | |
} | |
list-projects() { | |
local organization_id="$1" | |
logv "list-projects $1" | |
if [[ ! -z "$organization_id" ]]; then | |
api_curl "https://api.bugsnag.com/organizations/${organization_id}/projects" | | |
( [[ "${TABLE}" ]] && jq -r ".[] | \"\\(.id)\t\\(.name)\"" || cat ) | |
return | |
fi | |
list-organizations -j | jq -r '.[].id' | xargs -I{} $0 $FLAGS projects {} | |
} | |
set -e | |
case $subcommand in | |
curl) | |
require_environment | |
api_curl "$@" | |
;; | |
events) | |
require_environment | |
cached-get-events "$@" | |
;; | |
requests) | |
require_environment | |
get-requests "$@" | |
;; | |
organizations) | |
require_environment | |
list-organizations "$@" | |
;; | |
projects) | |
require_environment | |
list-projects "$@" | |
;; | |
help|h|\?) | |
usage | |
;; | |
*) | |
logerr "Unknown command: '$1'" | |
usage | |
exit -1 | |
;; | |
esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment