From d3586135fe3a7889a568d748d80991f770dd417a Mon Sep 17 00:00:00 2001 From: fanshaohua-fan Date: Fri, 15 Sep 2023 20:48:04 +0200 Subject: [PATCH] Fix #967 by adding http.xxx attributes on grpc server_interceptor (#971) --- .../opencensus/ext/grpc/server_interceptor.py | 89 ++++++++++++++++--- .../tests/test_server_interceptor.py | 38 +++++++- 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/contrib/opencensus-ext-grpc/opencensus/ext/grpc/server_interceptor.py b/contrib/opencensus-ext-grpc/opencensus/ext/grpc/server_interceptor.py index 7b82773f2..e8ffd6b91 100644 --- a/contrib/opencensus-ext-grpc/opencensus/ext/grpc/server_interceptor.py +++ b/contrib/opencensus-ext-grpc/opencensus/ext/grpc/server_interceptor.py @@ -25,11 +25,40 @@ from opencensus.trace import tracer as tracer_module from opencensus.trace.propagation import binary_format -ATTRIBUTE_COMPONENT = 'COMPONENT' -ATTRIBUTE_ERROR_NAME = 'ERROR_NAME' -ATTRIBUTE_ERROR_MESSAGE = 'ERROR_MESSAGE' +COMPONENT = attributes_helper.COMMON_ATTRIBUTES['COMPONENT'] +ERROR_NAME = attributes_helper.COMMON_ATTRIBUTES['ERROR_NAME'] +ERROR_MESSAGE = attributes_helper.COMMON_ATTRIBUTES['ERROR_MESSAGE'] + +HTTP_HOST = attributes_helper.COMMON_ATTRIBUTES['HTTP_HOST'] +HTTP_METHOD = attributes_helper.COMMON_ATTRIBUTES['HTTP_METHOD'] +HTTP_PATH = attributes_helper.COMMON_ATTRIBUTES['HTTP_PATH'] +HTTP_ROUTE = attributes_helper.COMMON_ATTRIBUTES['HTTP_ROUTE'] +HTTP_URL = attributes_helper.COMMON_ATTRIBUTES['HTTP_URL'] +HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES['HTTP_STATUS_CODE'] +GRPC_METHOD = attributes_helper.GRPC_ATTRIBUTES['GRPC_METHOD'] + RECV_PREFIX = 'Recv' +GRPC_HTTP_STATUS_MAPPING = { + grpc.StatusCode.OK: 200, + grpc.StatusCode.FAILED_PRECONDITION: 400, + grpc.StatusCode.INVALID_ARGUMENT: 400, + grpc.StatusCode.OUT_OF_RANGE: 400, + grpc.StatusCode.UNAUTHENTICATED: 401, + grpc.StatusCode.PERMISSION_DENIED: 403, + grpc.StatusCode.NOT_FOUND: 404, + grpc.StatusCode.ABORTED: 409, + grpc.StatusCode.ALREADY_EXISTS: 409, + grpc.StatusCode.RESOURCE_EXHAUSTED: 429, + grpc.StatusCode.CANCELLED: 499, + grpc.StatusCode.UNKNOWN: 500, + grpc.StatusCode.INTERNAL: 500, + grpc.StatusCode.DATA_LOSS: 500, + grpc.StatusCode.UNIMPLEMENTED: 501, + grpc.StatusCode.UNAVAILABLE: 503, + grpc.StatusCode.DEADLINE_EXCEEDED: 504 +} + class OpenCensusServerInterceptor(grpc.ServerInterceptor): def __init__(self, sampler=None, exporter=None): @@ -56,6 +85,12 @@ def new_behavior(request_or_iterator, servicer_context): # invoke the original rpc behavior response_or_iterator = behavior(request_or_iterator, servicer_context) + + http_status_code = _convert_grpc_code_to_http_status_code( + servicer_context._state.code + ) + span.add_attribute(HTTP_STATUS_CODE, http_status_code) + if response_streaming: response_or_iterator = grpc_utils.wrap_iter_with_message_events( # noqa: E501 request_or_response_iter=response_or_iterator, @@ -107,28 +142,60 @@ def _start_server_span(self, servicer_context): ) span.span_kind = span_module.SpanKind.SERVER + + grpc_call_details = servicer_context._rpc_event.call_details + grpc_host = grpc_call_details.host.decode('utf-8') + grpc_method = grpc_call_details.method.decode('utf-8') + + tracer.add_attribute_to_current_span( + COMPONENT, 'grpc' + ) + tracer.add_attribute_to_current_span( + GRPC_METHOD, grpc_method + ) + + tracer.add_attribute_to_current_span( + HTTP_HOST, grpc_host + ) + tracer.add_attribute_to_current_span( + HTTP_METHOD, 'POST' + ) + tracer.add_attribute_to_current_span( + HTTP_ROUTE, grpc_method + ) + tracer.add_attribute_to_current_span( + HTTP_PATH, grpc_method + ) tracer.add_attribute_to_current_span( - attribute_key=attributes_helper.COMMON_ATTRIBUTES.get( - ATTRIBUTE_COMPONENT), - attribute_value='grpc') + HTTP_URL, 'grpc://' + grpc_host + grpc_method + ) execution_context.set_opencensus_tracer(tracer) execution_context.set_current_span(span) return span +def _convert_grpc_code_to_http_status_code(grpc_state_code): + """ + Converts a gRPC state code into the corresponding HTTP response status. + See: + https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto + """ + if grpc_state_code is None: + return 200 + else: + return GRPC_HTTP_STATUS_MAPPING.get(grpc_state_code, 500) + + def _add_exc_info(span): exc_type, exc_value, tb = sys.exc_info() - span.add_attribute( - attributes_helper.COMMON_ATTRIBUTES.get( - ATTRIBUTE_ERROR_MESSAGE), - str(exc_value) - ) + span.add_attribute(ERROR_MESSAGE, str(exc_value)) span.stack_trace = stack_trace.StackTrace.from_traceback(tb) span.status = status.Status( code=code_pb2.UNKNOWN, message=str(exc_value) ) + span.add_attribute(HTTP_STATUS_CODE, 500) def _wrap_rpc_behavior(handler, fn): diff --git a/contrib/opencensus-ext-grpc/tests/test_server_interceptor.py b/contrib/opencensus-ext-grpc/tests/test_server_interceptor.py index 850c036fa..d74f3003b 100644 --- a/contrib/opencensus-ext-grpc/tests/test_server_interceptor.py +++ b/contrib/opencensus-ext-grpc/tests/test_server_interceptor.py @@ -14,6 +14,7 @@ import unittest +import grpc import mock from google.rpc import code_pb2 @@ -22,6 +23,9 @@ from opencensus.trace import execution_context from opencensus.trace import span as span_module +MOCK_HOST = b'localhost:5000' +MOCK_METHOD = b'/helloworld.Greeter/SayHello' + class TestOpenCensusServerInterceptor(unittest.TestCase): def test_constructor(self): @@ -38,7 +42,10 @@ def test_intercept_service_no_metadata(self): '.tracer_module.Tracer', MockTracer) mock_context = mock.Mock() mock_context.invocation_metadata = mock.Mock(return_value=None) - mock_context._rpc_event.call_details.method = 'hello' + + mock_context._rpc_event.call_details.host = MOCK_HOST + mock_context._rpc_event.call_details.method = MOCK_METHOD + mock_context._state.code = grpc.StatusCode.OK interceptor = server_interceptor.OpenCensusServerInterceptor( None, None) mock_handler = mock.Mock() @@ -53,6 +60,13 @@ def test_intercept_service_no_metadata(self): expected_attributes = { 'component': 'grpc', + 'grpc.method': '/helloworld.Greeter/SayHello', + 'http.host': 'localhost:5000', + 'http.method': 'POST', + 'http.route': '/helloworld.Greeter/SayHello', + 'http.path': '/helloworld.Greeter/SayHello', + 'http.url': 'grpc://localhost:5000/helloworld.Greeter/SayHello', + 'http.status_code': 200 } self.assertEqual( @@ -78,7 +92,9 @@ def test_intercept_service(self): mock_handler.response_streaming = rsp_streaming mock_continuation = mock.Mock(return_value=mock_handler) - mock_context._rpc_event.call_details.method = 'hello' + mock_context._rpc_event.call_details.host = MOCK_HOST + mock_context._rpc_event.call_details.method = MOCK_METHOD + mock_context._state.code = grpc.StatusCode.OK interceptor = server_interceptor.OpenCensusServerInterceptor( None, None) @@ -89,6 +105,13 @@ def test_intercept_service(self): expected_attributes = { 'component': 'grpc', + 'grpc.method': '/helloworld.Greeter/SayHello', + 'http.host': 'localhost:5000', + 'http.method': 'POST', + 'http.route': '/helloworld.Greeter/SayHello', + 'http.path': '/helloworld.Greeter/SayHello', + 'http.url': 'grpc://localhost:5000/helloworld.Greeter/SayHello', # noqa: E501 + 'http.status_code': 200 } self.assertEqual( @@ -110,7 +133,9 @@ def test_intercept_handler_exception(self): None, None) mock_context = mock.Mock() mock_context.invocation_metadata = mock.Mock(return_value=None) - mock_context._rpc_event.call_details.method = 'hello' + + mock_context._rpc_event.call_details.host = MOCK_HOST + mock_context._rpc_event.call_details.method = MOCK_METHOD mock_handler = mock.Mock() mock_handler.request_streaming = req_streaming mock_handler.response_streaming = rsp_streaming @@ -128,6 +153,13 @@ def test_intercept_handler_exception(self): expected_attributes = { 'component': 'grpc', + 'grpc.method': '/helloworld.Greeter/SayHello', + 'http.host': 'localhost:5000', + 'http.method': 'POST', + 'http.route': '/helloworld.Greeter/SayHello', + 'http.path': '/helloworld.Greeter/SayHello', + 'http.url': 'grpc://localhost:5000/helloworld.Greeter/SayHello', # noqa: E501 + 'http.status_code': 500, 'error.message': 'Test' }