Mengumpulkan log Sentry
Dokumen ini menjelaskan cara menyerap log Sentry ke Google Security Operations menggunakan Amazon S3. Sentry menghasilkan data operasional dalam bentuk peristiwa, masalah, data pemantauan performa, dan informasi pelacakan error. Integrasi ini memungkinkan Anda mengirim log tersebut ke Google SecOps untuk dianalisis dan dipantau, sehingga memberikan visibilitas ke dalam error aplikasi, masalah performa, dan interaksi pengguna dalam aplikasi yang dipantau Sentry.
Sebelum memulai
Pastikan Anda memenuhi prasyarat berikut:
- Instance Google SecOps.
- Akses istimewa ke tenant Sentry (Token Auth dengan cakupan API).
- Akses istimewa ke AWS (S3, Identity and Access Management (IAM), Lambda, EventBridge).
Kumpulkan prasyarat Sentry (ID, kunci API, ID organisasi, token)
- Login ke Sentry.
- Temukan Slug organisasi Anda:
- Buka Setelan > Organisasi > Setelan > ID Organisasi (slug muncul di samping nama organisasi).
- Buat Token Autentikasi:
- Buka Setelan > Setelan Developer > Token Pribadi.
- Klik Buat Baru.
- Cakupan (minimum):
org:read
,project:read
,event:read
. - Salin nilai token (ditampilkan satu kali). Ini digunakan sebagai:
Authorization: Bearer <token>
.
- (Jika dihosting sendiri): Catat URL dasar Anda (misalnya,
https://<your-domain>
); jika tidak, gunakanhttps://sentry.io
.
Mengonfigurasi bucket AWS S3 dan IAM untuk Google SecOps
- Buat bucket Amazon S3 dengan mengikuti panduan pengguna ini: Membuat bucket
- Simpan Name dan Region bucket untuk referensi di masa mendatang (misalnya,
sentry-logs
). - Buat Pengguna dengan mengikuti panduan pengguna ini: Membuat pengguna IAM.
- Pilih Pengguna yang dibuat.
- Pilih tab Kredensial keamanan.
- Klik Create Access Key di bagian Access Keys.
- Pilih Layanan pihak ketiga sebagai Kasus penggunaan.
- Klik Berikutnya.
- Opsional: Tambahkan tag deskripsi.
- Klik Create access key.
- Klik Download CSV file untuk menyimpan Access Key dan Secret Access Key untuk referensi di masa mendatang.
- Klik Selesai.
- Pilih tab Permissions.
- Klik Tambahkan izin di bagian Kebijakan izin.
- Pilih Tambahkan izin.
- Pilih Lampirkan kebijakan secara langsung.
- Cari kebijakan AmazonS3FullAccess.
- Pilih kebijakan.
- Klik Berikutnya.
- Klik Add permissions.
Mengonfigurasi kebijakan dan peran IAM untuk upload S3
- Di konsol AWS, buka IAM > Policies.
- Klik Buat kebijakan > tab JSON.
- Salin dan tempel kebijakan berikut.
Policy JSON (ganti
sentry-logs
jika Anda memasukkan nama bucket yang berbeda):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutObjects", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::sentry-logs/*" }, { "Sid": "AllowGetStateObject", "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::sentry-logs/sentry/events/state.json" } ] }
Klik Berikutnya > Buat kebijakan.
Buka IAM > Roles > Create role > AWS service > Lambda.
Lampirkan kebijakan yang baru dibuat.
Beri nama peran
WriteSentryToS3Role
, lalu klik Buat peran.
Buat fungsi Lambda
- Di Konsol AWS, buka Lambda > Functions > Create function.
- Klik Buat dari awal.
Berikan detail konfigurasi berikut:
Setelan Nilai Nama sentry_to_s3
Runtime Python 3.13 Arsitektur x86_64 Peran eksekusi WriteSentryToS3Role
Setelah fungsi dibuat, buka tab Code, hapus stub, dan tempelkan kode berikut (
sentry_to_s3.py
).#!/usr/bin/env python3 # Lambda: Pull Sentry project events (raw JSON) to S3 using Link "previous" cursor for duplicate-safe polling import os, json, time from urllib.request import Request, urlopen from urllib.parse import urlencode, urlparse, parse_qs import boto3 ORG = os.environ["SENTRY_ORG"].strip() TOKEN = os.environ["SENTRY_AUTH_TOKEN"].strip() S3_BUCKET = os.environ["S3_BUCKET"] S3_PREFIX = os.environ.get("S3_PREFIX", "sentry/events/") STATE_KEY = os.environ.get("STATE_KEY", "sentry/events/state.json") BASE = os.environ.get("SENTRY_API_BASE", "https://sentry.io").rstrip("/") MAX_PROJECTS = int(os.environ.get("MAX_PROJECTS", "100")) MAX_PAGES_PER_PROJECT = int(os.environ.get("MAX_PAGES_PER_PROJECT", "5")) s3 = boto3.client("s3") HDRS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json", "User-Agent": "chronicle-s3-sentry-lambda/1.0"} def _get_state() -> dict: try: obj = s3.get_object(Bucket=S3_BUCKET, Key=STATE_KEY) raw = obj["Body"].read() return json.loads(raw) if raw else {"projects": {}} except Exception: return {"projects": {}} def _put_state(state: dict): s3.put_object(Bucket=S3_BUCKET, Key=STATE_KEY, Body=json.dumps(state, separators=(",", ":")).encode("utf-8")) def _req(path: str, params: dict | None = None): url = f"{BASE}{path}" if params: url = f"{url}?{urlencode(params)}" req = Request(url, method="GET", headers=HDRS) with urlopen(req, timeout=60) as r: data = json.loads(r.read().decode("utf-8")) link = r.headers.get("Link") return data, link def _parse_link(link_header: str | None): """Return (prev_cursor, prev_has_more, next_cursor, next_has_more).""" if not link_header: return None, False, None, False prev_cursor, next_cursor = None, None prev_more, next_more = False, False parts = [p.strip() for p in link_header.split(",")] for p in parts: if "<" not in p or ">" not in p: continue url = p.split("<", 1)[1].split(">", 1)[0] rel = "previous" if 'rel="previous"' in p else ("next" if 'rel="next"' in p else None) has_more = 'results="true"' in p try: q = urlparse(url).query cur = parse_qs(q).get("cursor", [None])[0] except Exception: cur = None if rel == "previous": prev_cursor, prev_more = cur, has_more elif rel == "next": next_cursor, next_more = cur, has_more return prev_cursor, prev_more, next_cursor, next_more def _write_page(project_slug: str, payload: object, page_idx: int) -> str: ts = time.gmtime() key = f"{S3_PREFIX.rstrip('/')}/{time.strftime('%Y/%m/%d', ts)}/sentry-{project_slug}-{page_idx:05d}.json" s3.put_object(Bucket=S3_BUCKET, Key=key, Body=json.dumps(payload, separators=(",", ":")).encode("utf-8")) return key def list_projects(max_projects: int): projects, cursor = [], None while len(projects) < max_projects: params = {"cursor": cursor} if cursor else {} data, link = _req(f"/api/0/organizations/{ORG}/projects/", params) for p in data: slug = p.get("slug") if slug: projects.append(slug) if len(projects) >= max_projects: break # advance pagination _, _, next_cursor, next_more = _parse_link(link) cursor = next_cursor if next_more else None if not next_more: break return projects def fetch_project_events(project_slug: str, start_prev_cursor: str | None): # If we have a stored "previous" cursor, poll forward (newer) until no more results. # If not (first run), fetch the latest page, then optionally follow "next" (older) for initial backfill up to the limit. pages = 0 total = 0 latest_prev_cursor_to_store = None def _one(cursor: str | None): nonlocal pages, total, latest_prev_cursor_to_store params = {"cursor": cursor} if cursor else {} data, link = _req(f"/api/0/projects/{ORG}/{project_slug}/events/", params) _write_page(project_slug, data, pages) total += len(data) if isinstance(data, list) else 0 prev_c, prev_more, next_c, next_more = _parse_link(link) # capture the most recent "previous" cursor observed to store for the next run latest_prev_cursor_to_store = prev_c or latest_prev_cursor_to_store pages += 1 return prev_c, prev_more, next_c, next_more if start_prev_cursor: # Poll new pages toward "previous" until no more cur = start_prev_cursor while pages < MAX_PAGES_PER_PROJECT: prev_c, prev_more, _, _ = _one(cur) if not prev_more: break cur = prev_c else: # First run: start at newest, then (optionally) backfill a few older pages prev_c, _, next_c, next_more = _one(None) cur = next_c while next_more and pages < MAX_PAGES_PER_PROJECT: _, _, next_c, next_more = _one(cur) cur = next_c return {"project": project_slug, "pages": pages, "written": total, "store_prev_cursor": latest_prev_cursor_to_store} def lambda_handler(event=None, context=None): state = _get_state() state.setdefault("projects", {}) projects = list_projects(MAX_PROJECTS) summary = [] for slug in projects: start_prev = state["projects"].get(slug, {}).get("prev_cursor") res = fetch_project_events(slug, start_prev) if res.get("store_prev_cursor"): state["projects"][slug] = {"prev_cursor": res["store_prev_cursor"]} summary.append(res) _put_state(state) return {"ok": True, "projects": len(projects), "summary": summary} if __name__ == "__main__": print(lambda_handler())
Buka Configuration > Environment variables.
Klik Edit > Tambahkan variabel lingkungan baru.
Masukkan variabel lingkungan yang diberikan dalam tabel berikut, dengan mengganti nilai contoh dengan nilai Anda.
Variabel lingkungan
Kunci Nilai Contoh Deskripsi S3_BUCKET
sentry-logs
Nama bucket S3 tempat data akan disimpan. S3_PREFIX
sentry/events/
Awalan S3 opsional (subfolder) untuk objek. STATE_KEY
sentry/events/state.json
Kunci file status/titik pemeriksaan opsional. SENTRY_ORG
your-org-slug
Slug organisasi Sentry. SENTRY_AUTH_TOKEN
sntrys_************************
Token Auth Sentry dengan org:read, project:read, event:read. SENTRY_API_BASE
https://sentry.io
URL dasar Sentry API (dihosting sendiri: https://<your-domain>
).MAX_PROJECTS
100
Jumlah maksimum project yang akan diproses. MAX_PAGES_PER_PROJECT
5
Jumlah maksimum halaman per project per eksekusi. Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > your-function).
Pilih tab Configuration
Di panel General configuration, klik Edit.
Ubah Waktu Tunggu menjadi 5 menit (300 detik), lalu klik Simpan.
Membuat jadwal EventBridge
- Buka Amazon EventBridge > Scheduler > Create schedule.
- Berikan detail konfigurasi berikut:
- Jadwal berulang: Tarif (
1 hour
). - Target: Fungsi Lambda Anda
sentry_to_s3
. - Name:
sentry-1h
.
- Jadwal berulang: Tarif (
- Klik Buat jadwal.
(Opsional) Buat pengguna & kunci IAM hanya baca untuk Google SecOps
- Di AWS Console, buka IAM > Users.
- Klik Add users.
- Berikan detail konfigurasi berikut:
- Pengguna: Masukkan
secops-reader
. - Jenis akses: Pilih Kunci akses — Akses terprogram.
- Pengguna: Masukkan
- Klik Buat pengguna.
- Lampirkan kebijakan baca minimal (kustom): Pengguna > secops-reader > Izin > Tambahkan izin > Lampirkan kebijakan secara langsung > Buat kebijakan.
JSON:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::sentry-logs/*" }, { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::sentry-logs" } ] }
Nama =
secops-reader-policy
.Klik Buat kebijakan > cari/pilih > Berikutnya > Tambahkan izin.
Buat kunci akses untuk
secops-reader
: Kredensial keamanan > Kunci akses.Klik Create access key.
Download
.CSV
. (Anda akan menempelkan nilai ini ke feed).
Mengonfigurasi feed di Google SecOps untuk menyerap log Sentry
- Buka Setelan SIEM > Feed.
- Klik + Tambahkan Feed Baru.
- Di kolom Nama feed, masukkan nama untuk feed (misalnya,
Sentry Logs
). - Pilih Amazon S3 V2 sebagai Jenis sumber.
- Pilih Sentry sebagai Jenis log.
- Klik Berikutnya.
- Tentukan nilai untuk parameter input berikut:
- URI S3:
s3://sentry-logs/sentry/events/
- Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
- Usia File Maksimum: Menyertakan file yang diubah dalam jumlah hari terakhir. Defaultnya adalah 180 hari.
- ID Kunci Akses: Kunci akses pengguna dengan akses ke bucket S3.
- Kunci Akses Rahasia: Kunci rahasia pengguna dengan akses ke bucket S3.
- Namespace aset: Namespace aset.
- Label penyerapan: Label yang diterapkan ke peristiwa dari feed ini.
- URI S3:
- Klik Berikutnya.
- Tinjau konfigurasi feed baru Anda di layar Selesaikan, lalu klik Kirim.
Perlu bantuan lain? Dapatkan jawaban dari anggota Komunitas dan profesional Google SecOps.