3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SentryのEventの数を集計したい

3
Last updated at Posted at 2026-02-06

SentryのEvent数って集計できないですよね

弊システムのSentryの通知が多すぎるので改善キャンペーンを展開中です。
実績を確認するためにSentryのイベント数を月ごとに集計したくなりました。
image.png

画面上にはIssue単位のイベント数が表示されますが、それを合計するのはとても面倒でした。いままではダッシュボードを作って、ページングしながらExcelに張り付けてSumしてました。

image.png

ツール化する

先人がおり、必要な情報をCSV化してくれるPythonコードを書いてくれていました。

ただ、SentryのAPIが変わったりしてうまく動かなかったので作り直しました。

コード

import requests
import csv
import sys
from datetime import datetime, timedelta, timezone

def print_help():
    help_message = """
Sentry Issue Exporter

使用方法:
    python sentry.py <organization> <project_id> <api_token> [start_date] [end_date]

必須引数:
    organization    : Sentry の Organization Slug
    project_id      : Sentry の Project ID
    api_token       : Sentry API Token

オプション引数:
    start_date      : 取得開始日時(ISO 8601 形式)
    end_date        : 取得終了日時(ISO 8601 形式)
                     省略時は過去30日間が対象

入力例:
    python sentry.py my-org 123456 sntrys_abcdef1234567890
    python sentry.py my-org 123456 sntrys_abcdef1234567890 2024-01-01T00:00:00Z 2024-01-31T23:59:59Z
"""
    print(help_message)

args = sys.argv

# 引数のバリデーションチェック
if len(args) < 4 or len(args) > 6:
    print(f"エラー: 引数の数が正しくありません")
    print(f"受け取った引数の数: {len(args) - 1}")
    print_help()
    sys.exit(1)

if len(args) > 1 and (args[1].lower() == 'help' or args[1].lower() == 'man'):
    print_help()
    sys.exit(0)

organization_slug = args[1]
print(f"Organization: {organization_slug}")
project_id = args[2]
print(f"Project: {project_id}")
api_token = args[3]
print(f"ApiToken: {api_token}")

# Sentry APIの設定
sentry_api_url = f'https://sentry.io/api/0/organizations/{organization_slug}/issues/'

# ヘッダーの設定
headers = {
    'Authorization': f'Bearer {api_token}',
    'Content-Type': 'application/json',
}

# 対象期間の設定(例:過去30日間)
arg_start_date = args[4] if len(args) > 4 else None
arg_end_date = args[5] if len(args) > 5 else None

# 日付のパース
end_date = datetime.fromisoformat(arg_end_date.replace('Z', '+00:00')) if arg_end_date else datetime.now(timezone.utc)
end_date = end_date.replace(hour=23, minute=59, second=59, microsecond=0) # 日付の終わりに設定
start_date = datetime.fromisoformat(arg_start_date.replace('Z', '+00:00')) if arg_start_date else end_date - timedelta(days=30)

start = start_date.isoformat().replace('+00:00', 'Z')
end = end_date.isoformat().replace('+00:00', 'Z')

print(f"Fetching issues from {start} to {end}")

# Issueを取得する
issues = []
cursor = None

# レスポンスヘッダーからcursorを取り出す
def get_cursor_from_link_header(link_header):
  if not link_header:
    return None

  # Split by comma to get individual link entries
  links = link_header.split(', ')

  for link in links:
    if 'rel="next"' in link and 'results="true"' in link:
      # Extract cursor value
      cursor_start = link.find('cursor="') + len('cursor="')
      cursor_end = link.find('"', cursor_start)
      return link[cursor_start:cursor_end]

  return None

while True:
    params = {
        'start': start,
        'end': end,
        'project': project_id,
        'query': '',
    }
    if cursor:
        params['cursor'] = cursor

    print(f"Fetching issues")
    response = requests.get(sentry_api_url, headers=headers, params=params)
    data = response.json()
    issues.extend(data)
    print(f"Fetched {len(data)} issues, total so far: {len(issues)}")

    # 次のページがあるかどうか確認
    cursor = get_cursor_from_link_header(response.headers.get('link'))
    if not cursor:
        break

# Issueごとにイベントを集計する関数
def get_event_count(issue_id):
    url = f'https://sentry.io/api/0/issues/{issue_id}/events/'
    events = []
    cursor = None

    while True:
        params = {
            'start': start,
            'end': end,
            'query': '',
        }
        if cursor:
            params['cursor'] = cursor

        print(f"Fetching events for issue {issue_id}")
        response = requests.get(url, headers=headers, params=params)
        data = response.json()
        events.extend(data)
        print(f"Fetched {len(data)} events for issue {issue_id}, total so far: {len(events)}")

        cursor = get_cursor_from_link_header(response.headers.get('link'))
        if not cursor:
            break

    return len(events)

# 各Issueのイベント数を集計
for issue in issues:
    issue['event_count'] = get_event_count(issue['id'])

# CSVに書き出す
with open('sentry_issues.csv', 'w', newline='') as csvfile:
    fieldnames = ['id', 'title', 'status', 'culprit', 'permalink', 'firstSeen', 'lastSeen', 'event_count']
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    for issue in issues:
        writer.writerow({
            'id': issue['id'],
            'title': issue['title'],
            'status': issue['status'],
            'culprit': issue['culprit'],
            'permalink': issue['permalink'],
            'firstSeen': issue['firstSeen'],
            'lastSeen': issue['lastSeen'],
            'event_count': issue['event_count'],
        })

# 総イベントカウント数を表示
total_event_count = sum(issue['event_count'] for issue in issues)
print(f"Total event count: {total_event_count}")

起動方法

python3 sentry.py [organization] [project_id] [api_token] [start_date(yyyymmdd)] [end_date(yyyymmmdd]

dockerでの起動方法

ホストマシンで以下のコマンドを実施します

docker run -itd --name sentry-ana -v .:/tmp python:3
docker exec -it sentry-ana bash
pip3 install requests
cd /tmp
python3 sentry.py [organization] [project_id] [api_token] [start_date(yyyymmdd)] [end_date(yyyymmmdd]

結果

最後にTotal event countがでます。
image.png

課題

ただ、この結果、ダッシュボードで以下のようにEventsを出した結果と異なる数値が出るんですよねー。わからない。
とりあえず、Sentry通知数が改善されていくことが継続集計出来ればいいのでこのツールで満足です。
image.png

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?