diff --git a/packages/gapic-generator/tests/system/telemetry_requirements_matrix.csv b/packages/gapic-generator/tests/system/telemetry_requirements_matrix.csv index 06bf7a8c71a0..8f9763dec605 100644 --- a/packages/gapic-generator/tests/system/telemetry_requirements_matrix.csv +++ b/packages/gapic-generator/tests/system/telemetry_requirements_matrix.csv @@ -1,10 +1,10 @@ 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 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 -F1.2,Unary Success (HTTP),T4,0,HTTP/REST,Happy Path,1,CLIENT,POST,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" -F1.3,Server Error (HTTP),T4,0,HTTP/REST,Server Failure,1,CLIENT,POST,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" -F1.4,Client Timeout (HTTP),T4,0,HTTP/REST,Client Timeout,1,CLIENT,POST,ERROR,NOT SET,NOT SET,NOT SET,POST,504,NOT SET,googleapis.com,/v1beta1/echo:echo,localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,url.full -F1.5a,Retry Recovery - Attempt 1 (HTTP),T4,0,HTTP/REST,Retry Recovery,2,CLIENT,POST,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 -F1.5b,Retry Recovery - Attempt 2 (HTTP),T4,1,HTTP/REST,Retry Recovery,2,CLIENT,POST,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 +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" +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" +F1.4,Client Timeout (HTTP),T4,0,HTTP/REST,Client Timeout,1,CLIENT,POST /v1beta1/echo:echo,ERROR,NOT SET,NOT SET,NOT SET,POST,504,NOT SET,googleapis.com,/v1beta1/echo:echo,localhost,7469,NOT SET,NOT SET,NOT SET,T3.span_id,url.full +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 +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 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 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" 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" @@ -12,22 +12,22 @@ F1.9,Client Timeout (gRPC),T4,0,gRPC,Client Timeout,1,CLIENT,google.showcase.v1b 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" 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" 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 -F2.2,Unary Success (HTTP),T3,0,HTTP/REST,Happy Path,1,CLIENT,google.showcase.v1beta1.Echo/Echo,UNSET,http,google.showcase.v1beta1.Echo/Echo,OK,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Strict ceiling (no unknown attrs) -F2.3,Server Error (HTTP),T3,0,HTTP/REST,Server Failure,1,CLIENT,google.showcase.v1beta1.Echo/Echo,ERROR,http,google.showcase.v1beta1.Echo/Echo,BAD_REQUEST,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,BAD_REQUEST,Simulated unretryable invalid argument error.,NOT SET,None (Root),"gcp.errors.domain, gcp.errors.metadata.*" -F2.4,Client Timeout (HTTP),T3,0,HTTP/REST,Client Timeout,1,CLIENT,google.showcase.v1beta1.Echo/Echo,ERROR,http,google.showcase.v1beta1.Echo/Echo,GATEWAY_TIMEOUT,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,GATEWAY_TIMEOUT,Client deadline exceeded,NOT SET,None (Root),gcp.errors.domain +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) +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.*" +F2.4,Client Timeout (HTTP),T3,0,HTTP/REST,Client Timeout,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,ERROR,http,google.showcase.v1beta1.Echo/Echo,GATEWAY_TIMEOUT,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,GATEWAY_TIMEOUT,Client deadline exceeded,NOT SET,None (Root),gcp.errors.domain 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 -F2.6,Unary Success (gRPC),T3,0,gRPC,Happy Path,1,CLIENT,google.showcase.v1beta1.Echo/Echo,UNSET,grpc,google.showcase.v1beta1.Echo/Echo,OK,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Strict ceiling (no unknown attrs) -F2.7,Server Error (gRPC),T3,0,gRPC,Server Failure,1,CLIENT,google.showcase.v1beta1.Echo/Echo,ERROR,grpc,google.showcase.v1beta1.Echo/Echo,INVALID_ARGUMENT,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,INVALID_ARGUMENT,Simulated unretryable invalid argument error.,NOT SET,None (Root),"gcp.errors.domain, gcp.errors.metadata.*" -F2.8,Client Timeout (gRPC),T3,0,gRPC,Client Timeout,1,CLIENT,google.showcase.v1beta1.Echo/Echo,ERROR,grpc,google.showcase.v1beta1.Echo/Echo,DEADLINE_EXCEEDED,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,DEADLINE_EXCEEDED,Client deadline exceeded,NOT SET,None (Root),gcp.errors.domain -F3.1a,Retry Succeeds - Root (HTTP),T3,0,HTTP/REST,Retry Recovery,1,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,UNSET,http,google.showcase.v1beta1.SequenceService/AttemptSequence,OK,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Parent aggregates OK; T4 children nested under T3 -F3.1b,Retry Succeeds - Attempt 1 (HTTP),T4,0,HTTP/REST,Retry Recovery,2,CLIENT,POST,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 -F3.1c,Retry Succeeds - Attempt 2 (HTTP),T4,1,HTTP/REST,Retry Recovery,2,CLIENT,POST,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 -F3.2a,Retries Exhausted - Root (HTTP),T3,0,HTTP/REST,Retries Exhausted,1,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,ERROR,http,google.showcase.v1beta1.SequenceService/AttemptSequence,UNAVAILABLE,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,UNAVAILABLE,Persistent outage,NOT SET,None (Root),Parent aggregates UNAVAILABLE; all T4 children nested under T3 -F3.2b,Retries Exhausted - Attempt 1 (HTTP),T4,0,HTTP/REST,Retries Exhausted,2,CLIENT,POST,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 -F3.2c,Retries Exhausted - Attempt 2 (HTTP),T4,1,HTTP/REST,Retries Exhausted,2,CLIENT,POST,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 -F3.3a,Retry Succeeds - Root (gRPC),T3,0,gRPC,Retry Recovery,1,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,UNSET,grpc,google.showcase.v1beta1.SequenceService/AttemptSequence,OK,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,None (Root),Parent aggregates OK; T4 children nested under T3 +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) +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.*" +F2.8,Client Timeout (gRPC),T3,0,gRPC,Client Timeout,1,INTERNAL,google.showcase.v1beta1.Echo/Echo,ERROR,grpc,google.showcase.v1beta1.Echo/Echo,DEADLINE_EXCEEDED,NOT SET,NOT SET,NOT SET,googleapis.com,NOT SET,NOT SET,NOT SET,DEADLINE_EXCEEDED,Client deadline exceeded,NOT SET,None (Root),gcp.errors.domain +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 +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 +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 +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 +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 +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 +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 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 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 -F3.4a,Retries Exhausted - Root (gRPC),T3,0,gRPC,Retries Exhausted,1,CLIENT,google.showcase.v1beta1.SequenceService/AttemptSequence,ERROR,grpc,google.showcase.v1beta1.SequenceService/AttemptSequence,UNAVAILABLE,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,NOT SET,UNAVAILABLE,Persistent outage,NOT SET,None (Root),Parent aggregates UNAVAILABLE; all T4 children nested under T3 +F3.4a,Retries Exhausted - Root (gRPC),T3,0,gRPC,Retries Exhausted,1,INTERNAL,google.showcase.v1beta1.SequenceService/AttemptSequence,ERROR,grpc,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 F3.4b,Retries Exhausted - Attempt 1 (gRPC),T4,0,gRPC,Retries Exhausted,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 UNAVAILABLE; all T4 children nested under T3 F3.4c,Retries Exhausted - Attempt 2 (gRPC),T4,1,gRPC,Retries Exhausted,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 UNAVAILABLE; all T4 children nested under T3 diff --git a/packages/google-api-core/google/api_core/_observability.py b/packages/google-api-core/google/api_core/_observability.py index 85398158a982..6ad4c9604f1e 100644 --- a/packages/google-api-core/google/api_core/_observability.py +++ b/packages/google-api-core/google/api_core/_observability.py @@ -594,7 +594,9 @@ def _build_http_span_attributes( except Exception: # Fail-open on malformed URL parsing pass - span_name = resolved_method + span_name = ( + f"{resolved_method} {url_template}" if url_template else resolved_method + ) span_attributes: dict[str, Any] = { "http.request.method": resolved_method, "server.address": server_address or "", diff --git a/packages/google-api-core/google/api_core/gapic_v1/method.py b/packages/google-api-core/google/api_core/gapic_v1/method.py index a6e70fe7c6d8..8cda9604cf16 100644 --- a/packages/google-api-core/google/api_core/gapic_v1/method.py +++ b/packages/google-api-core/google/api_core/gapic_v1/method.py @@ -330,14 +330,18 @@ def __init__( tracer = trace.get_tracer("google.api_core") span_name, _, _ = _extract_rpc_identity(method_name) + endpoint_attrs = _observability._extract_endpoint_attributes( + client_options + ) span_attributes = { "rpc.system.name": "http" if kind.startswith("rest") else "grpc", "rpc.method": span_name, + **endpoint_attrs, } self._start_span_fn = functools.partial( tracer.start_as_current_span, span_name, - kind=trace.SpanKind.CLIENT, + kind=trace.SpanKind.INTERNAL, attributes=span_attributes, ) except (ImportError, AttributeError, TypeError): diff --git a/packages/google-api-core/tests/asyncio/gapic/test_method_async.py b/packages/google-api-core/tests/asyncio/gapic/test_method_async.py index d51ae6ea2a49..159eee84af8e 100644 --- a/packages/google-api-core/tests/asyncio/gapic/test_method_async.py +++ b/packages/google-api-core/tests/asyncio/gapic/test_method_async.py @@ -400,10 +400,11 @@ async def test_wrap_method_async_otel_tracing_enabled_success( assert result == "async_success" mock_otel.tracer.start_as_current_span.assert_called_once_with( "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="CLIENT", + kind="INTERNAL", attributes={ "rpc.system.name": expected_rpc_system, "rpc.method": "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", + "url.domain": "googleapis.com", }, ) mock_otel.span.set_attribute.assert_called_with("rpc.response.status_code", "OK") diff --git a/packages/google-api-core/tests/conftest.py b/packages/google-api-core/tests/conftest.py index 84cd3283f047..759a8bb64f34 100644 --- a/packages/google-api-core/tests/conftest.py +++ b/packages/google-api-core/tests/conftest.py @@ -44,6 +44,7 @@ def mock_otel(monkeypatch): mock_trace = mock.Mock() mock_trace.get_tracer.return_value = mock_tracer mock_trace.SpanKind.CLIENT = "CLIENT" + mock_trace.SpanKind.INTERNAL = "INTERNAL" mock_trace.StatusCode.ERROR = "ERROR" with ( diff --git a/packages/google-api-core/tests/unit/gapic/test_method.py b/packages/google-api-core/tests/unit/gapic/test_method.py index a979a948338e..488b54765fef 100644 --- a/packages/google-api-core/tests/unit/gapic/test_method.py +++ b/packages/google-api-core/tests/unit/gapic/test_method.py @@ -354,6 +354,7 @@ def test__deduplicate_metadata_tokens(headers, expected): _DEFAULT_SPAN_ATTRIBUTES = { "rpc.system.name": "grpc", "rpc.method": "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", + "url.domain": "googleapis.com", } @@ -456,10 +457,11 @@ def test_wrap_method_otel_tracing_enabled_success(mock_otel, kind): expected_attributes = { "rpc.system.name": "http" if kind == "rest" else "grpc", "rpc.method": "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", + "url.domain": "googleapis.com", } mock_otel.tracer.start_as_current_span.assert_called_once_with( "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="CLIENT", + kind="INTERNAL", attributes=expected_attributes, ) mock_otel.span.set_attribute.assert_called_with("rpc.response.status_code", "OK") @@ -485,7 +487,7 @@ def test_wrap_method_otel_tracing_custom_client_options(mock_otel): mock_provider.get_tracer.assert_called_once_with("google.api_core") mock_otel.tracer.start_as_current_span.assert_called_once_with( "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="CLIENT", + kind="INTERNAL", attributes=_DEFAULT_SPAN_ATTRIBUTES, ) mock_otel.span.set_attribute.assert_called_with("rpc.response.status_code", "OK") @@ -511,7 +513,7 @@ def test_wrap_method_otel_tracing_dict_client_options(mock_otel): mock_provider.get_tracer.assert_called_once_with("google.api_core") mock_otel.tracer.start_as_current_span.assert_called_once_with( "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="CLIENT", + kind="INTERNAL", attributes=_DEFAULT_SPAN_ATTRIBUTES, ) mock_otel.span.set_attribute.assert_called_with("rpc.response.status_code", "OK") @@ -695,7 +697,7 @@ def test_wrap_method_otel_tracing_attributes_deferred_gcp_client_omitted(mock_ot assert result == "success" mock_otel.tracer.start_as_current_span.assert_called_once_with( "google.cloud.secretmanager.v1.SecretManagerService/ListSecrets", - kind="CLIENT", + kind="INTERNAL", attributes=_DEFAULT_SPAN_ATTRIBUTES, ) @@ -714,7 +716,7 @@ def test_wrap_method_otel_tracing_attributes_no_service(mock_otel): assert result == "success" mock_otel.tracer.start_as_current_span.assert_called_once_with( "ListSecrets", - kind="CLIENT", + kind="INTERNAL", attributes={ **_DEFAULT_SPAN_ATTRIBUTES, "rpc.method": "ListSecrets", @@ -722,6 +724,35 @@ def test_wrap_method_otel_tracing_attributes_no_service(mock_otel): ) +def test_wrap_method_otel_tracing_endpoint_attributes_from_client_options( + mock_otel, +): + """Proves that T3 spans include url.domain, server.address, and server.port from client_options.""" + mock_target = mock.Mock(return_value="ok") + options = client_options_lib.ClientOptions( + api_endpoint="localhost:7469", + universe_domain="custom.domain.com", + ) + wrapped = google.api_core.gapic_v1.method.wrap_method( + mock_target, + client_options=options, + method_name="/google.showcase.v1beta1.Echo/Echo", + kind="grpc", + ) + assert wrapped() == "ok" + mock_otel.tracer.start_as_current_span.assert_called_once_with( + "google.showcase.v1beta1.Echo/Echo", + kind="INTERNAL", + attributes={ + "rpc.system.name": "grpc", + "rpc.method": "google.showcase.v1beta1.Echo/Echo", + "url.domain": "custom.domain.com", + "server.address": "localhost", + "server.port": 7469, + }, + ) + + def test_extract_error_attributes_standard_exception(): """Proves that _extract_error_attributes returns fallback error.type for exceptions without ErrorInfo.""" assert google.api_core.gapic_v1.method._extract_error_attributes( diff --git a/packages/google-api-core/tests/unit/test_observability.py b/packages/google-api-core/tests/unit/test_observability.py index 53425f42554e..68cd35e1f2a5 100644 --- a/packages/google-api-core/tests/unit/test_observability.py +++ b/packages/google-api-core/tests/unit/test_observability.py @@ -615,7 +615,7 @@ def test_build_http_span_attributes_with_request(): name, attrs, res_headers = _observability._TraceContext._build_http_span_attributes( request, url_template="/v1/echo" ) - assert name == "POST" + assert name == "POST /v1/echo" assert attrs["http.request.method"] == "POST" assert attrs["server.address"] == "example.com" assert attrs["server.port"] == 8443 @@ -637,7 +637,7 @@ def test_build_http_span_attributes_with_kwargs(): body="string-body", client_options=options, ) - assert name == "GET" + assert name == "GET /v1/items" assert attrs["server.address"] == "custom.endpoint.com" assert attrs["server.port"] == 9443 assert attrs["http.request.body.size"] == len("string-body") @@ -736,7 +736,7 @@ def test_trace_http_request_active(monkeypatch): mock_tracer.start_as_current_span.assert_called_once() call_args, call_kwargs = mock_tracer.start_as_current_span.call_args - assert call_args[0] == "POST" + assert call_args[0] == "POST /v1/test" attrs = call_kwargs["attributes"] assert attrs["http.request.method"] == "POST" assert attrs["server.address"] == "custom.googleapis.com" @@ -843,7 +843,7 @@ def test_trace_http_request_with_kwargs(monkeypatch): assert ctx._span is mock_span call_args, call_kwargs = mock_tracer.start_as_current_span.call_args - assert call_args[0] == "POST" + assert call_args[0] == "POST /v1/test" attrs = call_kwargs["attributes"] assert attrs["http.request.method"] == "POST" assert attrs["http.request.body.size"] == len("string-payload")