Skip to content

Commit 6855304

Browse files
committed
fix(google-api-core): align HTTP T3 and T4 OpenTelemetry span status and attributes
1 parent 2f1ae8f commit 6855304

3 files changed

Lines changed: 199 additions & 21 deletions

File tree

‎packages/gapic-generator/tests/system/telemetry_requirements_matrix.csv‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
Feature ID,Feature Name,Tier,Attempt,Transport,Scenario,Span Count,Span Kind,Span Name,Span Status,rpc.system.name,rpc.method,rpc.response.status_code,http.request.method,http.response.status_code,rpc.grpc.status_code,url.domain,url.template,server.address,server.port,error.type,status.message,resend_count,parent_span_id,Extra / Permitted Attributes
22
F1.1,Tracing Off (HTTP),T4,N/A,HTTP/REST,Tracing Off,0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,No spans emitted
3-
F1.2,Unary Success (HTTP),T4,0,HTTP/REST,Happy Path,1,CLIENT,POST /v1beta1/echo:echo,OK,NOT SET,NOT SET,NOT SET,POST,200,NOT SET,googleapis.com,/v1beta1/echo:echo,localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,"url.full, http.request.body.size"
4-
F1.3,Server Error (HTTP),T4,0,HTTP/REST,Server Failure,1,CLIENT,POST /v1beta1/echo:echo,ERROR,NOT SET,NOT SET,NOT SET,POST,400,NOT SET,googleapis.com,/v1beta1/echo:echo,localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,"url.full, http.request.body.size"
5-
F1.4,Client Timeout (HTTP),T4,0,HTTP/REST,Client Timeout,1,CLIENT,POST /v1beta1/echo:block,ERROR,NOT SET,NOT SET,NOT SET,POST,NOT SET,NOT SET,googleapis.com,/v1beta1/echo:block,localhost,7469,ReadTimeout,Read timed out,NOT SET,T3.span_id,url.full
6-
F1.5a,Retry Recovery - Attempt 1 (HTTP),T4,0,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,NOT SET,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,url.full
7-
F1.5b,Retry Recovery - Attempt 2 (HTTP),T4,1,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},OK,NOT SET,NOT SET,NOT SET,POST,200,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,url.full
3+
F1.2,Unary Success (HTTP),T4,0,HTTP/REST,Happy Path,1,CLIENT,POST /v1beta1/echo:echo,UNSET,http,NOT SET,NOT SET,POST,200,NOT SET,googleapis.com,/v1beta1/echo:echo,localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,"url.full, http.request.body.size"
4+
F1.3,Server Error (HTTP),T4,0,HTTP/REST,Server Failure,1,CLIENT,POST /v1beta1/echo:echo,ERROR,http,NOT SET,NOT SET,POST,400,NOT SET,googleapis.com,/v1beta1/echo:echo,localhost,7469,400,Simulated unretryable invalid argument error.,NOT SET,T3.span_id,"url.full, http.request.body.size"
5+
F1.4,Client Timeout (HTTP),T4,0,HTTP/REST,Client Timeout,1,CLIENT,POST /v1beta1/echo:block,ERROR,http,NOT SET,NOT SET,POST,NOT SET,NOT SET,googleapis.com,/v1beta1/echo:block,localhost,7469,ReadTimeout,Read timed out,NOT SET,T3.span_id,url.full
6+
F1.5a,Retry Recovery - Attempt 1 (HTTP),T4,0,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,http,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,503,Temporary glitch,NOT SET,T3.span_id,url.full
7+
F1.5b,Retry Recovery - Attempt 2 (HTTP),T4,1,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},UNSET,http,NOT SET,NOT SET,POST,200,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,url.full
88
F1.6,Tracing Off (gRPC),T4,N/A,gRPC,Tracing Off,0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,No spans emitted
99
F1.7,Unary Success (gRPC),T4,0,gRPC,Happy Path,1,CLIENT,google.showcase.v1beta1.Echo/Echo,UNSET,grpc,google.showcase.v1beta1.Echo/Echo,OK,NOT SET,NOT SET,0,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,T3.span_id,"rpc.system, rpc.service"
1010
F1.8,Server Error (gRPC),T4,0,gRPC,Server Failure,1,CLIENT,google.showcase.v1beta1.Echo/Echo,ERROR,grpc,google.showcase.v1beta1.Echo/Echo,NOT SET,NOT SET,NOT SET,3,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,T3.span_id,"rpc.system, rpc.service"
1111
F1.9,Client Timeout (gRPC),T4,0,gRPC,Client Timeout,1,CLIENT,google.showcase.v1beta1.Echo/Block,ERROR,grpc,google.showcase.v1beta1.Echo/Block,NOT SET,NOT SET,NOT SET,4,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,T3.span_id,"rpc.system, rpc.service"
1212
F1.10a,Retry Recovery - Attempt 1 (gRPC),T4,0,gRPC,Retry Recovery,2,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,ERROR,grpc,google.showcase.v1beta1.SequenceService/AttemptSequence,NOT SET,NOT SET,NOT SET,14,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,T3.span_id,"rpc.system, rpc.service"
1313
F1.10b,Retry Recovery - Attempt 2 (gRPC),T4,1,gRPC,Retry Recovery,2,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,UNSET,grpc,google.showcase.v1beta1.SequenceService/AttemptSequence,OK,NOT SET,NOT SET,0,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,T3.span_id,"rpc.system, rpc.service"
1414
F2.1,Tracing Off (HTTP),T3,N/A,HTTP/REST,Tracing Off,0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,No spans emitted
15-
F2.2,Unary Success (HTTP),T3,0,HTTP/REST,Happy Path,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,UNSET,http,google.showcase.v1beta1.Echo/Echo,OK,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Strict ceiling (no unknown attrs)
16-
F2.3,Server Error (HTTP),T3,0,HTTP/REST,Server Failure,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,ERROR,http,google.showcase.v1beta1.Echo/Echo,BAD_REQUEST,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,BAD_REQUEST,Simulated unretryable invalid argument error.,NOT SET,None (Root),"gcp.errors.domain, gcp.errors.metadata.*"
17-
F2.4,Client Timeout (HTTP),T3,0,HTTP/REST,Client Timeout,1,INTERNAL,google.showcase.v1beta1.Echo/Block,ERROR,http,google.showcase.v1beta1.Echo/Block,ReadTimeout,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,ReadTimeout,Read timed out,NOT SET,None (Root),gcp.errors.domain
15+
F2.2,Unary Success (HTTP),T3,0,HTTP/REST,Happy Path,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,UNSET,http,google.showcase.v1beta1.Echo/Echo,OK,NOT SET,200,NOT SET,googleapis.com,/v1beta1/echo:echo,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Strict ceiling (no unknown attrs)
16+
F2.3,Server Error (HTTP),T3,0,HTTP/REST,Server Failure,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,ERROR,http,google.showcase.v1beta1.Echo/Echo,BAD_REQUEST,NOT SET,400,NOT SET,googleapis.com,/v1beta1/echo:echo,NOT SET,NOT SET,BAD_REQUEST,Simulated unretryable invalid argument error.,NOT SET,None (Root),"gcp.errors.domain, gcp.errors.metadata.*"
17+
F2.4,Client Timeout (HTTP),T3,0,HTTP/REST,Client Timeout,1,INTERNAL,google.showcase.v1beta1.Echo/Block,ERROR,http,google.showcase.v1beta1.Echo/Block,ReadTimeout,NOT SET,NOT SET,NOT SET,googleapis.com,/v1beta1/echo:block,NOT SET,NOT SET,ReadTimeout,Read timed out,NOT SET,None (Root),gcp.errors.domain
1818
F2.5,Tracing Off (gRPC),T3,N/A,gRPC,Tracing Off,0,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,No spans emitted
1919
F2.6,Unary Success (gRPC),T3,0,gRPC,Happy Path,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,UNSET,grpc,google.showcase.v1beta1.Echo/Echo,OK,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Strict ceiling (no unknown attrs)
2020
F2.7,Server Error (gRPC),T3,0,gRPC,Server Failure,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,ERROR,grpc,google.showcase.v1beta1.Echo/Echo,INVALID_ARGUMENT,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,INVALID_ARGUMENT,Simulated unretryable invalid argument error.,NOT SET,None (Root),"gcp.errors.domain, gcp.errors.metadata.*"
2121
F2.8,Client Timeout (gRPC),T3,0,gRPC,Client Timeout,1,INTERNAL,google.showcase.v1beta1.Echo/Block,ERROR,grpc,google.showcase.v1beta1.Echo/Block,DEADLINE_EXCEEDED,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,DEADLINE_EXCEEDED,Deadline Exceeded,NOT SET,None (Root),gcp.errors.domain
22-
F3.1a,Retry Succeeds - Root (HTTP),T3,0,HTTP/REST,Retry Recovery,1,INTERNAL,google.showcase.v1beta1.SequenceService/AttemptSequence,UNSET,http,google.showcase.v1beta1.SequenceService/AttemptSequence,OK,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Parent aggregates OK; T4 children nested under T3
23-
F3.1b,Retry Succeeds - Attempt 1 (HTTP),T4,0,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,NOT SET,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,Parent aggregates OK; T4 children nested under T3
24-
F3.1c,Retry Succeeds - Attempt 2 (HTTP),T4,1,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},OK,NOT SET,NOT SET,NOT SET,POST,200,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,Parent aggregates OK; T4 children nested under T3
25-
F3.2a,Retries Exhausted - Root (HTTP),T3,0,HTTP/REST,Retries Exhausted,1,INTERNAL,google.showcase.v1beta1.SequenceService/AttemptSequence,ERROR,http,google.showcase.v1beta1.SequenceService/AttemptSequence,UNAVAILABLE,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,UNAVAILABLE,Persistent outage,NOT SET,None (Root),Parent aggregates UNAVAILABLE; all T4 children nested under T3
26-
F3.2b,Retries Exhausted - Attempt 1 (HTTP),T4,0,HTTP/REST,Retries Exhausted,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,NOT SET,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,Parent aggregates UNAVAILABLE; all T4 children nested under T3
27-
F3.2c,Retries Exhausted - Attempt 2 (HTTP),T4,1,HTTP/REST,Retries Exhausted,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,NOT SET,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,Parent aggregates UNAVAILABLE; all T4 children nested under T3
22+
F3.1a,Retry Succeeds - Root (HTTP),T3,0,HTTP/REST,Retry Recovery,1,INTERNAL,google.showcase.v1beta1.SequenceService/AttemptSequence,UNSET,http,google.showcase.v1beta1.SequenceService/AttemptSequence,OK,NOT SET,200,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Parent aggregates OK; T4 children nested under T3
23+
F3.1b,Retry Succeeds - Attempt 1 (HTTP),T4,0,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,http,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,503,Temporary glitch,NOT SET,T3.span_id,Parent aggregates OK; T4 children nested under T3
24+
F3.1c,Retry Succeeds - Attempt 2 (HTTP),T4,1,HTTP/REST,Retry Recovery,2,CLIENT,POST /v1beta1/{name=sequences/*},UNSET,http,NOT SET,NOT SET,POST,200,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,Parent aggregates OK; T4 children nested under T3
25+
F3.2a,Retries Exhausted - Root (HTTP),T3,0,HTTP/REST,Retries Exhausted,1,INTERNAL,google.showcase.v1beta1.SequenceService/AttemptSequence,ERROR,http,google.showcase.v1beta1.SequenceService/AttemptSequence,UNAVAILABLE,NOT SET,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},NOT SET,NOT SET,UNAVAILABLE,Persistent outage,NOT SET,None (Root),Parent aggregates UNAVAILABLE; all T4 children nested under T3
26+
F3.2b,Retries Exhausted - Attempt 1 (HTTP),T4,0,HTTP/REST,Retries Exhausted,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,http,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,503,Persistent outage,NOT SET,T3.span_id,Parent aggregates UNAVAILABLE; all T4 children nested under T3
27+
F3.2c,Retries Exhausted - Attempt 2 (HTTP),T4,1,HTTP/REST,Retries Exhausted,2,CLIENT,POST /v1beta1/{name=sequences/*},ERROR,http,NOT SET,NOT SET,POST,503,NOT SET,googleapis.com,/v1beta1/{name=sequences/*},localhost,7469,503,Persistent outage,NOT SET,T3.span_id,Parent aggregates UNAVAILABLE; all T4 children nested under T3
2828
F3.3a,Retry Succeeds - Root (gRPC),T3,0,gRPC,Retry Recovery,1,INTERNAL,google.showcase.v1beta1.SequenceService/AttemptSequence,UNSET,grpc,google.showcase.v1beta1.SequenceService/AttemptSequence,OK,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Parent aggregates OK; T4 children nested under T3
2929
F3.3b,Retry Succeeds - Attempt 1 (gRPC),T4,0,gRPC,Retry Recovery,2,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,ERROR,grpc,google.showcase.v1beta1.SequenceService/AttemptSequence,NOT SET,NOT SET,NOT SET,14,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,T3.span_id,Parent aggregates OK; T4 children nested under T3
3030
F3.3c,Retry Succeeds - Attempt 2 (gRPC),T4,1,gRPC,Retry Recovery,2,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,UNSET,grpc,google.showcase.v1beta1.SequenceService/AttemptSequence,OK,NOT SET,NOT SET,0,googleapis.com,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,T3.span_id,Parent aggregates OK; T4 children nested under T3

‎packages/google-api-core/google/api_core/_observability.py‎

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ def _build_http_span_attributes(
598598
f"{resolved_method} {url_template}" if url_template else resolved_method
599599
)
600600
span_attributes: dict[str, Any] = {
601+
"rpc.system.name": "http",
601602
"http.request.method": resolved_method,
602603
"server.address": server_address or "",
603604
"server.port": server_port or 443,
@@ -613,6 +614,32 @@ def _build_http_span_attributes(
613614

614615
return span_name, span_attributes, resolved_headers
615616

617+
@staticmethod
618+
def _extract_http_error_message(response: Any) -> str | None:
619+
"""Extract a human-readable error message from an HTTP error response."""
620+
json_fn = getattr(response, "json", None)
621+
if callable(json_fn):
622+
try:
623+
payload = json_fn()
624+
if isinstance(payload, dict):
625+
err_obj = payload.get("error")
626+
if isinstance(err_obj, dict):
627+
msg = err_obj.get("message")
628+
if isinstance(msg, str) and msg:
629+
return msg
630+
elif isinstance(err_obj, str) and err_obj:
631+
return err_obj
632+
except Exception:
633+
pass
634+
for attr in ("text", "reason"):
635+
try:
636+
val = getattr(response, attr, None)
637+
if isinstance(val, str) and val.strip():
638+
return val.strip()
639+
except Exception:
640+
continue
641+
return None
642+
616643
def __init__(
617644
self,
618645
request: Any = None,
@@ -632,6 +659,7 @@ def __init__(
632659
self._body = body
633660
self._client_options = client_options
634661
self._span: Any = None
662+
self._parent_span: Any = None
635663
self._cm: Any = None
636664

637665
def record_response(self, response: Any) -> None:
@@ -654,11 +682,18 @@ def record_response(self, response: Any) -> None:
654682
response, "status_code", getattr(response, "status", None)
655683
)
656684
if status_code is not None:
657-
span.set_attribute("http.response.status_code", int(status_code))
658-
if int(status_code) >= 400:
685+
code_int = int(status_code)
686+
span.set_attribute("http.response.status_code", code_int)
687+
if self._parent_span is not None:
688+
self._parent_span.set_attribute(
689+
"http.response.status_code", code_int
690+
)
691+
if code_int >= 400:
659692
span.set_status(Status(StatusCode.ERROR))
660-
else:
661-
span.set_status(Status(StatusCode.OK))
693+
span.set_attribute("error.type", str(code_int))
694+
error_msg = self._extract_http_error_message(response)
695+
if error_msg:
696+
span.set_attribute("status.message", error_msg)
662697

663698
headers = getattr(response, "headers", None)
664699
if headers and "Content-Length" in headers:
@@ -745,6 +780,23 @@ def __enter__(self) -> "_TraceContext":
745780
)
746781
)
747782

783+
parent_span = trace.get_current_span()
784+
if (
785+
parent_span is not None
786+
and getattr(parent_span, "is_recording", lambda: False)() is True
787+
and hasattr(parent_span, "set_attribute")
788+
):
789+
self._parent_span = parent_span
790+
if self._url_template:
791+
try:
792+
parent_attrs = getattr(parent_span, "attributes", None)
793+
if parent_attrs is None or "url.template" not in parent_attrs:
794+
parent_span.set_attribute(
795+
"url.template", self._url_template
796+
)
797+
except Exception:
798+
pass
799+
748800
self._cm = tracer.start_as_current_span(
749801
span_name,
750802
kind=trace.SpanKind.CLIENT,

0 commit comments

Comments
 (0)