foxray 0.1.2__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.
- foxray-0.1.2/LICENSE +21 -0
- foxray-0.1.2/NOTICE +34 -0
- foxray-0.1.2/PKG-INFO +308 -0
- foxray-0.1.2/README.md +263 -0
- foxray-0.1.2/foxray/__init__.py +3 -0
- foxray-0.1.2/foxray/__main__.py +3 -0
- foxray-0.1.2/foxray/core/__init__.py +2 -0
- foxray-0.1.2/foxray/core/alerts.py +225 -0
- foxray-0.1.2/foxray/core/auth.py +54 -0
- foxray-0.1.2/foxray/core/cron.py +176 -0
- foxray-0.1.2/foxray/core/dsn.py +63 -0
- foxray-0.1.2/foxray/core/envelope.py +166 -0
- foxray-0.1.2/foxray/core/filters.py +173 -0
- foxray-0.1.2/foxray/core/fingerprint.py +82 -0
- foxray-0.1.2/foxray/core/ingest.py +255 -0
- foxray-0.1.2/foxray/core/maintenance.py +321 -0
- foxray-0.1.2/foxray/core/models.py +178 -0
- foxray-0.1.2/foxray/core/monitors.py +150 -0
- foxray-0.1.2/foxray/core/normalize.py +358 -0
- foxray-0.1.2/foxray/core/present.py +146 -0
- foxray-0.1.2/foxray/core/scrub.py +96 -0
- foxray-0.1.2/foxray/core/settings.py +96 -0
- foxray-0.1.2/foxray/core/sql.py +996 -0
- foxray-0.1.2/foxray/core/storage.py +139 -0
- foxray-0.1.2/foxray/core/util.py +101 -0
- foxray-0.1.2/foxray/django/__init__.py +4 -0
- foxray-0.1.2/foxray/django/admin.py +406 -0
- foxray-0.1.2/foxray/django/apps.py +21 -0
- foxray-0.1.2/foxray/django/conf.py +39 -0
- foxray-0.1.2/foxray/django/management/__init__.py +0 -0
- foxray-0.1.2/foxray/django/management/commands/__init__.py +0 -0
- foxray-0.1.2/foxray/django/management/commands/foxray_create_project.py +18 -0
- foxray-0.1.2/foxray/django/management/commands/foxray_prune.py +20 -0
- foxray-0.1.2/foxray/django/management/commands/foxray_tick.py +16 -0
- foxray-0.1.2/foxray/django/migrations/0001_initial.py +696 -0
- foxray-0.1.2/foxray/django/migrations/0002_default_project.py +18 -0
- foxray-0.1.2/foxray/django/migrations/__init__.py +0 -0
- foxray-0.1.2/foxray/django/models.py +275 -0
- foxray-0.1.2/foxray/django/runtime.py +162 -0
- foxray-0.1.2/foxray/django/sdk.py +231 -0
- foxray-0.1.2/foxray/django/store.py +48 -0
- foxray-0.1.2/foxray/django/templates/admin/foxray/_style.html +21 -0
- foxray-0.1.2/foxray/django/templates/admin/foxray/issue_detail.html +49 -0
- foxray-0.1.2/foxray/django/templates/admin/foxray/storage.html +22 -0
- foxray-0.1.2/foxray/django/urls.py +14 -0
- foxray-0.1.2/foxray/django/views.py +69 -0
- foxray-0.1.2/foxray/standalone/__init__.py +2 -0
- foxray-0.1.2/foxray/standalone/cli.py +158 -0
- foxray-0.1.2/foxray/standalone/config.py +66 -0
- foxray-0.1.2/foxray/standalone/mail.py +56 -0
- foxray-0.1.2/foxray/standalone/store.py +105 -0
- foxray-0.1.2/foxray/standalone/web.py +783 -0
- foxray-0.1.2/foxray.egg-info/PKG-INFO +308 -0
- foxray-0.1.2/foxray.egg-info/SOURCES.txt +70 -0
- foxray-0.1.2/foxray.egg-info/dependency_links.txt +1 -0
- foxray-0.1.2/foxray.egg-info/entry_points.txt +2 -0
- foxray-0.1.2/foxray.egg-info/requires.txt +15 -0
- foxray-0.1.2/foxray.egg-info/top_level.txt +1 -0
- foxray-0.1.2/pyproject.toml +55 -0
- foxray-0.1.2/setup.cfg +4 -0
- foxray-0.1.2/tests/test_boundaries.py +29 -0
- foxray-0.1.2/tests/test_django.py +357 -0
- foxray-0.1.2/tests/test_django_zero_config.py +108 -0
- foxray-0.1.2/tests/test_events.py +184 -0
- foxray-0.1.2/tests/test_ingest.py +246 -0
- foxray-0.1.2/tests/test_maintenance.py +201 -0
- foxray-0.1.2/tests/test_monitors.py +132 -0
- foxray-0.1.2/tests/test_protocol.py +136 -0
- foxray-0.1.2/tests/test_rules.py +121 -0
- foxray-0.1.2/tests/test_sdk_any_version.py +43 -0
- foxray-0.1.2/tests/test_sdk_standalone.py +158 -0
- foxray-0.1.2/tests/test_store_contract.py +338 -0
foxray-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dan Quellhorst
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
foxray-0.1.2/NOTICE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Foxray
|
|
2
|
+
Copyright (c) 2026 Dan Quellhorst. MIT License (see LICENSE).
|
|
3
|
+
|
|
4
|
+
Foxray works with the Sentry SDKs. It is not affiliated with, endorsed by, or
|
|
5
|
+
sponsored by Functional Software, Inc. ("Sentry"). "Sentry" is a trademark of
|
|
6
|
+
Functional Software, Inc.
|
|
7
|
+
|
|
8
|
+
How it was built
|
|
9
|
+
----------------
|
|
10
|
+
Foxray is written from scratch (clean room) against public descriptions of
|
|
11
|
+
the protocol the Sentry SDKs speak:
|
|
12
|
+
|
|
13
|
+
- Sentry's SDK developer documentation (develop.sentry.dev): envelopes and
|
|
14
|
+
their item types, the event payload, the X-Sentry-Auth header and DSN
|
|
15
|
+
format, the X-Sentry-Rate-Limits header, and cron check-ins.
|
|
16
|
+
- The sentry-python SDK (MIT License, Copyright (c) 2018 Functional Software,
|
|
17
|
+
Inc.), read as a reference for what it actually sends (compression, auth
|
|
18
|
+
header, check-in and monitor_config fields, how it parses rate-limit
|
|
19
|
+
headers) and used, unmodified, in Foxray's tests to prove compatibility.
|
|
20
|
+
No SDK code is included in Foxray.
|
|
21
|
+
|
|
22
|
+
No code from Sentry's server (getsentry/sentry) or Relay (getsentry/relay),
|
|
23
|
+
which are under the Functional Source License, was read or used.
|
|
24
|
+
|
|
25
|
+
GlitchTip (MIT) was not used as a source: no GlitchTip code was read,
|
|
26
|
+
included or adapted. The comparison in the README is a short summary of the
|
|
27
|
+
project as publicly described, not derived from its code.
|
|
28
|
+
|
|
29
|
+
Optional third-party packages, not bundled
|
|
30
|
+
------------------------------------------
|
|
31
|
+
- Django (BSD-3-Clause), for the Django app.
|
|
32
|
+
- psycopg (LGPL-3.0), for Postgres with the standalone server.
|
|
33
|
+
- Brotli (MIT), to decode brotli-compressed envelopes.
|
|
34
|
+
- sentry-sdk (MIT), for capturing a Django app's own errors in-process.
|
foxray-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: foxray
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A light error tracker that accepts errors from the Sentry SDKs. Works as a Django app or a single small process.
|
|
5
|
+
Author: Dan Quellhorst
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/askrobots/foxray
|
|
8
|
+
Project-URL: Source, https://github.com/askrobots/foxray
|
|
9
|
+
Project-URL: Issues, https://github.com/askrobots/foxray/issues
|
|
10
|
+
Keywords: error-tracking,sentry,django,self-hosted,monitoring,cron-monitoring
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Web Environment
|
|
13
|
+
Classifier: Framework :: Django
|
|
14
|
+
Classifier: Framework :: Django :: 4.2
|
|
15
|
+
Classifier: Framework :: Django :: 5.2
|
|
16
|
+
Classifier: Framework :: Django :: 6.0
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: System Administrators
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
27
|
+
Classifier: Topic :: Software Development :: Bug Tracking
|
|
28
|
+
Classifier: Topic :: System :: Monitoring
|
|
29
|
+
Requires-Python: >=3.10
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
License-File: NOTICE
|
|
33
|
+
Provides-Extra: django
|
|
34
|
+
Requires-Dist: Django>=4.2; extra == "django"
|
|
35
|
+
Provides-Extra: postgres
|
|
36
|
+
Requires-Dist: psycopg>=3.1; extra == "postgres"
|
|
37
|
+
Provides-Extra: brotli
|
|
38
|
+
Requires-Dist: Brotli; extra == "brotli"
|
|
39
|
+
Provides-Extra: dev
|
|
40
|
+
Requires-Dist: pytest; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest-django; extra == "dev"
|
|
42
|
+
Requires-Dist: sentry-sdk>=2; extra == "dev"
|
|
43
|
+
Requires-Dist: Django<5,>=4.2; extra == "dev"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# Foxray
|
|
47
|
+
|
|
48
|
+
A light error tracker that accepts errors from the official Sentry SDKs. It
|
|
49
|
+
runs two ways from one codebase:
|
|
50
|
+
|
|
51
|
+
- **inside a Django app**, as `foxray.django`: errors from the app itself are
|
|
52
|
+
stored in-process (no DSN, no HTTP), and the Django admin is the UI;
|
|
53
|
+
- **on its own**, as one small stdlib-only process (`foxray serve`) with a
|
|
54
|
+
SQLite file or Postgres behind it.
|
|
55
|
+
|
|
56
|
+
**Works with the Sentry SDKs; not affiliated with Sentry.**
|
|
57
|
+
|
|
58
|
+
Install from PyPI: `pip install foxray` (add `[postgres]` or `[brotli]` for
|
|
59
|
+
those extras).
|
|
60
|
+
|
|
61
|
+
**Design rule: one process plus one database; no Redis, Celery, Kafka or
|
|
62
|
+
ClickHouse.** Everything other trackers push into side services lives in the
|
|
63
|
+
database, kept as simple as possible:
|
|
64
|
+
|
|
65
|
+
| Elsewhere it's in | In Foxray it's |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| Redis rate-limit counters | `foxray_rate`: one atomic upsert per project per minute |
|
|
68
|
+
| Redis/memcached dedupe, cooldowns | a unique index on event ids; `last_alerted_at` on the issue |
|
|
69
|
+
| Celery alert tasks | `foxray_alert`: an outbox written in the ingest transaction, sent after commit, retried |
|
|
70
|
+
| Celery beat (cron checks, cleanup) | `foxray_state` leases; run after ingest when due, or by `foxray tick` |
|
|
71
|
+
| Kafka/ClickHouse counts | `foxray_issue_day`, `foxray_counter`: daily counters |
|
|
72
|
+
|
|
73
|
+
So several processes (uvicorn workers, a cron `tick`) always agree, and
|
|
74
|
+
nothing else needs running.
|
|
75
|
+
|
|
76
|
+
## Add it to a Django app
|
|
77
|
+
|
|
78
|
+
`pip install sentry-sdk` and Foxray, add `"foxray.django"` to `INSTALLED_APPS`,
|
|
79
|
+
add `path("foxray/", include("foxray.django.urls"))` to your URLs, and run
|
|
80
|
+
`python manage.py migrate`. That's all: the migration creates a "default"
|
|
81
|
+
project, `AppConfig.ready()` starts the Sentry SDK with an in-process transport
|
|
82
|
+
(DjangoIntegration, CeleryIntegration if Celery is installed,
|
|
83
|
+
`traces_sample_rate=0`, no PII), errors appear in the admin under **Foxray >
|
|
84
|
+
Issues**, and alert emails go to `settings.ADMINS` (or, if that is empty, the
|
|
85
|
+
superusers) through Django's email backend. Access is Django's own: superusers
|
|
86
|
+
see everything; give staff `foxray.view_issue` (and `foxray.change_issue` to
|
|
87
|
+
resolve or ignore) through groups. Postgres or SQLite; Django 4.2+; Python 3.10+.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# settings.py
|
|
91
|
+
INSTALLED_APPS = [..., "foxray.django"]
|
|
92
|
+
|
|
93
|
+
# Optional; every key has a default. FOXRAY_<KEY> env vars fill in what isn't set here.
|
|
94
|
+
FOXRAY = {
|
|
95
|
+
"BASE_URL": "https://askrobots.com", # for links in alerts and DSNs
|
|
96
|
+
"ALERT_EMAILS": ["ops@example.com"], # default: ADMINS, else superusers
|
|
97
|
+
"ALERT_WEBHOOK": "", # POSTs alert JSON here too
|
|
98
|
+
"MAX_DB_MB": 500, # Foxray's own tables
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# urls.py
|
|
102
|
+
urlpatterns += [path("foxray/", include("foxray.django.urls"))]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- If you already call `sentry_sdk.init(...)` before apps load, or `SENTRY_DSN`
|
|
106
|
+
is set, Foxray leaves the SDK alone. (A left-over `SENTRY_DSN` with no SDK
|
|
107
|
+
started: set `FOXRAY = {"AUTO_INIT": True}` and Foxray starts anyway.) To send the app's own errors to Foxray
|
|
108
|
+
explicitly, call `foxray.django.sdk.init(**options)` instead of
|
|
109
|
+
`sentry_sdk.init`. `FOXRAY = {"AUTO_INIT": False}` turns auto-start off. It
|
|
110
|
+
also stays off under `manage.py test` and pytest, so a host app's test errors
|
|
111
|
+
don't land in its test database.
|
|
112
|
+
- Storing never raises into the app: if it fails, the error is logged with
|
|
113
|
+
`logging` (logger `foxray.sdk`, which the SDK is told to ignore).
|
|
114
|
+
- **Other apps** send over HTTP: copy a DSN from **Foxray > Projects** (or run
|
|
115
|
+
`manage.py foxray_create_project webhive`). It looks like
|
|
116
|
+
`https://<key>@askrobots.com/foxray/<project_id>`. The ingest views are
|
|
117
|
+
CSRF-exempt and don't use sessions or auth; the key is the credential.
|
|
118
|
+
- Cleanup runs by itself (see below). `manage.py foxray_tick` (monitors,
|
|
119
|
+
cleanup when due, alerts) and `manage.py foxray_prune` (cleanup now) are there
|
|
120
|
+
if you'd rather run them from cron or your scheduler.
|
|
121
|
+
|
|
122
|
+
**The trade-off:** an app that reports to itself can't report while it's
|
|
123
|
+
down, and an error storm adds load to the same app and database (rate limits
|
|
124
|
+
and per-issue sampling bound it). The standalone server on another box avoids
|
|
125
|
+
both.
|
|
126
|
+
|
|
127
|
+
## Run it on its own
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
pip install foxray # SQLite: standard library only
|
|
131
|
+
pip install "foxray[postgres]" # optional: Postgres via psycopg
|
|
132
|
+
|
|
133
|
+
export FOXRAY_DATABASE_URL=/var/lib/foxray/foxray.db # or postgres://user@host/foxray
|
|
134
|
+
export FOXRAY_BASE_URL=https://foxray.example.com
|
|
135
|
+
foxray create-project shop # prints the DSN
|
|
136
|
+
foxray create-user you@example.com # a UI login (asks for a password)
|
|
137
|
+
foxray serve --host 127.0.0.1 --port 8765
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`serve` is a threaded HTTP server: SDK ingest at `/api/<id>/envelope/` and
|
|
141
|
+
`/api/<id>/store/`, and an HTML UI behind a login (issues, issue pages with the
|
|
142
|
+
stack trace, monitors, projects with their filters and limits, alerts,
|
|
143
|
+
storage). It runs its own tick every minute. Put it behind a TLS proxy (Caddy,
|
|
144
|
+
nginx) and set `FOXRAY_TRUST_PROXY=1`. Other commands: `foxray projects`,
|
|
145
|
+
`foxray tick`, `foxray prune`, `foxray storage`.
|
|
146
|
+
|
|
147
|
+
Configuration comes from environment variables or an INI file
|
|
148
|
+
(`--config foxray.ini`, section `[foxray]`, same names in lower case; a `.toml`
|
|
149
|
+
file works on Python 3.11+). Environment variables win.
|
|
150
|
+
|
|
151
|
+
| Variable | Default | |
|
|
152
|
+
| --- | --- | --- |
|
|
153
|
+
| `FOXRAY_DATABASE_URL` / `DATABASE_URL` | `foxray.db` | SQLite path or `postgres://...` |
|
|
154
|
+
| `FOXRAY_HOST`, `FOXRAY_PORT` | `127.0.0.1`, `8765` | |
|
|
155
|
+
| `FOXRAY_BASE_URL` | `http://host:port` | public URL, for DSNs and alert links |
|
|
156
|
+
| `FOXRAY_SMTP_HOST`, `_PORT`, `_USER`, `_PASSWORD`, `_FROM` | off, 587 | email alerts (STARTTLS; `FOXRAY_SMTP_SSL=1` for 465) |
|
|
157
|
+
| `FOXRAY_ALERT_EMAILS` | | comma-separated; per project too |
|
|
158
|
+
| `FOXRAY_ALERT_WEBHOOK` | | POST alert JSON; per project too |
|
|
159
|
+
| `FOXRAY_RATE_LIMIT_PER_MINUTE` | 600 | events per project; 0 = off; per project too |
|
|
160
|
+
| `FOXRAY_SAMPLE_AFTER` | 1000 | event bodies per issue per day, then counts only |
|
|
161
|
+
| `FOXRAY_MAX_DB_MB` | 500 | hard size cap |
|
|
162
|
+
| `FOXRAY_EVENT_RETENTION_DAYS` | 30 | also `EVENTS_PER_ISSUE` (100), `ISSUE_RETENTION_DAYS` (90) |
|
|
163
|
+
| `FOXRAY_ALERT_COOLDOWN_SECONDS` | 3600 | |
|
|
164
|
+
| `FOXRAY_SECRET_KEY` | generated, kept in the DB | signs UI sessions |
|
|
165
|
+
|
|
166
|
+
Measured on an M-series Mac, Python 3.10, `bench/measure.py` (1,000 SDK-shaped
|
|
167
|
+
4 KB events, gzip, one keep-alive connection, everything stored):
|
|
168
|
+
|
|
169
|
+
| | idle RSS | 1,000 events |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| SQLite | 24.5 MB | 1.9 s (about 520/s) |
|
|
172
|
+
| Postgres 14 (local) | 34.9 MB | 5.7 s (about 175/s) |
|
|
173
|
+
|
|
174
|
+
## Point an SDK at it
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
import sentry_sdk
|
|
178
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
179
|
+
from sentry_sdk.integrations.celery import CeleryIntegration
|
|
180
|
+
|
|
181
|
+
sentry_sdk.init(
|
|
182
|
+
dsn="https://<key>@foxray.example.com/<project_id>",
|
|
183
|
+
integrations=[DjangoIntegration(), CeleryIntegration()],
|
|
184
|
+
traces_sample_rate=0,
|
|
185
|
+
send_default_pii=False,
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- Recent sentry-sdk releases (2.73 at the time of writing) deprecate
|
|
190
|
+
`send_default_pii` in favour of `data_collection`. Both default to off, so
|
|
191
|
+
you can leave it out.
|
|
192
|
+
- Cron monitors: `@sentry_sdk.crons.monitor(monitor_slug=..., monitor_config=...)`
|
|
193
|
+
or `capture_checkin(...)`. Crontab and interval schedules, timezones,
|
|
194
|
+
`checkin_margin` and `max_runtime` are honoured.
|
|
195
|
+
- **Brotli:** the Python SDK compresses with brotli whenever the `Brotli`
|
|
196
|
+
package is installed in the *sending* app (whitenoise setups often have it).
|
|
197
|
+
Foxray decodes brotli only when `Brotli` is installed where Foxray runs
|
|
198
|
+
(`pip install "foxray[brotli]"`). Otherwise it answers 415 and those events
|
|
199
|
+
are lost, so either install it or set
|
|
200
|
+
`_experiments={"transport_compression_algo": "gzip"}` on the senders. gzip
|
|
201
|
+
and deflate always work.
|
|
202
|
+
|
|
203
|
+
## What it keeps, and what it drops
|
|
204
|
+
|
|
205
|
+
| Item | |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| `event` (and the legacy `/store/` JSON) | stored: exception chain, frames with source context, `in_app`, message, level, platform, release, environment, server_name, transaction, tags, contexts, breadcrumbs (last 50), extra |
|
|
208
|
+
| `check_in` | stored; monitors created and scheduled from `monitor_config` |
|
|
209
|
+
| `transaction`, `session(s)`, `client_report`, `attachment`, `profile`, `replay*`, `statsd`, anything unknown | accepted (200), dropped, counted per project per day |
|
|
210
|
+
|
|
211
|
+
- User data is cut to `id` and request data to `url` (query string removed)
|
|
212
|
+
and `method` by default; frame local variables aren't kept unless
|
|
213
|
+
`KEEP_FRAME_VARS` is on. Everything kept is scrubbed before storage:
|
|
214
|
+
passwords, tokens, cookies, Authorization headers, anything under a
|
|
215
|
+
secret-looking key, card numbers (Luhn-checked), private keys, JWTs, and
|
|
216
|
+
Stripe/AWS/GitHub/Slack-style keys.
|
|
217
|
+
- **Grouping:** an SDK `fingerprint` wins (`{{ default }}` works). Otherwise
|
|
218
|
+
each exception's type plus its in-app frames as `module:function` (no line
|
|
219
|
+
numbers, so edits elsewhere don't split an issue). Messages group with
|
|
220
|
+
numbers, hex and ids blanked, using the logging template when there is one.
|
|
221
|
+
- **Answers:** `200 {"id": ...}`; 401/403 for a missing or wrong key; 413 for
|
|
222
|
+
too large (10 MB sent, 20 MB decompressed, 1 MB per event); 415 for an
|
|
223
|
+
unknown encoding; 429 with `Retry-After` and `X-Sentry-Rate-Limits` over the
|
|
224
|
+
rate limit. CORS is open, so browser SDKs work.
|
|
225
|
+
|
|
226
|
+
## Filters, alerts, cleanup
|
|
227
|
+
|
|
228
|
+
- **Inbound filters** (per project, in the DB, edited in the UI or the admin)
|
|
229
|
+
are applied before anything is stored. A dropped event still gets a 200 and
|
|
230
|
+
is counted by reason per day. You can drop by exception type or message
|
|
231
|
+
(substring or regex, 200-character limit, nested repeats refused, optionally
|
|
232
|
+
only for one environment or release), by environment, or by release. Built-in
|
|
233
|
+
switches drop crawlers by user agent and health-check transactions, and
|
|
234
|
+
(opt-in) browser-extension frames and localhost URLs on JavaScript events.
|
|
235
|
+
After N events per issue per day (1,000) only the count is kept. Issues can
|
|
236
|
+
be ignored for good, until N more events, or until a date.
|
|
237
|
+
- **Alerts** fire on a new issue or a regression (a resolved issue seen again,
|
|
238
|
+
or an ignored one past its "until"). Repeats never alert, and regressions
|
|
239
|
+
respect a per-issue cooldown. Per project they can be limited to new issues
|
|
240
|
+
and/or regressions, a minimum level, and environments. Monitors alert when they
|
|
241
|
+
go from healthy to missed, failed or timed out. Delivery is email and/or a
|
|
242
|
+
webhook, through the outbox.
|
|
243
|
+
- **Cleanup runs by itself.** At most every 10 minutes, the ingest request
|
|
244
|
+
that notices it's due runs one small batch on a short thread after
|
|
245
|
+
answering. A lease row means two never overlap. Defaults:
|
|
246
|
+
- event bodies older than 30 days go;
|
|
247
|
+
- each issue keeps its newest 100 bodies;
|
|
248
|
+
- resolved/ignored issues not seen for 90 days go, with their daily counts;
|
|
249
|
+
- daily counts go after 400 days;
|
|
250
|
+
- check-ins: 30 days or the newest 500 per monitor;
|
|
251
|
+
- sent alerts go after 30 days, failed ones after 7 days of retries;
|
|
252
|
+
- rate windows and drop counters go after 2 days.
|
|
253
|
+
|
|
254
|
+
Issue counts and first/last seen survive all of it.
|
|
255
|
+
- **Size cap** (`MAX_DB_MB`, 500). Over 90%, the oldest bodies go until the
|
|
256
|
+
store is under 80%. Still over 100%, Foxray counts only: it keeps grouping,
|
|
257
|
+
counting and alerting but stores no new bodies, shows a banner, and sends
|
|
258
|
+
one "storage full" alert. Ingest never fails because the store is full. On
|
|
259
|
+
SQLite the file is created with incremental auto-vacuum, so space goes back
|
|
260
|
+
to the disk after each prune (plus a WAL checkpoint). Postgres keeps freed
|
|
261
|
+
space for reuse: Foxray deletes in batches, lets autovacuum work, never runs
|
|
262
|
+
`VACUUM FULL`, and only prunes again once the tables grow. The Storage page
|
|
263
|
+
shows the size, cap, mode, rows per table and the last cleanup.
|
|
264
|
+
|
|
265
|
+
## Compared with GlitchTip and self-hosted Sentry
|
|
266
|
+
|
|
267
|
+
- **Self-hosted Sentry** is the full product: performance tracing, replays,
|
|
268
|
+
profiling, source maps and symbolication, release health, integrations,
|
|
269
|
+
teams. It is also a couple of dozen services (Kafka, ClickHouse, Redis,
|
|
270
|
+
Postgres, Snuba, Relay, ...) and its docs ask for about 16 GB of RAM. Its
|
|
271
|
+
server is under the Functional Source License.
|
|
272
|
+
- **GlitchTip** is the closest relative: open source (MIT), Sentry-SDK
|
|
273
|
+
compatible, a Django app with a proper multi-user UI, organizations and
|
|
274
|
+
teams, performance and uptime monitoring. A standard install runs PostgreSQL
|
|
275
|
+
plus a Redis-compatible store and a worker.
|
|
276
|
+
- **Foxray** is smaller on purpose: one process and one database (even just a
|
|
277
|
+
SQLite file), about 25 MB of RAM, or no extra process at all inside an
|
|
278
|
+
existing Django app. It does errors, grouping, alerts, cron monitors,
|
|
279
|
+
filters and its own cleanup. It does not do tracing, replays, profiles,
|
|
280
|
+
attachments, source maps (minified JavaScript stack traces stay minified),
|
|
281
|
+
release health, organizations/teams, or integrations beyond email and a
|
|
282
|
+
webhook. If you need those, use GlitchTip or Sentry.
|
|
283
|
+
|
|
284
|
+
## The name
|
|
285
|
+
|
|
286
|
+
Foxray: add up the letters of "sentry" as you go (a=1…z=26, wrapping past z)
|
|
287
|
+
and you get s x l f x w; in the phonetic alphabet that's Sierra X-ray Lima
|
|
288
|
+
Foxtrot X-ray Whiskey, and Foxtrot X-ray gives the name. It looks inside a
|
|
289
|
+
crash.
|
|
290
|
+
|
|
291
|
+
## Development
|
|
292
|
+
|
|
293
|
+
```sh
|
|
294
|
+
python3.10 -m venv .venv && .venv/bin/pip install -e ".[dev,postgres,brotli]"
|
|
295
|
+
.venv/bin/pytest # SQLite everywhere
|
|
296
|
+
FOXRAY_TEST_PG_URL=postgres://you@localhost/postgres .venv/bin/pytest
|
|
297
|
+
# + Postgres (creates and drops its own databases)
|
|
298
|
+
FOXRAY_TEST_DJANGO_PG=postgres://you@localhost/postgres .venv/bin/pytest tests/test_django.py tests/test_store_contract.py
|
|
299
|
+
python bench/measure.py [--db postgres://...] # idle RSS and 1,000-event ingest time
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Layout: `foxray/core` (stdlib only: protocol, normalising, grouping, scrubbing,
|
|
303
|
+
filters, cron, alerts, the SQL store, cleanup), `foxray/standalone` (server,
|
|
304
|
+
UI, CLI), `foxray/django` (models, migrations, admin, ingest views, in-process
|
|
305
|
+
transport). The storage contract in `tests/test_store_contract.py` runs
|
|
306
|
+
against SQLite, Postgres and the Django store alike.
|
|
307
|
+
|
|
308
|
+
MIT licensed; see `LICENSE` and `NOTICE`.
|
foxray-0.1.2/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# Foxray
|
|
2
|
+
|
|
3
|
+
A light error tracker that accepts errors from the official Sentry SDKs. It
|
|
4
|
+
runs two ways from one codebase:
|
|
5
|
+
|
|
6
|
+
- **inside a Django app**, as `foxray.django`: errors from the app itself are
|
|
7
|
+
stored in-process (no DSN, no HTTP), and the Django admin is the UI;
|
|
8
|
+
- **on its own**, as one small stdlib-only process (`foxray serve`) with a
|
|
9
|
+
SQLite file or Postgres behind it.
|
|
10
|
+
|
|
11
|
+
**Works with the Sentry SDKs; not affiliated with Sentry.**
|
|
12
|
+
|
|
13
|
+
Install from PyPI: `pip install foxray` (add `[postgres]` or `[brotli]` for
|
|
14
|
+
those extras).
|
|
15
|
+
|
|
16
|
+
**Design rule: one process plus one database; no Redis, Celery, Kafka or
|
|
17
|
+
ClickHouse.** Everything other trackers push into side services lives in the
|
|
18
|
+
database, kept as simple as possible:
|
|
19
|
+
|
|
20
|
+
| Elsewhere it's in | In Foxray it's |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| Redis rate-limit counters | `foxray_rate`: one atomic upsert per project per minute |
|
|
23
|
+
| Redis/memcached dedupe, cooldowns | a unique index on event ids; `last_alerted_at` on the issue |
|
|
24
|
+
| Celery alert tasks | `foxray_alert`: an outbox written in the ingest transaction, sent after commit, retried |
|
|
25
|
+
| Celery beat (cron checks, cleanup) | `foxray_state` leases; run after ingest when due, or by `foxray tick` |
|
|
26
|
+
| Kafka/ClickHouse counts | `foxray_issue_day`, `foxray_counter`: daily counters |
|
|
27
|
+
|
|
28
|
+
So several processes (uvicorn workers, a cron `tick`) always agree, and
|
|
29
|
+
nothing else needs running.
|
|
30
|
+
|
|
31
|
+
## Add it to a Django app
|
|
32
|
+
|
|
33
|
+
`pip install sentry-sdk` and Foxray, add `"foxray.django"` to `INSTALLED_APPS`,
|
|
34
|
+
add `path("foxray/", include("foxray.django.urls"))` to your URLs, and run
|
|
35
|
+
`python manage.py migrate`. That's all: the migration creates a "default"
|
|
36
|
+
project, `AppConfig.ready()` starts the Sentry SDK with an in-process transport
|
|
37
|
+
(DjangoIntegration, CeleryIntegration if Celery is installed,
|
|
38
|
+
`traces_sample_rate=0`, no PII), errors appear in the admin under **Foxray >
|
|
39
|
+
Issues**, and alert emails go to `settings.ADMINS` (or, if that is empty, the
|
|
40
|
+
superusers) through Django's email backend. Access is Django's own: superusers
|
|
41
|
+
see everything; give staff `foxray.view_issue` (and `foxray.change_issue` to
|
|
42
|
+
resolve or ignore) through groups. Postgres or SQLite; Django 4.2+; Python 3.10+.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# settings.py
|
|
46
|
+
INSTALLED_APPS = [..., "foxray.django"]
|
|
47
|
+
|
|
48
|
+
# Optional; every key has a default. FOXRAY_<KEY> env vars fill in what isn't set here.
|
|
49
|
+
FOXRAY = {
|
|
50
|
+
"BASE_URL": "https://askrobots.com", # for links in alerts and DSNs
|
|
51
|
+
"ALERT_EMAILS": ["ops@example.com"], # default: ADMINS, else superusers
|
|
52
|
+
"ALERT_WEBHOOK": "", # POSTs alert JSON here too
|
|
53
|
+
"MAX_DB_MB": 500, # Foxray's own tables
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# urls.py
|
|
57
|
+
urlpatterns += [path("foxray/", include("foxray.django.urls"))]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- If you already call `sentry_sdk.init(...)` before apps load, or `SENTRY_DSN`
|
|
61
|
+
is set, Foxray leaves the SDK alone. (A left-over `SENTRY_DSN` with no SDK
|
|
62
|
+
started: set `FOXRAY = {"AUTO_INIT": True}` and Foxray starts anyway.) To send the app's own errors to Foxray
|
|
63
|
+
explicitly, call `foxray.django.sdk.init(**options)` instead of
|
|
64
|
+
`sentry_sdk.init`. `FOXRAY = {"AUTO_INIT": False}` turns auto-start off. It
|
|
65
|
+
also stays off under `manage.py test` and pytest, so a host app's test errors
|
|
66
|
+
don't land in its test database.
|
|
67
|
+
- Storing never raises into the app: if it fails, the error is logged with
|
|
68
|
+
`logging` (logger `foxray.sdk`, which the SDK is told to ignore).
|
|
69
|
+
- **Other apps** send over HTTP: copy a DSN from **Foxray > Projects** (or run
|
|
70
|
+
`manage.py foxray_create_project webhive`). It looks like
|
|
71
|
+
`https://<key>@askrobots.com/foxray/<project_id>`. The ingest views are
|
|
72
|
+
CSRF-exempt and don't use sessions or auth; the key is the credential.
|
|
73
|
+
- Cleanup runs by itself (see below). `manage.py foxray_tick` (monitors,
|
|
74
|
+
cleanup when due, alerts) and `manage.py foxray_prune` (cleanup now) are there
|
|
75
|
+
if you'd rather run them from cron or your scheduler.
|
|
76
|
+
|
|
77
|
+
**The trade-off:** an app that reports to itself can't report while it's
|
|
78
|
+
down, and an error storm adds load to the same app and database (rate limits
|
|
79
|
+
and per-issue sampling bound it). The standalone server on another box avoids
|
|
80
|
+
both.
|
|
81
|
+
|
|
82
|
+
## Run it on its own
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
pip install foxray # SQLite: standard library only
|
|
86
|
+
pip install "foxray[postgres]" # optional: Postgres via psycopg
|
|
87
|
+
|
|
88
|
+
export FOXRAY_DATABASE_URL=/var/lib/foxray/foxray.db # or postgres://user@host/foxray
|
|
89
|
+
export FOXRAY_BASE_URL=https://foxray.example.com
|
|
90
|
+
foxray create-project shop # prints the DSN
|
|
91
|
+
foxray create-user you@example.com # a UI login (asks for a password)
|
|
92
|
+
foxray serve --host 127.0.0.1 --port 8765
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`serve` is a threaded HTTP server: SDK ingest at `/api/<id>/envelope/` and
|
|
96
|
+
`/api/<id>/store/`, and an HTML UI behind a login (issues, issue pages with the
|
|
97
|
+
stack trace, monitors, projects with their filters and limits, alerts,
|
|
98
|
+
storage). It runs its own tick every minute. Put it behind a TLS proxy (Caddy,
|
|
99
|
+
nginx) and set `FOXRAY_TRUST_PROXY=1`. Other commands: `foxray projects`,
|
|
100
|
+
`foxray tick`, `foxray prune`, `foxray storage`.
|
|
101
|
+
|
|
102
|
+
Configuration comes from environment variables or an INI file
|
|
103
|
+
(`--config foxray.ini`, section `[foxray]`, same names in lower case; a `.toml`
|
|
104
|
+
file works on Python 3.11+). Environment variables win.
|
|
105
|
+
|
|
106
|
+
| Variable | Default | |
|
|
107
|
+
| --- | --- | --- |
|
|
108
|
+
| `FOXRAY_DATABASE_URL` / `DATABASE_URL` | `foxray.db` | SQLite path or `postgres://...` |
|
|
109
|
+
| `FOXRAY_HOST`, `FOXRAY_PORT` | `127.0.0.1`, `8765` | |
|
|
110
|
+
| `FOXRAY_BASE_URL` | `http://host:port` | public URL, for DSNs and alert links |
|
|
111
|
+
| `FOXRAY_SMTP_HOST`, `_PORT`, `_USER`, `_PASSWORD`, `_FROM` | off, 587 | email alerts (STARTTLS; `FOXRAY_SMTP_SSL=1` for 465) |
|
|
112
|
+
| `FOXRAY_ALERT_EMAILS` | | comma-separated; per project too |
|
|
113
|
+
| `FOXRAY_ALERT_WEBHOOK` | | POST alert JSON; per project too |
|
|
114
|
+
| `FOXRAY_RATE_LIMIT_PER_MINUTE` | 600 | events per project; 0 = off; per project too |
|
|
115
|
+
| `FOXRAY_SAMPLE_AFTER` | 1000 | event bodies per issue per day, then counts only |
|
|
116
|
+
| `FOXRAY_MAX_DB_MB` | 500 | hard size cap |
|
|
117
|
+
| `FOXRAY_EVENT_RETENTION_DAYS` | 30 | also `EVENTS_PER_ISSUE` (100), `ISSUE_RETENTION_DAYS` (90) |
|
|
118
|
+
| `FOXRAY_ALERT_COOLDOWN_SECONDS` | 3600 | |
|
|
119
|
+
| `FOXRAY_SECRET_KEY` | generated, kept in the DB | signs UI sessions |
|
|
120
|
+
|
|
121
|
+
Measured on an M-series Mac, Python 3.10, `bench/measure.py` (1,000 SDK-shaped
|
|
122
|
+
4 KB events, gzip, one keep-alive connection, everything stored):
|
|
123
|
+
|
|
124
|
+
| | idle RSS | 1,000 events |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| SQLite | 24.5 MB | 1.9 s (about 520/s) |
|
|
127
|
+
| Postgres 14 (local) | 34.9 MB | 5.7 s (about 175/s) |
|
|
128
|
+
|
|
129
|
+
## Point an SDK at it
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import sentry_sdk
|
|
133
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
134
|
+
from sentry_sdk.integrations.celery import CeleryIntegration
|
|
135
|
+
|
|
136
|
+
sentry_sdk.init(
|
|
137
|
+
dsn="https://<key>@foxray.example.com/<project_id>",
|
|
138
|
+
integrations=[DjangoIntegration(), CeleryIntegration()],
|
|
139
|
+
traces_sample_rate=0,
|
|
140
|
+
send_default_pii=False,
|
|
141
|
+
)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- Recent sentry-sdk releases (2.73 at the time of writing) deprecate
|
|
145
|
+
`send_default_pii` in favour of `data_collection`. Both default to off, so
|
|
146
|
+
you can leave it out.
|
|
147
|
+
- Cron monitors: `@sentry_sdk.crons.monitor(monitor_slug=..., monitor_config=...)`
|
|
148
|
+
or `capture_checkin(...)`. Crontab and interval schedules, timezones,
|
|
149
|
+
`checkin_margin` and `max_runtime` are honoured.
|
|
150
|
+
- **Brotli:** the Python SDK compresses with brotli whenever the `Brotli`
|
|
151
|
+
package is installed in the *sending* app (whitenoise setups often have it).
|
|
152
|
+
Foxray decodes brotli only when `Brotli` is installed where Foxray runs
|
|
153
|
+
(`pip install "foxray[brotli]"`). Otherwise it answers 415 and those events
|
|
154
|
+
are lost, so either install it or set
|
|
155
|
+
`_experiments={"transport_compression_algo": "gzip"}` on the senders. gzip
|
|
156
|
+
and deflate always work.
|
|
157
|
+
|
|
158
|
+
## What it keeps, and what it drops
|
|
159
|
+
|
|
160
|
+
| Item | |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| `event` (and the legacy `/store/` JSON) | stored: exception chain, frames with source context, `in_app`, message, level, platform, release, environment, server_name, transaction, tags, contexts, breadcrumbs (last 50), extra |
|
|
163
|
+
| `check_in` | stored; monitors created and scheduled from `monitor_config` |
|
|
164
|
+
| `transaction`, `session(s)`, `client_report`, `attachment`, `profile`, `replay*`, `statsd`, anything unknown | accepted (200), dropped, counted per project per day |
|
|
165
|
+
|
|
166
|
+
- User data is cut to `id` and request data to `url` (query string removed)
|
|
167
|
+
and `method` by default; frame local variables aren't kept unless
|
|
168
|
+
`KEEP_FRAME_VARS` is on. Everything kept is scrubbed before storage:
|
|
169
|
+
passwords, tokens, cookies, Authorization headers, anything under a
|
|
170
|
+
secret-looking key, card numbers (Luhn-checked), private keys, JWTs, and
|
|
171
|
+
Stripe/AWS/GitHub/Slack-style keys.
|
|
172
|
+
- **Grouping:** an SDK `fingerprint` wins (`{{ default }}` works). Otherwise
|
|
173
|
+
each exception's type plus its in-app frames as `module:function` (no line
|
|
174
|
+
numbers, so edits elsewhere don't split an issue). Messages group with
|
|
175
|
+
numbers, hex and ids blanked, using the logging template when there is one.
|
|
176
|
+
- **Answers:** `200 {"id": ...}`; 401/403 for a missing or wrong key; 413 for
|
|
177
|
+
too large (10 MB sent, 20 MB decompressed, 1 MB per event); 415 for an
|
|
178
|
+
unknown encoding; 429 with `Retry-After` and `X-Sentry-Rate-Limits` over the
|
|
179
|
+
rate limit. CORS is open, so browser SDKs work.
|
|
180
|
+
|
|
181
|
+
## Filters, alerts, cleanup
|
|
182
|
+
|
|
183
|
+
- **Inbound filters** (per project, in the DB, edited in the UI or the admin)
|
|
184
|
+
are applied before anything is stored. A dropped event still gets a 200 and
|
|
185
|
+
is counted by reason per day. You can drop by exception type or message
|
|
186
|
+
(substring or regex, 200-character limit, nested repeats refused, optionally
|
|
187
|
+
only for one environment or release), by environment, or by release. Built-in
|
|
188
|
+
switches drop crawlers by user agent and health-check transactions, and
|
|
189
|
+
(opt-in) browser-extension frames and localhost URLs on JavaScript events.
|
|
190
|
+
After N events per issue per day (1,000) only the count is kept. Issues can
|
|
191
|
+
be ignored for good, until N more events, or until a date.
|
|
192
|
+
- **Alerts** fire on a new issue or a regression (a resolved issue seen again,
|
|
193
|
+
or an ignored one past its "until"). Repeats never alert, and regressions
|
|
194
|
+
respect a per-issue cooldown. Per project they can be limited to new issues
|
|
195
|
+
and/or regressions, a minimum level, and environments. Monitors alert when they
|
|
196
|
+
go from healthy to missed, failed or timed out. Delivery is email and/or a
|
|
197
|
+
webhook, through the outbox.
|
|
198
|
+
- **Cleanup runs by itself.** At most every 10 minutes, the ingest request
|
|
199
|
+
that notices it's due runs one small batch on a short thread after
|
|
200
|
+
answering. A lease row means two never overlap. Defaults:
|
|
201
|
+
- event bodies older than 30 days go;
|
|
202
|
+
- each issue keeps its newest 100 bodies;
|
|
203
|
+
- resolved/ignored issues not seen for 90 days go, with their daily counts;
|
|
204
|
+
- daily counts go after 400 days;
|
|
205
|
+
- check-ins: 30 days or the newest 500 per monitor;
|
|
206
|
+
- sent alerts go after 30 days, failed ones after 7 days of retries;
|
|
207
|
+
- rate windows and drop counters go after 2 days.
|
|
208
|
+
|
|
209
|
+
Issue counts and first/last seen survive all of it.
|
|
210
|
+
- **Size cap** (`MAX_DB_MB`, 500). Over 90%, the oldest bodies go until the
|
|
211
|
+
store is under 80%. Still over 100%, Foxray counts only: it keeps grouping,
|
|
212
|
+
counting and alerting but stores no new bodies, shows a banner, and sends
|
|
213
|
+
one "storage full" alert. Ingest never fails because the store is full. On
|
|
214
|
+
SQLite the file is created with incremental auto-vacuum, so space goes back
|
|
215
|
+
to the disk after each prune (plus a WAL checkpoint). Postgres keeps freed
|
|
216
|
+
space for reuse: Foxray deletes in batches, lets autovacuum work, never runs
|
|
217
|
+
`VACUUM FULL`, and only prunes again once the tables grow. The Storage page
|
|
218
|
+
shows the size, cap, mode, rows per table and the last cleanup.
|
|
219
|
+
|
|
220
|
+
## Compared with GlitchTip and self-hosted Sentry
|
|
221
|
+
|
|
222
|
+
- **Self-hosted Sentry** is the full product: performance tracing, replays,
|
|
223
|
+
profiling, source maps and symbolication, release health, integrations,
|
|
224
|
+
teams. It is also a couple of dozen services (Kafka, ClickHouse, Redis,
|
|
225
|
+
Postgres, Snuba, Relay, ...) and its docs ask for about 16 GB of RAM. Its
|
|
226
|
+
server is under the Functional Source License.
|
|
227
|
+
- **GlitchTip** is the closest relative: open source (MIT), Sentry-SDK
|
|
228
|
+
compatible, a Django app with a proper multi-user UI, organizations and
|
|
229
|
+
teams, performance and uptime monitoring. A standard install runs PostgreSQL
|
|
230
|
+
plus a Redis-compatible store and a worker.
|
|
231
|
+
- **Foxray** is smaller on purpose: one process and one database (even just a
|
|
232
|
+
SQLite file), about 25 MB of RAM, or no extra process at all inside an
|
|
233
|
+
existing Django app. It does errors, grouping, alerts, cron monitors,
|
|
234
|
+
filters and its own cleanup. It does not do tracing, replays, profiles,
|
|
235
|
+
attachments, source maps (minified JavaScript stack traces stay minified),
|
|
236
|
+
release health, organizations/teams, or integrations beyond email and a
|
|
237
|
+
webhook. If you need those, use GlitchTip or Sentry.
|
|
238
|
+
|
|
239
|
+
## The name
|
|
240
|
+
|
|
241
|
+
Foxray: add up the letters of "sentry" as you go (a=1…z=26, wrapping past z)
|
|
242
|
+
and you get s x l f x w; in the phonetic alphabet that's Sierra X-ray Lima
|
|
243
|
+
Foxtrot X-ray Whiskey, and Foxtrot X-ray gives the name. It looks inside a
|
|
244
|
+
crash.
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```sh
|
|
249
|
+
python3.10 -m venv .venv && .venv/bin/pip install -e ".[dev,postgres,brotli]"
|
|
250
|
+
.venv/bin/pytest # SQLite everywhere
|
|
251
|
+
FOXRAY_TEST_PG_URL=postgres://you@localhost/postgres .venv/bin/pytest
|
|
252
|
+
# + Postgres (creates and drops its own databases)
|
|
253
|
+
FOXRAY_TEST_DJANGO_PG=postgres://you@localhost/postgres .venv/bin/pytest tests/test_django.py tests/test_store_contract.py
|
|
254
|
+
python bench/measure.py [--db postgres://...] # idle RSS and 1,000-event ingest time
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Layout: `foxray/core` (stdlib only: protocol, normalising, grouping, scrubbing,
|
|
258
|
+
filters, cron, alerts, the SQL store, cleanup), `foxray/standalone` (server,
|
|
259
|
+
UI, CLI), `foxray/django` (models, migrations, admin, ingest views, in-process
|
|
260
|
+
transport). The storage contract in `tests/test_store_contract.py` runs
|
|
261
|
+
against SQLite, Postgres and the Django store alike.
|
|
262
|
+
|
|
263
|
+
MIT licensed; see `LICENSE` and `NOTICE`.
|