Skip to content

Commit b74b772

Browse files
committed
🐛 on grpc when no status code into header, fallback to OK (previously Unkown)
It's a quick fix, need test and maybe a better implementation and understanding (and being able to create tonic middleware that use tonic response and not http response) FIX #122 Signed-off-by: David Bernard <david.bernard.31@gmail.com>
1 parent ebb74c3 commit b74b772

File tree

5 files changed

+155
-18
lines changed

5 files changed

+155
-18
lines changed

examples/grpc/proto/helloworld.proto

+8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
syntax = "proto3";
22

3+
import "google/protobuf/empty.proto";
34
package helloworld;
45

56
service Greeter {
67
rpc SayHello (HelloRequest) returns (HelloReply) {}
8+
rpc SayStatus (StatusRequest) returns (google.protobuf.Empty) {}
79
}
810

911
message HelloRequest {
@@ -13,3 +15,9 @@ message HelloRequest {
1315
message HelloReply {
1416
string message = 1;
1517
}
18+
19+
message StatusRequest {
20+
// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc
21+
int32 code = 1;
22+
string message = 2;
23+
}

examples/grpc/src/client.rs

+31-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use hello_world::greeter_client::GreeterClient;
2-
use hello_world::HelloRequest;
2+
use hello_world::{HelloRequest, StatusRequest};
33
use tonic::transport::Channel;
4+
use tonic::Code;
45
use tonic_tracing_opentelemetry::middleware::client::OtelGrpcLayer;
56
use tower::ServiceBuilder;
67

@@ -21,14 +22,35 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2122
let channel = ServiceBuilder::new().layer(OtelGrpcLayer).service(channel);
2223

2324
let mut client = GreeterClient::new(channel);
24-
25-
let request = tonic::Request::new(HelloRequest {
26-
name: "Tonic".into(),
27-
});
28-
29-
let response = client.say_hello(request).await?;
30-
31-
println!("RESPONSE={:?}", response);
25+
{
26+
let request = tonic::Request::new(HelloRequest {
27+
name: "Tonic".into(),
28+
});
29+
30+
let response = client.say_hello(request).await?;
31+
32+
println!("RESPONSE={:?}", response);
33+
}
34+
{
35+
let request = tonic::Request::new(StatusRequest {
36+
code: Code::NotFound.into(),
37+
message: "not found...".into(),
38+
});
39+
40+
let response = client.say_status(request).await;
41+
42+
println!("RESPONSE={:?}", response);
43+
}
44+
{
45+
let request = tonic::Request::new(StatusRequest {
46+
code: Code::DeadlineExceeded.into(),
47+
message: "deadline...".into(),
48+
});
49+
50+
let response = client.say_status(request).await;
51+
52+
println!("RESPONSE={:?}", response);
53+
}
3254

3355
opentelemetry::global::shutdown_tracer_provider();
3456
Ok(())

examples/grpc/src/server.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use hello_world::greeter_server::{Greeter, GreeterServer};
2-
use hello_world::{HelloReply, HelloRequest};
2+
use hello_world::{HelloReply, HelloRequest, StatusRequest};
3+
use tonic::Code;
34
use tonic::{transport::Server, Request, Response, Status};
45
use tonic_tracing_opentelemetry::middleware::{filters, server};
56

@@ -32,6 +33,12 @@ impl Greeter for MyGreeter {
3233
};
3334
Ok(Response::new(reply))
3435
}
36+
37+
#[tracing::instrument(skip(self, request))]
38+
async fn say_status(&self, request: Request<StatusRequest>) -> Result<Response<()>, Status> {
39+
let request = request.into_inner();
40+
Err(Status::new(Code::from(request.code), request.message))
41+
}
3542
}
3643

3744
#[tokio::main]

rust-toolchain.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[toolchain]
2+
channel = "1.72.1"

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

+106-8
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,73 @@ pub fn http_host<B>(req: &http::Request<B>) -> &str {
101101
.unwrap_or("")
102102
}
103103

104-
/// If "grpc-status" can not be extracted from http response, the status "2" (UNKNOWN error) is defined
105-
//TODO create similar but with tonic::Response<B> ?
104+
/// gRPC status codes
105+
/// [gRPC status codes]: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc
106+
/// copied from tonic
107+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
108+
pub enum GrpcCode {
109+
/// The operation completed successfully.
110+
Ok = 0,
111+
112+
/// The operation was cancelled.
113+
Cancelled = 1,
114+
115+
/// Unknown error.
116+
Unknown = 2,
117+
118+
/// Client specified an invalid argument.
119+
InvalidArgument = 3,
120+
121+
/// Deadline expired before operation could complete.
122+
DeadlineExceeded = 4,
123+
124+
/// Some requested entity was not found.
125+
NotFound = 5,
126+
127+
/// Some entity that we attempted to create already exists.
128+
AlreadyExists = 6,
129+
130+
/// The caller does not have permission to execute the specified operation.
131+
PermissionDenied = 7,
132+
133+
/// Some resource has been exhausted.
134+
ResourceExhausted = 8,
135+
136+
/// The system is not in a state required for the operation's execution.
137+
FailedPrecondition = 9,
138+
139+
/// The operation was aborted.
140+
Aborted = 10,
141+
142+
/// Operation was attempted past the valid range.
143+
OutOfRange = 11,
144+
145+
/// Operation is not implemented or not supported.
146+
Unimplemented = 12,
147+
148+
/// Internal error.
149+
Internal = 13,
150+
151+
/// The service is currently unavailable.
152+
Unavailable = 14,
153+
154+
/// Unrecoverable data loss or corruption.
155+
DataLoss = 15,
156+
157+
/// The request does not have valid authentication credentials
158+
Unauthenticated = 16,
159+
}
160+
161+
/// If "grpc-status" can not be extracted from http response, the status "0" (Ok) is defined
162+
//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)
106163
pub fn grpc_update_span_from_response<B>(
107164
span: &tracing::Span,
108165
response: &http::Response<B>,
109166
is_spankind_server: bool,
110167
) {
111-
let status = response
112-
.headers()
113-
.get("grpc-status")
114-
.and_then(|v| v.to_str().ok())
115-
.and_then(|v| v.parse::<u16>().ok())
116-
.unwrap_or(2);
168+
let status = grpc_status_from_http_header(response.headers())
169+
.or_else(|| grpc_status_from_http_status(response.status()))
170+
.unwrap_or(GrpcCode::Ok as u16);
117171
span.record("rpc.grpc.status_code", status);
118172

119173
if grpc_status_is_error(status, is_spankind_server) {
@@ -123,6 +177,36 @@ pub fn grpc_update_span_from_response<B>(
123177
}
124178
}
125179

180+
/// based on [Status in tonic](https://docs.rs/tonic/latest/tonic/struct.Status.html#method.from_header_map)
181+
fn grpc_status_from_http_header(headers: &HeaderMap) -> Option<u16> {
182+
headers
183+
.get("grpc-status")
184+
.and_then(|v| v.to_str().ok())
185+
.and_then(|v| v.parse::<u16>().ok())
186+
}
187+
188+
fn grpc_status_from_http_status(status_code: http::StatusCode) -> Option<u16> {
189+
match status_code {
190+
// Borrowed from https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md
191+
http::StatusCode::BAD_REQUEST => Some(GrpcCode::Internal as u16),
192+
http::StatusCode::UNAUTHORIZED => Some(GrpcCode::Unauthenticated as u16),
193+
http::StatusCode::FORBIDDEN => Some(GrpcCode::PermissionDenied as u16),
194+
http::StatusCode::NOT_FOUND => Some(GrpcCode::Unimplemented as u16),
195+
http::StatusCode::TOO_MANY_REQUESTS
196+
| http::StatusCode::BAD_GATEWAY
197+
| http::StatusCode::SERVICE_UNAVAILABLE
198+
| http::StatusCode::GATEWAY_TIMEOUT => Some(GrpcCode::Unavailable as u16),
199+
// We got a 200 but no trailers, we can infer that this request is finished.
200+
//
201+
// This can happen when a streaming response sends two Status but
202+
// gRPC requires that we end the stream after the first status.
203+
//
204+
// https://github.com/hyperium/tonic/issues/681
205+
http::StatusCode::OK => None,
206+
_ => Some(GrpcCode::Unknown as u16),
207+
}
208+
}
209+
126210
#[inline]
127211
#[must_use]
128212
/// see [Semantic Conventions for gRPC | OpenTelemetry](https://opentelemetry.io/docs/specs/semconv/rpc/grpc/)
@@ -166,4 +250,18 @@ mod tests {
166250
let uri: Uri = input.parse().unwrap();
167251
check!(url_scheme(&uri) == expected);
168252
}
253+
254+
#[rstest]
255+
#[case(0)]
256+
#[case(16)]
257+
#[case(-1)]
258+
fn test_grpc_status_from_http_header(#[case] input: i32) {
259+
let mut headers = http::HeaderMap::new();
260+
headers.insert("grpc-status", input.to_string().parse().unwrap());
261+
if input > -1 {
262+
assert_eq!(grpc_status_from_http_header(&headers), Some(input as u16));
263+
} else {
264+
assert_eq!(grpc_status_from_http_header(&headers), None);
265+
}
266+
}
169267
}

0 commit comments

Comments
 (0)