Lightweight self-hosted debugger and metrics dashboard for FastAPI.
Project description
โก fastapi-flare
Lightweight self-hosted debugger and metrics dashboard for FastAPI.
Zero-config by default (SQLite) โ PostgreSQL-ready for production.
What is fastapi-flare?
fastapi-flare is a self-hosted error tracking and metrics library for FastAPI applications.
It automatically captures HTTP and unhandled exceptions, stores them locally or in PostgreSQL, and exposes a dark-theme dashboard โ all with a single line of code.
No external services. No SaaS. No noise.
Features
| ๐ One-line setup | setup(app) โ works immediately, no config required |
| ๐ Auto-capture | HTTP 4xx/5xx and unhandled Python exceptions |
| ๐งต Non-HTTP capture | Background tasks, workers, logger.exception(), stray asyncio tasks |
| ๐งฉ Issue grouping | Deterministic fingerprint collapses repeated errors into one issue with occurrence count, first/last seen, resolve/reopen |
| ๐ฅ๏ธ Admin dashboard | Built-in at /flare โ dark theme, filters, pagination |
| ๐๏ธ Dual storage | SQLite (zero-config default) or PostgreSQL (production) |
| ๐ฅ Fire-and-forget | Logging never blocks your request handlers |
| โ๏ธ Background worker | Async task runs retention cleanup every 5 seconds |
| ๐ Retention policies | Time-based (default 7 days) + count-based (10k entries) |
| ๐ Auth-ready | Protect the dashboard with any FastAPI Depends() |
| ๐ Env-configurable | All settings available via FLARE_* environment variables |
Installation
pip install fastapi-flare
Requirements: Python 3.11+, FastAPI.
aiosqliteandasyncpgare bundled โ no extra installs needed for either backend.
Quick Start
Zero-config (SQLite, works immediately):
from fastapi import FastAPI
from fastapi_flare import setup
app = FastAPI()
setup(app)
# Dashboard at http://localhost:8000/flare
# Creates flare.db automatically โ no setup required.
PostgreSQL (production):
from fastapi_flare import setup, FlareConfig
setup(app, config=FlareConfig(
storage_backend="postgresql",
pg_dsn="postgresql://user:password@localhost:5432/mydb",
))
Storage Backends
SQLite (default)
Zero-config local file storage. Works immediately without any external dependencies.
Ideal for development, quick testing, small deployments, and air-gapped environments.
setup(app, config=FlareConfig(
storage_backend="sqlite", # default โ can be omitted
sqlite_path="flare.db", # path to the .db file
))
Via environment variables:
FLARE_STORAGE_BACKEND=sqlite
FLARE_SQLITE_PATH=/data/flare.db
Uses WAL mode and indexed queries for efficient reads and writes.
PostgreSQL (production)
Production-grade backend using asyncpg with a connection pool.
Direct INSERT on every log entry โ no intermediate buffer or drain step.
setup(app, config=FlareConfig(
storage_backend="postgresql",
pg_dsn="postgresql://user:password@localhost:5432/mydb",
))
Via environment variables:
FLARE_STORAGE_BACKEND=postgresql
FLARE_PG_DSN=postgresql://user:password@localhost:5432/mydb
Special characters in passwords:
URL-encode@as%40,#as%23,&as%26, etc.
Example:password@123โFLARE_PG_DSN=postgresql://user:password%40123@host:5432/db
The table flare_logs (or your custom name) is created automatically on first connection.
Multi-Project Isolation
You can run multiple independent APIs storing their logs in the same PostgreSQL server.
Two isolation strategies are available โ choose what fits best:
Strategy 1 โ One database per project (full isolation)
Each API points to a different database. Complete separation at the database level.
# API checkout
FLARE_PG_DSN=postgresql://user:pass@host:5432/checkout_db
# API auth
FLARE_PG_DSN=postgresql://user:pass@host:5432/auth_db
# API orders
FLARE_PG_DSN=postgresql://user:pass@host:5432/orders_db
Strategy 2 โ One database, separate tables (centralized)
All APIs share one database, each writing to its own table.
Simpler to manage โ one database to back up, one server to monitor.
# All APIs point to the same database
FLARE_PG_DSN=postgresql://user:pass@host:5432/mydb
# Each project gets its own table
FLARE_PG_TABLE_NAME=flare_logs_checkout # API checkout
FLARE_PG_TABLE_NAME=flare_logs_auth # API auth
FLARE_PG_TABLE_NAME=flare_logs_orders # API orders
Each table is created automatically by flare on first connection.
Full Configuration
from fastapi_flare import setup, FlareConfig
setup(app, config=FlareConfig(
# โโ Storage (choose one) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
storage_backend="sqlite", # "sqlite" (default) | "postgresql"
# SQLite options
sqlite_path="flare.db",
# PostgreSQL options
pg_dsn="postgresql://user:pass@localhost:5432/mydb",
pg_table_name="flare_logs", # custom table name for multi-project setups
# โโ Retention โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
max_entries=10_000, # count-based cap
retention_hours=168, # time-based retention (7 days)
# โโ Dashboard โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
dashboard_path="/flare",
dashboard_title="My App โ Errors",
dashboard_auth_dependency=None, # e.g. Depends(verify_token)
# โโ Request tracking (HTTP Requests tab) โโโโโโโโโโโโโโโโโโโโโโโโโโโ
track_requests=True, # enable the HTTP Requests tab (default: True)
track_2xx_requests=False, # also record successful 2xx responses (default: False)
request_max_entries=1000, # ring buffer size for tracked requests
capture_request_headers=False, # store request headers per entry (adds data volume)
# โโ Non-HTTP error capture โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
capture_logging=False, # forward WARNING+ from logging module
capture_logging_loggers=None, # "myapp.worker,myapp.jobs" โ None = root
capture_asyncio_errors=False, # capture stray asyncio task failures
# โโ Worker โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
worker_interval_seconds=5,
worker_batch_size=100,
))
Tip โ showing all requests in the HTTP Requests tab:
By default only 4xx and 5xx are recorded. To also capture 200 OK and other successful responses, settrack_2xx_requests=True(orFLARE_TRACK_2XX_REQUESTS=true).
Environment Variables
All options can be configured via FLARE_* environment variables โ no code changes needed:
FLARE_STORAGE_BACKEND=postgresql
FLARE_PG_DSN=postgresql://user:pass@localhost:5432/mydb
FLARE_PG_TABLE_NAME=flare_logs
FLARE_RETENTION_HOURS=72
FLARE_MAX_ENTRIES=5000
FLARE_DASHBOARD_PATH=/errors
FLARE_DASHBOARD_TITLE="Production Errors"
# Request tracking
FLARE_TRACK_REQUESTS=true
FLARE_TRACK_2XX_REQUESTS=true # record 200 OK and other successful responses
FLARE_REQUEST_MAX_ENTRIES=1000
FLARE_CAPTURE_REQUEST_HEADERS=false
# Non-HTTP error capture
FLARE_CAPTURE_LOGGING=true # forward logger.exception / logger.error
FLARE_CAPTURE_LOGGING_LOGGERS=myapp.worker # optional comma-separated allow-list
FLARE_CAPTURE_ASYNCIO_ERRORS=true # capture stray asyncio tasks
Dashboard
The built-in dashboard gives you full visibility into your application errors without leaving your infrastructure.
| Feature | Detail |
|---|---|
| URL | {dashboard_path} (default /flare) |
| Stats cards | Errors/Warnings in last 24h, total entries, latest error time |
| Filters | Level (ERROR / WARNING), event name, full-text search |
| Table | Timestamp, level badge, event, message, endpoint, HTTP status |
| Detail modal | Full message, error, stack trace, request metadata, context JSON |
| Storage overview | Backend info, connection status, pool stats (PostgreSQL) or file size (SQLite) |
| Auto-refresh | 30s polling toggle |
Issue Grouping (new in 0.3.0)
Until 0.2.x every captured error was a standalone row. Five hundred occurrences
of the same ValueError meant five hundred lines to read. Starting in 0.3.0
fastapi-flare groups semantically-equivalent errors into issues the way
Sentry and Rollbar do โ with occurrence counting, first/last-seen, and a
resolve/reopen workflow.
What changed
- New aba Issues at
/flare/issuesโ grouped view. - New table
flare_issues(auto-migrated on both SQLite and PostgreSQL). - New column
issue_fingerprintonflare_logs(idempotentADD COLUMN IF NOT EXISTS). - Every
push_log()now computes a deterministic fingerprint and upserts the issue. - New JSON API under
/flare/api/issues. - The existing Errors tab (
/flare) is unchanged โ it remains the raw stream.
No configuration required. Upgrading is transparent; existing deployments
get the new table and column on next start, and rows created before the upgrade
simply have issue_fingerprint = NULL.
How the fingerprint works
For each captured log, a 16-char blake2b hash is computed from:
| Scenario | Hash input |
|---|---|
Has stack trace (generic exception / logger.exception) |
exception_type | endpoint | top-5 frames |
HTTP-only signal (raised HTTPException) |
http | status_code | endpoint |
Validation error (RequestValidationError) |
validation | endpoint |
| Fallback | event | endpoint | message[:200] |
Each stack frame is normalised to (basename(file), function_name) โ line
numbers and absolute paths are dropped on purpose so ordinary refactors
(moving code down the file, renaming a /srv/app/... path) do not re-fingerprint
the same bug. Rename the file or the function and the issue does split โ that's
usually the signal you want.
This is intentionally different from the cooldown fingerprint in
fastapi_flare.alerting, which stays coarse (event + endpoint) to throttle
Slack/Discord pings.
Behaviour you can rely on
- 500 identical errors โ 1 issue with
occurrence_count = 500, the latestlast_seen, and the originalfirst_seenpreserved. - Same
ValueError, different call path โ separate issues. Lets you tell apart an actual regression from an unrelated code path that happens to raise the same type. - Resolved issues reopen automatically the next time the same fingerprint
fires โ
resolvedflips back tofalseandresolved_atis cleared. - Level upgrades, never downgrades. A WARNING issue that later hits ERROR becomes ERROR for good.
- Issue state survives retention. When
retention_hourspurges raw logs, theflare_issuesrow keepsoccurrence_count,first_seen, andlast_seen. Only the drill-down list of occurrences shrinks.
Using the dashboard
Open /flare/issues:
- Stat cards at the top โ Open / New (24h) / Resolved (7d) / Total.
- Filter chips โ All / Open / Resolved.
- Search โ matches on
exception_type,endpoint, or sample message. - Row click โ opens an issue modal with the list of occurrences. Each occurrence row is clickable and shows the full stack trace, request body, headers, and context, the same as the Errors tab.
- Resolve / Reopen button inside the modal. The change is reflected immediately in the stat cards and list.
JSON API
| Method | Path | Description |
|---|---|---|
GET |
/flare/api/issues |
Paginated list. Query: page, limit, resolved=true/false, search=... |
GET |
/flare/api/issues/stats |
Counts used by the stat cards |
GET |
/flare/api/issues/{fingerprint} |
Issue detail + paginated occurrences |
PATCH |
/flare/api/issues/{fingerprint} |
Body {"resolved": true | false} |
Example:
curl -s http://localhost:8003/flare/api/issues?resolved=false | jq
# { "issues": [ {...} ], "total": 12, "page": 1, "limit": 50, "pages": 1 }
curl -X PATCH http://localhost:8003/flare/api/issues/63d05fb9d296a8e1 \
-H 'Content-Type: application/json' \
-d '{"resolved": true}'
# { "ok": true, "action": "issue_status", "detail": "Issue resolved" }
Full reference (fingerprint internals, storage model, migration, gotchas):
docs/issues.md.
Try it โ the demo app
A dedicated example under examples/example_issues.py exercises every
grouping scenario:
poetry run uvicorn examples.example_issues:app --reload --port 8003
| Route | What it proves |
|---|---|
GET /boom/value-error (ร10) |
1 issue with occurrence_count = 10 |
GET /boom/key-error (ร5) |
separate issue (different exception type) |
GET /boom/deep (ร3) |
separate issue (same ValueError, different stack) |
GET /items/{iid} |
404 โ HTTP 404 issue per endpoint |
GET /users |
403 โ HTTP 403 issue |
POST /signup with bad body |
422 โ RequestValidationError issue per endpoint |
POST /orders with bad total |
500 โ RuntimeError issue |
GET /trigger/manual |
captured via capture_exception() outside the request path |
GET /stress/{n} |
generates n errors at once to exercise pagination |
Full validation walkthrough:
# 1. Start the app
poetry run uvicorn examples.example_issues:app --reload --port 8003
# 2. Fire traffic
for i in {1..10}; do curl -s http://localhost:8003/boom/value-error >/dev/null; done
for i in {1..5}; do curl -s http://localhost:8003/boom/key-error >/dev/null; done
curl -s http://localhost:8003/items/999 >/dev/null
curl -s http://localhost:8003/stress/50 >/dev/null
# 3. Open /flare/issues โ you'll see one row per issue kind with the right counts.
# 4. Click a row โ modal shows every occurrence. Click one โ full stack trace.
# 5. Click Resolve โ issue leaves the Open filter.
# 6. Fire the same endpoint again โ issue reopens automatically.
What's stored โ FlareIssue
class FlareIssue(BaseModel):
fingerprint: str # 16-char blake2b hex, primary key
exception_type: str | None # "ValueError" | "HTTP 404" | "RequestValidationError" | ...
endpoint: str | None
sample_message: str # first message seen for this issue
sample_request_id: str | None
occurrence_count: int
first_seen: datetime
last_seen: datetime
level: Literal["ERROR", "WARNING"] # upgrades to ERROR, never downgrades
resolved: bool
resolved_at: datetime | None
Known limitations
- Dynamic path params โ endpoints like
/items/1and/items/2currently produce separate issues because handlers still capturerequest.url.path(raw). A follow-up will use the matched route template (/items/{iid}) instead, collapsing those into a single issue. - SQLite multi-tenancy โ
flare_issuesuses a fixed name on SQLite (matches the rest of the SQLite backend, which already has fixedlogs,requests,flare_settings,flare_metrics_snapshots). PostgreSQL derives the issues table frompg_table_namelike everything else, so multi-project PG setups work out of the box. - No backfill โ logs captured before 0.3.0 have
NULLforissue_fingerprintand are not surfaced on the Issues tab. Running traffic after upgrade populates the grouping from that point forward.
Log Entry Schema
Every captured error is stored as a structured FlareLogEntry:
class FlareLogEntry(BaseModel):
id: str # backend-native ID (row id for PG/SQLite)
timestamp: datetime
level: Literal["ERROR", "WARNING"]
event: str # e.g. "http_exception", "unhandled_exception"
message: str
request_id: str | None # UUID from X-Request-ID header
issue_fingerprint: str | None # links this row to a FlareIssue (v0.3.0+)
endpoint: str | None
http_method: str | None
http_status: int | None
ip_address: str | None
duration_ms: int | None
error: str | None
stack_trace: str | None
context: dict | None # additional structured data
request_body: dict | None # captured request body (if enabled)
Capturing Non-HTTP Errors
By default fastapi-flare captures HTTP 4xx/5xx and unhandled exceptions
inside the request path. You can also route errors that happen outside
any request โ background tasks, workers, cron jobs, consumers, startup
code, detached asyncio tasks โ into the same dashboard.
Three mechanisms are available. Use any combination.
1. Python logging integration (automatic)
Forwards every WARNING / ERROR record from Python's standard logging
into Flare. Zero changes to your existing code โ any logger.exception(...)
or logger.error(...) already in your codebase starts showing up on /flare.
setup(app, config=FlareConfig(
capture_logging=True,
# Optional: only listen to specific loggers.
# Empty / omitted = attach to the root logger (catches everything that propagates).
capture_logging_loggers="myapp.worker,myapp.jobs",
))
Anywhere in your code:
import logging
logger = logging.getLogger("myapp.worker")
try:
process_job()
except Exception:
logger.exception("job failed") # โ appears in /flare
The captured entry gets event=log.<logger-name>, endpoint=None, and a
context auto-populated with logger, module, func, line, file.
2. Manual capture (explicit)
When you've already caught an exception and want to record it without
re-raising, call capture_exception:
from fastapi_flare import capture_exception
try:
await charge_customer(order)
except StripeError as e:
await capture_exception(
e,
event="payment.retry_exhausted",
context={"order_id": order.id, "attempts": 5},
)
# handle gracefully โ user is not affected
For non-exception signals (rate limits, degraded deps, audit events):
from fastapi_flare import capture_message
await capture_message(
"rate-limit hit on outbound API",
level="WARNING",
event="outbound.rate_limited",
context={"api": "sendgrid", "hits": 142},
)
3. asyncio unhandled-task capture
asyncio.create_task(...) that raises without being awaited normally
disappears silently โ the event loop just prints a warning to stderr.
Enable capture so these land on the dashboard:
setup(app, config=FlareConfig(
capture_asyncio_errors=True,
))
Now any detached task that blows up gets recorded with
event=asyncio.unhandled, full stack trace, and a context describing
the task.
context โ free-form metadata
The context dict you pass (or the one auto-filled by the logging handler)
is stored as JSON and rendered under the Context section of the modal.
Use it for anything that doesn't fit the fixed fields โ job IDs, provider
names, feature flags, versions, etc. Keys matching
FlareConfig.sensitive_fields (password, token, api_key, โฆ) are
automatically redacted before storage.
Low-level API
All three mechanisms ultimately call push_log, which is still public
if you need full control:
from fastapi_flare.queue import push_log
await push_log(
config,
level="ERROR",
event="payment_failed",
message="Stripe charge declined",
context={"order_id": "ord_123", "amount": 2500},
)
Protecting the Dashboard
Secure the dashboard using any FastAPI dependency:
from fastapi import HTTPException, Security
from fastapi.security import HTTPBearer
bearer = HTTPBearer()
def verify_token(token=Security(bearer)):
if token.credentials != "my-secret":
raise HTTPException(status_code=401, detail="Unauthorized")
setup(app, config=FlareConfig(
dashboard_auth_dependency=verify_token,
))
Zitadel Authentication
fastapi-flare has built-in support for protecting the /flare dashboard via Zitadel OIDC.
Two integration modes are available:
| Mode | When to use |
|---|---|
| Browser (PKCE) | Users access /flare from a browser โ automatically redirected to the Zitadel login page |
| Bearer Token | API clients send Authorization: Bearer <token> โ no redirect |
Prerequisites
In the Zitadel console:
- Create a Web Application inside a project (type: PKCE / User Agent)
- Note the Domain โ e.g.
auth.mycompany.com - Note the Client ID of the application
- Note the Project ID (visible in the project's general settings)
- For browser mode: register the callback URL โ e.g.
https://myapp.com/flare/callback
Browser Mode (PKCE)
setup(app, config=FlareConfig(
zitadel_domain="auth.mycompany.com",
zitadel_client_id="000000000000000001",
zitadel_project_id="000000000000000002",
zitadel_redirect_uri="https://myapp.com/flare/callback",
zitadel_session_secret="<32-byte-hex>",
))
Via environment variables:
FLARE_ZITADEL_DOMAIN=auth.mycompany.com
FLARE_ZITADEL_CLIENT_ID=000000000000000001
FLARE_ZITADEL_PROJECT_ID=000000000000000002
FLARE_ZITADEL_REDIRECT_URI=https://myapp.com/flare/callback
FLARE_ZITADEL_SESSION_SECRET=<32-byte-hex>
# Generate: python -c "import secrets; print(secrets.token_hex(32))"
Flow:
- User opens
/flareโ no session โ redirected to/flare/auth/login - PKCE challenge generated โ redirected to Zitadel login
- User logs in โ Zitadel redirects to
callback-url?code=... fastapi-flareexchanges code for token โ creates signed session cookie- User redirected to
/flareโ access granted โ
Routes created automatically:
| Route | Purpose |
|---|---|
GET /flare/auth/login |
Starts the PKCE flow โ redirects to Zitadel |
GET <callback-path> |
Receives the code, exchanges it, creates the session |
GET /flare/auth/logout |
Clears the session โ redirects to login |
API Mode (Bearer Token)
When zitadel_redirect_uri is not set, the dashboard validates the Authorization: Bearer <token> header directly. No redirect flow.
Manual Mode (custom dependency)
from fastapi_flare.zitadel import make_zitadel_dependency
dep = make_zitadel_dependency(
domain="auth.mycompany.com",
client_id="000000000000000001",
project_id="000000000000000002",
)
setup(app, config=FlareConfig(dashboard_auth_dependency=dep))
Running the Example
# Zero-config SQLite (no setup needed)
poetry run uvicorn examples.example:app --reload --port 8000
# Dashboard at http://localhost:8000/flare
PostgreSQL example โ set in your .env:
FLARE_STORAGE_BACKEND=postgresql
FLARE_PG_DSN=postgresql://user:pass@localhost:5432/mydb
Test routes:
| Route | Behavior |
|---|---|
GET / |
Returns 200 OK |
GET /boom |
Triggers RuntimeError โ captured as ERROR |
GET /items/999 |
Triggers HTTPException 404 โ captured as WARNING |
GET /flare |
Opens the error dashboard |
Non-HTTP capture demo
A dedicated example exercises the capture_logging,
capture_asyncio_errors, capture_exception, and capture_message
features end-to-end:
poetry run uvicorn examples.example_non_http_capture:app --reload --port 8002
# Dashboard at http://localhost:8002/flare
| Route | What it captures |
|---|---|
GET /trigger/logger |
logger.exception(...) inside a handler |
GET /trigger/manual |
capture_exception(e, context=...) |
GET /trigger/asyncio |
A stray asyncio.create_task that raises |
GET /trigger/background |
A worker thread that logs via logger.exception |
GET /trigger/warn |
capture_message(...) at WARNING level |
The app also emits two entries on startup โ proving that records
outside the request path are visible on /flare before anyone hits
an endpoint.
Comparison
| Project | What it does |
|---|---|
sentry-sdk |
Full error tracking SaaS โ more features, external dependency |
fastapi-analytics |
Endpoint analytics / performance โ not error-focused |
fastapi-middleware-logger |
HTTP logging only, no storage or dashboard |
fastapi-flare |
Self-hosted, zero-config error tracking โ SQLite or PostgreSQL |
Why not Sentry?
| fastapi-flare | Sentry | |
|---|---|---|
| Hosting | Self-hosted, your infra | External SaaS |
| Account required | No | Yes |
| Setup | One setup(app) call |
SDK + DSN + account config |
| Storage | SQLite or PostgreSQL | Kafka, ClickHouse, Postgres, โฆ |
| Cost | Zero | Free tier โ paid plans |
| Privacy | Data never leaves your server | Data sent to third-party |
| Customization | Full source access | Configuration only |
fastapi-flare is the right choice when you need fast, private, zero-dependency error visibility โ especially in self-hosted, air-gapped, or cost-sensitive environments.
License
MIT ยฉ Gabriel
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastapi_flare-0.4.0.tar.gz.
File metadata
- Download URL: fastapi_flare-0.4.0.tar.gz
- Upload date:
- Size: 114.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.11.9 Windows/10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a89c5898d0b55a2e2c7c0d4db93349530496cb025d1fc6e2c997bd5da2df3893
|
|
| MD5 |
83d33d56bc2be0cac656146e2f8859b9
|
|
| BLAKE2b-256 |
0e59dae7dd0a47b6b414baa4ec542dde2171e10e1f9aff0624631e7f33d21659
|
File details
Details for the file fastapi_flare-0.4.0-py3-none-any.whl.
File metadata
- Download URL: fastapi_flare-0.4.0-py3-none-any.whl
- Upload date:
- Size: 124.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.11.9 Windows/10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9194e94080e6b9d8e139a7c3ebae90afc2c852e705f62bee96a385cd6fbe588
|
|
| MD5 |
0b4bba524578de2b08b104ff04cc04d4
|
|
| BLAKE2b-256 |
56c5c4c0def9cabd0bfe96648ec45f694b024417a12851369b35d123c60ec04c
|