# Python (SDK tương thích Sentry)

> Dùng sentry-sdk chính thức cho Python với DSN Belvador và hàm lọc dữ liệu nhạy cảm do Belvador cung cấp.

Với Python, Belvador dùng **`sentry-sdk` chính thức** với DSN Belvador của bạn, cộng các tuỳ chọn khởi tạo và hàm lọc dữ liệu ở dưới.

> [!NOTE]
> Đây là cách cài tương thích, không phải gói của Belvador. Bộ test của Belvador chạy hàm `scrub_event` bằng Python trên dữ liệu độc hại, nhưng Belvador không chứng nhận bản thân `sentry-sdk` hay các tích hợp framework của nó. Node của bạn lọc thêm một lần trước khi lưu — xem [Bảo vệ dữ liệu](https://belvador.dev/vi/docs/privacy).

## Trước khi bắt đầu

- Một project có nền tảng **Python** và DSN của nó.
- Phiên bản Python được bản `sentry-sdk` hiện tại hỗ trợ (xem [hướng dẫn Python của Sentry](https://docs.sentry.io/platforms/python/)).

## 1. Cài đặt

```bash
pip install --upgrade "sentry-sdk"
```

## 2. Khởi tạo lúc ứng dụng chạy

Đặt đoạn này ở nơi ứng dụng khởi động — tốt nhất là trước khi import framework web, để tích hợp của framework cũng được bật.

```python title="app.py"
import os

import sentry_sdk

import re

SENSITIVE_KEY = re.compile(r"password|passwd|pwd|secret|api_key|apikey|api-key|access_token|auth|credentials|credit_card|card_number|cvv|private_key|privatekey|session|cookie|token|bearer|ssn|dob", re.I)
DROP_HEADER = re.compile(r"^(authorization|proxy-authorization|cookie|set-cookie|x-forwarded-for|x-real-ip|cf-connecting-ip|true-client-ip)$", re.I)
IN_TEXT = [
    (re.compile(r"\bBearer\s+[A-Za-z0-9._~+/-]+=*", re.I), "Bearer [Filtered]"),
    (re.compile(r"\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}"), "[Filtered]"),
    (re.compile(r"\b[sr]k_(live|test)_[A-Za-z0-9]{8,}\b"), "[Filtered]"),
    (re.compile(r"([?&#;\s\"']?[^=&#\s\"':]*(?:token|key|secret|password|session|auth)[^=&#\s\"':]*[\"']?\s*[=:]\s*[\"']?)[^&#\s\"',}]+", re.I), r"\1[Filtered]"),
]


def scrub_text(text):
    for pattern, repl in IN_TEXT:
        text = pattern.sub(repl, text)
    return text


def scrub_value(value, depth=0):
    if isinstance(value, str):
        return scrub_text(value)
    if depth > 8:
        return value
    if isinstance(value, (list, tuple)):
        return [scrub_value(v, depth + 1) for v in value]
    if isinstance(value, dict):
        return {k: "[Filtered]" if SENSITIVE_KEY.search(str(k)) else scrub_value(v, depth + 1) for k, v in value.items()}
    return value


def scrub_event(event, hint):
    request = event.get("request")
    if request:
        for field in ("data", "cookies", "env"):
            request.pop(field, None)
        headers = request.get("headers") or {}
        for name in list(headers):
            if DROP_HEADER.match(name):
                del headers[name]
            elif SENSITIVE_KEY.search(name):
                headers[name] = "[Filtered]"
        if isinstance(request.get("url"), str):
            request["url"] = scrub_text(request["url"])
        if request.get("query_string"):
            request["query_string"] = scrub_value(request["query_string"])
    user = event.get("user")
    if user is not None:
        event["user"] = {"id": user["id"]} if "id" in user else None
    if event.get("message"):
        event["message"] = scrub_text(event["message"])
    if event.get("logentry"):
        event["logentry"] = scrub_value(event["logentry"])
    for ex in (event.get("exception") or {}).get("values") or []:
        if ex.get("value"):
            ex["value"] = scrub_text(ex["value"])
    for field in ("extra", "contexts", "tags"):
        if event.get(field):
            event[field] = scrub_value(event[field])
    crumbs = event.get("breadcrumbs")
    if isinstance(crumbs, dict) and crumbs.get("values"):
        crumbs["values"] = [scrub_value(b) for b in crumbs["values"]]
    return event


sentry_sdk.init(
    dsn=os.environ.get("BELVADOR_DSN", "https://<public-key>@ingest.belvador.dev/<project-id>"),
    release=os.environ.get("APP_VERSION"),
    environment=os.environ.get("APP_ENV", "production"),
    send_default_pii=False,
    traces_sample_rate=0,
    max_breadcrumbs=50,
    max_value_length=8192,
    before_send=scrub_event,
)
```

| Tuỳ chọn | Vì sao |
|---|---|
| `dsn` | Đọc `BELVADOR_DSN` từ biến môi trường, nếu không có thì dùng DSN của project |
| `release`, `environment` | Lấy từ `APP_VERSION` và `APP_ENV` (mặc định `production`) |
| `send_default_pii=False` | SDK không tự thêm địa chỉ IP, cookie hay thông tin người dùng |
| `traces_sample_rate=0` | SDK Sentry không gửi span; muốn có traces thì dùng [OpenTelemetry](https://belvador.dev/vi/docs/otel/python) |
| `max_breadcrumbs=50`, `max_value_length=8192` | Cùng giới hạn với SDK của Belvador |
| `before_send=scrub_event` | Xoá body request, cookie, header xác thực và header IP người dùng; chỉ giữ `user.id`; che giá trị dưới tên nhạy cảm và thông tin đăng nhập trong văn bản |

## Framework

`sentry-sdk` tự bật tích hợp cho các framework nó tìm thấy, như Django, Flask và FastAPI, nên lỗi chưa xử lý trong request được báo. Tuỳ chọn riêng cho từng framework có trong [hướng dẫn Python của Sentry](https://docs.sentry.io/platforms/python/). Belvador chưa kiểm chứng từng framework.

## Báo những lỗi bạn đã xử lý

```python
import sentry_sdk

try:
    charge_card(order)
except PaymentError as error:
    sentry_sdk.capture_exception(error)
    raise
```

## Kiểm tra

```python
import sentry_sdk

sentry_sdk.capture_exception(Exception("Belvador Python test"))
sentry_sdk.flush(timeout=2)
```

Chạy cùng `sentry_sdk.init(...)` như ứng dụng của bạn. Issue hiện trong project sau vài giây.

## Giới hạn cần biết

- Hàm lọc dựa trên mẫu ký tự. Bí mật nằm dưới tên lạ, hoặc trong một câu thông báo tự do, có thể lọt qua.
- Giữ `traces_sample_rate=0`; dữ liệu tracing không được lưu và bị đếm vào **Chưa hỗ trợ** trong số liệu nhận của project.
- Job định kỳ và task Celery beat báo được lượt chạy bằng `sentry_sdk.crons` — xem [Monitor bằng SDK Sentry chính thức](https://belvador.dev/vi/docs/monitors/sentry-sdks).
