| Crates.io | sentry-tunnel |
| lib.rs | sentry-tunnel |
| version | 0.1.3 |
| created_at | 2025-11-06 15:12:00.007825+00 |
| updated_at | 2025-11-06 16:36:17.915196+00 |
| description | A Sentry tunnel middleware for Axum |
| homepage | |
| repository | https://github.com/AprilNEA/sentry-tunnel |
| max_upload_size | |
| id | 1919783 |
| size | 62,035 |
A flexible Sentry tunnel middleware for Axum that helps bypass ad-blockers and content blockers by proxying Sentry error reports through your own domain.
Add this to your Cargo.toml:
[dependencies]
sentry-tunnel = "0.1"
extension (default): Router extension trait for adding tunnel to existing Routerstandalone: Standalone service creation (depends on extension)service: Convenience feature enabling both extension and standaloneutoipa: OpenAPI/utoipa support for documenting the tunnel endpoint (depends on extension)To disable default features and use only the core:
[dependencies]
sentry-tunnel = { version = "0.1", default-features = false }
Add the Sentry tunnel directly to your existing Axum router:
use axum::{routing::get, Router};
use sentry_tunnel::{SentryTunnelBuilder, SentryTunnelExt};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.sentry_tunnel(
SentryTunnelBuilder::new("o123456.ingest.sentry.io")
.allow_project_ids(["123456", "789012"])
.path("/tunnel")
.timeout_secs(30)
.build(),
);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
Create a standalone Sentry tunnel service and nest it under a path:
use axum::{routing::get, Router};
#[tokio::main]
async fn main() {
let sentry_service = sentry_tunnel::create_sentry_tunnel_service(
sentry_tunnel::SentryTunnelConfig::new(
"o123456.ingest.sentry.io",
vec!["123456".to_string(), "789012".to_string()],
),
);
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.nest("/sentry", sentry_service);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
Use the core handler with any web framework:
use sentry_tunnel::{handle_sentry_tunnel_inner, SentryTunnelConfig};
use std::sync::Arc;
async fn my_handler(body: &[u8]) -> Result<(), sentry_tunnel::SentryTunnelError> {
let config = Arc::new(SentryTunnelConfig::new(
"o123456.ingest.sentry.io",
vec!["123456".to_string()],
));
handle_sentry_tunnel_inner(config, body).await
}
use sentry_tunnel::SentryTunnelBuilder;
let config = SentryTunnelBuilder::new("o123456.ingest.sentry.io")
.allow_project_id("123456")
.allow_project_id("789012")
.path("/tunnel")
.timeout_secs(30)
.build();
use sentry_tunnel::SentryTunnelConfig;
let config = SentryTunnelConfig::new(
"o123456.ingest.sentry.io",
vec!["123456".to_string()],
)
.with_path("/custom-tunnel")
.with_timeout(60);
Configure your Sentry client to use the tunnel:
Sentry.init({
dsn: "https://examplePublicKey@o123456.ingest.sentry.io/123456",
tunnel: "/tunnel", // or "/sentry/tunnel" if using nested service
});
import sentry_sdk
sentry_sdk.init(
dsn="https://examplePublicKey@o123456.ingest.sentry.io/123456",
tunnel="https://yourdomain.com/tunnel",
)
The tunnel validates:
This prevents:
Run the examples:
# Extension trait example
cargo run --example extension --features extension
# Standalone service example
cargo run --example standalone --features standalone
# Both methods
cargo run --example basic --features service
Licensed under either of:
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.