def test_get_set(self): original = propagators._RESPONSE_PROPAGATOR propagators._RESPONSE_PROPAGATOR = None self.assertIsNone(get_global_response_propagator()) prop = TraceResponsePropagator() set_global_response_propagator(prop) self.assertIs(prop, get_global_response_propagator()) propagators._RESPONSE_PROPAGATOR = original
def test_response_headers(self): orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) response = self.fetch("/") headers = response.headers spans = self.sorted_spans(self.memory_exporter.get_finished_spans()) self.assertEqual(len(spans), 3) server_span = spans[1] self.assertIn("traceresponse", headers) self.assertEqual( headers["access-control-expose-headers"], "traceresponse", ) self.assertEqual( headers["traceresponse"], "00-{0}-{1}-01".format( trace.format_trace_id(server_span.get_span_context().trace_id), trace.format_span_id(server_span.get_span_context().span_id), ), ) self.memory_exporter.clear() set_global_response_propagator(orig)
async def otel_send(message): with self.tracer.start_as_current_span(" ".join( (server_span_name, scope["type"], "send"))) as send_span: if callable(self.client_response_hook): self.client_response_hook(send_span, message) if send_span.is_recording(): if message["type"] == "http.response.start": status_code = message["status"] set_status_code(server_span, status_code) set_status_code(send_span, status_code) elif message["type"] == "websocket.send": set_status_code(server_span, 200) set_status_code(send_span, 200) send_span.set_attribute("type", message["type"]) propagator = get_global_response_propagator() if propagator: propagator.inject( message, context=set_span_in_context( server_span, trace.context_api.Context()), setter=asgi_setter, ) await send(message)
def _start_response(status, response_headers, *args, **kwargs): if flask.request and ( excluded_urls is None or not excluded_urls.url_disabled(flask.request.url)): span = flask.request.environ.get(_ENVIRON_SPAN_KEY) propagator = get_global_response_propagator() if propagator: propagator.inject( response_headers, setter=otel_wsgi.default_response_propagation_setter, ) if span: otel_wsgi.add_response_attributes(span, status, response_headers) status_code = otel_wsgi._parse_status_code(status) if status_code is not None: duration_attrs[ SpanAttributes.HTTP_STATUS_CODE] = status_code if (span.is_recording() and span.kind == trace.SpanKind.SERVER): custom_attributes = otel_wsgi.collect_custom_response_headers_attributes( response_headers) if len(custom_attributes) > 0: span.set_attributes(custom_attributes) else: _logger.warning( "Flask environ's OpenTelemetry span " "missing at _start_response(%s)", status, ) if response_hook is not None: response_hook(span, status, response_headers) return start_response(status, response_headers, *args, **kwargs)
def _start_response(status, response_headers, *args, **kwargs): if flask.request and ( excluded_urls is None or not excluded_urls.url_disabled(flask.request.url)): span = flask.request.environ.get(_ENVIRON_SPAN_KEY) propagator = get_global_response_propagator() if propagator: propagator.inject( response_headers, setter=otel_wsgi.default_response_propagation_setter, ) if span: otel_wsgi.add_response_attributes(span, status, response_headers) else: _logger.warning( "Flask environ's OpenTelemetry span " "missing at _start_response(%s)", status, ) if response_hook is not None: response_hook(span, status, response_headers) return start_response(status, response_headers, *args, **kwargs)
def _start_span(tracer, handler, start_time) -> _TraceContext: token = context.attach(extract(handler.request.headers)) span = tracer.start_span( _get_operation_name(handler, handler.request), kind=trace.SpanKind.SERVER, start_time=start_time, ) if span.is_recording(): attributes = _get_attributes_from_request(handler.request) for key, value in attributes.items(): span.set_attribute(key, value) activation = trace.use_span(span, end_on_exit=True) activation.__enter__() # pylint: disable=E1101 ctx = _TraceContext(activation, span, token) setattr(handler, _HANDLER_CONTEXT_KEY, ctx) # finish handler is called after the response is sent back to # the client so it is too late to inject trace response headers # there. propagator = get_global_response_propagator() if propagator: propagator.inject(handler, setter=response_propagation_setter) return ctx
def test_trace_response(self): orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) response = self.client.get("/hello/123") headers = response.headers span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertIn("traceresponse", headers) self.assertEqual( headers["access-control-expose-headers"], "traceresponse", ) self.assertEqual( headers["traceresponse"], "00-{0}-{1}-01".format( trace.format_trace_id(span.get_span_context().trace_id), trace.format_span_id(span.get_span_context().span_id), ), ) set_global_response_propagator(orig)
def test_traceresponse_header(self): """Test a traceresponse header is sent when a global propagator is set.""" orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() span = self.memory_exporter.get_finished_spans()[-1] self.assertEqual(trace_api.SpanKind.SERVER, span.kind) response_start, response_body, *_ = self.get_all_output() self.assertEqual(response_body["body"], b"*") self.assertEqual(response_start["status"], 200) traceresponse = "00-{0}-{1}-01".format( format_trace_id(span.get_span_context().trace_id), format_span_id(span.get_span_context().span_id), ) self.assertListEqual( response_start["headers"], [ [b"Content-Type", b"text/plain"], [b"traceresponse", f"{traceresponse}".encode()], [b"access-control-expose-headers", b"traceresponse"], ], ) set_global_response_propagator(orig)
def _start_span(tracer, handler, start_time) -> _TraceContext: span, token = _start_internal_or_server_span( tracer=tracer, span_name=_get_operation_name(handler, handler.request), start_time=start_time, context_carrier=handler.request.headers, context_getter=textmap.default_getter, ) if span.is_recording(): attributes = _get_attributes_from_request(handler.request) for key, value in attributes.items(): span.set_attribute(key, value) span.set_attribute("tornado.handler", _get_full_handler_name(handler)) if span.is_recording() and span.kind == trace.SpanKind.SERVER: custom_attributes = _collect_custom_request_headers_attributes( handler.request.headers) if len(custom_attributes) > 0: span.set_attributes(custom_attributes) activation = trace.use_span(span, end_on_exit=True) activation.__enter__() # pylint: disable=E1101 ctx = _TraceContext(activation, span, token) setattr(handler, _HANDLER_CONTEXT_KEY, ctx) # finish handler is called after the response is sent back to # the client so it is too late to inject trace response headers # there. propagator = get_global_response_propagator() if propagator: propagator.inject(handler, setter=response_propagation_setter) return ctx
def test_trace_response(self): orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) response = self.client().simulate_get(path="/hello?q=abc") self.assertTraceResponseHeaderMatchesSpan( response.headers, self.memory_exporter.get_finished_spans()[0]) set_global_response_propagator(orig)
def test_response_headers(self): orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) response = self.client.get("/hello/500") self.assertTraceResponseHeaderMatchesSpan( response.headers, self.memory_exporter.get_finished_spans()[0] ) set_global_response_propagator(orig)
def trace_tween(request): # pylint: disable=E1101 if _excluded_urls.url_disabled(request.url): request.environ[_ENVIRON_ENABLED_KEY] = False # short-circuit when we don't want to trace anything return handler(request) request.environ[_ENVIRON_ENABLED_KEY] = True request.environ[_ENVIRON_STARTTIME_KEY] = _time_ns() try: response = handler(request) response_or_exception = response except HTTPException as exc: # If the exception is a pyramid HTTPException, # that's still valuable information that isn't necessarily # a 500. For instance, HTTPFound is a 302. # As described in docs, Pyramid exceptions are all valid # response types response_or_exception = exc raise finally: span = request.environ.get(_ENVIRON_SPAN_KEY) enabled = request.environ.get(_ENVIRON_ENABLED_KEY) if not span and enabled: _logger.warning( "Pyramid environ's OpenTelemetry span missing." "If the OpenTelemetry tween was added manually, make sure" "PyramidInstrumentor().instrument_config(config) is called" ) elif enabled: otel_wsgi.add_response_attributes( span, response_or_exception.status, response_or_exception.headers, ) propagator = get_global_response_propagator() if propagator: propagator.inject(response.headers) activation = request.environ.get(_ENVIRON_ACTIVATION_KEY) if isinstance(response_or_exception, HTTPException): activation.__exit__( type(response_or_exception), response_or_exception, getattr(response_or_exception, "__traceback__", None), ) else: activation.__exit__(None, None, None) context.detach(request.environ.get(_ENVIRON_TOKEN)) return response
def test_response_headers(self): orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) response = self.fetch("/") spans = self.sorted_spans(self.memory_exporter.get_finished_spans()) self.assertEqual(len(spans), 3) self.assertTraceResponseHeaderMatchesSpan(response.headers, spans[1]) self.memory_exporter.clear() set_global_response_propagator(orig)
def test_trace_response(self): orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) response = self.client.get("/hello/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.assertTraceResponseHeaderMatchesSpan( response.headers, span_list[0], ) set_global_response_propagator(orig)
def process_response(self, request, response): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return response is_asgi_request = _is_asgi_request(request) if not _is_asgi_supported and is_asgi_request: return response activation = request.META.pop(self._environ_activation_key, None) span = request.META.pop(self._environ_span_key, None) if activation and span: if is_asgi_request: set_status_code(span, response.status_code) else: add_response_attributes( span, f"{response.status_code} {response.reason_phrase}", response, ) propagator = get_global_response_propagator() if propagator: propagator.inject(response) # record any exceptions raised while processing the request exception = request.META.pop(self._environ_exception_key, None) if _DjangoMiddleware._otel_response_hook: _DjangoMiddleware._otel_response_hook( # pylint: disable=not-callable span, request, response ) if exception: activation.__exit__( type(exception), exception, getattr(exception, "__traceback__", None), ) else: activation.__exit__(None, None, None) if request.META.get(self._environ_token, None) is not None: detach(request.META.get(self._environ_token)) request.META.pop(self._environ_token) return response
def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201 span = req.env.get(_ENVIRON_SPAN_KEY) if not span or not span.is_recording(): return status = resp.status reason = None if resource is None: status = "404" reason = "NotFound" else: if _ENVIRON_EXC in req.env: exc = req.env[_ENVIRON_EXC] exc_type = type(exc) else: exc_type, exc = None, None if exc_type and not req_succeeded: if "HTTPNotFound" in exc_type.__name__: status = "404" reason = "NotFound" else: status = "500" reason = f"{exc_type.__name__}: {exc}" status = status.split(" ")[0] try: status_code = int(status) span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code) span.set_status( Status( status_code=http_status_to_status_code(status_code, server_span=True), description=reason, )) except ValueError: pass propagator = get_global_response_propagator() if propagator: propagator.inject(resp, setter=_response_propagation_setter) if self._response_hook: self._response_hook(span, req, resp)
async def otel_send(message): with self.tracer.start_as_current_span( " ".join((server_span_name, scope["type"], "send")) ) as send_span: if callable(self.client_response_hook): self.client_response_hook(send_span, message) if send_span.is_recording(): if message["type"] == "http.response.start": status_code = message["status"] duration_attrs[ SpanAttributes.HTTP_STATUS_CODE ] = status_code set_status_code(server_span, status_code) set_status_code(send_span, status_code) elif message["type"] == "websocket.send": set_status_code(server_span, 200) set_status_code(send_span, 200) send_span.set_attribute("type", message["type"]) if ( server_span.is_recording() and server_span.kind == trace.SpanKind.SERVER and "headers" in message ): custom_response_attributes = ( collect_custom_response_headers_attributes(message) ) if len(custom_response_attributes) > 0: server_span.set_attributes( custom_response_attributes ) propagator = get_global_response_propagator() if propagator: propagator.inject( message, context=set_span_in_context( server_span, trace.context_api.Context() ), setter=asgi_setter, ) await send(message)
def test_trace_response(self): orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) response = self.client().simulate_get(path="/hello?q=abc") headers = response.headers span = self.memory_exporter.get_finished_spans()[0] self.assertIn("traceresponse", headers) self.assertEqual( headers["access-control-expose-headers"], "traceresponse", ) self.assertEqual( headers["traceresponse"], "00-{0}-{1}-01".format( format_trace_id(span.get_span_context().trace_id), format_span_id(span.get_span_context().span_id), ), ) set_global_response_propagator(orig)
def process_response(self, req, resp, resource, req_succeeded=None): # pylint:disable=R0201 span = req.env.get(_ENVIRON_SPAN_KEY) if not span or not span.is_recording(): return status = resp.status reason = None if resource is None: status = "404" reason = "NotFound" exc_type, exc, _ = exc_info() if exc_type and not req_succeeded: if "HTTPNotFound" in exc_type.__name__: status = "404" reason = "NotFound" else: status = "500" reason = "{}: {}".format(exc_type.__name__, exc) status = status.split(" ")[0] try: status_code = int(status) except ValueError: pass finally: span.set_attribute("http.status_code", status_code) span.set_status( Status( status_code=http_status_to_status_code(status_code), description=reason, )) propagator = get_global_response_propagator() if propagator: propagator.inject(resp, setter=_response_propagation_setter) if self._response_hook: self._response_hook(span, req, resp)
def test_websocket_traceresponse_header(self): """Test a traceresponse header is set for websocket messages""" orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) self.scope = { "type": "websocket", "http_version": "1.1", "scheme": "ws", "path": "/", "query_string": b"", "headers": [], "client": ("127.0.0.1", 32767), "server": ("127.0.0.1", 80), } app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_input({"type": "websocket.connect"}) self.send_input({"type": "websocket.receive", "text": "ping"}) self.send_input({"type": "websocket.disconnect"}) _, socket_send, *_ = self.get_all_output() span = self.memory_exporter.get_finished_spans()[-1] self.assertEqual(trace_api.SpanKind.SERVER, span.kind) traceresponse = "00-{0}-{1}-01".format( format_trace_id(span.get_span_context().trace_id), format_span_id(span.get_span_context().span_id), ) self.assertListEqual( socket_send["headers"], [ [b"traceresponse", f"{traceresponse}".encode()], [b"access-control-expose-headers", b"traceresponse"], ], ) set_global_response_propagator(orig)
def process_response( self, req, resp, resource, req_succeeded=None ): # pylint:disable=R0201,R0912 span = req.env.get(_ENVIRON_SPAN_KEY) if not span or not span.is_recording(): return status = resp.status reason = None if resource is None: status = "404" reason = "NotFound" else: if _ENVIRON_EXC in req.env: exc = req.env[_ENVIRON_EXC] exc_type = type(exc) else: exc_type, exc = None, None if exc_type and not req_succeeded: if "HTTPNotFound" in exc_type.__name__: status = "404" reason = "NotFound" else: status = "500" reason = f"{exc_type.__name__}: {exc}" status = status.split(" ")[0] try: status_code = int(status) span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code) span.set_status( Status( status_code=http_status_to_status_code( status_code, server_span=True ), description=reason, ) ) # Falcon 1 does not support response headers. So # send an empty dict. response_headers = {} if _falcon_version > 1: response_headers = resp.headers if span.is_recording() and span.kind == trace.SpanKind.SERVER: custom_attributes = ( otel_wsgi.collect_custom_response_headers_attributes( response_headers.items() ) ) if len(custom_attributes) > 0: span.set_attributes(custom_attributes) except ValueError: pass propagator = get_global_response_propagator() if propagator: propagator.inject(resp, setter=_response_propagation_setter) if self._response_hook: self._response_hook(span, req, resp)
def trace_tween(request): # pylint: disable=E1101 if _excluded_urls.url_disabled(request.url): request.environ[_ENVIRON_ENABLED_KEY] = False # short-circuit when we don't want to trace anything return handler(request) request.environ[_ENVIRON_ENABLED_KEY] = True request.environ[_ENVIRON_STARTTIME_KEY] = _time_ns() response = None status = None try: response = handler(request) except HTTPException as exc: # If the exception is a pyramid HTTPException, # that's still valuable information that isn't necessarily # a 500. For instance, HTTPFound is a 302. # As described in docs, Pyramid exceptions are all valid # response types response = exc raise except BaseException: # In the case that a non-HTTPException is bubbled up we # should infer a internal server error and raise status = "500 InternalServerError" raise finally: span = request.environ.get(_ENVIRON_SPAN_KEY) enabled = request.environ.get(_ENVIRON_ENABLED_KEY) if not span and enabled: _logger.warning( "Pyramid environ's OpenTelemetry span missing." "If the OpenTelemetry tween was added manually, make sure" "PyramidInstrumentor().instrument_config(config) is called" ) elif enabled: status = getattr(response, "status", status) if status is not None: otel_wsgi.add_response_attributes( span, status, getattr(response, "headerlist", None), ) if span.is_recording() and span.kind == trace.SpanKind.SERVER: custom_attributes = ( otel_wsgi.collect_custom_response_headers_attributes( getattr(response, "headerlist", None))) if len(custom_attributes) > 0: span.set_attributes(custom_attributes) propagator = get_global_response_propagator() if propagator and hasattr(response, "headers"): propagator.inject(response.headers) activation = request.environ.get(_ENVIRON_ACTIVATION_KEY) # Only considering HTTPServerError # to make sure 200, 300 and 400 exceptions are not reported as error if isinstance(response, HTTPServerError): activation.__exit__( type(response), response, getattr(response, "__traceback__", None), ) else: activation.__exit__(None, None, None) env_token = request.environ.get(_ENVIRON_TOKEN, None) if env_token is not None: context.detach(env_token) return response
def test_server_timing_is_default_response_propagator(self): _configure_tracing(_Options()) propagtor = get_global_response_propagator() self.assertIsInstance(propagtor, _ServerTimingResponsePropagator)
def process_response(self, request, response): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return response is_asgi_request = _is_asgi_request(request) if not _is_asgi_supported and is_asgi_request: return response activation = request.META.pop(self._environ_activation_key, None) span = request.META.pop(self._environ_span_key, None) active_requests_count_attrs = request.META.pop( self._environ_active_request_attr_key, None ) duration_attrs = request.META.pop( self._environ_duration_attr_key, None ) if duration_attrs: duration_attrs[ SpanAttributes.HTTP_STATUS_CODE ] = response.status_code request_start_time = request.META.pop(self._environ_timer_key, None) if activation and span: if is_asgi_request: set_status_code(span, response.status_code) if span.is_recording() and span.kind == SpanKind.SERVER: custom_headers = {} for key, value in response.items(): asgi_setter.set(custom_headers, key, value) custom_res_attributes = ( asgi_collect_custom_response_attributes(custom_headers) ) for key, value in custom_res_attributes.items(): span.set_attribute(key, value) else: add_response_attributes( span, f"{response.status_code} {response.reason_phrase}", response.items(), ) if span.is_recording() and span.kind == SpanKind.SERVER: custom_attributes = ( wsgi_collect_custom_response_headers_attributes( response.items() ) ) if len(custom_attributes) > 0: span.set_attributes(custom_attributes) propagator = get_global_response_propagator() if propagator: propagator.inject(response) # record any exceptions raised while processing the request exception = request.META.pop(self._environ_exception_key, None) if _DjangoMiddleware._otel_response_hook: _DjangoMiddleware._otel_response_hook( # pylint: disable=not-callable span, request, response ) if exception: activation.__exit__( type(exception), exception, getattr(exception, "__traceback__", None), ) else: activation.__exit__(None, None, None) if request_start_time is not None: duration = max( round((default_timer() - request_start_time) * 1000), 0 ) self._duration_histogram.record(duration, duration_attrs) self._active_request_counter.add(-1, active_requests_count_attrs) if request.META.get(self._environ_token, None) is not None: detach(request.META.get(self._environ_token)) request.META.pop(self._environ_token) return response
def test_server_timing_is_global_response_propagator_disabled_code(self): _configure_tracing(_Options(trace_response_header_enabled=False)) self.assertIsNone(get_global_response_propagator())
def test_server_timing_is_global_response_propagator_disabled_env(self): _configure_tracing(_Options()) self.assertIsNone(get_global_response_propagator())