Skip to content

Commit f79e0cc

Browse files
committed
refactor: move common grpc functions into a grpc module
1 parent 081e86e commit f79e0cc

File tree

5 files changed

+209
-244
lines changed

5 files changed

+209
-244
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
use http::HeaderMap;
2+
3+
/// [`gRPC` status codes](https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc)
4+
/// copied from tonic
5+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6+
#[allow(dead_code)]
7+
pub enum GrpcCode {
8+
/// The operation completed successfully.
9+
Ok = 0,
10+
11+
/// The operation was cancelled.
12+
Cancelled = 1,
13+
14+
/// Unknown error.
15+
Unknown = 2,
16+
17+
/// Client specified an invalid argument.
18+
InvalidArgument = 3,
19+
20+
/// Deadline expired before operation could complete.
21+
DeadlineExceeded = 4,
22+
23+
/// Some requested entity was not found.
24+
NotFound = 5,
25+
26+
/// Some entity that we attempted to create already exists.
27+
AlreadyExists = 6,
28+
29+
/// The caller does not have permission to execute the specified operation.
30+
PermissionDenied = 7,
31+
32+
/// Some resource has been exhausted.
33+
ResourceExhausted = 8,
34+
35+
/// The system is not in a state required for the operation's execution.
36+
FailedPrecondition = 9,
37+
38+
/// The operation was aborted.
39+
Aborted = 10,
40+
41+
/// Operation was attempted past the valid range.
42+
OutOfRange = 11,
43+
44+
/// Operation is not implemented or not supported.
45+
Unimplemented = 12,
46+
47+
/// Internal error.
48+
Internal = 13,
49+
50+
/// The service is currently unavailable.
51+
Unavailable = 14,
52+
53+
/// Unrecoverable data loss or corruption.
54+
DataLoss = 15,
55+
56+
/// The request does not have valid authentication credentials
57+
Unauthenticated = 16,
58+
}
59+
60+
/// If "grpc-status" can not be extracted from http response, the status "0" (Ok) is defined
61+
//TODO create similar but with tonic::Response<B> ? and use of [Status in tonic](https://docs.rs/tonic/latest/tonic/struct.Status.html) (more complete)
62+
pub fn update_span_from_response<B>(
63+
span: &tracing::Span,
64+
response: &http::Response<B>,
65+
is_spankind_server: bool,
66+
) {
67+
let status = status_from_http_header(response.headers())
68+
.or_else(|| status_from_http_status(response.status()))
69+
.unwrap_or(GrpcCode::Ok as u16);
70+
span.record("rpc.grpc.status_code", status);
71+
72+
if status_is_error(status, is_spankind_server) {
73+
span.record("otel.status_code", "ERROR");
74+
} else {
75+
span.record("otel.status_code", "OK");
76+
}
77+
}
78+
79+
/// based on [Status in tonic](https://docs.rs/tonic/latest/tonic/struct.Status.html#method.from_header_map)
80+
fn status_from_http_header(headers: &HeaderMap) -> Option<u16> {
81+
headers
82+
.get("grpc-status")
83+
.and_then(|v| v.to_str().ok())
84+
.and_then(|v| v.parse::<u16>().ok())
85+
}
86+
87+
fn status_from_http_status(status_code: http::StatusCode) -> Option<u16> {
88+
match status_code {
89+
// Borrowed from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
90+
http::StatusCode::BAD_REQUEST => Some(GrpcCode::Internal as u16),
91+
http::StatusCode::UNAUTHORIZED => Some(GrpcCode::Unauthenticated as u16),
92+
http::StatusCode::FORBIDDEN => Some(GrpcCode::PermissionDenied as u16),
93+
http::StatusCode::NOT_FOUND => Some(GrpcCode::Unimplemented as u16),
94+
http::StatusCode::TOO_MANY_REQUESTS
95+
| http::StatusCode::BAD_GATEWAY
96+
| http::StatusCode::SERVICE_UNAVAILABLE
97+
| http::StatusCode::GATEWAY_TIMEOUT => Some(GrpcCode::Unavailable as u16),
98+
// We got a 200 but no trailers, we can infer that this request is finished.
99+
//
100+
// This can happen when a streaming response sends two Status but
101+
// gRPC requires that we end the stream after the first status.
102+
//
103+
// https://github.com/hyperium/tonic/issues/681
104+
http::StatusCode::OK => None,
105+
_ => Some(GrpcCode::Unknown as u16),
106+
}
107+
}
108+
109+
#[inline]
110+
#[must_use]
111+
/// see [Semantic Conventions for gRPC | OpenTelemetry](https://opentelemetry.io/docs/specs/semconv/rpc/grpc/)
112+
/// see [GRPC Core: Status codes and their use in gRPC](https://grpc.github.io/grpc/core/md_doc_statuscodes.html)
113+
pub fn status_is_error(status: u16, is_spankind_server: bool) -> bool {
114+
if is_spankind_server {
115+
status == 2 || status == 4 || status == 12 || status == 13 || status == 14 || status == 15
116+
} else {
117+
status != 0
118+
}
119+
}
120+
121+
fn update_span_from_error<E>(span: &tracing::Span, error: &E)
122+
where
123+
E: std::error::Error,
124+
{
125+
span.record("otel.status_code", "ERROR");
126+
span.record("rpc.grpc.status_code", 2);
127+
span.record("exception.message", error.to_string());
128+
error
129+
.source()
130+
.map(|s| span.record("exception.message", s.to_string()));
131+
}
132+
133+
pub fn update_span_from_response_or_error<B, E>(
134+
span: &tracing::Span,
135+
response: &Result<http::Response<B>, E>,
136+
) where
137+
E: std::error::Error,
138+
{
139+
match response {
140+
Ok(response) => {
141+
update_span_from_response(span, response, true);
142+
}
143+
Err(err) => {
144+
update_span_from_error(span, err);
145+
}
146+
}
147+
}
148+
149+
// [opentelemetry-specification/.../rpc.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md)
150+
//TODO create similar but with tonic::Request<B> ?
151+
#[allow(clippy::needless_pass_by_value)]
152+
pub(crate) fn make_span_from_request<B>(
153+
req: &http::Request<B>,
154+
kind: opentelemetry::trace::SpanKind,
155+
) -> tracing::Span {
156+
use crate::http::{extract_service_method, http_host, user_agent};
157+
use crate::otel_trace_span;
158+
use tracing::field::Empty;
159+
160+
let (service, method) = extract_service_method(req.uri());
161+
otel_trace_span!(
162+
"GRPC request",
163+
http.user_agent = %user_agent(req),
164+
otel.name = format!("{service}/{method}"),
165+
otel.kind = ?kind,
166+
otel.status_code = Empty,
167+
rpc.system ="grpc",
168+
rpc.service = %service,
169+
rpc.method = %method,
170+
rpc.grpc.status_code = Empty, // to set on response
171+
server.address = %http_host(req),
172+
exception.message = Empty, // to set on response
173+
exception.details = Empty, // to set on response
174+
)
175+
}
176+
177+
// if let Some(host_name) = SYSTEM.host_name() {
178+
// attributes.push(NET_HOST_NAME.string(host_name));
179+
// }
180+
181+
#[cfg(test)]
182+
mod tests {
183+
use super::*;
184+
use rstest::rstest;
185+
186+
#[rstest]
187+
#[case(0)]
188+
#[case(16)]
189+
#[case(-1)]
190+
fn test_status_from_http_header(#[case] input: i32) {
191+
let mut headers = http::HeaderMap::new();
192+
headers.insert("grpc-status", input.to_string().parse().unwrap());
193+
if input > -1 {
194+
assert_eq!(
195+
status_from_http_header(&headers),
196+
Some(u16::try_from(input).unwrap())
197+
);
198+
} else {
199+
assert_eq!(status_from_http_header(&headers), None);
200+
}
201+
}
202+
}
Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,7 @@
1-
use std::error::Error;
1+
use super::grpc;
22

3-
use crate::http::{extract_service_method, http_host, user_agent};
4-
use crate::otel_trace_span;
5-
use tracing::field::Empty;
3+
pub use grpc::update_span_from_response_or_error;
64

7-
use super::grpc_update_span_from_response;
8-
9-
// [opentelemetry-specification/.../rpc.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md)
10-
//TODO create similar but with tonic::Request<B> ?
115
pub fn make_span_from_request<B>(req: &http::Request<B>) -> tracing::Span {
12-
let (service, method) = extract_service_method(req.uri());
13-
otel_trace_span!(
14-
"GRPC request",
15-
http.user_agent = %user_agent(req),
16-
otel.name = format!("{service}/{method}"),
17-
otel.kind = ?opentelemetry::trace::SpanKind::Client,
18-
otel.status_code = Empty,
19-
rpc.system ="grpc",
20-
rpc.service = %service,
21-
rpc.method = %method,
22-
rpc.grpc.status_code = Empty, // to set on response
23-
server.address = %http_host(req),
24-
exception.message = Empty, // to set on response
25-
exception.details = Empty, // to set on response
26-
)
27-
}
28-
29-
fn update_span_from_error<E>(span: &tracing::Span, error: &E)
30-
where
31-
E: Error,
32-
{
33-
span.record("otel.status_code", "ERROR");
34-
span.record("rpc.grpc.status_code", 2);
35-
span.record("exception.message", error.to_string());
36-
error
37-
.source()
38-
.map(|s| span.record("exception.message", s.to_string()));
39-
}
40-
41-
pub fn update_span_from_response_or_error<B, E>(
42-
span: &tracing::Span,
43-
response: &Result<http::Response<B>, E>,
44-
) where
45-
E: Error,
46-
{
47-
match response {
48-
Ok(response) => {
49-
grpc_update_span_from_response(span, response, false);
50-
}
51-
Err(err) => {
52-
update_span_from_error(span, err);
53-
}
54-
}
6+
grpc::make_span_from_request(req, opentelemetry::trace::SpanKind::Client)
557
}
Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,6 @@
1-
use crate::http::{extract_service_method, http_host, user_agent};
2-
use crate::otel_trace_span;
3-
use tracing::field::Empty;
1+
use super::grpc;
2+
pub use grpc::update_span_from_response_or_error;
43

5-
use super::grpc_update_span_from_response;
6-
7-
//TODO create similar but with tonic::Request<B> ?
8-
/// see [Semantic Conventions for gRPC | OpenTelemetry](https://opentelemetry.io/docs/specs/semconv/rpc/grpc/#grpc-status)
94
pub fn make_span_from_request<B>(req: &http::Request<B>) -> tracing::Span {
10-
let (service, method) = extract_service_method(req.uri());
11-
otel_trace_span!(
12-
"GRPC request",
13-
http.user_agent = %user_agent(req),
14-
otel.name = format!("{service}/{method}"),
15-
otel.kind = ?opentelemetry::trace::SpanKind::Server,
16-
otel.status_code = Empty,
17-
rpc.system ="grpc",
18-
rpc.service = %service,
19-
rpc.method = %method,
20-
rpc.grpc.status_code = Empty, // to set on response
21-
server.address = %http_host(req),
22-
exception.message = Empty, // to set on response
23-
exception.details = Empty, // to set on response
24-
)
25-
}
26-
27-
// fn update_span_from_error<E>(span: &tracing::Span, error: &E) {
28-
// span.record("otel.status_code", "ERROR");
29-
// span.record("rpc.grpc.status_code", 2);
30-
// }
31-
32-
fn update_span_from_error<E>(span: &tracing::Span, error: &E)
33-
where
34-
E: std::error::Error,
35-
{
36-
span.record("otel.status_code", "ERROR");
37-
span.record("rpc.grpc.status_code", 2);
38-
span.record("exception.message", error.to_string());
39-
error
40-
.source()
41-
.map(|s| span.record("exception.message", s.to_string()));
42-
}
43-
44-
pub fn update_span_from_response_or_error<B, E>(
45-
span: &tracing::Span,
46-
response: &Result<http::Response<B>, E>,
47-
) where
48-
E: std::error::Error,
49-
{
50-
match response {
51-
Ok(response) => {
52-
grpc_update_span_from_response(span, response, true);
53-
}
54-
Err(err) => {
55-
update_span_from_error(span, err);
56-
}
57-
}
5+
grpc::make_span_from_request(req, opentelemetry::trace::SpanKind::Server)
586
}

tracing-opentelemetry-instrumentation-sdk/src/http/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod grpc;
12
pub mod grpc_client;
23
pub mod grpc_server;
34
pub mod http_server;

0 commit comments

Comments
 (0)