import binascii import collections import grpc from opentelemetry.trace import StatusCode, SpanKind class _ClientCallDetails( collections.namedtuple( "_ClientCallDetails", ("method", "timeout", "metadata", "credentials") ), grpc.ClientCallDetails, ): pass def _encode_span_id(span_id: int) -> str: return binascii.hexlify(span_id.to_bytes(8, "big")).decode() def _encode_trace_id(trace_id: int) -> str: return binascii.hexlify(trace_id.to_bytes(16, "big")).decode() # Using OtelInterceptor with gRPC: # 1. Instantiate the interceptor: interceptors = [OtelInterceptor()] # 2. Intercept the channel: channel = grpc.intercept_channel(channel, *interceptors) class OtelInterceptor( grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor, grpc.StreamUnaryClientInterceptor, grpc.StreamStreamClientInterceptor, ): def _intercept_call(self, continuation, client_call_details, request_or_iterator): from chromadb.telemetry.opentelemetry import tracer if tracer is None: return continuation(client_call_details, request_or_iterator) with tracer.start_as_current_span( f"RPC {client_call_details.method}", kind=SpanKind.CLIENT ) as span: # Prepare metadata for propagation metadata = ( client_call_details.metadata[:] if client_call_details.metadata else [] ) metadata.extend( [ ( "chroma-traceid", _encode_trace_id(span.get_span_context().trace_id), ), ("chroma-spanid", _encode_span_id(span.get_span_context().span_id)), ] ) # Update client call details with new metadata new_client_details = _ClientCallDetails( client_call_details.method, client_call_details.timeout, tuple(metadata), # Ensure metadata is a tuple client_call_details.credentials, ) try: result = continuation(new_client_details, request_or_iterator) # Set attributes based on the result if hasattr(result, "details") and result.details(): span.set_attribute("rpc.detail", result.details()) span.set_attribute("rpc.status_code", result.code().name.lower()) # Set span status based on gRPC call result if result.code() != grpc.StatusCode.OK: span.set_status(StatusCode.ERROR, description=str(result.code())) return result except Exception as e: # Log exception details and re-raise span.set_attribute("rpc.error", str(e)) span.set_status(StatusCode.ERROR, description=str(e)) raise def intercept_unary_unary(self, continuation, client_call_details, request): return self._intercept_call(continuation, client_call_details, request) def intercept_unary_stream(self, continuation, client_call_details, request): return self._intercept_call(continuation, client_call_details, request) def intercept_stream_unary( self, continuation, client_call_details, request_iterator ): return self._intercept_call(continuation, client_call_details, request_iterator) def intercept_stream_stream( self, continuation, client_call_details, request_iterator ): return self._intercept_call(continuation, client_call_details, request_iterator)