Skip to content

Commit ca84eaa

Browse files
authored
feat: Add basic session support (#240)
1 parent 951a899 commit ca84eaa

File tree

12 files changed

+544
-30
lines changed

12 files changed

+544
-30
lines changed

sentry-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ test = ["client"]
2424

2525
[dependencies]
2626
sentry-types = { version = "0.19.1", path = "../sentry-types" }
27+
serde = { version = "1.0.104", features = ["derive"] }
2728
lazy_static = "1.4.0"
2829
im = { version = "14.2.0", optional = true }
2930
rand = { version = "0.7.3", optional = true }

sentry-core/src/api.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,27 @@ pub fn last_event_id() -> Option<Uuid> {
261261
Hub::with(|hub| hub.last_event_id())
262262
}}
263263
}
264+
265+
/// Start a new session for Release Health.
266+
///
267+
/// This is still **experimental** for the moment and is not recommended to be
268+
/// used with a very high volume of sessions (_request-mode_ sessions).
269+
///
270+
/// # Examples
271+
///
272+
/// ```
273+
/// sentry::start_session();
274+
///
275+
/// // capturing any event / error here will update the sessions `errors` count,
276+
/// // up until we call `sentry::end_session`.
277+
///
278+
/// sentry::end_session();
279+
/// ```
280+
pub fn start_session() {
281+
Hub::with_active(|hub| hub.start_session())
282+
}
283+
284+
/// End the current Release Health Session.
285+
pub fn end_session() {
286+
Hub::with_active(|hub| hub.end_session())
287+
}

sentry-core/src/client.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rand::random;
1111
use crate::constants::SDK_INFO;
1212
use crate::protocol::{ClientSdkInfo, Event};
1313
use crate::types::{Dsn, Uuid};
14-
use crate::{ClientOptions, Hub, Integration, Scope, Transport};
14+
use crate::{ClientOptions, Envelope, Hub, Integration, Scope, Transport};
1515

1616
impl<T: Into<ClientOptions>> From<T> for Client {
1717
fn from(o: T) -> Client {
@@ -142,6 +142,14 @@ impl Client {
142142
mut event: Event<'static>,
143143
scope: Option<&Scope>,
144144
) -> Option<Event<'static>> {
145+
if let Some(scope) = scope {
146+
scope.update_session_from_event(&event);
147+
}
148+
149+
if !self.sample_should_send() {
150+
return None;
151+
}
152+
145153
// event_id and sdk_info are set before the processors run so that the
146154
// processors can poke around in that data.
147155
if event.event_id.is_nil() {
@@ -236,17 +244,33 @@ impl Client {
236244
/// Captures an event and sends it to sentry.
237245
pub fn capture_event(&self, event: Event<'static>, scope: Option<&Scope>) -> Uuid {
238246
if let Some(ref transport) = *self.transport.read().unwrap() {
239-
if self.sample_should_send() {
240-
if let Some(event) = self.prepare_event(event, scope) {
241-
let event_id = event.event_id;
242-
transport.send_envelope(event.into());
243-
return event_id;
247+
if let Some(event) = self.prepare_event(event, scope) {
248+
let event_id = event.event_id;
249+
let mut envelope: Envelope = event.into();
250+
let session_item = scope.and_then(|scope| {
251+
scope
252+
.session
253+
.lock()
254+
.unwrap()
255+
.as_mut()
256+
.and_then(|session| session.create_envelope_item())
257+
});
258+
if let Some(session_item) = session_item {
259+
envelope.add(session_item);
244260
}
261+
transport.send_envelope(envelope);
262+
return event_id;
245263
}
246264
}
247265
Default::default()
248266
}
249267

268+
pub(crate) fn capture_envelope(&self, envelope: Envelope) {
269+
if let Some(ref transport) = *self.transport.read().unwrap() {
270+
transport.send_envelope(envelope);
271+
}
272+
}
273+
250274
/// Drains all pending events and shuts down the transport behind the
251275
/// client. After shutting down the transport is removed.
252276
///

sentry-core/src/clientoptions.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ pub struct ClientOptions {
8282
/// The timeout on client drop for draining events on shutdown.
8383
pub shutdown_timeout: Duration,
8484
// Other options not documented in Unified API
85+
/// Enable Release Health Session tracking.
86+
///
87+
/// When automatic session tracking is enabled, a new "user-mode" session
88+
/// is started at the time of `sentry::init`, and will persist for the
89+
/// application lifetime.
90+
pub auto_session_tracking: bool,
8591
/// Border frames which indicate a border from a backtrace to
8692
/// useless internals. Some are automatically included.
8793
pub extra_border_frames: Vec<&'static str>,
@@ -147,6 +153,7 @@ impl fmt::Debug for ClientOptions {
147153
.field("http_proxy", &self.http_proxy)
148154
.field("https_proxy", &self.https_proxy)
149155
.field("shutdown_timeout", &self.shutdown_timeout)
156+
.field("auto_session_tracking", &self.auto_session_tracking)
150157
.field("extra_border_frames", &self.extra_border_frames)
151158
.field("trim_backtraces", &self.trim_backtraces)
152159
.field("user_agent", &self.user_agent)
@@ -176,6 +183,7 @@ impl Default for ClientOptions {
176183
http_proxy: None,
177184
https_proxy: None,
178185
shutdown_timeout: Duration::from_secs(2),
186+
auto_session_tracking: false,
179187
extra_border_frames: vec![],
180188
trim_backtraces: true,
181189
user_agent: Cow::Borrowed(&USER_AGENT),

sentry-core/src/envelope.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use std::io::Write;
22

33
use crate::protocol::Event;
4+
use crate::session::Session;
45
use crate::types::Uuid;
56

6-
#[derive(Clone, Debug, PartialEq)]
7+
#[derive(Clone, Debug)]
78
#[non_exhaustive]
8-
enum EnvelopeItem {
9+
pub(crate) enum EnvelopeItem {
910
Event(Event<'static>),
11+
Session(Session),
1012
// TODO:
11-
// * Session,
1213
// * Attachment,
1314
// etc…
1415
}
@@ -27,7 +28,7 @@ impl From<Event<'static>> for EnvelopeItem {
2728
///
2829
/// See the [documentation on Envelopes](https://develop.sentry.dev/sdk/envelopes/)
2930
/// for more details.
30-
#[derive(Clone, Default, Debug, PartialEq)]
31+
#[derive(Clone, Default, Debug)]
3132
pub struct Envelope {
3233
event_id: Option<Uuid>,
3334
items: Vec<EnvelopeItem>,
@@ -39,6 +40,13 @@ impl Envelope {
3940
Default::default()
4041
}
4142

43+
pub(crate) fn add<I>(&mut self, item: I)
44+
where
45+
I: Into<EnvelopeItem>,
46+
{
47+
self.items.push(item.into());
48+
}
49+
4250
/// Returns the Envelopes Uuid, if any.
4351
pub fn uuid(&self) -> Option<&Uuid> {
4452
self.event_id.as_ref()
@@ -48,12 +56,11 @@ impl Envelope {
4856
///
4957
/// [`Event`]: protocol/struct.Event.html
5058
pub fn event(&self) -> Option<&Event<'static>> {
51-
// until we actually add more items:
52-
#[allow(clippy::unnecessary_filter_map)]
5359
self.items
5460
.iter()
5561
.filter_map(|item| match item {
5662
EnvelopeItem::Event(event) => Some(event),
63+
_ => None,
5764
})
5865
.next()
5966
}
@@ -77,14 +84,13 @@ impl Envelope {
7784
// write each item:
7885
for item in &self.items {
7986
// we write them to a temporary buffer first, since we need their length
80-
serde_json::to_writer(
81-
&mut item_buf,
82-
match item {
83-
EnvelopeItem::Event(event) => event,
84-
},
85-
)?;
87+
match item {
88+
EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
89+
EnvelopeItem::Session(session) => serde_json::to_writer(&mut item_buf, session)?,
90+
}
8691
let item_type = match item {
8792
EnvelopeItem::Event(_) => "event",
93+
EnvelopeItem::Session(_) => "session",
8894
};
8995
writeln!(
9096
writer,
@@ -113,6 +119,7 @@ impl From<Event<'static>> for Envelope {
113119
#[cfg(test)]
114120
mod test {
115121
use super::*;
122+
116123
fn to_buf(envelope: Envelope) -> Vec<u8> {
117124
let mut vec = Vec::new();
118125
envelope.to_writer(&mut vec).unwrap();

sentry-core/src/hub.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ use std::thread;
99
use std::time::Duration;
1010

1111
use crate::protocol::{Breadcrumb, Event, Level};
12+
use crate::session::{Session, SessionStatus};
1213
use crate::types::Uuid;
1314
use crate::{event_from_error, Integration, IntoBreadcrumbs, Scope, ScopeGuard};
1415
#[cfg(feature = "client")]
15-
use crate::{scope::Stack, Client};
16+
use crate::{scope::Stack, Client, Envelope};
1617

1718
#[cfg(feature = "client")]
1819
lazy_static::lazy_static! {
@@ -311,6 +312,46 @@ impl Hub {
311312
})
312313
}
313314

315+
/// Start a new session for Release Health.
316+
///
317+
/// See the global [`start_session`](fn.start_session.html)
318+
/// for more documentation.
319+
pub fn start_session(&self) {
320+
with_client_impl! {{
321+
self.inner.with_mut(|stack| {
322+
let top = stack.top_mut();
323+
if let Some(session) = Session::from_stack(top) {
324+
// When creating a *new* session, we make sure it is unique,
325+
// as to no inherit *backwards* to any parents.
326+
let mut scope = Arc::make_mut(&mut top.scope);
327+
scope.session = Arc::new(Mutex::new(Some(session)));
328+
}
329+
})
330+
}}
331+
}
332+
333+
/// End the current Release Health Session.
334+
///
335+
/// See the global [`end_session`](fn.end_session.html)
336+
/// for more documentation.
337+
pub fn end_session(&self) {
338+
with_client_impl! {{
339+
self.inner.with_mut(|stack| {
340+
let top = stack.top_mut();
341+
if let Some(mut session) = top.scope.session.lock().unwrap().take() {
342+
session.close();
343+
if let Some(item) = session.create_envelope_item() {
344+
let mut envelope = Envelope::new();
345+
envelope.add(item);
346+
if let Some(ref client) = top.client {
347+
client.capture_envelope(envelope);
348+
}
349+
}
350+
}
351+
})
352+
}}
353+
}
354+
314355
/// Pushes a new scope.
315356
///
316357
/// This returns a guard that when dropped will pop the scope again.

sentry-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ mod hub;
6464
mod integration;
6565
mod intodsn;
6666
mod scope;
67+
mod session;
6768
mod transport;
6869

6970
// public api or exports from this crate

sentry-core/src/scope/noop.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,6 @@ use std::fmt;
22

33
use crate::protocol::{Context, Event, Level, User, Value};
44

5-
/// The minimal scope.
6-
///
7-
/// In minimal API mode all modification functions are available as normally
8-
/// just that generally calling them is impossible.
9-
#[derive(Debug, Clone)]
10-
pub struct Scope;
11-
125
/// A minimal API scope guard.
136
///
147
/// Doesn't do anything but can be debug formatted.
@@ -21,6 +14,13 @@ impl fmt::Debug for ScopeGuard {
2114
}
2215
}
2316

17+
/// The minimal scope.
18+
///
19+
/// In minimal API mode all modification functions are available as normally
20+
/// just that generally calling them is impossible.
21+
#[derive(Debug, Clone)]
22+
pub struct Scope;
23+
2424
impl Scope {
2525
/// Clear the scope.
2626
///

sentry-core/src/scope/real.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::borrow::Cow;
22
use std::fmt;
3-
use std::sync::{Arc, PoisonError, RwLock};
3+
use std::sync::{Arc, Mutex, PoisonError, RwLock};
44

55
use crate::protocol::{Breadcrumb, Context, Event, Level, User, Value};
6+
use crate::session::Session;
67
use crate::Client;
78

89
#[derive(Debug)]
@@ -41,6 +42,7 @@ pub struct Scope {
4142
pub(crate) tags: im::HashMap<String, String>,
4243
pub(crate) contexts: im::HashMap<String, Context>,
4344
pub(crate) event_processors: im::Vector<Arc<EventProcessor>>,
45+
pub(crate) session: Arc<Mutex<Option<Session>>>,
4446
}
4547

4648
impl fmt::Debug for Scope {
@@ -55,6 +57,7 @@ impl fmt::Debug for Scope {
5557
.field("tags", &self.tags)
5658
.field("contexts", &self.contexts)
5759
.field("event_processors", &self.event_processors.len())
60+
.field("session", &self.session)
5861
.finish()
5962
}
6063
}
@@ -71,6 +74,7 @@ impl Default for Scope {
7174
tags: Default::default(),
7275
contexts: Default::default(),
7376
event_processors: Default::default(),
77+
session: Default::default(),
7478
}
7579
}
7680
}
@@ -89,8 +93,8 @@ impl Stack {
8993
}
9094

9195
pub fn push(&mut self) {
92-
let scope = self.layers[self.layers.len() - 1].clone();
93-
self.layers.push(scope);
96+
let layer = self.layers[self.layers.len() - 1].clone();
97+
self.layers.push(layer);
9498
}
9599

96100
pub fn pop(&mut self) {
@@ -257,4 +261,10 @@ impl Scope {
257261

258262
Some(event)
259263
}
264+
265+
pub(crate) fn update_session_from_event(&self, event: &Event<'static>) {
266+
if let Some(session) = self.session.lock().unwrap().as_mut() {
267+
session.update_from_event(event);
268+
}
269+
}
260270
}

0 commit comments

Comments
 (0)