Septeni Engineer's Blog

セプテーニ・オリジナルのエンジニアが綴る技術ブログ

業務十倍効率化計画 Sentry API ft. Shell Script

みなさんこんにちは、2 年目を迎えました清水です。Scala 全然書かずに TypeScript やら Shell Script ばっか書いてます。今回はエラートラッキングツール Sentry の API を使って業務を効率化したよという内容で書かせていただきました。

概要をざっくりと説明すると、Sentry API と Shell Script でこれまで 100 分くらいかかっていたミーティングが 10 分にまで短縮できたよというような内容です(Sentry について全然知らなくても読めます)。

ちなみに、GitLab API で業務改善した同じ毛色のブログも書いてますので、この記事を読んでみて面白いと思ったらぜひ読んでみてください(http://labs.septeni.co.jp/entry/2019/03/05/120000)。

Sentry のエラーの対応基準の策定の背景と課題

弊チームには Sentry に上がったエラーを確認し、対応(エラーの原因調査および解決)するしないを決める週次の場があります。Sentry に上がってくるエラーの中で、たとえば日次バッチ処理の完走を妨げているエラーは顧客影響もあり明らかに「対応する」一択なので問題になりませんが、顧客影響があるのか判断しかねるエラーに対する対応基準が定まっておらず、しばしば議論を白熱させる要因となっていました。

対応基準の策定

これ以降は対応基準が曖昧であるエラーを話題の対象とします。基準を以下のように定めました。

2 週間のうちのエラー発生回数の合計を営業日数で割った値が 5 以上である

この基準になった背景としては会社全体の共通資産としている Sentry のレートリミットに抵触しないような時間あたりの発生回数から日あたりの発生回数に換算してかなりざっくりと定めたものです。具体例を挙げると、2 週間(10 営業日)のうちエラーが合計 50 回発生した場合は対応が必要ということになります。

でも問題が…

この運用を始めてすぐに面倒なことに気付きました。それは 2 週間のエラー発生回数の合計を人間の脳みそで計算しなければならないということです。

Sentry の issues 一覧画面には、右上のドロップダウンから表示期間を選択でき、直感的には Last 14 days で指定すれば EVENTS カラムに表示される数値は直近 14 日間のエラー回数の合算が表示されるような気がしてしまいます。しかし、EVENTS カラムに表示される数値は初回発生日から今日までの全エラー発生回数の合算であり、特定の期間のものではないのです。

そのため、2 週間の発生回数の合計を知るためには画像のように GRAPH カラムの期間を 14d にし、棒グラフをマウスオーバーしたときに表示される events を端から端まで足し合わせなくてはなりません。

せっかく基準を定めて意気揚々と取り組んでみたものの、こんな非人道的なこと誰もやりたくないですよね。

f:id:smzst:20190819100925p:plain
図1. issues 画面の GRAPH カラムに表示された棒グラフにカーソルを当て 1 日ずつエラー発生回数を数えあげる様子

Sentry API について

実践する前に、Sentry API を叩くのに必要なトークンの取得方法、今回叩くエンドポイントと使い方、レスポンスについて説明します。

Token の取得方法

実際に Sentry API 叩いてみようという気持ちになってきた方以外はこの項目を読み飛ばしてしまっても問題ありません(気持ちはあるけど Sentry 使ってない方もごめんなさい)。

さて、サイドバー左上部のアイコンのあたりをクリックするとこのようなドロップダウンが表示されますので、これの API keys をクリックしてください。

f:id:smzst:20190819101202p:plain
図2. サイドバー左上部をクリックすることで表示されるドロップダウンから API Keys を選択する様子

そうすると、このような画面に遷移します。右上の Create New Token ボタンから新しくトークンを作成しましょう。遷移先ではトークンの Scopes が設定できますが、私は特にいじらず元々選択されていた状態で作成しました。作成後は、この画面の AUTH TOKEN 欄にできたてほやほやのトークンが表示されます。

f:id:smzst:20190819102454p:plain
図3. 作成した Sentry API Token が AUTH TOKEN 欄に表示されている

今回のエンドポイント

プロジェクトの issues(エラー)のリストを返す List a Project's Issues のエンドポイントを使います。organization_slug は組織名、project_slug は対象としたいプロジェクト名を指定します。

GET   /api/0/projects/{organization_slug}/{project_slug}/issues/

ドキュメントの Example を拝借しましたが以下のような感じのレスポンスが得られます。これを見ると stats.14d[1] を合算すればよさそうです(ブラウザの events 合計と突き合わせて確認済み)。

[
  {
    "annotations": [], 
    "assignedTo": null, 
    "count": "1", 
    "culprit": "io.sentry.example.ApiRequest in perform", 
    "firstSeen": "2018-11-06T21:19:56Z", 
    "hasSeen": false, 
    "id": "2", 
    "isBookmarked": false, 
    "isPublic": false, 
    "isSubscribed": true, 
    "lastSeen": "2018-11-06T21:19:56Z", 
    "level": "error", 
    "logger": null, 
    "metadata": {
      "filename": "ApiRequest.java", 
      "type": "ApiException", 
      "value": "Authentication failed, token expired!"
    }, 
    "numComments": 0, 
    "permalink": "https://sentry.io/the-interstellar-jurisdiction/pump-station/issues/2/", 
    "project": {
      "id": "2", 
      "name": "Pump Station", 
      "slug": "pump-station"
    }, 
    "shareId": null, 
    "shortId": "PUMP-STATION-2", 
    "stats": {
      "14d": [
        [
          1541455200.0, 
          604
        ], 
        [
          1541458800.0, 
          369
        ],
        ...,
      ]
    }, 
    "status": "unresolved", 
    "statusDetails": {}, 
    "subscriptionDetails": null, 
    "title": "ApiException: Authentication failed, token expired!", 
    "type": "error", 
    "userCount": 0
  },
  ...,
]

いざ、自動化

おまたせしました。前置きが長くなってしまいましたが、Sentry API を使って人間らしさを取り戻していきましょう。毎度おなじみの Shell Script でやっていきます。ただし、Shell Script のコード自体の説明はしないです(需要なさそう…)。こんな感じで書いたら実現できるよ程度のものとしてお読みください。

やってることは単純で、プロジェクトの一覧(PROJECTS)でループし、エラーの 1 営業日あたりの発生回数が基準値を超えている、あるいはコメント数が 0 のエラーをリンクとともに標準出力しています。なお、ブログ用にエラーハンドリングや細々した処理は省いています。コード中の ORG, PROJECTS, TOKEN に関しては省略しています(適宜どこかで定義されているものとしてお読みください)。

なお、突然コメント数が登場してきましたが、これは基準は超えていないが顧客影響があるかもしれない初出のエラーを取りこぼさないためにチェックしています。一度チェックしたエラーにはコメントを付けるようにしているので、コメントが 0 件は初出のエラーを意味します。この Shell Script だけで完結できるための工夫です。

#!/bin/bash -eu

# 対応基準として定めた日あたりのエラー発生回数
CRITERIA=5
# 営業日(引数があればそれを、なければデフォルトで 10 を使う)
BUSINESS_DAYS=${1:-10}

function main () {  
  ENDPOINT="https://sentry.io/api/0/projects/$ORG/$1/issues/"
  QUERY_STRING="query=is:unresolved&statsPeriod=14d"
  RESP="$(curl -s -H "Authorization: Bearer $TOKEN" "$ENDPOINT?$QUERY_STRING")"
  
  RESP_LENGTH="$(echo $RESP | jq 'length - 1')"
  
  for i in `seq 0 $RESP_LENGTH`; do
    # 14 日間のエラー回数の合計を営業日で割って平均を算出
    AVE="$(echo $RESP | jq "[.[$i].stats.\"14d\"[][1]] | add / $BUSINESS_DAYS")"
    # コメント数を取得
    NUM_COMMENTS="$(echo $RESP | jq ".[$i].numComments")"
    
    # 基準の 5 を超えている、またはコメントが 0 件のエラーを issues のリンクとともに標準出力
    if [ $(echo "$AVE >= $CRITERIA" | bc) = 1 ] || [ $NUM_COMMENTS = 0 ]; then
        PERMALINK="$(echo $RESP | jq -r ".[$i].permalink")"
        echo "comments: $NUM_COMMENTS"
        echo "average: $AVE"
        echo $PERMALINK
    fi
  done
}

for project in $PROJECTS; do
  echo $project
  main $project
  sleep 0.1
done

さいごに

これまで対応するしないを週次の場でアドホックに決めていたため 1 時間(場合によっては 2 時間)くらいかかっていましたが、基準を設け自動化することによって 10 分(長くても 30 分)ほどまで短縮することができました。

このブログはどちらかというと技術的な側面が強いので Sentry API だの Shell Script だの言ってますが、結局のところ「基準を設ける」ことが一番大事ですね。自動化なんてスパイスみたいなもんです。

それではみなさんもよき自動化ライフを。