@@ -64,6 +64,55 @@ def is_otel_capabilities_enabled(
6464 return False
6565
6666
67+ def _extract_t4_attributes (request : Any ) -> dict [str , Any ]:
68+ """Extracts Google Cloud semantic and resource attributes from a gRPC request object.
69+
70+ Args:
71+ request: The gRPC request object.
72+
73+ Returns:
74+ dict[str, Any]: A dictionary of semantic attributes.
75+ """
76+ attrs : dict [str , Any ] = {}
77+ if request is None :
78+ return attrs
79+
80+ name = getattr (request , "name" , None )
81+ if name and isinstance (name , str ):
82+ attrs ["gcp.resource.name" ] = name
83+ if "projects/" in name :
84+ parts = name .split ("/" )
85+ try :
86+ idx = parts .index ("projects" )
87+ if idx + 1 < len (parts ):
88+ attrs ["gcp.project_id" ] = parts [idx + 1 ]
89+ except ValueError :
90+ pass
91+
92+ parent = getattr (request , "parent" , None )
93+ if parent and isinstance (parent , str ):
94+ attrs ["gcp.resource.parent" ] = parent
95+ if "gcp.project_id" not in attrs and "projects/" in parent :
96+ parts = parent .split ("/" )
97+ try :
98+ idx = parts .index ("projects" )
99+ if idx + 1 < len (parts ):
100+ attrs ["gcp.project_id" ] = parts [idx + 1 ]
101+ except ValueError :
102+ pass
103+
104+ return attrs
105+
106+
107+ def _client_request_hook (span : Any , request : Any ) -> None :
108+ """OpenTelemetry client request hook to inject GCP resource attributes into the span."""
109+ if span is None or not getattr (span , "is_recording" , lambda : True )():
110+ return
111+ attrs = _extract_t4_attributes (request )
112+ for key , value in attrs .items ():
113+ span .set_attribute (key , value )
114+
115+
67116def _get_tracer_provider (
68117 client_options : ClientOptions | dict [str , Any ] | None = None ,
69118) -> opentelemetry .trace .TracerProvider | None :
@@ -102,7 +151,8 @@ def get_otel_interceptor(
102151 import opentelemetry .instrumentation .grpc as otel_grpc # type: ignore[import-not-found]
103152
104153 interceptor : ClientInterceptor = otel_grpc .client_interceptor (
105- tracer_provider = _get_tracer_provider (client_options )
154+ tracer_provider = _get_tracer_provider (client_options ),
155+ request_hook = _client_request_hook ,
106156 )
107157
108158 def otel_interceptor (channel : grpc .Channel ) -> grpc .Channel :
@@ -131,5 +181,6 @@ def get_otel_async_interceptor(
131181 import opentelemetry .instrumentation .grpc as otel_grpc # type: ignore[import-not-found]
132182
133183 return otel_grpc .aio_client_interceptors (
134- tracer_provider = _get_tracer_provider (client_options )
184+ tracer_provider = _get_tracer_provider (client_options ),
185+ request_hook = _client_request_hook ,
135186 )
0 commit comments