Muffin-Sentry
Muffin-Sentry — Sentry integration for the Muffin ASGI framework.
Requirements
- Python 3.11 - 3.15
- Muffin
- sentry-sdk >= 2, < 3
Installation
Install using pip:
pip install muffin-sentry
Usage
from muffin import Application
import muffin_sentry
app = Application("example", SENTRY_DSN="https://<public>@sentry.io/<project_id>")
sentry = muffin_sentry.Plugin(app)
# Add a custom processor (must be sync)
@sentry.processor
def enrich_event(event, hint, request):
if user := getattr(request, "user", None):
event["user"] = {"id": str(user.id)}
return event
# Raise unhandled exception
@app.route("/fail")
async def fail(request):
raise RuntimeError("Boom")
# Manually capture a message
@app.route("/capture")
async def capture(request):
sentry.capture_message("Manual log")
return "OK"
# Update scope manually
@app.route("/scope")
async def tag_scope(request):
sentry.scope.set_tag("section", "test")
sentry.capture_exception(Exception("With scope tag"))
return "OK"
Plugin(app) calls setup() for you. Calling muffin_sentry.Plugin() and then sentry.setup(app) is
equivalent.
Configuration
Options are read from the application config with the SENTRY_ prefix (SENTRY_DSN,
SENTRY_SDK_OPTIONS, ...), or from environment variables with the {CFG.NAME}_SENTRY_ prefix,
MUFFIN_SENTRY_ by default. JSON is parsed for complex values:
export MUFFIN_SENTRY_DSN=https://<public>@sentry.io/<project_id>
export MUFFIN_SENTRY_SDK_OPTIONS='{"environment": "production", "traces_sample_rate": 0.05}'
Environment variables are applied by Muffin before the plugin is set up, so they win over the values
passed to Application(...).
- Via Muffin application config (recommended):
app = Application(
"app",
SENTRY_DSN="https://...",
SENTRY_SDK_OPTIONS={"traces_sample_rate": 0.5},
)
- Or by calling
.setup()manually:
sentry.setup(app, dsn="https://...", sdk_options={"traces_sample_rate": 0.5})
Available options:
| Name | Default value | Description |
|---|---|---|
dsn |
"" |
Sentry DSN; when empty sentry_sdk.init() is never called |
sdk_options |
{"traces_sample_rate": 0.1, "profiles_sample_rate": 0.0} |
Keyword arguments for sentry_sdk.init() |
ignore_errors |
() |
Exception classes the middleware reports no event for |
sdk_options is replaced as a whole (no deep merge), so a custom dict has to repeat the defaults you
want to keep:
app = Application(
"app",
SENTRY_DSN="https://...",
SENTRY_SDK_OPTIONS={
"environment": "production",
"release": "myapp@1.4.2",
"traces_sample_rate": 0.05, # 10% default, 0 disables tracing
"send_default_pii": True, # send user IPs and headers to Sentry
},
)
ResponseError (Muffin's 404) and ResponseRedirect (muffin.redirect) are handled by the middleware
itself: they are control flow, not failures, so no event is sent and the transaction gets the real
status code. ignore_errors is for your own exception classes and compares by exact type.
API
plugin.scope
Property returning the current Sentry Scope (sentry_sdk.get_current_scope()). Inside a request it is
the isolation scope created by the middleware, so tags, users and breadcrumbs set there are attached to
events captured during that request. Before setup() it returns the global scope; it never raises.
plugin.processor(fn)
Decorator registering an event processor. fn(event, hint, request) is called synchronously from
process_data() after the request context (url, query_string, method, headers) has been added
to the event. REMOTE_ADDR is added only when send_default_pii is enabled. Return the modified
event, or None to keep the event unchanged. Processors are never awaited: an async def processor
returns a coroutine that is stored as the event, so keep them synchronous.
plugin.capture_exception(exc) / plugin.capture_message(msg)
Proxies to sentry_sdk.capture_exception() / capture_message(), which do nothing when no Sentry
client is active (no dsn configured). The difference from calling the SDK directly is what is
attached: the request isolation scope, the plugin's tags (framework, asgi.type), the scope modified
by your processors and the active transaction all come along automatically.
plugin.middleware
Runs around every request:
- opens an isolation scope, clears breadcrumbs, tags
framework=muffinandasgi.type(http/websocket/...), and registers the event processor that fillsevent["request"]; - starts a Sentry session for the request and ends it when the request is done, which is what release health reports;
- continues the incoming trace (
sentry-trace/baggageheaders) and starts a transaction named after the request path withop="{asgi.type}.server", so distributed traces connect the frontend request with backend errors. The event is then renamed after the matched view (module.function), so routes with ids (/users/42) do not create one transaction name per id; - sets the HTTP status on the transaction and captures unhandled exceptions, except the classes listed
in
ignore_errors.
Consequently a request that ends up with ResponseError (Muffin's 404, for example) produces a
transaction with status not_found but no error event. Disable tracing by setting
traces_sample_rate to 0 in sdk_options.
Notes
- Event processors must be synchronous functions.
- Sentry sessions and transactions are handled automatically inside the plugin middleware.
sentry_sdk.init()is called only whendsnis set; without it the plugin is a thin no-op.
Bug Tracker
Found a bug or have a feature request? Please open an issue at: https://github.com/klen/muffin-sentry/issues
Contributing
Development happens at: https://github.com/klen/muffin-sentry
Pull requests and suggestions are welcome!
License
Licensed under the MIT license.
Credits
- Created by klen (Kirill Klenov)
Metadata
Release files for muffin-sentry 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| muffin_sentry-2.1.0.tar.gz | 6.5 kB | Without provenance | Details for muffin_sentry-2.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| muffin_sentry-2.1.0-py3-none-any.whl | Python 3 | none | any | Details for muffin_sentry-2.1.0-py3-none-any.whl |
Total release size: 13.0 kB
Release files / muffin_sentry-2.1.0.tar.gz
| Download URL | muffin_sentry-2.1.0.tar.gz |
|---|---|
| Size | 6.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
340c2d086d30eb6ad8aae955100e69021159641524af3b73ca87ffe09ce64729
|
|
BLAKE2b-256 checksum How to use checksums |
d88e6b695fa53efc1bf350a1d0d58dbd488c96db3643e42e743c625dc29cabe9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / muffin_sentry-2.1.0-py3-none-any.whl
| Download URL | muffin_sentry-2.1.0-py3-none-any.whl |
|---|---|
| Size | 6.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b54b67e4adeac96c0ca47771de383884e4070189f16f3b62f65a41a77ccb6c84
|
|
BLAKE2b-256 checksum How to use checksums |
81aa3eb9cfafac6bbc22f998b6d518d84ecacc5167cb833bd145263d7d4bed1a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for muffin-sentry 2.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.