|
3 | 3 | //! |
4 | 4 | //! To use this middleware just configure Sentry and then add it to your actix web app as a |
5 | 5 | //! middleware. Because actix is generally working with non sendable objects and highly concurrent |
6 | | -//! this middleware creates a new hub per request. As a result many of the sentry integrations |
7 | | -//! such as breadcrumbs do not work unless you bind the actix hub. |
| 6 | +//! this middleware creates a new Hub per request. |
8 | 7 | //! |
9 | 8 | //! # Example |
10 | 9 | //! |
|
59 | 58 | //! # Reusing the Hub |
60 | 59 | //! |
61 | 60 | //! This integration will automatically create a new per-request Hub from the main Hub, and update the |
62 | | -//! current Hub instance. For example, the following will capture a message in the current request's Hub: |
| 61 | +//! current Hub instance. For example, the following in the handler or in any of the subsequent |
| 62 | +//! middleware will capture a message in the current request's Hub: |
63 | 63 | //! |
64 | 64 | //! ``` |
65 | 65 | //! sentry::capture_message("Something is not well", sentry::Level::Warning); |
66 | 66 | //! ``` |
| 67 | +//! |
| 68 | +//! It is recommended to register the Sentry middleware as the last, i.e. the first to be executed |
| 69 | +//! when processing a request, so that the rest of the processing will run with the correct Hub. |
67 | 70 |
|
68 | 71 | #![doc(html_favicon_url = "https://sentry-brand.storage.googleapis.com/favicon.ico")] |
69 | 72 | #![doc(html_logo_url = "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png")] |
@@ -276,6 +279,7 @@ where |
276 | 279 | let hub = Arc::new(Hub::new_from_top( |
277 | 280 | inner.hub.clone().unwrap_or_else(Hub::main), |
278 | 281 | )); |
| 282 | + |
279 | 283 | let client = hub.client(); |
280 | 284 | let track_sessions = client.as_ref().is_some_and(|client| { |
281 | 285 | let options = client.options(); |
@@ -332,7 +336,8 @@ where |
332 | 336 | scope.add_event_processor(move |event| Some(process_event(event, &sentry_req))); |
333 | 337 | parent_span |
334 | 338 | }); |
335 | | - let fut = svc.call(req).bind_hub(hub.clone()); |
| 339 | + |
| 340 | + let fut = Hub::run(hub.clone(), || svc.call(req)).bind_hub(hub.clone()); |
336 | 341 | let mut res: Self::Response = match fut.await { |
337 | 342 | Ok(res) => res, |
338 | 343 | Err(e) => { |
@@ -461,6 +466,7 @@ mod tests { |
461 | 466 | use actix_web::{get, web, App, HttpRequest, HttpResponse}; |
462 | 467 | use futures::executor::block_on; |
463 | 468 |
|
| 469 | + use futures::future::join_all; |
464 | 470 | use sentry::Level; |
465 | 471 |
|
466 | 472 | use super::*; |
@@ -703,4 +709,51 @@ mod tests { |
703 | 709 | } |
704 | 710 | assert_eq!(items.next(), None); |
705 | 711 | } |
| 712 | + |
| 713 | + /// Tests that the per-request Hub is used in the handler and both sides of the roundtrip |
| 714 | + /// through middleware |
| 715 | + #[actix_web::test] |
| 716 | + async fn test_middleware_and_handler_use_correct_hub() { |
| 717 | + sentry::test::with_captured_events(|| { |
| 718 | + block_on(async { |
| 719 | + sentry::capture_message("message outside", Level::Error); |
| 720 | + |
| 721 | + let handler = || { |
| 722 | + // an event was captured in the middleware |
| 723 | + assert!(Hub::current().last_event_id().is_some()); |
| 724 | + sentry::capture_message("second message", Level::Error); |
| 725 | + HttpResponse::Ok() |
| 726 | + }; |
| 727 | + |
| 728 | + let app = init_service( |
| 729 | + App::new() |
| 730 | + .wrap_fn(|req, srv| { |
| 731 | + // the event captured outside the per-request Hub is not there |
| 732 | + assert!(Hub::current().last_event_id().is_none()); |
| 733 | + |
| 734 | + let event_id = sentry::capture_message("first message", Level::Error); |
| 735 | + |
| 736 | + srv.call(req).map(move |res| { |
| 737 | + // a different event was captured in the handler |
| 738 | + assert!(Hub::current().last_event_id().is_some()); |
| 739 | + assert_ne!(Some(event_id), Hub::current().last_event_id()); |
| 740 | + res |
| 741 | + }) |
| 742 | + }) |
| 743 | + .wrap(Sentry::builder().with_hub(Hub::current()).finish()) |
| 744 | + .service(web::resource("/test").to(handler)), |
| 745 | + ) |
| 746 | + .await; |
| 747 | + |
| 748 | + // test with multiple requests in parallel |
| 749 | + let mut futures = Vec::new(); |
| 750 | + for _ in 0..16 { |
| 751 | + let req = TestRequest::get().uri("/test").to_request(); |
| 752 | + futures.push(call_service(&app, req)); |
| 753 | + } |
| 754 | + |
| 755 | + join_all(futures).await; |
| 756 | + }) |
| 757 | + }); |
| 758 | + } |
706 | 759 | } |
0 commit comments