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

Match stable Semantic Conventions for HTTP Spans (take 2) #141

Merged
merged 4 commits into from
Apr 10, 2024
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
1 change: 1 addition & 0 deletions reqwest-tracing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Breaking changes
- Upgraded `reqwest-middleware` to `0.3.0`.
- Removed support for `opentelemetry` 0.13 to 0.19
- The keys emitted by the crate now match the stable Semantic Conventions for HTTP Spans.

### Changed
- The keys emitted by the crate now match the stable Semantic Conventions for HTTP Spans.
Expand Down
6 changes: 3 additions & 3 deletions reqwest-tracing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ pub use middleware::TracingMiddleware;
pub use reqwest_otel_span_builder::{
default_on_request_end, default_on_request_failure, default_on_request_success,
default_span_name, DefaultSpanBackend, DisableOtelPropagation, OtelName, OtelPathNames,
ReqwestOtelSpanBackend, SpanBackendWithUrl, ERROR_CAUSE_CHAIN, ERROR_MESSAGE, HTTP_HOST,
HTTP_METHOD, HTTP_SCHEME, HTTP_STATUS_CODE, HTTP_URL, HTTP_USER_AGENT, NET_HOST_PORT,
OTEL_KIND, OTEL_NAME, OTEL_STATUS_CODE,
ReqwestOtelSpanBackend, SpanBackendWithUrl, ERROR_CAUSE_CHAIN, ERROR_MESSAGE,
HTTP_REQUEST_METHOD, HTTP_RESPONSE_STATUS_CODE, OTEL_KIND, OTEL_NAME, OTEL_STATUS_CODE,
SERVER_ADDRESS, SERVER_PORT, URL_FULL, URL_SCHEME, USER_AGENT_ORIGINAL,
};

#[doc(hidden)]
Expand Down
53 changes: 26 additions & 27 deletions reqwest-tracing/src/reqwest_otel_span_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,36 @@ use std::borrow::Cow;

use http::Extensions;
use matchit::Router;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{Request, Response, StatusCode as RequestStatusCode, Url};
use reqwest_middleware::{Error, Result};
use tracing::{warn, Span};

use crate::reqwest_otel_span;

/// The `http.method` field added to the span by [`reqwest_otel_span`]
pub const HTTP_METHOD: &str = "http.method";
/// The `http.scheme` field added to the span by [`reqwest_otel_span`]
pub const HTTP_SCHEME: &str = "http.scheme";
/// The `http.host` field added to the span by [`reqwest_otel_span`]
pub const HTTP_HOST: &str = "http.host";
/// The `http.url` field added to the span by [`reqwest_otel_span`]
pub const HTTP_URL: &str = "http.url";
/// The `host.port` field added to the span by [`reqwest_otel_span`]
pub const NET_HOST_PORT: &str = "net.host.port";
/// The `http.request.method` field added to the span by [`reqwest_otel_span`]
pub const HTTP_REQUEST_METHOD: &str = "http.request.method";
/// The `url.scheme` field added to the span by [`reqwest_otel_span`]
pub const URL_SCHEME: &str = "url.scheme";
/// The `server.address` field added to the span by [`reqwest_otel_span`]
pub const SERVER_ADDRESS: &str = "server.address";
/// The `server.port` field added to the span by [`reqwest_otel_span`]
pub const SERVER_PORT: &str = "server.port";
/// The `url.full` field added to the span by [`reqwest_otel_span`]
pub const URL_FULL: &str = "url.full";
/// The `user_agent.original` field added to the span by [`reqwest_otel_span`]
pub const USER_AGENT_ORIGINAL: &str = "user_agent.original";
/// The `otel.kind` field added to the span by [`reqwest_otel_span`]
pub const OTEL_KIND: &str = "otel.kind";
/// The `otel.name` field added to the span by [`reqwest_otel_span`]
pub const OTEL_NAME: &str = "otel.name";
/// The `otel.status_code` field added to the span by [`reqwest_otel_span`]
pub const OTEL_STATUS_CODE: &str = "otel.status_code";
/// The `http.response.status_code` field added to the span by [`reqwest_otel_span`]
pub const HTTP_RESPONSE_STATUS_CODE: &str = "http.response.status_code";
/// The `error.message` field added to the span by [`reqwest_otel_span`]
pub const ERROR_MESSAGE: &str = "error.message";
/// The `error.cause_chain` field added to the span by [`reqwest_otel_span`]
pub const ERROR_CAUSE_CHAIN: &str = "error.cause_chain";
/// The `http.status_code` field added to the span by [`reqwest_otel_span`]
pub const HTTP_STATUS_CODE: &str = "http.status_code";
/// The `http.user_agent` added to the span by [`reqwest_otel_span`]
pub const HTTP_USER_AGENT: &str = "http.user_agent";

/// [`ReqwestOtelSpanBackend`] allows you to customise the span attached by
/// [`TracingMiddleware`] to incoming requests.
Expand Down Expand Up @@ -61,12 +60,10 @@ pub fn default_on_request_end(span: &Span, outcome: &Result<Response>) {
#[inline]
pub fn default_on_request_success(span: &Span, response: &Response) {
let span_status = get_span_status(response.status());
let user_agent = get_header_value("user_agent", response.headers());
if let Some(span_status) = span_status {
span.record(OTEL_STATUS_CODE, span_status);
}
span.record(HTTP_STATUS_CODE, response.status().as_u16());
span.record(HTTP_USER_AGENT, user_agent.as_str());
span.record(HTTP_RESPONSE_STATUS_CODE, response.status().as_u16());
}

/// Populates default failure fields for a given [`reqwest_otel_span!`] span.
Expand All @@ -79,7 +76,7 @@ pub fn default_on_request_failure(span: &Span, e: &Error) {
span.record(ERROR_CAUSE_CHAIN, error_cause_chain.as_str());
if let Error::Reqwest(e) = e {
if let Some(status) = e.status() {
span.record(HTTP_STATUS_CODE, status.as_u16());
span.record(HTTP_RESPONSE_STATUS_CODE, status.as_u16());
}
}
}
Expand All @@ -106,7 +103,7 @@ pub fn default_span_name<'a>(req: &'a Request, ext: &'a Extensions) -> Cow<'a, s
}

/// The default [`ReqwestOtelSpanBackend`] for [`TracingMiddleware`]. Note that it doesn't include
/// the `http.url` field in spans, you can use [`SpanBackendWithUrl`] to add it.
/// the `url.full` field in spans, you can use [`SpanBackendWithUrl`] to add it.
///
/// [`TracingMiddleware`]: crate::middleware::TracingMiddleware
pub struct DefaultSpanBackend;
Expand All @@ -122,20 +119,15 @@ impl ReqwestOtelSpanBackend for DefaultSpanBackend {
}
}

fn get_header_value(key: &str, headers: &HeaderMap) -> String {
let header_default = &HeaderValue::from_static("");
format!("{:?}", headers.get(key).unwrap_or(header_default)).replace('"', "")
}

/// Similar to [`DefaultSpanBackend`] but also adds the `http.url` attribute to request spans.
/// Similar to [`DefaultSpanBackend`] but also adds the `url.full` attribute to request spans.
///
/// [`TracingMiddleware`]: crate::middleware::TracingMiddleware
pub struct SpanBackendWithUrl;

impl ReqwestOtelSpanBackend for SpanBackendWithUrl {
fn on_request_start(req: &Request, ext: &mut Extensions) -> Span {
let name = default_span_name(req, ext);
reqwest_otel_span!(name = name, req, http.url = %remove_credentials(req.url()))
reqwest_otel_span!(name = name, req, url.full = %remove_credentials(req.url()))
}

fn on_request_end(span: &Span, outcome: &Result<Response>, _: &mut Extensions) {
Expand Down Expand Up @@ -330,6 +322,13 @@ fn remove_credentials(url: &Url) -> Cow<'_, str> {
mod tests {
use super::*;

use reqwest::header::{HeaderMap, HeaderValue};

fn get_header_value(key: &str, headers: &HeaderMap) -> String {
let header_default = &HeaderValue::from_static("");
format!("{:?}", headers.get(key).unwrap_or(header_default)).replace('"', "")
}

#[test]
fn get_header_value_for_span_attribute() {
let expect = "IMPORTANT_HEADER";
Expand Down
30 changes: 16 additions & 14 deletions reqwest-tracing/src/reqwest_otel_span_macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
/// It empowers you to add custom properties to the span on top of the default properties provided by the macro
///
/// Default Fields:
/// - http.method
/// - http.scheme
/// - http.host
/// - net.host
/// - http.request.method
/// - url.scheme
/// - server.address
/// - server.port
/// - otel.kind
/// - otel.name
/// - otel.status_code
/// - http.user_agent
/// - http.status_code
/// - user_agent.original
/// - http.response.status_code
/// - error.message
/// - error.cause_chain
///
Expand Down Expand Up @@ -53,7 +53,7 @@
///
/// If nothing else is specified, the span generated by `reqwest_otel_span!` is identical to the one you'd
/// get by using [`DefaultSpanBackend`]. Note that to avoid leaking sensitive information, the
/// macro doesn't include `http.url`, even though it's required by opentelemetry. You can add the
/// macro doesn't include `url.full`, even though it's required by opentelemetry. You can add the
/// URL attribute explicitly by using [`SpanBackendWithUrl`] instead of `DefaultSpanBackend` or
/// adding the field on your own implementation.
///
Expand Down Expand Up @@ -122,23 +122,25 @@ macro_rules! reqwest_otel_span {
let url = $request.url();
let scheme = url.scheme();
let host = url.host_str().unwrap_or("");
let host_port = url.port().unwrap_or(0) as i64;
let host_port = url.port_or_known_default().unwrap_or(0) as i64;
let otel_name = $name.to_string();
let header_default = &::http::HeaderValue::from_static("");
let user_agent = format!("{:?}", $request.headers().get("user_agent").unwrap_or(header_default)).replace('"', "");

macro_rules! request_span {
($lvl:expr) => {
$crate::reqwest_otel_span_macro::private::span!(
$lvl,
"HTTP request",
http.method = %method,
http.scheme = %scheme,
http.host = %host,
net.host.port = %host_port,
http.request.method = %method,
url.scheme = %scheme,
server.address = %host,
server.port = %host_port,
user_agent.original = %user_agent,
otel.kind = "client",
otel.name = %otel_name,
otel.status_code = tracing::field::Empty,
http.user_agent = tracing::field::Empty,
http.status_code = tracing::field::Empty,
http.response.status_code = tracing::field::Empty,
error.message = tracing::field::Empty,
error.cause_chain = tracing::field::Empty,
$($field)*
Expand Down
Loading