self-hosted/install/error-handling.sh
Hubert Deng 0d3191d9ac
Enforce error reporting (#1777)
enforce error reporting
2022-10-26 18:07:57 -07:00

216 lines
6.9 KiB
Bash

echo "${_group}Setting up error handling ..."
export SENTRY_DSN='https://19555c489ded4769978daae92f2346ca@self-hosted.getsentry.net/3'
export SENTRY_ORG=self-hosted
export SENTRY_PROJECT=installer
jq="docker run --rm -i sentry-self-hosted-jq-local"
sentry_cli="docker run --rm -v /tmp:/work -e SENTRY_ORG=$SENTRY_ORG -e SENTRY_PROJECT=$SENTRY_PROJECT -e SENTRY_DSN=$SENTRY_DSN getsentry/sentry-cli"
send_envelope() {
# Send envelope
$sentry_cli send-envelope "$envelope_file"
}
generate_breadcrumb_json() {
# Based on https://stackoverflow.com/a/1521498
while IFS="" read -r line || [ -n "$line" ]; do
jq -n -c --arg message "$line" \
--arg category log \
--arg level info \
'$ARGS.named'
done <$log_path
}
send_event() {
# Use traceback hash as the UUID since it is 32 characters long
local event_hash=$1
local error_message=$2
local traceback_json=$3
local envelope_file="sentry-envelope-${event_hash}"
local envelope_file_path="/tmp/$envelope_file"
local log_path="$basedir/$log_file"
# If the envelope file exists, we've already sent it
if [[ -f $envelope_file_path ]]; then
echo "Looks like you've already sent this error to us, we're on it :)"
return
fi
# If we haven't sent the envelope file, make it and send to Sentry
# The format is documented at https://develop.sentry.dev/sdk/envelopes/
# Grab length of log file, needed for the envelope header to send an attachment
local file_length=$(wc -c <$log_path | awk '{print $1}')
# Add header for initial envelope information
jq -n -c --arg event_id "$event_hash" \
--arg dsn "$SENTRY_DSN" \
'$ARGS.named' >$envelope_file_path
# Add header to specify the event type of envelope to be sent
echo '{"type":"event"}' >>$envelope_file_path
# Next we construct the meat of the event payload, which we build up
# inside out using jq
# See https://develop.sentry.dev/sdk/event-payloads/
# for details about the event payload
# First, create the breadcrumb payload
# https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/
breadcrumbs=$(generate_breadcrumb_json | jq -s -c)
# Next we need the exception payload
# https://develop.sentry.dev/sdk/event-payloads/exception/
# but first we need to make the stacktrace which goes in the exception payload
frames=$(echo "$traceback_json" | jq -s -c)
stacktrace=$(jq -n -c --argjson frames $frames '$ARGS.named')
exception=$(
jq -n -c --arg "type" Error \
--arg value "$error_message" \
--argjson stacktrace $stacktrace \
'$ARGS.named'
)
event_body=$(
jq -n -c --arg level error \
--argjson exception "{\"values\":[$exception]}" \
--argjson breadcrumbs "{\"values\": $breadcrumbs}" \
'$ARGS.named'
)
echo "$event_body" >>$envelope_file_path
# Add attachment to the event
attachment=$(
jq -n -c --arg "type" attachment \
--arg length $file_length \
--arg content_type "text/plain" \
--arg filename install_log.txt \
'{"type": $type,"length": $length|tonumber,"content_type": $content_type,"filename": $filename}'
)
echo "$attachment" >>$envelope_file_path
cat $log_path >>$envelope_file_path
# Send envelope
send_envelope $envelope_file
}
if [[ -z "${REPORT_SELF_HOSTED_ISSUES:-}" ]]; then
echo
echo "Hey, so ... we would love to automatically find out about issues with your"
echo "Sentry instance so that we can improve the product. Turns out there is an app"
echo "for that, called Sentry. Would you be willing to let us automatically send data"
echo "about your instance upstream to Sentry for development and debugging purposes?"
echo
echo " y / yes / 1"
echo " n / no / 0"
echo
echo "(Btw, we send this to our own self-hosted Sentry instance, not to Sentry SaaS,"
echo "so that we can be in this together.)"
echo
echo "Here's the info we may collect:"
echo
echo " - OS username"
echo " - IP address"
echo " - install log"
echo " - runtime errors"
echo " - performance data"
echo
echo "Thirty (30) day retention. No marketing. Privacy policy at sentry.io/privacy."
echo
yn=""
until [ ! -z "$yn" ]; do
read -p "y or n? " yn
case $yn in
y | yes | 1)
export REPORT_SELF_HOSTED_ISSUES=1
echo
echo -n "Thank you."
;;
n | no | 0)
export REPORT_SELF_HOSTED_ISSUES=0
echo
echo -n "Understood."
;;
*) yn="" ;;
esac
done
echo " To avoid this prompt in the future, use one of these flags:"
echo
echo " --report-self-hosted-issues"
echo " --no-report-self-hosted-issues"
echo
echo "or set the REPORT_SELF_HOSTED_ISSUES environment variable:"
echo
echo " REPORT_SELF_HOSTED_ISSUES=1 to send data"
echo " REPORT_SELF_HOSTED_ISSUES=0 to not send data"
echo
sleep 5
fi
# Make sure we can use sentry-cli if we need it.
if [ "$REPORT_SELF_HOSTED_ISSUES" == 1 ]; then
if ! docker pull getsentry/sentry-cli:latest; then
echo "Failed to pull sentry-cli, won't report to Sentry after all."
export REPORT_SELF_HOSTED_ISSUES=0
fi
fi
# Courtesy of https://stackoverflow.com/a/2183063/90297
trap_with_arg() {
func="$1"
shift
for sig; do
trap "$func $sig" "$sig"
done
}
DID_CLEAN_UP=0
# the cleanup function will be the exit point
cleanup() {
local retcode=$?
local cmd="${BASH_COMMAND}"
if [[ "$DID_CLEAN_UP" -eq 1 ]]; then
return 0
fi
DID_CLEAN_UP=1
if [[ "$1" != "EXIT" ]]; then
set +o xtrace
printf -v err '%s' "Error in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}."
printf -v cmd_exit '%s' "'$cmd' exited with status $retcode"
printf '%s\n%s\n' "$err" "$cmd_exit"
local stack_depth=${#FUNCNAME[@]}
local traceback=""
local traceback_json=""
if [ $stack_depth -gt 2 ]; then
for ((i = $(($stack_depth - 1)), j = 1; i > 0; i--, j++)); do
local indent="$(yes a | head -$j | tr -d '\n')"
local src=${BASH_SOURCE[$i]}
local lineno=${BASH_LINENO[$i - 1]}
local funcname=${FUNCNAME[$i]}
JSON=$(
jq -n -c --arg filename $src \
--arg "function" $funcname \
--arg lineno "$lineno" \
'{"filename": $filename, "function": $function, "lineno": $lineno|tonumber}'
)
printf -v traceback_json '%s\n' "$traceback_json$JSON"
printf -v traceback '%s\n' "$traceback${indent//a/-}> $src:$funcname:$lineno"
done
fi
echo "$traceback"
# Only send event when report issues flag is set and if trap signal is not INT (ctrl+c)
if [[ "$REPORT_SELF_HOSTED_ISSUES" == 1 && "$1" != "INT" ]]; then
local event_hash=$(echo -n "$cmd_exit $traceback" | docker run -i --rm busybox md5sum | cut -d' ' -f1)
send_event "$event_hash" "$cmd_exit" "$traceback_json"
fi
if [[ -n "$MINIMIZE_DOWNTIME" ]]; then
echo "*NOT* cleaning up, to clean your environment run \"docker compose stop\"."
else
echo "Cleaning up..."
fi
fi
if [[ -z "$MINIMIZE_DOWNTIME" ]]; then
$dc stop -t $STOP_TIMEOUT &>/dev/null
fi
}
echo "${_endgroup}"