reqly 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {reqly-0.2.0 → reqly-0.3.0}/PKG-INFO +238 -206
- {reqly-0.2.0 → reqly-0.3.0}/README.md +26 -3
- {reqly-0.2.0 → reqly-0.3.0}/pyproject.toml +10 -3
- {reqly-0.2.0 → reqly-0.3.0}/reqly/__init__.py +30 -19
- reqly-0.3.0/reqly/integrations/django.py +142 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/integrations/fastapi.py +40 -6
- reqly-0.3.0/reqly/integrations/litestar.py +13 -0
- reqly-0.3.0/reqly/integrations/starlette.py +37 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly.egg-info/PKG-INFO +238 -206
- {reqly-0.2.0 → reqly-0.3.0}/reqly.egg-info/SOURCES.txt +4 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly.egg-info/requires.txt +11 -0
- {reqly-0.2.0 → reqly-0.3.0}/setup.cfg +4 -4
- reqly-0.3.0/tests/test_more_frameworks.py +250 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/core/__init__.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/core/buffer.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/core/capture.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/core/client.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/core/config.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/core/sampling.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/core/shipper.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/integrations/__init__.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly/integrations/flask.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly.egg-info/dependency_links.txt +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/reqly.egg-info/top_level.txt +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_buffer.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_capture.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_fastapi_integration.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_flask_integration.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_instrument.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_sampling.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_shipper.py +0 -0
- {reqly-0.2.0 → reqly-0.3.0}/tests/test_v2_fields.py +0 -0
|
@@ -1,206 +1,238 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: reqly
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Self-hosted API monitoring for FastAPI and
|
|
5
|
-
Author: Tanish Poddar
|
|
6
|
-
License-Expression: GPL-3.0-or-later
|
|
7
|
-
Project-URL: Homepage, https://reqly.tanisheesh.in
|
|
8
|
-
Project-URL: Repository, https://github.com/tanisheesh/reqly
|
|
9
|
-
Project-URL: Issues, https://github.com/tanisheesh/reqly/issues
|
|
10
|
-
Project-URL: Documentation, https://github.com/tanisheesh/reqly/blob/main/docs/SETUP.md
|
|
11
|
-
Project-URL: Changelog, https://github.com/tanisheesh/reqly/blob/main/sdk/CHANGELOG.md
|
|
12
|
-
Project-URL: Live Demo, https://reqly-eventflow-dashboard.onrender.com
|
|
13
|
-
Keywords: observability,apm,monitoring,fastapi,flask,telemetry,metrics,latency,percentiles,alerting,anomaly-detection,release-tracking,self-hosted,timescaledb
|
|
14
|
-
Classifier: Development Status :: 4 - Beta
|
|
15
|
-
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Classifier: Topic :: System :: Monitoring
|
|
24
|
-
Classifier: Framework ::
|
|
25
|
-
Classifier: Framework ::
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
`
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
| |
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reqly
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Self-hosted API monitoring for FastAPI, Flask, Django, Starlette and Litestar in two lines: latency percentiles, error rates and release tracking per route, with deploy-aware alerts and weekly AI anomaly reports.
|
|
5
|
+
Author: Tanish Poddar
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://reqly.tanisheesh.in
|
|
8
|
+
Project-URL: Repository, https://github.com/tanisheesh/reqly
|
|
9
|
+
Project-URL: Issues, https://github.com/tanisheesh/reqly/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/tanisheesh/reqly/blob/main/docs/SETUP.md
|
|
11
|
+
Project-URL: Changelog, https://github.com/tanisheesh/reqly/blob/main/sdk/CHANGELOG.md
|
|
12
|
+
Project-URL: Live Demo, https://reqly-eventflow-dashboard.onrender.com
|
|
13
|
+
Keywords: observability,apm,monitoring,fastapi,flask,django,starlette,litestar,telemetry,metrics,latency,percentiles,alerting,anomaly-detection,release-tracking,self-hosted,timescaledb
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Classifier: Framework :: Django
|
|
25
|
+
Classifier: Framework :: FastAPI
|
|
26
|
+
Classifier: Framework :: Flask
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
30
|
+
Provides-Extra: fastapi
|
|
31
|
+
Requires-Dist: fastapi>=0.100; extra == "fastapi"
|
|
32
|
+
Provides-Extra: starlette
|
|
33
|
+
Requires-Dist: starlette>=0.27; extra == "starlette"
|
|
34
|
+
Provides-Extra: litestar
|
|
35
|
+
Requires-Dist: litestar>=2.0; extra == "litestar"
|
|
36
|
+
Provides-Extra: flask
|
|
37
|
+
Requires-Dist: flask>=2.3; extra == "flask"
|
|
38
|
+
Provides-Extra: django
|
|
39
|
+
Requires-Dist: django>=4.2; extra == "django"
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
42
|
+
Requires-Dist: fastapi>=0.100; extra == "dev"
|
|
43
|
+
Requires-Dist: litestar>=2.0; extra == "dev"
|
|
44
|
+
Requires-Dist: flask>=2.3; extra == "dev"
|
|
45
|
+
Requires-Dist: django>=4.2; extra == "dev"
|
|
46
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
47
|
+
|
|
48
|
+
# reqly
|
|
49
|
+
|
|
50
|
+
**Self-hosted API monitoring for FastAPI, Flask, Django, Starlette and Litestar — two lines of code.**
|
|
51
|
+
Latency percentiles, error rates and release tracking for every route, shipped to your own
|
|
52
|
+
[Reqly](https://github.com/tanisheesh/reqly) collector — which turns them into deploy-aware
|
|
53
|
+
hourly alerts and weekly AI anomaly reports.
|
|
54
|
+
|
|
55
|
+
[](https://pypi.org/project/reqly/)
|
|
56
|
+
[](https://pypi.org/project/reqly/)
|
|
57
|
+
[](https://github.com/tanisheesh/reqly/blob/main/LICENSE)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install reqly
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
**FastAPI**
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import reqly
|
|
73
|
+
from fastapi import FastAPI
|
|
74
|
+
|
|
75
|
+
app = FastAPI()
|
|
76
|
+
reqly.instrument(
|
|
77
|
+
app,
|
|
78
|
+
service_name="checkout-api",
|
|
79
|
+
collector_url="https://reqly.example.com",
|
|
80
|
+
api_key="your-ingest-key",
|
|
81
|
+
)
|
|
82
|
+
# Every route is now tracked: latency, errors, status codes, release
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Flask**
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import reqly
|
|
89
|
+
from flask import Flask
|
|
90
|
+
|
|
91
|
+
app = Flask(__name__)
|
|
92
|
+
reqly.instrument(app, service_name="checkout-api") # settings from REQLY_* env vars
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Starlette / Litestar** — same call:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
reqly.instrument(app, service_name="checkout-api")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Django** (also Django REST Framework and Django Ninja) — Django has no app object, so add
|
|
102
|
+
the middleware first in `MIDDLEWARE`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
# settings.py
|
|
106
|
+
MIDDLEWARE = [
|
|
107
|
+
"reqly.integrations.django.ReqlyMiddleware",
|
|
108
|
+
# ...
|
|
109
|
+
]
|
|
110
|
+
REQLY = {"service_name": "checkout-api", "api_key": "your-ingest-key"} # optional
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`instrument()` detects the framework by itself — no decorators, no middleware to wire up.
|
|
114
|
+
Routes are recorded as templates in one style across frameworks: Django's
|
|
115
|
+
`users/<int:pk>/` and DRF's `^users/(?P<pk>[^/.]+)/$` both become `/users/{pk}/`.
|
|
116
|
+
The release you're running is picked up automatically from your CI or host
|
|
117
|
+
(`GITHUB_SHA`, `RENDER_GIT_COMMIT`, `VERCEL_GIT_COMMIT_SHA`, …), so deploys show up in
|
|
118
|
+
Reqly with no extra code.
|
|
119
|
+
|
|
120
|
+
## What you get
|
|
121
|
+
|
|
122
|
+
From the SDK, per request: method, **route template** (`/orders/{id}`, never the raw
|
|
123
|
+
path), status code, duration, error type, host, **release**, environment and
|
|
124
|
+
request/response **body size**.
|
|
125
|
+
|
|
126
|
+
In the Reqly dashboard and collector:
|
|
127
|
+
|
|
128
|
+
- **p50 / p95 / p99 latency** per route and per service — real percentiles from
|
|
129
|
+
mergeable sketches, not the max of per-route numbers
|
|
130
|
+
- **Error rates, status codes and top routes** over 1h / 6h / 24h / 7d
|
|
131
|
+
- **Deploy markers and per-release health** — each release's error rate and p95
|
|
132
|
+
- **Hourly alerts** to Slack, Discord or a webhook when a route breaks from its usual
|
|
133
|
+
weekday-hour pattern, with **root-cause hints**
|
|
134
|
+
- **Weekly AI report** — statistics find the anomalies, Groq (Llama 3.3-70b) writes the
|
|
135
|
+
summary; plain-text fallback without an API key
|
|
136
|
+
|
|
137
|
+
An alert from the demo data looks like this:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
🔴 Anomaly — flask-demo /orders (Friday 15:00-16:00 UTC, z=5.37)
|
|
141
|
+
• error rate 30.0% vs 2.2% usual · p95 6588ms vs 1576ms usual
|
|
142
|
+
• running release v2 — vs v1: errors 2.6% → 33.1%, p95 2072ms → 4501ms
|
|
143
|
+
• 100% of errors came from host pod-3, which served 23% of requests
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Not on Python?** Node, Java, Go and .NET apps can report to the same collector through
|
|
147
|
+
OpenTelemetry — no Reqly SDK needed. See the
|
|
148
|
+
[OpenTelemetry guide](https://github.com/tanisheesh/reqly/blob/main/docs/OTEL.md).
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
Every option can be passed to `instrument()` or set as an environment variable.
|
|
153
|
+
Resolution order: **argument → environment variable → default**.
|
|
154
|
+
|
|
155
|
+
| argument | environment variable | default |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `service_name` | `REQLY_SERVICE_NAME` | `sys.argv[0]` basename |
|
|
158
|
+
| `collector_url` | `REQLY_COLLECTOR_URL` | `http://localhost:8000` |
|
|
159
|
+
| `api_key` | `REQLY_API_KEY` | `None` |
|
|
160
|
+
| `release` | `REQLY_RELEASE`, then CI variables (`GITHUB_SHA`, `CI_COMMIT_SHA`, `RENDER_GIT_COMMIT`, `VERCEL_GIT_COMMIT_SHA`, `RAILWAY_GIT_COMMIT_SHA`, `HEROKU_SLUG_COMMIT`, `K_REVISION`, …) | auto-detected, else `None` |
|
|
161
|
+
| `environment` | `REQLY_ENVIRONMENT` | `None` |
|
|
162
|
+
| `sample_rate` | `REQLY_SAMPLE_RATE` | `1.0` |
|
|
163
|
+
| `flush_interval_seconds` | `REQLY_FLUSH_INTERVAL_SECONDS` | `5.0` |
|
|
164
|
+
| `max_batch_size` | `REQLY_MAX_BATCH_SIZE` | `200` |
|
|
165
|
+
| `max_queue_size` | `REQLY_MAX_QUEUE_SIZE` | `2000` |
|
|
166
|
+
| `ignore_routes` | `REQLY_IGNORE_ROUTES` (comma-separated) | `/health,/metrics` |
|
|
167
|
+
| `capture_request_body` | `REQLY_CAPTURE_REQUEST_BODY` | `False` (not implemented yet) |
|
|
168
|
+
|
|
169
|
+
With `sample_rate` below 1.0, request counts in the dashboard are the sampled volume;
|
|
170
|
+
latency percentiles and error rates stay unbiased.
|
|
171
|
+
|
|
172
|
+
## Design guarantees
|
|
173
|
+
|
|
174
|
+
**Fail-open** — any internal SDK error is caught and logged once; instrumentation disables
|
|
175
|
+
itself rather than raise into your app. A slow or unreachable collector never blocks
|
|
176
|
+
request threads — shipping happens on a background thread with strict HTTP timeouts.
|
|
177
|
+
|
|
178
|
+
**Bounded cardinality** — routes are recorded as the framework's matched template
|
|
179
|
+
(`/users/{id}`), never the raw path (`/users/123`). Unmatched paths (404s, scanners)
|
|
180
|
+
collapse into a single `__unmatched__` bucket.
|
|
181
|
+
|
|
182
|
+
**Bounded memory** — events wait in a fixed-size in-memory queue; under backpressure the
|
|
183
|
+
oldest events are dropped and counted instead of growing without limit.
|
|
184
|
+
|
|
185
|
+
**Safe retries** — batches are retried with exponential backoff on `408`, `429` and any
|
|
186
|
+
`5xx` (for example a collector restart behind a proxy); other `4xx` responses are dropped
|
|
187
|
+
immediately. Every event carries a unique `event_id` the collector deduplicates on, so a
|
|
188
|
+
retry never double-counts.
|
|
189
|
+
|
|
190
|
+
**Pre-fork servers** — under gunicorn `--preload` (or uWSGI without lazy-apps) each
|
|
191
|
+
forked worker restarts its own flush thread and HTTP connection pool, so workers' events
|
|
192
|
+
are shipped instead of silently queuing forever.
|
|
193
|
+
|
|
194
|
+
## Compatibility
|
|
195
|
+
|
|
196
|
+
| | Supported |
|
|
197
|
+
|---|---|
|
|
198
|
+
| Python | 3.9 – 3.13 |
|
|
199
|
+
| FastAPI | 0.100+ (including routes in `app.mount()`ed sub-apps) |
|
|
200
|
+
| Starlette | 0.27+ (including `Mount`) |
|
|
201
|
+
| Litestar | 2.0+ |
|
|
202
|
+
| Flask | 2.3+ |
|
|
203
|
+
| Django | 4.2+, sync and async views; DRF and Django Ninja |
|
|
204
|
+
| Collector | any version; `release`, `environment` and body sizes are stored by collector 0.3.0+ and ignored by older ones |
|
|
205
|
+
|
|
206
|
+
## Self-hosting the collector
|
|
207
|
+
|
|
208
|
+
The SDK sends data to a Reqly collector you run. The full stack — collector,
|
|
209
|
+
TimescaleDB and dashboard — starts with Docker Compose:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
git clone https://github.com/tanisheesh/reqly.git
|
|
213
|
+
cd reqly
|
|
214
|
+
docker compose up -d
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Setup, configuration and AWS deployment:
|
|
218
|
+
[docs/SETUP.md](https://github.com/tanisheesh/reqly/blob/main/docs/SETUP.md) ·
|
|
219
|
+
[infra/DEPLOY.md](https://github.com/tanisheesh/reqly/blob/main/infra/DEPLOY.md) ·
|
|
220
|
+
[ingest API spec](https://github.com/tanisheesh/reqly/blob/main/docs/INGEST_SPEC.md)
|
|
221
|
+
|
|
222
|
+
## Live demo
|
|
223
|
+
|
|
224
|
+
Reqly monitors [EventFlow](https://eventflow-g2h5.onrender.com), a Flask event management
|
|
225
|
+
app, in production:
|
|
226
|
+
|
|
227
|
+
- **Demo app** → [eventflow-g2h5.onrender.com](https://eventflow-g2h5.onrender.com)
|
|
228
|
+
- **Metrics dashboard** → [reqly-eventflow-dashboard.onrender.com](https://reqly-eventflow-dashboard.onrender.com)
|
|
229
|
+
|
|
230
|
+
> Log in as **Administrator** (`admin@eventhub.com` / `Admin@123`) → click **Metrics** in the nav.
|
|
231
|
+
|
|
232
|
+
## Changelog
|
|
233
|
+
|
|
234
|
+
See [CHANGELOG.md](https://github.com/tanisheesh/reqly/blob/main/sdk/CHANGELOG.md).
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
GPL-3.0-or-later — see [LICENSE](https://github.com/tanisheesh/reqly/blob/main/LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# reqly
|
|
2
2
|
|
|
3
|
-
**Self-hosted API monitoring for FastAPI and
|
|
3
|
+
**Self-hosted API monitoring for FastAPI, Flask, Django, Starlette and Litestar — two lines of code.**
|
|
4
4
|
Latency percentiles, error rates and release tracking for every route, shipped to your own
|
|
5
5
|
[Reqly](https://github.com/tanisheesh/reqly) collector — which turns them into deploy-aware
|
|
6
6
|
hourly alerts and weekly AI anomaly reports.
|
|
@@ -45,7 +45,27 @@ app = Flask(__name__)
|
|
|
45
45
|
reqly.instrument(app, service_name="checkout-api") # settings from REQLY_* env vars
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
**Starlette / Litestar** — same call:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
reqly.instrument(app, service_name="checkout-api")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Django** (also Django REST Framework and Django Ninja) — Django has no app object, so add
|
|
55
|
+
the middleware first in `MIDDLEWARE`:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
# settings.py
|
|
59
|
+
MIDDLEWARE = [
|
|
60
|
+
"reqly.integrations.django.ReqlyMiddleware",
|
|
61
|
+
# ...
|
|
62
|
+
]
|
|
63
|
+
REQLY = {"service_name": "checkout-api", "api_key": "your-ingest-key"} # optional
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`instrument()` detects the framework by itself — no decorators, no middleware to wire up.
|
|
67
|
+
Routes are recorded as templates in one style across frameworks: Django's
|
|
68
|
+
`users/<int:pk>/` and DRF's `^users/(?P<pk>[^/.]+)/$` both become `/users/{pk}/`.
|
|
49
69
|
The release you're running is picked up automatically from your CI or host
|
|
50
70
|
(`GITHUB_SHA`, `RENDER_GIT_COMMIT`, `VERCEL_GIT_COMMIT_SHA`, …), so deploys show up in
|
|
51
71
|
Reqly with no extra code.
|
|
@@ -129,8 +149,11 @@ are shipped instead of silently queuing forever.
|
|
|
129
149
|
| | Supported |
|
|
130
150
|
|---|---|
|
|
131
151
|
| Python | 3.9 – 3.13 |
|
|
132
|
-
| FastAPI | 0.100+ |
|
|
152
|
+
| FastAPI | 0.100+ (including routes in `app.mount()`ed sub-apps) |
|
|
153
|
+
| Starlette | 0.27+ (including `Mount`) |
|
|
154
|
+
| Litestar | 2.0+ |
|
|
133
155
|
| Flask | 2.3+ |
|
|
156
|
+
| Django | 4.2+, sync and async views; DRF and Django Ninja |
|
|
134
157
|
| Collector | any version; `release`, `environment` and body sizes are stored by collector 0.3.0+ and ignored by older ones |
|
|
135
158
|
|
|
136
159
|
## Self-hosting the collector
|
|
@@ -4,14 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "reqly"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Self-hosted API monitoring for FastAPI and
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Self-hosted API monitoring for FastAPI, Flask, Django, Starlette and Litestar in two lines: latency percentiles, error rates and release tracking per route, with deploy-aware alerts and weekly AI anomaly reports."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = "GPL-3.0-or-later"
|
|
12
12
|
authors = [{ name = "Tanish Poddar" }]
|
|
13
13
|
keywords = [
|
|
14
|
-
"observability", "apm", "monitoring", "fastapi", "flask", "
|
|
14
|
+
"observability", "apm", "monitoring", "fastapi", "flask", "django",
|
|
15
|
+
"starlette", "litestar", "telemetry",
|
|
15
16
|
"metrics", "latency", "percentiles", "alerting", "anomaly-detection",
|
|
16
17
|
"release-tracking", "self-hosted", "timescaledb"
|
|
17
18
|
]
|
|
@@ -26,6 +27,7 @@ classifiers = [
|
|
|
26
27
|
"Programming Language :: Python :: 3.13",
|
|
27
28
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
29
|
"Topic :: System :: Monitoring",
|
|
30
|
+
"Framework :: Django",
|
|
29
31
|
"Framework :: FastAPI",
|
|
30
32
|
"Framework :: Flask",
|
|
31
33
|
]
|
|
@@ -43,11 +45,16 @@ Changelog = "https://github.com/tanisheesh/reqly/blob/main/sdk/CHANGELOG.md"
|
|
|
43
45
|
|
|
44
46
|
[project.optional-dependencies]
|
|
45
47
|
fastapi = ["fastapi>=0.100"]
|
|
48
|
+
starlette = ["starlette>=0.27"]
|
|
49
|
+
litestar = ["litestar>=2.0"]
|
|
46
50
|
flask = ["flask>=2.3"]
|
|
51
|
+
django = ["django>=4.2"]
|
|
47
52
|
dev = [
|
|
48
53
|
"pytest>=8.0",
|
|
49
54
|
"fastapi>=0.100",
|
|
55
|
+
"litestar>=2.0",
|
|
50
56
|
"flask>=2.3",
|
|
57
|
+
"django>=4.2",
|
|
51
58
|
"httpx>=0.27",
|
|
52
59
|
]
|
|
53
60
|
|
|
@@ -11,26 +11,30 @@ __all__ = ["instrument"]
|
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger("reqly")
|
|
13
13
|
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
# id(app) -> (app, client) for every instrumented app. Holding the app
|
|
15
|
+
# itself keeps its id from being reused by a new object, so a repeat
|
|
16
|
+
# instrument() call on the same app is recognized reliably -- without
|
|
17
|
+
# setting attributes on the app (Litestar apps use __slots__ and refuse).
|
|
18
|
+
_CLIENTS: dict[int, tuple[object, ReqlyClient]] = {}
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def _detect_framework(app) -> str:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
# Walk the class hierarchy so subclasses of an app class (a project's
|
|
23
|
+
# own `class App(FastAPI)`) are recognized too. FastAPI subclasses
|
|
24
|
+
# Starlette, so it must be checked first.
|
|
25
|
+
modules = [cls.__module__ or "" for cls in type(app).__mro__]
|
|
26
|
+
for framework in ("fastapi", "litestar", "starlette", "flask"):
|
|
27
|
+
if any(m == framework or m.startswith(framework + ".") for m in modules):
|
|
28
|
+
return framework
|
|
29
|
+
# Fallback to duck typing if the module names aren't conclusive.
|
|
30
|
+
if hasattr(app, "add_middleware") and hasattr(app, "router"):
|
|
31
|
+
return "starlette"
|
|
29
32
|
if hasattr(app, "before_request") and hasattr(app, "wsgi_app"):
|
|
30
33
|
return "flask"
|
|
31
34
|
raise TypeError(
|
|
32
35
|
"reqly.instrument(): could not detect framework for app of type "
|
|
33
|
-
f"{type(app)!r}. Supported: FastAPI, Flask
|
|
36
|
+
f"{type(app)!r}. Supported: FastAPI, Starlette, Litestar, Flask "
|
|
37
|
+
"(Django: add reqly.integrations.django.ReqlyMiddleware to MIDDLEWARE)."
|
|
34
38
|
)
|
|
35
39
|
|
|
36
40
|
|
|
@@ -49,7 +53,8 @@ def instrument(
|
|
|
49
53
|
release: str | None = None,
|
|
50
54
|
environment: str | None = None,
|
|
51
55
|
) -> ReqlyClient | None:
|
|
52
|
-
"""Instrument a FastAPI or Flask app with one line.
|
|
56
|
+
"""Instrument a FastAPI, Starlette, Litestar or Flask app with one line.
|
|
57
|
+
(Django: add ``reqly.integrations.django.ReqlyMiddleware`` to MIDDLEWARE.)
|
|
53
58
|
|
|
54
59
|
Config resolution order for any omitted argument: explicit kwarg >
|
|
55
60
|
environment variable (REQLY_*) > default. See core.config.Config
|
|
@@ -60,9 +65,8 @@ def instrument(
|
|
|
60
65
|
rather than raising, so adding Reqly can never be the reason an
|
|
61
66
|
app fails to start.
|
|
62
67
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
existing = getattr(app, _APP_CLIENT_ATTR, None)
|
|
68
|
+
entry = _CLIENTS.get(id(app))
|
|
69
|
+
existing = entry[1] if entry is not None and entry[0] is app else None
|
|
66
70
|
if existing is not None:
|
|
67
71
|
# A second call would add a second middleware and a second flush
|
|
68
72
|
# thread, double-counting every request.
|
|
@@ -96,13 +100,20 @@ def instrument(
|
|
|
96
100
|
from .integrations.fastapi import instrument_fastapi
|
|
97
101
|
|
|
98
102
|
instrument_fastapi(app, client)
|
|
103
|
+
elif framework == "starlette":
|
|
104
|
+
from .integrations.starlette import instrument_starlette
|
|
105
|
+
|
|
106
|
+
instrument_starlette(app, client)
|
|
107
|
+
elif framework == "litestar":
|
|
108
|
+
from .integrations.litestar import instrument_litestar
|
|
109
|
+
|
|
110
|
+
instrument_litestar(app, client)
|
|
99
111
|
else:
|
|
100
112
|
from .integrations.flask import instrument_flask
|
|
101
113
|
|
|
102
114
|
instrument_flask(app, client)
|
|
103
115
|
|
|
104
|
-
_CLIENTS[id(app)] = client
|
|
105
|
-
setattr(app, _APP_CLIENT_ATTR, client)
|
|
116
|
+
_CLIENTS[id(app)] = (app, client)
|
|
106
117
|
return client
|
|
107
118
|
except Exception:
|
|
108
119
|
logger.warning(
|