Skip to content

Commit

Permalink
Fix #967 by adding http.xxx attributes on grpc server_interceptor (#971)
Browse files Browse the repository at this point in the history
  • Loading branch information
fanshaohua-fan authored Sep 15, 2023
1 parent 3a2d8df commit d358613
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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,
Expand Down Expand Up @@ -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):
Expand Down
38 changes: 35 additions & 3 deletions contrib/opencensus-ext-grpc/tests/test_server_interceptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import unittest

import grpc
import mock
from google.rpc import code_pb2

Expand All @@ -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):
Expand All @@ -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()
Expand All @@ -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(
Expand All @@ -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)

Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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'
}

Expand Down

0 comments on commit d358613

Please sign in to comment.