From e3e19ef1e1da882da244c34bc9f3b99ab4e1e95d Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Thu, 14 May 2026 13:12:59 -0400 Subject: [PATCH 1/3] Adopt `opentelemetry-semantic-conventions` for OTel attribute names Signed-off-by: Zhiwei Liang --- Cargo.lock | 9 +++++++++ Cargo.toml | 1 + crates/factor-outbound-http/Cargo.toml | 1 + crates/factor-outbound-http/src/spin.rs | 13 ++++++++----- crates/factor-outbound-http/src/wasi.rs | 18 ++++++++++++------ crates/factor-outbound-networking/Cargo.toml | 1 + crates/factor-outbound-networking/src/lib.rs | 5 ++++- crates/telemetry/src/traces.rs | 4 +++- crates/trigger-http/Cargo.toml | 1 + crates/trigger-http/src/instrument.rs | 7 ++++--- 10 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11d66990aa..84cba3369f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6134,6 +6134,12 @@ dependencies = [ "tonic", ] +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb3a2f78c2d55362cd6c313b8abedfbc0142ab3c2676822068fd2ab7d51f9b7" + [[package]] name = "opentelemetry_sdk" version = "0.28.0" @@ -8807,6 +8813,7 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "hyper-util", + "opentelemetry-semantic-conventions", "pin-project-lite", "reqwest 0.12.9", "rustls 0.23.37", @@ -8877,6 +8884,7 @@ dependencies = [ "futures-util", "http 1.3.1", "ip_network", + "opentelemetry-semantic-conventions", "rustls 0.23.37", "rustls-pki-types", "rustls-platform-verifier", @@ -9525,6 +9533,7 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "hyper-util", + "opentelemetry-semantic-conventions", "pin-project-lite", "rand 0.9.1", "rustls 0.23.37", diff --git a/Cargo.toml b/Cargo.toml index 41f3807b93..f1523c9d18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -162,6 +162,7 @@ opentelemetry_sdk = {version = "0.28", features = [ "experimental_logs_batch_log_processor_with_async_runtime", "experimental_async_runtime" ]} +opentelemetry-semantic-conventions = "0.28" path-absolutize = "3" pin-project-lite = "0.2.16" quote = "1" diff --git a/crates/factor-outbound-http/Cargo.toml b/crates/factor-outbound-http/Cargo.toml index 51046dc150..7ccfd3f63b 100644 --- a/crates/factor-outbound-http/Cargo.toml +++ b/crates/factor-outbound-http/Cargo.toml @@ -27,6 +27,7 @@ tokio-rustls = { workspace = true } tower-service = { workspace = true } tracing = { workspace = true } tracing-opentelemetry = { workspace = true } +opentelemetry-semantic-conventions = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } wasmtime-wasi-http = { workspace = true } diff --git a/crates/factor-outbound-http/src/spin.rs b/crates/factor-outbound-http/src/spin.rs index 02ff1411d1..a3ec7a4dbc 100644 --- a/crates/factor-outbound-http/src/spin.rs +++ b/crates/factor-outbound-http/src/spin.rs @@ -2,6 +2,9 @@ use std::sync::Arc; use futures::stream::TryStreamExt as _; use http_body_util::BodyExt; +use opentelemetry_semantic_conventions::attribute::{ + HTTP_REQUEST_METHOD, HTTP_RESPONSE_STATUS_CODE, SERVER_ADDRESS, SERVER_PORT, URL_FULL, +}; use spin_factor_outbound_networking::config::blocked_networks::BlockedNetworks; use spin_world::MAX_HOST_BUFFERED_BYTES; use spin_world::v1::{ @@ -120,7 +123,7 @@ impl spin_http::Host for crate::InstanceState { drop(permit); tracing::trace!("Returning response from outbound request to {req_url}"); - span.record("http.response.status_code", resp.status().as_u16()); + span.record(HTTP_RESPONSE_STATUS_CODE, resp.status().as_u16()); response_from_reqwest(resp).await } } @@ -162,14 +165,14 @@ fn record_request_fields(span: &Span, req: &Request) { // Set otel.name to just the method name to fit with OpenTelemetry conventions // span.record("otel.name", method) - .record("http.request.method", method) - .record("url.full", req.uri.clone()); + .record(HTTP_REQUEST_METHOD, method) + .record(URL_FULL, req.uri.clone()); if let Ok(uri) = req.uri.parse::() && let Some(authority) = uri.authority() { - span.record("server.address", authority.host()); + span.record(SERVER_ADDRESS, authority.host()); if let Some(port) = authority.port() { - span.record("server.port", port.as_u16()); + span.record(SERVER_PORT, port.as_u16()); } } } diff --git a/crates/factor-outbound-http/src/wasi.rs b/crates/factor-outbound-http/src/wasi.rs index 9b80e1df20..28bd31ea8c 100644 --- a/crates/factor-outbound-http/src/wasi.rs +++ b/crates/factor-outbound-http/src/wasi.rs @@ -26,6 +26,9 @@ use hyper_util::{ }, rt::{TokioExecutor, TokioIo}, }; +use opentelemetry_semantic_conventions::attribute::{ + HTTP_RESPONSE_STATUS_CODE, SERVER_ADDRESS, SERVER_PORT, URL_FULL, +}; use spin_factor_outbound_networking::{ ComponentTlsClientConfigs, TlsClientConfig, config::{allowed_hosts::OutboundAllowedHosts, blocked_networks::BlockedNetworks}, @@ -269,6 +272,9 @@ impl> Body for BetweenBytesTimeoutBody { let mut record_body_size_once = |body_size: u64| { if let Some(span) = me.span.take() { + // `http.response.body.size` is incubating (behind semconv_experimental) + // in opentelemetry-semantic-conventions 0.28. Leave as literal to avoid + // enabling the experimental feature. span.record("http.response.body.size", body_size); } }; @@ -484,12 +490,12 @@ impl RequestSender { // Backfill span fields after potentially updating the URL in the interceptor let span = tracing::Span::current(); if let Some(addr) = override_connect_addr { - span.record("server.address", addr.ip().to_string()); - span.record("server.port", addr.port()); + span.record(SERVER_ADDRESS, addr.ip().to_string()); + span.record(SERVER_PORT, addr.port()); } else if let Some(authority) = request.uri().authority() { - span.record("server.address", authority.host()); + span.record(SERVER_ADDRESS, authority.host()); if let Some(port) = authority.port_u16() { - span.record("server.port", port); + span.record(SERVER_PORT, port); } } @@ -523,7 +529,7 @@ impl RequestSender { } *uri = builder.build().unwrap(); } - tracing::Span::current().record("url.full", uri.to_string()); + tracing::Span::current().record(URL_FULL, uri.to_string()); let is_self_request = match request.uri().authority() { // Some SDKs require an authority, so we support e.g. http://self.alt/self-request @@ -630,7 +636,7 @@ impl RequestSender { .map(|body| body.map_err(hyper_request_error).boxed_unsync()); let span = tracing::Span::current(); - span.record("http.response.status_code", resp.status().as_u16()); + span.record(HTTP_RESPONSE_STATUS_CODE, resp.status().as_u16()); record_content_length_header(&span, resp.headers(), "http.response.header.content-length"); diff --git a/crates/factor-outbound-networking/Cargo.toml b/crates/factor-outbound-networking/Cargo.toml index 3bdd471954..f340c27093 100644 --- a/crates/factor-outbound-networking/Cargo.toml +++ b/crates/factor-outbound-networking/Cargo.toml @@ -21,6 +21,7 @@ spin-manifest = { path = "../manifest" } spin-outbound-networking-config = { path = "../outbound-networking-config" } spin-serde = { path = "../serde" } tracing = { workspace = true } +opentelemetry-semantic-conventions = { workspace = true } url = { workspace = true } webpki-root-certs = "1.0.7" diff --git a/crates/factor-outbound-networking/src/lib.rs b/crates/factor-outbound-networking/src/lib.rs index 57b22041ff..5b20c46be3 100644 --- a/crates/factor-outbound-networking/src/lib.rs +++ b/crates/factor-outbound-networking/src/lib.rs @@ -5,6 +5,7 @@ mod tls; use std::{collections::HashMap, sync::Arc}; use futures_util::FutureExt as _; +use opentelemetry_semantic_conventions::attribute::SERVER_PORT; use spin_factor_variables::VariablesFactor; use spin_factor_wasi::{SocketAddrUse, WasiFactor}; use spin_factors::{ @@ -227,8 +228,10 @@ impl FactorInstanceBuilder for InstanceBuilder { pub fn record_address_fields(address: &str) { if let Ok(url) = Url::parse(address) { let span = tracing::Span::current(); + // `db.address` and `db.namespace` are incubating in opentelemetry-semantic-conventions 0.28. + // Leaving as string literals to avoid enabling the semconv_experimental feature. span.record("db.address", url.host_str().unwrap_or_default()); - span.record("server.port", url.port().unwrap_or_default()); + span.record(SERVER_PORT, url.port().unwrap_or_default()); span.record("db.namespace", url.path().trim_start_matches('/')); } } diff --git a/crates/telemetry/src/traces.rs b/crates/telemetry/src/traces.rs index c608f2561d..c21b38e1ff 100644 --- a/crates/telemetry/src/traces.rs +++ b/crates/telemetry/src/traces.rs @@ -95,6 +95,8 @@ pub fn mark_as_error(err: &E, blame: Option) { let current_span = tracing::Span::current(); current_span.set_status(opentelemetry::trace::Status::error(err.to_string())); if let Some(blame) = blame { - current_span.set_attribute("error.blame", blame.as_str()); + // `error.blame` is a Spin-specific attribute, not part of the OTel semantic conventions. + const ERROR_BLAME: &str = "error.blame"; + current_span.set_attribute(ERROR_BLAME, blame.as_str()); } } diff --git a/crates/trigger-http/Cargo.toml b/crates/trigger-http/Cargo.toml index ee595bd55b..811e69067c 100644 --- a/crates/trigger-http/Cargo.toml +++ b/crates/trigger-http/Cargo.toml @@ -38,6 +38,7 @@ terminal = { path = "../terminal" } tokio = { workspace = true, features = ["full"] } tokio-rustls = { workspace = true } tracing = { workspace = true } +opentelemetry-semantic-conventions = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } wasmtime-wasi-http = { workspace = true } diff --git a/crates/trigger-http/src/instrument.rs b/crates/trigger-http/src/instrument.rs index 81974a0096..e94e5f3e83 100644 --- a/crates/trigger-http/src/instrument.rs +++ b/crates/trigger-http/src/instrument.rs @@ -1,5 +1,6 @@ use anyhow::Result; use http::Response; +use opentelemetry_semantic_conventions::attribute::{ERROR_TYPE, HTTP_RESPONSE_STATUS_CODE}; use tracing::Level; use crate::Body; @@ -52,13 +53,13 @@ pub(crate) fn finalize_http_span( } // Set status code - span.record("http.response.status_code", response.status().as_u16()); + span.record(HTTP_RESPONSE_STATUS_CODE, response.status().as_u16()); Ok(response) } Err(err) => { instrument_error(&err); - span.record("http.response.status_code", 500); + span.record(HTTP_RESPONSE_STATUS_CODE, 500); span.record("otel.name", method); Err(err) } @@ -69,7 +70,7 @@ pub(crate) fn finalize_http_span( pub(crate) fn instrument_error(err: &anyhow::Error) { let span = tracing::Span::current(); tracing::event!(target:module_path!(), Level::INFO, error = %err); - span.record("error.type", format!("{err:?}")); + span.record(ERROR_TYPE, format!("{err:?}")); } /// MatchedRoute is used as a response extension to track the route that was matched for OTel From 2424006c7a3a8c3f0b386ca5ba7df475b47d65a3 Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Fri, 15 May 2026 11:56:14 -0400 Subject: [PATCH 2/3] Use OTel semconv constants for HTTP span fields Signed-off-by: Zhiwei Liang --- Cargo.lock | 12 +++++------ Cargo.toml | 2 +- crates/factor-outbound-http/src/spin.rs | 4 ++-- crates/factor-outbound-http/src/wasi.rs | 23 ++++++++++---------- crates/telemetry/src/traces.rs | 4 +--- crates/trigger-http/src/instrument.rs | 28 +++++++++++++------------ 6 files changed, 37 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84cba3369f..4516c95b23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10512,9 +10512,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -10524,9 +10524,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -10535,9 +10535,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", diff --git a/Cargo.toml b/Cargo.toml index f1523c9d18..c5cf89a120 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -191,7 +191,7 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["loggin toml = "0.8" toml_edit = "0.22" tower-service = "0.3.3" -tracing = { version = "0.1.41", features = ["log"] } +tracing = { version = "0.1.44", features = ["log"] } url = "2.5.7" tracing-opentelemetry = { version = "0.29", default-features = false, features = ["metrics"] } walkdir = "2" diff --git a/crates/factor-outbound-http/src/spin.rs b/crates/factor-outbound-http/src/spin.rs index a3ec7a4dbc..911cabfe9e 100644 --- a/crates/factor-outbound-http/src/spin.rs +++ b/crates/factor-outbound-http/src/spin.rs @@ -17,8 +17,8 @@ use crate::intercept::InterceptOutcome; impl spin_http::Host for crate::InstanceState { #[instrument(name = "spin_outbound_http.send_request", skip_all, - fields(otel.kind = "client", url.full = Empty, http.request.method = Empty, - http.response.status_code = Empty, otel.name = Empty, server.address = Empty, server.port = Empty))] + fields(otel.kind = "client", {URL_FULL} = Empty, {HTTP_REQUEST_METHOD} = Empty, + {HTTP_RESPONSE_STATUS_CODE} = Empty, otel.name = Empty, {SERVER_ADDRESS} = Empty, {SERVER_PORT} = Empty))] async fn send_request(&mut self, req: Request) -> Result { self.hooks.otel.reparent_tracing_span(); diff --git a/crates/factor-outbound-http/src/wasi.rs b/crates/factor-outbound-http/src/wasi.rs index 28bd31ea8c..d823c823cd 100644 --- a/crates/factor-outbound-http/src/wasi.rs +++ b/crates/factor-outbound-http/src/wasi.rs @@ -27,7 +27,7 @@ use hyper_util::{ rt::{TokioExecutor, TokioIo}, }; use opentelemetry_semantic_conventions::attribute::{ - HTTP_RESPONSE_STATUS_CODE, SERVER_ADDRESS, SERVER_PORT, URL_FULL, + HTTP_REQUEST_METHOD, HTTP_RESPONSE_STATUS_CODE, SERVER_ADDRESS, SERVER_PORT, URL_FULL, }; use spin_factor_outbound_networking::{ ComponentTlsClientConfigs, TlsClientConfig, @@ -138,13 +138,14 @@ impl p3::WasiHttpHooks for InstanceHttpHooks { skip_all, fields( otel.kind = "client", - url.full = Empty, - http.request.method = %request.method(), + {URL_FULL} = Empty, + {HTTP_REQUEST_METHOD} = %request.method(), otel.name = %request.method(), + // Incubating convention; not yet a stable `opentelemetry_semantic_conventions` constant. http.response.body.size = Empty, - http.response.status_code = Empty, - server.address = Empty, - server.port = Empty, + {HTTP_RESPONSE_STATUS_CODE} = Empty, + {SERVER_ADDRESS} = Empty, + {SERVER_PORT} = Empty, ) )] #[allow(clippy::type_complexity)] @@ -403,12 +404,12 @@ impl p2::WasiHttpHooks for InstanceHttpHooks { skip_all, fields( otel.kind = "client", - url.full = Empty, - http.request.method = %request.method(), + {URL_FULL} = Empty, + {HTTP_REQUEST_METHOD} = %request.method(), otel.name = %request.method(), - http.response.status_code = Empty, - server.address = Empty, - server.port = Empty, + {HTTP_RESPONSE_STATUS_CODE} = Empty, + {SERVER_ADDRESS} = Empty, + {SERVER_PORT} = Empty, ) )] fn send_request( diff --git a/crates/telemetry/src/traces.rs b/crates/telemetry/src/traces.rs index c21b38e1ff..c608f2561d 100644 --- a/crates/telemetry/src/traces.rs +++ b/crates/telemetry/src/traces.rs @@ -95,8 +95,6 @@ pub fn mark_as_error(err: &E, blame: Option) { let current_span = tracing::Span::current(); current_span.set_status(opentelemetry::trace::Status::error(err.to_string())); if let Some(blame) = blame { - // `error.blame` is a Spin-specific attribute, not part of the OTel semantic conventions. - const ERROR_BLAME: &str = "error.blame"; - current_span.set_attribute(ERROR_BLAME, blame.as_str()); + current_span.set_attribute("error.blame", blame.as_str()); } } diff --git a/crates/trigger-http/src/instrument.rs b/crates/trigger-http/src/instrument.rs index e94e5f3e83..14dbf94a08 100644 --- a/crates/trigger-http/src/instrument.rs +++ b/crates/trigger-http/src/instrument.rs @@ -1,6 +1,8 @@ use anyhow::Result; use http::Response; -use opentelemetry_semantic_conventions::attribute::{ERROR_TYPE, HTTP_RESPONSE_STATUS_CODE}; +use opentelemetry_semantic_conventions::attribute::{ + ERROR_TYPE, HTTP_RESPONSE_STATUS_CODE, HTTP_ROUTE, +}; use tracing::Level; use crate::Body; @@ -11,18 +13,18 @@ macro_rules! http_span { tracing::info_span!( "spin_trigger_http.handle_http_request", "otel.kind" = "server", - "http.request.method" = %$request.method(), - "network.peer.address" = %$addr.ip(), - "network.peer.port" = %$addr.port(), - "network.protocol.name" = "http", - "url.path" = $request.uri().path(), - "url.query" = $request.uri().query().unwrap_or(""), - "url.scheme" = $request.uri().scheme_str().unwrap_or(""), - "client.address" = $request.headers().get("x-forwarded-for").and_then(|val| val.to_str().ok()), + {opentelemetry_semantic_conventions::attribute::HTTP_REQUEST_METHOD} = %$request.method(), + {opentelemetry_semantic_conventions::attribute::NETWORK_PEER_ADDRESS} = %$addr.ip(), + {opentelemetry_semantic_conventions::attribute::NETWORK_PEER_PORT} = %$addr.port(), + {opentelemetry_semantic_conventions::attribute::NETWORK_PROTOCOL_NAME} = "http", + {opentelemetry_semantic_conventions::attribute::URL_PATH} = $request.uri().path(), + {opentelemetry_semantic_conventions::attribute::URL_QUERY} = $request.uri().query().unwrap_or(""), + {opentelemetry_semantic_conventions::attribute::URL_SCHEME} = $request.uri().scheme_str().unwrap_or(""), + {opentelemetry_semantic_conventions::attribute::CLIENT_ADDRESS} = $request.headers().get("x-forwarded-for").and_then(|val| val.to_str().ok()), // Recorded later - "error.type" = ::tracing::field::Empty, - "http.response.status_code" = ::tracing::field::Empty, - "http.route" = ::tracing::field::Empty, + {opentelemetry_semantic_conventions::attribute::ERROR_TYPE} = ::tracing::field::Empty, + {opentelemetry_semantic_conventions::attribute::HTTP_RESPONSE_STATUS_CODE} = ::tracing::field::Empty, + {opentelemetry_semantic_conventions::attribute::HTTP_ROUTE} = ::tracing::field::Empty, "otel.name" = ::tracing::field::Empty, ) }; @@ -46,7 +48,7 @@ pub(crate) fn finalize_http_span( let matched_route = response.extensions().get::(); // Set otel.name and http.route if let Some(MatchedRoute { route }) = matched_route { - span.record("http.route", route); + span.record(HTTP_ROUTE, route); span.record("otel.name", format!("{method} {route}")); } else { span.record("otel.name", method); From 0b5917b8ac80be056f72d2b388bf8a2b45686b7e Mon Sep 17 00:00:00 2001 From: Zhiwei Liang Date: Fri, 15 May 2026 12:22:54 -0400 Subject: [PATCH 3/3] Refactor to use OpenTelemetry attribute aliasing for HTTP span fields Updated the usage of OpenTelemetry semantic conventions in the outbound HTTP crate to utilize the new aliasing for attribute names. Signed-off-by: Zhiwei Liang --- crates/factor-outbound-http/src/spin.rs | 21 ++++++------- crates/factor-outbound-http/src/wasi.rs | 39 +++++++++++++------------ crates/trigger-http/src/instrument.rs | 15 +++++----- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/crates/factor-outbound-http/src/spin.rs b/crates/factor-outbound-http/src/spin.rs index 911cabfe9e..5c47204bfb 100644 --- a/crates/factor-outbound-http/src/spin.rs +++ b/crates/factor-outbound-http/src/spin.rs @@ -2,9 +2,7 @@ use std::sync::Arc; use futures::stream::TryStreamExt as _; use http_body_util::BodyExt; -use opentelemetry_semantic_conventions::attribute::{ - HTTP_REQUEST_METHOD, HTTP_RESPONSE_STATUS_CODE, SERVER_ADDRESS, SERVER_PORT, URL_FULL, -}; +use opentelemetry_semantic_conventions::attribute as otel_attribute; use spin_factor_outbound_networking::config::blocked_networks::BlockedNetworks; use spin_world::MAX_HOST_BUFFERED_BYTES; use spin_world::v1::{ @@ -17,8 +15,8 @@ use crate::intercept::InterceptOutcome; impl spin_http::Host for crate::InstanceState { #[instrument(name = "spin_outbound_http.send_request", skip_all, - fields(otel.kind = "client", {URL_FULL} = Empty, {HTTP_REQUEST_METHOD} = Empty, - {HTTP_RESPONSE_STATUS_CODE} = Empty, otel.name = Empty, {SERVER_ADDRESS} = Empty, {SERVER_PORT} = Empty))] + fields(otel.kind = "client", {otel_attribute::URL_FULL} = Empty, {otel_attribute::HTTP_REQUEST_METHOD} = Empty, + {otel_attribute::HTTP_RESPONSE_STATUS_CODE} = Empty, otel.name = Empty, {otel_attribute::SERVER_ADDRESS} = Empty, {otel_attribute::SERVER_PORT} = Empty))] async fn send_request(&mut self, req: Request) -> Result { self.hooks.otel.reparent_tracing_span(); @@ -123,7 +121,10 @@ impl spin_http::Host for crate::InstanceState { drop(permit); tracing::trace!("Returning response from outbound request to {req_url}"); - span.record(HTTP_RESPONSE_STATUS_CODE, resp.status().as_u16()); + span.record( + otel_attribute::HTTP_RESPONSE_STATUS_CODE, + resp.status().as_u16(), + ); response_from_reqwest(resp).await } } @@ -165,14 +166,14 @@ fn record_request_fields(span: &Span, req: &Request) { // Set otel.name to just the method name to fit with OpenTelemetry conventions // span.record("otel.name", method) - .record(HTTP_REQUEST_METHOD, method) - .record(URL_FULL, req.uri.clone()); + .record(otel_attribute::HTTP_REQUEST_METHOD, method) + .record(otel_attribute::URL_FULL, req.uri.clone()); if let Ok(uri) = req.uri.parse::() && let Some(authority) = uri.authority() { - span.record(SERVER_ADDRESS, authority.host()); + span.record(otel_attribute::SERVER_ADDRESS, authority.host()); if let Some(port) = authority.port() { - span.record(SERVER_PORT, port.as_u16()); + span.record(otel_attribute::SERVER_PORT, port.as_u16()); } } } diff --git a/crates/factor-outbound-http/src/wasi.rs b/crates/factor-outbound-http/src/wasi.rs index d823c823cd..ed151f5647 100644 --- a/crates/factor-outbound-http/src/wasi.rs +++ b/crates/factor-outbound-http/src/wasi.rs @@ -26,9 +26,7 @@ use hyper_util::{ }, rt::{TokioExecutor, TokioIo}, }; -use opentelemetry_semantic_conventions::attribute::{ - HTTP_REQUEST_METHOD, HTTP_RESPONSE_STATUS_CODE, SERVER_ADDRESS, SERVER_PORT, URL_FULL, -}; +use opentelemetry_semantic_conventions::attribute as otel_attribute; use spin_factor_outbound_networking::{ ComponentTlsClientConfigs, TlsClientConfig, config::{allowed_hosts::OutboundAllowedHosts, blocked_networks::BlockedNetworks}, @@ -138,14 +136,14 @@ impl p3::WasiHttpHooks for InstanceHttpHooks { skip_all, fields( otel.kind = "client", - {URL_FULL} = Empty, - {HTTP_REQUEST_METHOD} = %request.method(), + {otel_attribute::URL_FULL} = Empty, + {otel_attribute::HTTP_REQUEST_METHOD} = %request.method(), otel.name = %request.method(), // Incubating convention; not yet a stable `opentelemetry_semantic_conventions` constant. http.response.body.size = Empty, - {HTTP_RESPONSE_STATUS_CODE} = Empty, - {SERVER_ADDRESS} = Empty, - {SERVER_PORT} = Empty, + {otel_attribute::HTTP_RESPONSE_STATUS_CODE} = Empty, + {otel_attribute::SERVER_ADDRESS} = Empty, + {otel_attribute::SERVER_PORT} = Empty, ) )] #[allow(clippy::type_complexity)] @@ -404,12 +402,12 @@ impl p2::WasiHttpHooks for InstanceHttpHooks { skip_all, fields( otel.kind = "client", - {URL_FULL} = Empty, - {HTTP_REQUEST_METHOD} = %request.method(), + {otel_attribute::URL_FULL} = Empty, + {otel_attribute::HTTP_REQUEST_METHOD} = %request.method(), otel.name = %request.method(), - {HTTP_RESPONSE_STATUS_CODE} = Empty, - {SERVER_ADDRESS} = Empty, - {SERVER_PORT} = Empty, + {otel_attribute::HTTP_RESPONSE_STATUS_CODE} = Empty, + {otel_attribute::SERVER_ADDRESS} = Empty, + {otel_attribute::SERVER_PORT} = Empty, ) )] fn send_request( @@ -491,12 +489,12 @@ impl RequestSender { // Backfill span fields after potentially updating the URL in the interceptor let span = tracing::Span::current(); if let Some(addr) = override_connect_addr { - span.record(SERVER_ADDRESS, addr.ip().to_string()); - span.record(SERVER_PORT, addr.port()); + span.record(otel_attribute::SERVER_ADDRESS, addr.ip().to_string()); + span.record(otel_attribute::SERVER_PORT, addr.port()); } else if let Some(authority) = request.uri().authority() { - span.record(SERVER_ADDRESS, authority.host()); + span.record(otel_attribute::SERVER_ADDRESS, authority.host()); if let Some(port) = authority.port_u16() { - span.record(SERVER_PORT, port); + span.record(otel_attribute::SERVER_PORT, port); } } @@ -530,7 +528,7 @@ impl RequestSender { } *uri = builder.build().unwrap(); } - tracing::Span::current().record(URL_FULL, uri.to_string()); + tracing::Span::current().record(otel_attribute::URL_FULL, uri.to_string()); let is_self_request = match request.uri().authority() { // Some SDKs require an authority, so we support e.g. http://self.alt/self-request @@ -637,7 +635,10 @@ impl RequestSender { .map(|body| body.map_err(hyper_request_error).boxed_unsync()); let span = tracing::Span::current(); - span.record(HTTP_RESPONSE_STATUS_CODE, resp.status().as_u16()); + span.record( + otel_attribute::HTTP_RESPONSE_STATUS_CODE, + resp.status().as_u16(), + ); record_content_length_header(&span, resp.headers(), "http.response.header.content-length"); diff --git a/crates/trigger-http/src/instrument.rs b/crates/trigger-http/src/instrument.rs index 14dbf94a08..e214835cdc 100644 --- a/crates/trigger-http/src/instrument.rs +++ b/crates/trigger-http/src/instrument.rs @@ -1,8 +1,6 @@ use anyhow::Result; use http::Response; -use opentelemetry_semantic_conventions::attribute::{ - ERROR_TYPE, HTTP_RESPONSE_STATUS_CODE, HTTP_ROUTE, -}; +use opentelemetry_semantic_conventions::attribute as otel_attribute; use tracing::Level; use crate::Body; @@ -48,20 +46,23 @@ pub(crate) fn finalize_http_span( let matched_route = response.extensions().get::(); // Set otel.name and http.route if let Some(MatchedRoute { route }) = matched_route { - span.record(HTTP_ROUTE, route); + span.record(otel_attribute::HTTP_ROUTE, route); span.record("otel.name", format!("{method} {route}")); } else { span.record("otel.name", method); } // Set status code - span.record(HTTP_RESPONSE_STATUS_CODE, response.status().as_u16()); + span.record( + otel_attribute::HTTP_RESPONSE_STATUS_CODE, + response.status().as_u16(), + ); Ok(response) } Err(err) => { instrument_error(&err); - span.record(HTTP_RESPONSE_STATUS_CODE, 500); + span.record(otel_attribute::HTTP_RESPONSE_STATUS_CODE, 500); span.record("otel.name", method); Err(err) } @@ -72,7 +73,7 @@ pub(crate) fn finalize_http_span( pub(crate) fn instrument_error(err: &anyhow::Error) { let span = tracing::Span::current(); tracing::event!(target:module_path!(), Level::INFO, error = %err); - span.record(ERROR_TYPE, format!("{err:?}")); + span.record(otel_attribute::ERROR_TYPE, format!("{err:?}")); } /// MatchedRoute is used as a response extension to track the route that was matched for OTel