Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit aba6a32

Browse files
wkiserliyanhui1228
authored andcommitted
Attach stack traces and status to the current span on gRPC server failures (#140)
1 parent 68569f2 commit aba6a32

File tree

2 files changed

+51
-26
lines changed

2 files changed

+51
-26
lines changed

opencensus/trace/ext/grpc/server_interceptor.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@
1313
# limitations under the License.
1414
import collections
1515
import logging
16+
import sys
1617

18+
from google.rpc import code_pb2
1719
import grpc
1820

1921
from opencensus.trace import attributes_helper
20-
from opencensus.trace import tracer as tracer_module
2122
from opencensus.trace import execution_context
23+
from opencensus.trace import stack_trace as stack_trace
24+
from opencensus.trace import status
25+
from opencensus.trace import tracer as tracer_module
2226
from opencensus.trace.ext import grpc as oc_grpc
2327
from opencensus.trace.propagation import binary_format
2428

@@ -29,8 +33,10 @@
2933
RpcRequestInfo = collections.namedtuple(
3034
'RPCRequestInfo', ('request', 'context')
3135
)
36+
# exc_info is the three tuple defined at:
37+
# https://docs.python.org/3/library/sys.html#sys.exc_info
3238
RpcResponseInfo = collections.namedtuple(
33-
'RPCCallbackInfo', ('request', 'context', 'response', 'exc')
39+
'RPCCallbackInfo', ('request', 'context', 'response', 'exc_info')
3440
)
3541

3642

@@ -60,19 +66,21 @@ def proxy(self, prop_name):
6066
def _wrapper(request, context, *args, **kwargs):
6167
for callback in self._pre_handler_callbacks:
6268
callback(RpcRequestInfo(request, context))
63-
exc = None
69+
exc_info = (None, None, None)
6470
response = None
6571
try:
6672
response = getattr(
6773
self.handler, prop_name
6874
)(request, context, *args, **kwargs)
6975
except Exception as e:
70-
logging.error(e)
71-
exc = e
76+
logging.exception(e)
77+
exc_info = sys.exc_info()
7278
raise
7379
finally:
7480
for callback in self._post_handler_callbacks:
75-
callback(RpcResponseInfo(request, context, response, exc))
81+
callback(RpcResponseInfo(
82+
request, context, response, exc_info)
83+
)
7684
return response
7785

7886
return _wrapper
@@ -116,11 +124,22 @@ def _start_server_span(self, rpc_request_info):
116124

117125
def _end_server_span(self, rpc_response_info):
118126
tracer = execution_context.get_opencensus_tracer()
119-
if rpc_response_info.exc is not None:
120-
tracer.add_attribute_to_current_span(
127+
exc_type, exc_value, tb = rpc_response_info.exc_info
128+
if exc_type is not None:
129+
current_span = tracer.current_span()
130+
current_span.add_attribute(
121131
attributes_helper.COMMON_ATTRIBUTES.get(
122132
ATTRIBUTE_ERROR_MESSAGE),
123-
str(rpc_response_info.exc))
133+
str(exc_value)
134+
)
135+
current_span.stack_trace = stack_trace.StackTrace.from_traceback(
136+
tb
137+
)
138+
current_span.status = status.Status(
139+
code=code_pb2.UNKNOWN,
140+
message=str(exc_value)
141+
)
142+
124143
tracer.end_span()
125144

126145
def intercept_handler(self, continuation, handler_call_details):

tests/unit/trace/ext/grpc/test_server_interceptor.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
import unittest
1616

1717
import mock
18+
from google.rpc import code_pb2
1819

1920
from opencensus.trace import execution_context
21+
from opencensus.trace import span as span_module
2022
from opencensus.trace.ext.grpc import server_interceptor
2123

2224

@@ -37,8 +39,6 @@ def test_rpc_handler_wrapper(self):
3739
self.assertEqual(wrapper.response_streaming, False)
3840

3941
def test_intercept_handler_no_metadata(self):
40-
current_span = mock.Mock()
41-
mock_tracer = MockTracer(None, None, None)
4242
patch = mock.patch(
4343
'opencensus.trace.ext.grpc.server_interceptor.tracer_module.Tracer',
4444
MockTracer)
@@ -57,12 +57,10 @@ def test_intercept_handler_no_metadata(self):
5757
}
5858

5959
self.assertEqual(
60-
execution_context.get_opencensus_tracer().current_span.attributes,
60+
execution_context.get_opencensus_tracer().current_span().attributes,
6161
expected_attributes)
6262

6363
def test_intercept_handler(self):
64-
current_span = mock.Mock()
65-
mock_tracer = MockTracer(None, None, None)
6664
patch = mock.patch(
6765
'opencensus.trace.ext.grpc.server_interceptor.tracer_module.Tracer',
6866
MockTracer)
@@ -83,7 +81,7 @@ def test_intercept_handler(self):
8381
}
8482

8583
self.assertEqual(
86-
execution_context.get_opencensus_tracer().current_span.attributes,
84+
execution_context.get_opencensus_tracer().current_span().attributes,
8785
expected_attributes)
8886

8987
def test_intercept_service(self):
@@ -95,8 +93,6 @@ def test_intercept_service(self):
9593
self.assertTrue(mock_handler.called)
9694

9795
def test_intercept_handler_exception(self):
98-
current_span = mock.Mock()
99-
mock_tracer = MockTracer(None, None, None)
10096
patch = mock.patch(
10197
'opencensus.trace.ext.grpc.server_interceptor.tracer_module.Tracer',
10298
MockTracer)
@@ -120,26 +116,36 @@ def test_intercept_handler_exception(self):
120116
'/error/message': 'Test'
121117
}
122118

119+
current_span = execution_context.get_opencensus_tracer().current_span()
123120
self.assertEqual(
124-
execution_context.get_opencensus_tracer().current_span.attributes,
121+
execution_context.get_opencensus_tracer().current_span().attributes,
125122
expected_attributes)
126123

124+
# check that the stack trace is attached to the current span
125+
self.assertIsNotNone(current_span.stack_trace)
126+
self.assertIsNotNone(current_span.stack_trace.stack_trace_hash_id)
127+
self.assertNotEqual(current_span.stack_trace.stack_frames, [])
128+
129+
# check that the status obj is attached to the current span
130+
self.assertIsNotNone(current_span.status)
131+
self.assertEqual(current_span.status.code, code_pb2.UNKNOWN)
132+
self.assertEqual(current_span.status.message, 'Test')
133+
127134

128135
class MockTracer(object):
129136
def __init__(self, *args, **kwargs):
130-
self.current_span = mock.Mock()
131-
self.current_span.attributes = {}
137+
self._current_span = span_module.Span('mock_span')
132138
execution_context.set_opencensus_tracer(self)
133139

134140
def start_span(self, name):
135-
self.current_span.name = name
136-
span = mock.Mock()
137-
span.__enter__ = mock.Mock()
138-
span.__exit__ = mock.Mock()
139-
return span
141+
self._current_span.name = name
142+
return self._current_span
140143

141144
def end_span(self):
142145
return
143146

144147
def add_attribute_to_current_span(self, attribute_key, attribute_value):
145-
self.current_span.attributes[attribute_key] = attribute_value
148+
self._current_span.attributes[attribute_key] = attribute_value
149+
150+
def current_span(self):
151+
return self._current_span

0 commit comments

Comments
 (0)