Skip to content

Commit c8ef5fe

Browse files
authored
feat: Preliminary work to integrate Performance Monitoring (#276)
1 parent 6729dda commit c8ef5fe

File tree

2 files changed

+252
-4
lines changed

2 files changed

+252
-4
lines changed

sentry-types/src/protocol/envelope.rs

+58-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use uuid::Uuid;
44

55
use super::v7::Event;
66
use super::v7::SessionUpdate;
7+
use super::v7::Transaction;
78

89
/// An Envelope Item.
910
///
@@ -22,9 +23,14 @@ pub enum EnvelopeItem {
2223
/// See the [Session Item documentation](https://develop.sentry.dev/sdk/envelopes/#session)
2324
/// for more details.
2425
SessionUpdate(SessionUpdate<'static>),
25-
/* TODO:
26-
* * Attachment,
27-
* etc… */
26+
/// A Transaction Item.
27+
///
28+
/// See the [Transaction Item documentation](https://develop.sentry.dev/sdk/envelopes/#transaction)
29+
/// for more details.
30+
Transaction(Transaction<'static>),
31+
// TODO:
32+
// * Attachment,
33+
// etc…
2834
}
2935

3036
impl From<Event<'static>> for EnvelopeItem {
@@ -39,6 +45,12 @@ impl From<SessionUpdate<'static>> for EnvelopeItem {
3945
}
4046
}
4147

48+
impl From<Transaction<'static>> for EnvelopeItem {
49+
fn from(session: Transaction<'static>) -> Self {
50+
EnvelopeItem::Transaction(session)
51+
}
52+
}
53+
4254
/// An Iterator over the items of an Envelope.
4355
#[derive(Clone)]
4456
pub struct EnvelopeItemIter<'s> {
@@ -82,6 +94,8 @@ impl Envelope {
8294
if self.event_id.is_none() {
8395
if let EnvelopeItem::Event(ref event) = item {
8496
self.event_id = Some(event.event_id);
97+
} else if let EnvelopeItem::Transaction(ref transaction) = item {
98+
self.event_id = Some(transaction.event_id);
8599
}
86100
}
87101
self.items.push(item);
@@ -136,10 +150,14 @@ impl Envelope {
136150
EnvelopeItem::SessionUpdate(session) => {
137151
serde_json::to_writer(&mut item_buf, session)?
138152
}
153+
EnvelopeItem::Transaction(transaction) => {
154+
serde_json::to_writer(&mut item_buf, transaction)?
155+
}
139156
}
140157
let item_type = match item {
141158
EnvelopeItem::Event(_) => "event",
142159
EnvelopeItem::SessionUpdate(_) => "session",
160+
EnvelopeItem::Transaction(_) => "transaction",
143161
};
144162
writeln!(
145163
writer,
@@ -164,12 +182,20 @@ impl From<Event<'static>> for Envelope {
164182
}
165183
}
166184

185+
impl From<Transaction<'static>> for Envelope {
186+
fn from(transaction: Transaction<'static>) -> Self {
187+
let mut envelope = Self::default();
188+
envelope.add_item(transaction);
189+
envelope
190+
}
191+
}
192+
167193
#[cfg(test)]
168194
mod test {
169195
use chrono::{DateTime, Utc};
170196

171197
use super::*;
172-
use crate::protocol::v7::{SessionAttributes, SessionStatus};
198+
use crate::protocol::v7::{SessionAttributes, SessionStatus, Span};
173199

174200
fn to_str(envelope: Envelope) -> String {
175201
let mut vec = Vec::new();
@@ -229,6 +255,34 @@ mod test {
229255
r#"{}
230256
{"type":"session","length":222}
231257
{"sid":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c","did":"[email protected]","started":"2020-07-20T14:51:14.296Z","init":true,"duration":1.234,"status":"ok","errors":123,"attrs":{"release":"[email protected]","environment":"production"}}
258+
"#
259+
)
260+
}
261+
262+
#[test]
263+
fn test_transaction() {
264+
let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
265+
let span_id = Uuid::parse_str("d42cee9f-c3e7-4f5c-ada9-47ab601a14d2").unwrap();
266+
let trace_id = Uuid::parse_str("335e53d6-1447-4acc-9f89-e632b776cc28").unwrap();
267+
let start_timestamp = "2020-07-20T14:51:14.296Z".parse::<DateTime<Utc>>().unwrap();
268+
let spans = vec![Span {
269+
span_id,
270+
trace_id,
271+
start_timestamp,
272+
..Default::default()
273+
}];
274+
let transaction = Transaction {
275+
event_id,
276+
start_timestamp,
277+
spans,
278+
..Default::default()
279+
};
280+
let envelope: Envelope = transaction.into();
281+
assert_eq!(
282+
to_str(envelope),
283+
r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
284+
{"type":"transaction","length":216}
285+
{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5cada947ab601a14d2","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
232286
"#
233287
)
234288
}

sentry-types/src/protocol/v7.rs

+194
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,7 @@ pub struct ClientSdkPackage {
10381038
/// to `Context`.
10391039
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
10401040
#[serde(rename_all = "snake_case", tag = "type")]
1041+
#[non_exhaustive]
10411042
pub enum Context {
10421043
/// Device data.
10431044
Device(Box<DeviceContext>),
@@ -1049,6 +1050,8 @@ pub enum Context {
10491050
App(Box<AppContext>),
10501051
/// Web browser data.
10511052
Browser(Box<BrowserContext>),
1053+
/// Tracing data.
1054+
Trace(Box<TraceContext>),
10521055
/// Generic other context data.
10531056
#[serde(rename = "unknown")]
10541057
Other(Map<String, Value>),
@@ -1063,6 +1066,7 @@ impl Context {
10631066
Context::Runtime(..) => "runtime",
10641067
Context::App(..) => "app",
10651068
Context::Browser(..) => "browser",
1069+
Context::Trace(..) => "trace",
10661070
Context::Other(..) => "unknown",
10671071
}
10681072
}
@@ -1217,6 +1221,29 @@ pub struct BrowserContext {
12171221
pub other: Map<String, Value>,
12181222
}
12191223

1224+
/// Holds information about a tracing event.
1225+
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
1226+
pub struct TraceContext {
1227+
/// The ID of the trace event
1228+
#[serde(default = "event::default_id", serialize_with = "event::serialize_id")]
1229+
pub span_id: Uuid,
1230+
/// Determines which trace the transaction belongs to.
1231+
#[serde(default = "event::default_id", serialize_with = "event::serialize_id")]
1232+
pub trace_id: Uuid,
1233+
/// Determines the parent of this transaction if any.
1234+
#[serde(default, skip_serializing_if = "Option::is_none")]
1235+
pub parent_span_id: Option<String>,
1236+
/// Short code identifying the type of operation the transaction is measuring.
1237+
#[serde(default, skip_serializing_if = "Option::is_none")]
1238+
pub op: Option<String>,
1239+
/// Human readable detail description.
1240+
#[serde(default, skip_serializing_if = "Option::is_none")]
1241+
pub description: Option<String>,
1242+
/// Describes the status of the span (e.g. `ok`, `cancelled`, etc.)
1243+
#[serde(default, skip_serializing_if = "Option::is_none")]
1244+
pub status: Option<String>,
1245+
}
1246+
12201247
macro_rules! into_context {
12211248
($kind:ident, $ty:ty) => {
12221249
impl From<$ty> for Context {
@@ -1232,6 +1259,7 @@ into_context!(Device, DeviceContext);
12321259
into_context!(Os, OsContext);
12331260
into_context!(Runtime, RuntimeContext);
12341261
into_context!(Browser, BrowserContext);
1262+
into_context!(Trace, TraceContext);
12351263

12361264
mod event {
12371265
use super::*;
@@ -1454,3 +1482,169 @@ impl<'a> fmt::Display for Event<'a> {
14541482
write!(f, "Event(id: {}, ts: {})", self.event_id, self.timestamp)
14551483
}
14561484
}
1485+
1486+
/// Represents a tracing span.
1487+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
1488+
pub struct Span {
1489+
/// The ID of the span
1490+
#[serde(default = "event::default_id", serialize_with = "event::serialize_id")]
1491+
pub span_id: Uuid,
1492+
/// Determines which trace the span belongs to.
1493+
#[serde(default = "event::default_id", serialize_with = "event::serialize_id")]
1494+
pub trace_id: Uuid,
1495+
/// Determines the parent of this span, if any.
1496+
#[serde(default, skip_serializing_if = "Option::is_none")]
1497+
pub parent_span_id: Option<String>,
1498+
/// Determines whether this span is generated in the same process as its parent, if any.
1499+
#[serde(default, skip_serializing_if = "Option::is_none")]
1500+
pub same_process_as_parent: Option<bool>,
1501+
/// Short code identifying the type of operation the span is measuring.
1502+
#[serde(default, skip_serializing_if = "Option::is_none")]
1503+
pub op: Option<String>,
1504+
/// Longer description of the span's operation, which uniquely identifies the span
1505+
/// but is consistent across instances of the span.
1506+
#[serde(default, skip_serializing_if = "Option::is_none")]
1507+
pub description: Option<String>,
1508+
/// The timestamp at the measuring of the span finished.
1509+
#[serde(default, skip_serializing_if = "Option::is_none")]
1510+
pub timestamp: Option<DateTime<Utc>>,
1511+
/// The timestamp at the measuring of the span started.
1512+
#[serde(default = "event::default_timestamp", with = "ts_seconds_float")]
1513+
pub start_timestamp: DateTime<Utc>,
1514+
/// Describes the status of the span (e.g. `ok`, `cancelled`, etc.)
1515+
#[serde(default, skip_serializing_if = "Option::is_none")]
1516+
pub status: Option<String>,
1517+
/// Optional tags to be attached to the span.
1518+
#[serde(default, skip_serializing_if = "Map::is_empty")]
1519+
pub tags: Map<String, String>,
1520+
/// Optional extra information to be sent with the span.
1521+
#[serde(default, skip_serializing_if = "Map::is_empty")]
1522+
pub data: Map<String, Value>,
1523+
}
1524+
1525+
impl Default for Span {
1526+
fn default() -> Self {
1527+
Span {
1528+
span_id: event::default_id(),
1529+
trace_id: event::default_id(),
1530+
timestamp: Default::default(),
1531+
tags: Default::default(),
1532+
start_timestamp: event::default_timestamp(),
1533+
description: Default::default(),
1534+
status: Default::default(),
1535+
parent_span_id: Default::default(),
1536+
same_process_as_parent: Default::default(),
1537+
op: Default::default(),
1538+
data: Default::default(),
1539+
}
1540+
}
1541+
}
1542+
1543+
impl Span {
1544+
/// Creates a new span with the current timestamp and random id.
1545+
pub fn new() -> Span {
1546+
Default::default()
1547+
}
1548+
1549+
/// Finalizes the span.
1550+
pub fn finish(&mut self) {
1551+
self.timestamp = Some(Utc::now());
1552+
}
1553+
}
1554+
1555+
impl fmt::Display for Span {
1556+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1557+
write!(
1558+
f,
1559+
"Span(id: {}, ts: {})",
1560+
self.span_id, self.start_timestamp
1561+
)
1562+
}
1563+
}
1564+
1565+
/// Represents a tracing transaction.
1566+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
1567+
pub struct Transaction<'a> {
1568+
/// The ID of the event
1569+
#[serde(default = "event::default_id", serialize_with = "event::serialize_id")]
1570+
pub event_id: Uuid,
1571+
/// The transaction name.
1572+
#[serde(default, skip_serializing_if = "Option::is_none")]
1573+
pub name: Option<String>,
1574+
/// Optional tags to be attached to the event.
1575+
#[serde(default, skip_serializing_if = "Map::is_empty")]
1576+
pub tags: Map<String, String>,
1577+
/// SDK metadata
1578+
#[serde(default, skip_serializing_if = "Option::is_none")]
1579+
pub sdk: Option<Cow<'a, ClientSdkInfo>>,
1580+
/// A platform identifier for this event.
1581+
#[serde(
1582+
default = "event::default_platform",
1583+
skip_serializing_if = "event::is_default_platform"
1584+
)]
1585+
pub platform: Cow<'a, str>,
1586+
/// The end time of the transaction.
1587+
#[serde(default, skip_serializing_if = "Option::is_none")]
1588+
pub timestamp: Option<DateTime<Utc>>,
1589+
/// The start time of the transaction.
1590+
#[serde(default = "event::default_timestamp", with = "ts_seconds_float")]
1591+
pub start_timestamp: DateTime<Utc>,
1592+
/// The collection of finished spans part of this transaction.
1593+
pub spans: Vec<Span>,
1594+
/// Optional contexts.
1595+
#[serde(default, skip_serializing_if = "Map::is_empty")]
1596+
pub contexts: Map<String, Context>,
1597+
}
1598+
1599+
impl<'a> Default for Transaction<'a> {
1600+
fn default() -> Self {
1601+
Transaction {
1602+
event_id: event::default_id(),
1603+
name: Default::default(),
1604+
tags: Default::default(),
1605+
sdk: Default::default(),
1606+
platform: event::default_platform(),
1607+
timestamp: Default::default(),
1608+
start_timestamp: event::default_timestamp(),
1609+
spans: Default::default(),
1610+
contexts: Default::default(),
1611+
}
1612+
}
1613+
}
1614+
1615+
impl<'a> Transaction<'a> {
1616+
/// Creates a new span transaction the current timestamp and random id.
1617+
pub fn new() -> Transaction<'a> {
1618+
Default::default()
1619+
}
1620+
1621+
/// Creates a fully owned version of the transaction.
1622+
pub fn into_owned(self) -> Transaction<'static> {
1623+
Transaction {
1624+
event_id: self.event_id,
1625+
name: self.name,
1626+
tags: self.tags,
1627+
sdk: self.sdk.map(|x| Cow::Owned(x.into_owned())),
1628+
platform: Cow::Owned(self.platform.into_owned()),
1629+
timestamp: self.timestamp,
1630+
start_timestamp: self.start_timestamp,
1631+
spans: self.spans,
1632+
contexts: self.contexts,
1633+
}
1634+
}
1635+
1636+
/// Finalizes the transaction to be dispatched.
1637+
pub fn finish(&mut self) {
1638+
self.timestamp = Some(Utc::now());
1639+
}
1640+
}
1641+
1642+
impl<'a> fmt::Display for Transaction<'a> {
1643+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1644+
write!(
1645+
f,
1646+
"Transaction(id: {}, ts: {})",
1647+
self.event_id, self.start_timestamp
1648+
)
1649+
}
1650+
}

0 commit comments

Comments
 (0)