From 72a31a0f3428dde676aad7ab523c4970f40e56a7 Mon Sep 17 00:00:00 2001 From: Jess Frazelle Date: Tue, 8 Mar 2022 18:59:53 -0800 Subject: [PATCH] feat(tracing): Transactions and Spans can carry Request data (#439) This allows request data to be included in Transactions and Spans, which means they can provide more meaningful information about the operation(s) represented by these structs. Signed-off-by: Jess Frazelle Co-authored-by: Betty Da --- CHANGELOG.md | 12 +++++++ sentry-core/src/performance.rs | 51 +++++++++++++++++++++++++++++ sentry-types/src/protocol/v7.rs | 7 +++- sentry/examples/performance-demo.rs | 14 ++++++++ sentry/tests/test_tracing.rs | 10 +++++- 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3477a4ab..ab7cae19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## Unreleased + +**Features**: + +- Request data can now be attached to Transactions and Spans via `set_transaction`. ([#439](https://github.com/getsentry/sentry-rust/pull/439)) + +**Thank you**: + +Features, fixes and improvements in this release have been contributed by: + +- [@jessfraz](https://github.com/jessfraz) + ## 0.25.0 **Breaking Changes**: diff --git a/sentry-core/src/performance.rs b/sentry-core/src/performance.rs index 91b6f99f..02deb332 100644 --- a/sentry-core/src/performance.rs +++ b/sentry-core/src/performance.rs @@ -202,6 +202,14 @@ impl TransactionOrSpan { } } + /// Set the HTTP request information for this Transaction/Span. + pub fn set_request(&self, request: protocol::Request) { + match self { + TransactionOrSpan::Transaction(transaction) => transaction.set_request(request), + TransactionOrSpan::Span(span) => span.set_request(request), + } + } + /// Returns the headers needed for distributed tracing. pub fn iter_headers(&self) -> TraceHeadersIter { match self { @@ -355,6 +363,14 @@ impl Transaction { inner.context.status = Some(status); } + /// Set the HTTP request information for this Transaction. + pub fn set_request(&self, request: protocol::Request) { + let mut inner = self.inner.lock().unwrap(); + if let Some(transaction) = inner.transaction.as_mut() { + transaction.request = Some(request); + } + } + /// Returns the headers needed for distributed tracing. pub fn iter_headers(&self) -> TraceHeadersIter { let inner = self.inner.lock().unwrap(); @@ -454,6 +470,41 @@ impl Span { span.status = Some(status); } + /// Set the HTTP request information for this Span. + pub fn set_request(&self, request: protocol::Request) { + let mut span = self.span.lock().unwrap(); + // Extract values from the request to be used as data in the span. + if let Some(method) = request.method { + span.data.insert("method".into(), method.into()); + } + if let Some(url) = request.url { + span.data.insert("url".into(), url.to_string().into()); + } + if let Some(data) = request.data { + if let Ok(data) = serde_json::from_str::(&data) { + span.data.insert("data".into(), data); + } else { + span.data.insert("data".into(), data.into()); + } + } + if let Some(query_string) = request.query_string { + span.data.insert("query_string".into(), query_string.into()); + } + if let Some(cookies) = request.cookies { + span.data.insert("cookies".into(), cookies.into()); + } + if !request.headers.is_empty() { + if let Ok(headers) = serde_json::to_value(request.headers) { + span.data.insert("headers".into(), headers); + } + } + if !request.env.is_empty() { + if let Ok(env) = serde_json::to_value(request.env) { + span.data.insert("env".into(), env); + } + } + } + /// Returns the headers needed for distributed tracing. pub fn iter_headers(&self) -> TraceHeadersIter { let span = self.span.lock().unwrap(); diff --git a/sentry-types/src/protocol/v7.rs b/sentry-types/src/protocol/v7.rs index 268572b1..430a35d0 100644 --- a/sentry-types/src/protocol/v7.rs +++ b/sentry-types/src/protocol/v7.rs @@ -15,7 +15,7 @@ use std::ops; use std::str; use std::time::SystemTime; -use ::debugid::{CodeId, DebugId}; +use self::debugid::{CodeId, DebugId}; use serde::Serializer; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -1942,6 +1942,9 @@ pub struct Transaction<'a> { /// Optional contexts. #[serde(default, skip_serializing_if = "Map::is_empty")] pub contexts: Map, + /// Optionally HTTP request data to be sent along. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub request: Option, } impl<'a> Default for Transaction<'a> { @@ -1959,6 +1962,7 @@ impl<'a> Default for Transaction<'a> { start_timestamp: SystemTime::now(), spans: Default::default(), contexts: Default::default(), + request: Default::default(), } } } @@ -1984,6 +1988,7 @@ impl<'a> Transaction<'a> { start_timestamp: self.start_timestamp, spans: self.spans, contexts: self.contexts, + request: self.request, } } diff --git a/sentry/examples/performance-demo.rs b/sentry/examples/performance-demo.rs index 12436c84..6fb7fc66 100644 --- a/sentry/examples/performance-demo.rs +++ b/sentry/examples/performance-demo.rs @@ -1,6 +1,8 @@ use std::thread; use std::time::Duration; +use sentry::protocol::Request; + // cargo run --example performance-demo fn main() { let _sentry = sentry::init(sentry::ClientOptions { @@ -12,6 +14,12 @@ fn main() { let transaction = sentry::start_transaction(sentry::TransactionContext::new("transaction", "root span")); + let tx_request = Request { + url: Some("https://honk.beep".parse().unwrap()), + method: Some("GET".to_string()), + ..Request::default() + }; + transaction.set_request(tx_request); sentry::configure_scope(|scope| scope.set_span(Some(transaction.clone().into()))); main_span1(); @@ -76,6 +84,12 @@ where sentry::start_transaction(ctx).into() } }; + let span_request = Request { + url: Some("https://beep.beep".parse().unwrap()), + method: Some("GET".to_string()), + ..Request::default() + }; + span1.set_request(span_request); sentry::configure_scope(|scope| scope.set_span(Some(span1.clone()))); let rv = f(); diff --git a/sentry/tests/test_tracing.rs b/sentry/tests/test_tracing.rs index 4c8038a0..d50e234a 100644 --- a/sentry/tests/test_tracing.rs +++ b/sentry/tests/test_tracing.rs @@ -1,7 +1,7 @@ #![cfg(feature = "test")] use log_ as log; -use sentry::protocol::{Context, Value}; +use sentry::protocol::{Context, Request, Value}; use tracing_ as tracing; use tracing_subscriber::prelude::*; @@ -146,6 +146,13 @@ fn test_set_transaction() { || { let ctx = sentry::TransactionContext::new("old name", "ye, whatever"); let trx = sentry::start_transaction(ctx); + let request = Request { + url: Some("https://honk.beep".parse().unwrap()), + method: Some("GET".to_string()), + ..Request::default() + }; + trx.set_request(request); + sentry::configure_scope(|scope| scope.set_span(Some(trx.clone().into()))); sentry::configure_scope(|scope| scope.set_transaction(Some("new name"))); @@ -164,4 +171,5 @@ fn test_set_transaction() { }; assert_eq!(transaction.name.as_deref().unwrap(), "new name"); + assert!(transaction.request.is_some()); }