actix_web_opentelemetry/
util.rs1use actix_http::header::{self, CONTENT_LENGTH};
2use actix_web::{
3 dev::ServiceRequest,
4 http::{Method, Version},
5};
6use opentelemetry::{KeyValue, Value};
7use opentelemetry_semantic_conventions::{
8 attribute::MESSAGING_MESSAGE_BODY_SIZE,
9 trace::{
10 CLIENT_ADDRESS, HTTP_REQUEST_METHOD, HTTP_ROUTE, NETWORK_PEER_ADDRESS,
11 NETWORK_PROTOCOL_VERSION, SERVER_ADDRESS, SERVER_PORT, URL_PATH, URL_QUERY, URL_SCHEME,
12 USER_AGENT_ORIGINAL,
13 },
14};
15
16#[cfg(feature = "awc")]
17#[inline]
18pub(super) fn http_url(uri: &actix_web::http::Uri) -> String {
19 let scheme = uri.scheme().map(|s| s.as_str()).unwrap_or_default();
20 let host = uri.host().unwrap_or_default();
21 let path = uri.path();
22 let port = uri.port_u16().filter(|&p| p != 80 && p != 443);
23 let (query, query_delimiter) = if let Some(query) = uri.query() {
24 (query, "?")
25 } else {
26 ("", "")
27 };
28
29 if let Some(port) = port {
30 format!("{scheme}://{host}:{port}{path}{query_delimiter}{query}")
31 } else {
32 format!("{scheme}://{host}{path}{query_delimiter}{query}")
33 }
34}
35
36#[inline]
37pub(super) fn http_method_str(method: &Method) -> Value {
38 match method {
39 &Method::OPTIONS => "OPTIONS".into(),
40 &Method::GET => "GET".into(),
41 &Method::POST => "POST".into(),
42 &Method::PUT => "PUT".into(),
43 &Method::DELETE => "DELETE".into(),
44 &Method::HEAD => "HEAD".into(),
45 &Method::TRACE => "TRACE".into(),
46 &Method::CONNECT => "CONNECT".into(),
47 &Method::PATCH => "PATCH".into(),
48 other => other.to_string().into(),
49 }
50}
51
52#[inline]
53pub(super) fn protocol_version(version: Version) -> Value {
54 match version {
55 Version::HTTP_09 => "0.9".into(),
56 Version::HTTP_10 => "1.0".into(),
57 Version::HTTP_11 => "1.1".into(),
58 Version::HTTP_2 => "2".into(),
59 Version::HTTP_3 => "3".into(),
60 other => format!("{:?}", other).into(),
61 }
62}
63
64#[inline]
65pub(super) fn url_scheme(scheme: &str) -> Value {
66 match scheme {
67 "http" => "http".into(),
68 "https" => "https".into(),
69 other => other.to_string().into(),
70 }
71}
72
73pub(super) fn trace_attributes_from_request(
74 req: &ServiceRequest,
75 http_route: &str,
76) -> Vec<KeyValue> {
77 let conn_info = req.connection_info();
78 let remote_addr = conn_info.realip_remote_addr();
79
80 let mut attributes = Vec::with_capacity(14);
81
82 attributes.push(KeyValue::new(HTTP_ROUTE, http_route.to_owned()));
85 if let Some(remote) = remote_addr {
86 attributes.push(KeyValue::new(CLIENT_ADDRESS, remote.to_string()));
87 }
88 if let Some(peer_addr) = req.peer_addr().map(|socket| socket.ip().to_string()) {
89 if Some(peer_addr.as_str()) != remote_addr {
90 attributes.push(KeyValue::new(NETWORK_PEER_ADDRESS, peer_addr));
92 }
93 }
94 let mut host_parts = conn_info.host().split_terminator(':');
95 if let Some(host) = host_parts.next() {
96 attributes.push(KeyValue::new(SERVER_ADDRESS, host.to_string()));
97 }
98 if let Some(port) = host_parts.next().and_then(|port| port.parse::<i64>().ok()) {
99 if port != 80 && port != 443 {
100 attributes.push(KeyValue::new(SERVER_PORT, port));
101 }
102 }
103 if let Some(path_query) = req.uri().path_and_query() {
104 if path_query.path() != "/" {
105 attributes.push(KeyValue::new(URL_PATH, path_query.path().to_string()));
106 }
107 if let Some(query) = path_query.query() {
108 attributes.push(KeyValue::new(URL_QUERY, query.to_string()));
109 }
110 }
111 attributes.push(KeyValue::new(URL_SCHEME, url_scheme(conn_info.scheme())));
112
113 attributes.push(KeyValue::new(
116 HTTP_REQUEST_METHOD,
117 http_method_str(req.method()),
118 ));
119 attributes.push(KeyValue::new(
120 NETWORK_PROTOCOL_VERSION,
121 protocol_version(req.version()),
122 ));
123
124 if let Some(content_length) = req
125 .headers()
126 .get(CONTENT_LENGTH)
127 .and_then(|len| len.to_str().ok().and_then(|s| s.parse::<i64>().ok()))
128 .filter(|&len| len > 0)
129 {
130 attributes.push(KeyValue::new(MESSAGING_MESSAGE_BODY_SIZE, content_length));
131 }
132
133 if let Some(user_agent) = req
134 .headers()
135 .get(header::USER_AGENT)
136 .and_then(|s| s.to_str().ok())
137 {
138 attributes.push(KeyValue::new(USER_AGENT_ORIGINAL, user_agent.to_string()));
139 }
140
141 attributes
142}
143
144#[cfg(feature = "metrics")]
146pub fn metrics_attributes_from_request(
147 req: &ServiceRequest,
148 http_route: std::borrow::Cow<'static, str>,
149) -> Vec<KeyValue> {
150 let conn_info = req.connection_info();
151
152 let mut attributes = Vec::with_capacity(7);
153 attributes.push(KeyValue::new(HTTP_ROUTE, http_route));
154 attributes.push(KeyValue::new(
155 HTTP_REQUEST_METHOD,
156 http_method_str(req.method()),
157 ));
158 attributes.push(KeyValue::new(
159 NETWORK_PROTOCOL_VERSION,
160 protocol_version(req.version()),
161 ));
162
163 let mut host_parts = conn_info.host().split_terminator(':');
164 if let Some(host) = host_parts.next() {
165 attributes.push(KeyValue::new(SERVER_ADDRESS, host.to_string()));
166 }
167 if let Some(port) = host_parts.next().and_then(|port| port.parse::<i64>().ok()) {
168 attributes.push(KeyValue::new(SERVER_PORT, port))
169 }
170 attributes.push(KeyValue::new(URL_SCHEME, url_scheme(conn_info.scheme())));
171
172 attributes
173}