Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for pre-aggregated Sessions #284

Merged
merged 4 commits into from
Jan 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sentry-actix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ actix-web = { version = "3", default-features = false }
futures-util = { version = "0.3.5", default-features = false }

[dev-dependencies]
sentry = { version = "0.22.0", path = "../sentry", default-features = false, features = ["test"] }
sentry = { version = "0.22.0", path = "../sentry", features = ["test"] }
actix-rt = "1.1.1"
futures = "0.3"
28 changes: 21 additions & 7 deletions sentry-actix/examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,42 @@ use actix_web::{get, App, Error, HttpRequest, HttpServer};
use sentry::Level;

#[get("/")]
async fn failing(_req: HttpRequest) -> Result<String, Error> {
async fn healthy(_req: HttpRequest) -> Result<String, Error> {
Ok("All good".into())
}

#[get("/err")]
async fn errors(_req: HttpRequest) -> Result<String, Error> {
Err(io::Error::new(io::ErrorKind::Other, "An error happens here").into())
}

#[get("/hello")]
async fn hello_world(_req: HttpRequest) -> Result<String, Error> {
#[get("/msg")]
async fn captures_message(_req: HttpRequest) -> Result<String, Error> {
sentry::capture_message("Something is not well", Level::Warning);
Ok("Hello World".into())
}

#[actix_web::main]
async fn main() -> io::Result<()> {
let _guard = sentry::init(());
let _guard = sentry::init(sentry::ClientOptions {
auto_session_tracking: true,
session_mode: sentry::SessionMode::Request,
..Default::default()
});
env::set_var("RUST_BACKTRACE", "1");

let addr = "127.0.0.1:3001";

println!("Starting server on http://{}", addr);

HttpServer::new(|| {
App::new()
.wrap(sentry_actix::Sentry::new())
.service(failing)
.service(hello_world)
.service(healthy)
.service(errors)
.service(captures_message)
})
.bind("127.0.0.1:3001")?
.bind(addr)?
.run()
.await?;

Expand Down
65 changes: 64 additions & 1 deletion sentry-actix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@
//! }
//! ```
//!
//! # Using Release Health
//!
//! The actix middleware will automatically start a new session for each request
//! when `auto_session_tracking` is enabled and the client is configured to
//! use `SessionMode::Request`.
//!
//! ```
//! let _sentry = sentry::init(sentry::ClientOptions {
//! session_mode: sentry::SessionMode::Request,
//! auto_session_tracking: true,
//! ..Default::default()
//! });
//! ```
//!
//! # Reusing the Hub
//!
//! This integration will automatically create a new per-request Hub from the main Hub, and update the
Expand Down Expand Up @@ -185,9 +199,17 @@ where
inner.hub.clone().unwrap_or_else(Hub::main),
));
let client = hub.client();
let track_sessions = client.as_ref().map_or(false, |client| {
let options = client.options();
options.auto_session_tracking
&& options.session_mode == sentry_core::SessionMode::Request
});
if track_sessions {
hub.start_session();
}
let with_pii = client
.as_ref()
.map_or(false, |x| x.options().send_default_pii);
.map_or(false, |client| client.options().send_default_pii);

let (tx, sentry_req) = sentry_request_from_http(&req, with_pii);
hub.configure_scope(|scope| {
Expand Down Expand Up @@ -454,4 +476,45 @@ mod tests {
assert_eq!(event.level, Level::Error);
assert_eq!(request.method, Some("GET".into()));
}

#[actix_rt::test]
async fn test_track_session() {
let envelopes = sentry::test::with_captured_envelopes_options(
|| {
block_on(async {
#[get("/")]
async fn hello() -> impl actix_web::Responder {
String::from("Hello there!")
}

let middleware = Sentry::builder().with_hub(Hub::current()).finish();

let mut app = init_service(App::new().wrap(middleware).service(hello)).await;

for _ in 0..5 {
let req = TestRequest::get().uri("/").to_request();
call_service(&mut app, req).await;
}
})
},
sentry::ClientOptions {
release: Some("some-release".into()),
session_mode: sentry::SessionMode::Request,
auto_session_tracking: true,
..Default::default()
},
);
assert_eq!(envelopes.len(), 1);

let mut items = envelopes[0].items();
if let Some(sentry::protocol::EnvelopeItem::SessionAggregates(aggregate)) = items.next() {
let aggregates = &aggregate.aggregates;

assert_eq!(aggregates[0].distinct_id, None);
assert_eq!(aggregates[0].exited, 5);
} else {
panic!("expected session");
}
assert_eq!(items.next(), None);
}
}
1 change: 1 addition & 0 deletions sentry-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ test = ["client"]
[dependencies]
sentry-types = { version = "0.22.0", path = "../sentry-types" }
serde = { version = "1.0.104", features = ["derive"] }
chrono = "0.4.10"
lazy_static = "1.4.0"
rand = { version = "0.8.1", optional = true }
serde_json = "1.0.46"
Expand Down
31 changes: 18 additions & 13 deletions sentry-core/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::constants::SDK_INFO;
use crate::protocol::{ClientSdkInfo, Event};
use crate::session::SessionFlusher;
use crate::types::{Dsn, Uuid};
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, SessionMode, Transport};

impl<T: Into<ClientOptions>> From<T> for Client {
fn from(o: T) -> Client {
Expand Down Expand Up @@ -60,7 +60,7 @@ impl fmt::Debug for Client {
impl Clone for Client {
fn clone(&self) -> Client {
let transport = Arc::new(RwLock::new(self.transport.read().unwrap().clone()));
let session_flusher = SessionFlusher::new(transport.clone());
let session_flusher = SessionFlusher::new(transport.clone(), self.options.session_mode);
Client {
options: self.options.clone(),
transport,
Expand Down Expand Up @@ -128,7 +128,7 @@ impl Client {
sdk_info.integrations.push(integration.name().to_string());
}

let session_flusher = SessionFlusher::new(transport.clone());
let session_flusher = SessionFlusher::new(transport.clone(), options.session_mode);
Client {
options,
transport,
Expand Down Expand Up @@ -257,16 +257,20 @@ impl Client {
if let Some(event) = self.prepare_event(event, scope) {
let event_id = event.event_id;
let mut envelope: Envelope = event.into();
let session_item = scope.and_then(|scope| {
scope
.session
.lock()
.unwrap()
.as_mut()
.and_then(|session| session.create_envelope_item())
});
if let Some(session_item) = session_item {
envelope.add_item(session_item);
// For request-mode sessions, we aggregate them all instead of
// flushing them out early.
if self.options.session_mode == SessionMode::Application {
let session_item = scope.and_then(|scope| {
scope
.session
.lock()
.unwrap()
.as_mut()
.and_then(|session| session.create_envelope_item())
});
if let Some(session_item) = session_item {
envelope.add_item(session_item);
}
}
transport.send_envelope(envelope);
return event_id;
Expand Down Expand Up @@ -294,6 +298,7 @@ impl Client {
/// If no timeout is provided the client will wait for as long a
/// `shutdown_timeout` in the client options.
pub fn close(&self, timeout: Option<Duration>) -> bool {
self.session_flusher.flush();
let transport_opt = self.transport.write().unwrap().take();
if let Some(transport) = transport_opt {
sentry_debug!("client close; request transport to shut down");
Expand Down
32 changes: 32 additions & 0 deletions sentry-core/src/clientoptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,34 @@ use crate::{Integration, IntoDsn, TransportFactory};
/// Type alias for before event/breadcrumb handlers.
pub type BeforeCallback<T> = Arc<dyn Fn(T) -> Option<T> + Send + Sync>;

/// The Session Mode of the SDK.
///
/// Depending on the use-case, the SDK can be set to two different session modes:
///
/// * **Application Mode Sessions**:
/// This mode should be used for user-attended programs, which typically have
/// a single long running session that span the applications' lifetime.
///
/// * **Request Mode Sessions**:
/// This mode is intended for servers that use one session per incoming
/// request, and thus have a lot of very short lived sessions.
///
/// Setting the SDK to *request-mode* sessions means that session durations will
/// not be tracked, and sessions will be pre-aggregated before being sent upstream.
/// This applies both to automatic and manually triggered sessions.
///
/// **NOTE**: Support for *request-mode* sessions was added in Sentry `21.2`.
///
/// See the [Documentation on Session Modes](https://develop.sentry.dev/sdk/sessions/#sdk-considerations)
/// for more information.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum SessionMode {
/// Long running application session.
Application,
/// Lots of short per-request sessions.
Request,
}

/// Configuration settings for the client.
///
/// These options are explained in more detail in the general
Expand Down Expand Up @@ -97,6 +125,8 @@ pub struct ClientOptions {
/// is started at the time of `sentry::init`, and will persist for the
/// application lifetime.
pub auto_session_tracking: bool,
/// Determine how Sessions are being tracked.
pub session_mode: SessionMode,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we consider combining this with the session tracking flag into a session_mode: Application | Request | Off?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this a bit, and a tri-state would not work when you don’t want to have auto-tracking, but want to manually track request-mode sessions.

Also related to your other question: Request-Mode applies to all sessions, I updated the doc comment to accordingly. So choosing between application/request-mode basically means that you make a choice if you want aggregation at the cost of losing durations, or not.

/// Border frames which indicate a border from a backtrace to
/// useless internals. Some are automatically included.
pub extra_border_frames: Vec<&'static str>,
Expand Down Expand Up @@ -164,6 +194,7 @@ impl fmt::Debug for ClientOptions {
.field("https_proxy", &self.https_proxy)
.field("shutdown_timeout", &self.shutdown_timeout)
.field("auto_session_tracking", &self.auto_session_tracking)
.field("session_mode", &self.session_mode)
.field("extra_border_frames", &self.extra_border_frames)
.field("trim_backtraces", &self.trim_backtraces)
.field("user_agent", &self.user_agent)
Expand Down Expand Up @@ -194,6 +225,7 @@ impl Default for ClientOptions {
https_proxy: None,
shutdown_timeout: Duration::from_secs(2),
auto_session_tracking: false,
session_mode: SessionMode::Application,
extra_border_frames: vec![],
trim_backtraces: true,
user_agent: Cow::Borrowed(&USER_AGENT),
Expand Down
2 changes: 1 addition & 1 deletion sentry-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ mod transport;
// public api or exports from this crate
pub use crate::api::*;
pub use crate::breadcrumbs::IntoBreadcrumbs;
pub use crate::clientoptions::ClientOptions;
pub use crate::clientoptions::{ClientOptions, SessionMode};
pub use crate::error::{capture_error, event_from_error, parse_type_from_debug};
pub use crate::futures::{SentryFuture, SentryFutureExt};
pub use crate::hub::Hub;
Expand Down
Loading