Skip to content

Commit

Permalink
feat: Add a Http Service integration with trace propagation (NATIVE-314)
Browse files Browse the repository at this point in the history
This creates a new sentry-tower feature that does trace propagation for
Http Services.
  • Loading branch information
Swatinem committed Jan 10, 2022
1 parent 281f3ff commit 6a479ff
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 2 deletions.
4 changes: 4 additions & 0 deletions sentry-tower/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ Sentry integration for tower-based crates.
"""
edition = "2018"

[features]
http = []

[dependencies]
tower-layer = "0.3"
tower-service = "0.3"
http = { version = "0.2.6", optional = true }
sentry-core = { version = "0.23.0", path = "../sentry-core", default-features = false, features = ["client"] }

[dev-dependencies]
Expand Down
104 changes: 104 additions & 0 deletions sentry-tower/src/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use std::collections::BTreeMap;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

use tower_layer::Layer;
use tower_service::Service;

#[derive(Clone)]
pub struct SentryHttpLayer;

#[derive(Clone)]
pub struct SentryHttpService<S> {
service: S,
}

impl<S> Layer<S> for SentryHttpLayer {
type Service = SentryHttpService<S>;

fn layer(&self, service: S) -> Self::Service {
Self::Service { service }
}
}

pub struct SentryHttpFuture<F> {
transaction: Option<sentry_core::Transaction>,
future: F,
}

impl Future for SentryHttpFuture<F>
where
F: Future,
{
type Output = F::Output;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// https://doc.rust-lang.org/std/pin/index.html#pinning-is-structural-for-field
let future = unsafe { self.map_unchecked_mut(|s| &mut s.future) };
match future.poll(cx) {
Poll::Ready(res) => {
if let Some(transaction) = self.transaction.take() {
transaction.finish();
}
Poll::Ready(res)
}
Poll::Pending => Poll::Pending,
}
}
}

impl<S, Body> Service<Request<Body>> for SentryHttpService<S>
where
S: Service<Request<Body>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = SentryHttpFuture<S::Future>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}

fn call(&mut self, request: Request<Body>) -> Self::Future {
let transaction = sentry::configure_scope(|scope| {
// https://develop.sentry.dev/sdk/event-payloads/request/
let mut ctx = BTreeMap::new();

// TODO: method, url, query_string

// headers
let mut headers = BTreeMap::new();
for (header, value) in request.headers() {
headers.insert(
header.to_string(),
value.to_str().unwrap_or("<Opaque header value>").into(),
);
}
ctx.insert("headers".into(), headers);

scope.set_context("request", sentry_core::protocol::Context::Other(ctx));

// TODO: maybe make transaction creation optional?
let transaction = if true {
let tx_ctx = sentry_core::TransactionContext::continue_from_headers(
// TODO: whats the name here?
"",
"http",
request.headers(),
);
Some(sentry_core::start_transaction(tx_ctx))
} else {
None
};

scope.set_span(transaction.clone());
transaction
});

SentryHttpFuture {
transaction,
future: self.service.call(request),
}
}
}
8 changes: 6 additions & 2 deletions sentry-tower/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,17 @@
#![doc(html_logo_url = "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png")]
#![warn(missing_docs)]

use sentry_core::{Hub, SentryFuture, SentryFutureExt};
use std::marker::PhantomData;
use std::sync::Arc;
use std::task::{Context, Poll};

use sentry_core::{Hub, SentryFuture, SentryFutureExt};
use tower_layer::Layer;
use tower_service::Service;

#[cfg(feature = "http")]
mod http;

/// Provides a hub for each request
pub trait HubProvider<H, Request>
where
Expand Down Expand Up @@ -140,7 +144,7 @@ pub struct NewFromTopProvider;

impl<Request> HubProvider<Arc<Hub>, Request> for NewFromTopProvider {
fn hub(&self, _request: &Request) -> Arc<Hub> {
// The Clippy lint here is a falste positive, the suggestion to write
// The Clippy lint here is a false positive, the suggestion to write
// `Hub::with(Hub::new_from_top)` does not compiles:
// 143 | Hub::with(Hub::new_from_top).into()
// | ^^^^^^^^^ implementation of `std::ops::FnOnce` is not general enough
Expand Down

0 comments on commit 6a479ff

Please sign in to comment.