def test_regex(self): regexes = ExcludeList( [r"^https?://site\.com/path/123$", r"^http://.*\?arg=foo"] ) self.assertTrue(regexes.url_disabled("http://site.com/path/123")) self.assertTrue(regexes.url_disabled("https://site.com/path/123")) self.assertFalse(regexes.url_disabled("http://site.com/path/123/abc")) self.assertFalse(regexes.url_disabled("http://site,com/path/123")) self.assertTrue( regexes.url_disabled("http://site.com/path/123?arg=foo") ) self.assertTrue( regexes.url_disabled("http://site.com/path/123?arg=foo,arg2=foo2") ) self.assertFalse( regexes.url_disabled("https://site.com/path/123?arg=foo") )
def get_excluded_urls(): urls = configuration.Configuration().TORNADO_EXCLUDED_URLS or "" if urls: urls = str.split(urls, ",") return ExcludeList(urls)
class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase): def setUp(self): super().setUp() self.app = Flask(__name__) FlaskInstrumentor().instrument_app(self.app) self._common_initialization() def tearDown(self): super().tearDown() with self.disable_logging(): FlaskInstrumentor().uninstrument_app(self.app) def test_uninstrument(self): resp = self.client.get("/hello/123") self.assertEqual(200, resp.status_code) self.assertEqual([b"Hello: 123"], list(resp.response)) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) FlaskInstrumentor().uninstrument_app(self.app) resp = self.client.get("/hello/123") self.assertEqual(200, resp.status_code) self.assertEqual([b"Hello: 123"], list(resp.response)) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) # pylint: disable=no-member def test_only_strings_in_environ(self): """ Some WSGI servers (such as Gunicorn) expect keys in the environ object to be strings OpenTelemetry should adhere to this convention. """ nonstring_keys = set() def assert_environ(): for key in request.environ: if not isinstance(key, str): nonstring_keys.add(key) return "hi" self.app.route("/assert_environ")(assert_environ) self.client.get("/assert_environ") self.assertEqual(nonstring_keys, set()) def test_simple(self): expected_attrs = expected_attributes({ "http.target": "/hello/123", "http.route": "/hello/<int:helloid>" }) self.client.get("/hello/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.assertEqual(span_list[0].name, "_hello_endpoint") self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) self.assertEqual(span_list[0].attributes, expected_attrs) def test_404(self): expected_attrs = expected_attributes({ "http.method": "POST", "http.target": "/bye", "http.status_text": "NOT FOUND", "http.status_code": 404, }) resp = self.client.post("/bye") self.assertEqual(404, resp.status_code) resp.close() span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.assertEqual(span_list[0].name, "HTTP POST") self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) self.assertEqual(span_list[0].attributes, expected_attrs) def test_internal_error(self): expected_attrs = expected_attributes({ "http.target": "/hello/500", "http.route": "/hello/<int:helloid>", "http.status_text": "INTERNAL SERVER ERROR", "http.status_code": 500, }) resp = self.client.get("/hello/500") self.assertEqual(500, resp.status_code) resp.close() span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.assertEqual(span_list[0].name, "_hello_endpoint") self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) self.assertEqual(span_list[0].attributes, expected_attrs) @patch( "opentelemetry.ext.flask._excluded_urls", ExcludeList(["http://localhost/excluded_arg/123", "excluded_noarg"]), ) def test_exclude_lists(self): self.client.get("/excluded_arg/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) self.client.get("/excluded_arg/125") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.client.get("/excluded_noarg") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.client.get("/excluded_noarg2") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1)
class _DjangoMiddleware(MiddlewareMixin): """Django Middleware for OpenTelemetry """ _environ_activation_key = ( "opentelemetry-instrumentor-django.activation_key") _environ_token = "opentelemetry-instrumentor-django.token" _environ_span_key = "opentelemetry-instrumentor-django.span_key" _excluded_urls = Configuration().DJANGO_EXCLUDED_URLS or [] if _excluded_urls: _excluded_urls = ExcludeList(str.split(_excluded_urls, ",")) else: _excluded_urls = ExcludeList(_excluded_urls) _traced_request_attrs = [ attr.strip() for attr in ( Configuration().DJANGO_TRACED_REQUEST_ATTRS or "").split(",") ] @staticmethod def _get_span_name(request): try: if getattr(request, "resolver_match"): match = request.resolver_match else: match = resolve(request.get_full_path()) if hasattr(match, "route"): return match.route # Instead of using `view_name`, better to use `_func_name` as some applications can use similar # view names in different modules if hasattr(match, "_func_name"): return match._func_name # pylint: disable=protected-access # Fallback for safety as `_func_name` private field return match.view_name except Resolver404: return "HTTP {}".format(request.method) def process_request(self, request): # request.META is a dictionary containing all available HTTP headers # Read more about request.META here: # https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.META if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return environ = request.META token = attach(extract(get_header_from_environ, environ)) tracer = get_tracer(__name__, __version__) attributes = collect_request_attributes(environ) for attr in self._traced_request_attrs: value = getattr(request, attr, None) if value is not None: attributes[attr] = str(value) span = tracer.start_span( self._get_span_name(request), kind=SpanKind.SERVER, attributes=attributes, start_time=environ.get( "opentelemetry-instrumentor-django.starttime_key"), ) activation = tracer.use_span(span, end_on_exit=True) activation.__enter__() request.META[self._environ_activation_key] = activation request.META[self._environ_span_key] = span request.META[self._environ_token] = token def process_exception(self, request, exception): # Django can call this method and process_response later. In order # to avoid __exit__ and detach from being called twice then, the # respective keys are being removed here. if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return if self._environ_activation_key in request.META.keys(): request.META[self._environ_activation_key].__exit__( type(exception), exception, getattr(exception, "__traceback__", None), ) request.META.pop(self._environ_activation_key) detach(request.environ[self._environ_token]) request.META.pop(self._environ_token, None) def process_response(self, request, response): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return response if (self._environ_activation_key in request.META.keys() and self._environ_span_key in request.META.keys()): add_response_attributes( request.META[self._environ_span_key], "{} {}".format(response.status_code, response.reason_phrase), response, ) request.META.pop(self._environ_span_key) request.META[self._environ_activation_key].__exit__( None, None, None) request.META.pop(self._environ_activation_key) if self._environ_token in request.META.keys(): detach(request.environ.get(self._environ_token)) request.META.pop(self._environ_token) return response
def get_excluded_urls(): urls = configuration.Configuration().FLASK_EXCLUDED_URLS or [] if urls: urls = str.split(urls, ",") return ExcludeList(urls)
class TestTornadoInstrumentation(TornadoTest): def test_http_calls(self): methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] for method in methods: self._test_http_method_call(method) def _test_http_method_call(self, method): body = "" if method in ["POST", "PUT", "PATCH"] else None response = self.fetch("/", method=method, body=body) self.assertEqual(response.code, 201) spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 3) manual, server, client = self.sorted_spans(spans) self.assertEqual(manual.name, "manual") self.assertEqual(manual.parent, server.context) self.assertEqual(manual.context.trace_id, client.context.trace_id) self.assertEqual(server.name, "MainHandler." + method.lower()) self.assertTrue(server.parent.is_remote) self.assertNotEqual(server.parent, client.context) self.assertEqual(server.parent.span_id, client.context.span_id) self.assertEqual(server.context.trace_id, client.context.trace_id) self.assertEqual(server.kind, SpanKind.SERVER) self.assert_span_has_attributes( server, { "component": "tornado", "http.method": method, "http.scheme": "http", "http.host": "127.0.0.1:" + str(self.get_http_port()), "http.target": "/", "net.peer.ip": "127.0.0.1", "http.status_text": "Created", "http.status_code": 201, }, ) self.assertEqual(client.name, method) self.assertFalse(client.context.is_remote) self.assertIsNone(client.parent) self.assertEqual(client.kind, SpanKind.CLIENT) self.assert_span_has_attributes( client, { "component": "tornado", "http.url": self.get_url("/"), "http.method": method, "http.status_code": 201, }, ) self.memory_exporter.clear() def test_not_recording(self): mock_tracer = Mock() mock_span = Mock() mock_span.is_recording.return_value = False mock_tracer.start_span.return_value = mock_span mock_tracer.use_span.return_value.__enter__ = mock_span mock_tracer.use_span.return_value.__exit__ = mock_span with patch("opentelemetry.trace.get_tracer") as tracer: tracer.return_value = mock_tracer self.fetch("/") self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called) def test_async_handler(self): self._test_async_handler("/async", "AsyncHandler") def test_coroutine_handler(self): self._test_async_handler("/cor", "CoroutineHandler") def _test_async_handler(self, url, handler_name): response = self.fetch(url) self.assertEqual(response.code, 201) spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 5) sub2, sub1, sub_wrapper, server, client = self.sorted_spans(spans) self.assertEqual(sub2.name, "sub-task-2") self.assertEqual(sub2.parent, sub_wrapper.context) self.assertEqual(sub2.context.trace_id, client.context.trace_id) self.assertEqual(sub1.name, "sub-task-1") self.assertEqual(sub1.parent, sub_wrapper.context) self.assertEqual(sub1.context.trace_id, client.context.trace_id) self.assertEqual(sub_wrapper.name, "sub-task-wrapper") self.assertEqual(sub_wrapper.parent, server.context) self.assertEqual(sub_wrapper.context.trace_id, client.context.trace_id) self.assertEqual(server.name, handler_name + ".get") self.assertTrue(server.parent.is_remote) self.assertNotEqual(server.parent, client.context) self.assertEqual(server.parent.span_id, client.context.span_id) self.assertEqual(server.context.trace_id, client.context.trace_id) self.assertEqual(server.kind, SpanKind.SERVER) self.assert_span_has_attributes( server, { "component": "tornado", "http.method": "GET", "http.scheme": "http", "http.host": "127.0.0.1:" + str(self.get_http_port()), "http.target": url, "net.peer.ip": "127.0.0.1", "http.status_text": "Created", "http.status_code": 201, }, ) self.assertEqual(client.name, "GET") self.assertFalse(client.context.is_remote) self.assertIsNone(client.parent) self.assertEqual(client.kind, SpanKind.CLIENT) self.assert_span_has_attributes( client, { "component": "tornado", "http.url": self.get_url(url), "http.method": "GET", "http.status_code": 201, }, ) def test_500(self): response = self.fetch("/error") self.assertEqual(response.code, 500) spans = self.sorted_spans(self.memory_exporter.get_finished_spans()) self.assertEqual(len(spans), 2) server, client = spans self.assertEqual(server.name, "BadHandler.get") self.assertEqual(server.kind, SpanKind.SERVER) self.assert_span_has_attributes( server, { "component": "tornado", "http.method": "GET", "http.scheme": "http", "http.host": "127.0.0.1:" + str(self.get_http_port()), "http.target": "/error", "net.peer.ip": "127.0.0.1", "http.status_code": 500, }, ) self.assertEqual(client.name, "GET") self.assertEqual(client.kind, SpanKind.CLIENT) self.assert_span_has_attributes( client, { "component": "tornado", "http.url": self.get_url("/error"), "http.method": "GET", "http.status_code": 500, }, ) def test_404(self): response = self.fetch("/missing-url") self.assertEqual(response.code, 404) spans = self.sorted_spans(self.memory_exporter.get_finished_spans()) self.assertEqual(len(spans), 2) server, client = spans self.assertEqual(server.name, "ErrorHandler.get") self.assertEqual(server.kind, SpanKind.SERVER) self.assert_span_has_attributes( server, { "component": "tornado", "http.method": "GET", "http.scheme": "http", "http.host": "127.0.0.1:" + str(self.get_http_port()), "http.target": "/missing-url", "net.peer.ip": "127.0.0.1", "http.status_text": "Not Found", "http.status_code": 404, }, ) self.assertEqual(client.name, "GET") self.assertEqual(client.kind, SpanKind.CLIENT) self.assert_span_has_attributes( client, { "component": "tornado", "http.url": self.get_url("/missing-url"), "http.method": "GET", "http.status_code": 404, }, ) def test_dynamic_handler(self): response = self.fetch("/dyna") self.assertEqual(response.code, 404) self.memory_exporter.clear() self._app.add_handlers(r".+", [(r"/dyna", DynamicHandler)]) response = self.fetch("/dyna") self.assertEqual(response.code, 202) spans = self.sorted_spans(self.memory_exporter.get_finished_spans()) self.assertEqual(len(spans), 2) server, client = spans self.assertEqual(server.name, "DynamicHandler.get") self.assertTrue(server.parent.is_remote) self.assertNotEqual(server.parent, client.context) self.assertEqual(server.parent.span_id, client.context.span_id) self.assertEqual(server.context.trace_id, client.context.trace_id) self.assertEqual(server.kind, SpanKind.SERVER) self.assert_span_has_attributes( server, { "component": "tornado", "http.method": "GET", "http.scheme": "http", "http.host": "127.0.0.1:" + str(self.get_http_port()), "http.target": "/dyna", "net.peer.ip": "127.0.0.1", "http.status_text": "Accepted", "http.status_code": 202, }, ) self.assertEqual(client.name, "GET") self.assertFalse(client.context.is_remote) self.assertIsNone(client.parent) self.assertEqual(client.kind, SpanKind.CLIENT) self.assert_span_has_attributes( client, { "component": "tornado", "http.url": self.get_url("/dyna"), "http.method": "GET", "http.status_code": 202, }, ) @patch( "opentelemetry.instrumentation.tornado._excluded_urls", ExcludeList(["healthz", "ping"]), ) def test_exclude_lists(self): def test_excluded(path): self.fetch(path) spans = self.sorted_spans( self.memory_exporter.get_finished_spans()) self.assertEqual(len(spans), 1) client = spans[0] self.assertEqual(client.name, "GET") self.assertEqual(client.kind, SpanKind.CLIENT) self.assert_span_has_attributes( client, { "component": "tornado", "http.url": self.get_url(path), "http.method": "GET", "http.status_code": 200, }, ) self.memory_exporter.clear() test_excluded("/healthz") test_excluded("/ping") @patch( "opentelemetry.instrumentation.tornado._traced_attrs", ["uri", "full_url", "query"], ) def test_traced_attrs(self): self.fetch("/ping?q=abc&b=123") spans = self.sorted_spans(self.memory_exporter.get_finished_spans()) self.assertEqual(len(spans), 2) server_span = spans[0] self.assertEqual(server_span.kind, SpanKind.SERVER) self.assert_span_has_attributes(server_span, { "uri": "/ping?q=abc&b=123", "query": "q=abc&b=123" }) self.memory_exporter.clear()
class TestProgrammatic(InstrumentationTest, TestBase, WsgiTestBase): def setUp(self): super().setUp() config = Configurator() PyramidInstrumentor().instrument_config(config) self.config = config self._common_initialization(self.config) def tearDown(self): super().tearDown() with self.disable_logging(): PyramidInstrumentor().uninstrument_config(self.config) def test_uninstrument(self): resp = self.client.get("/hello/123") self.assertEqual(200, resp.status_code) self.assertEqual([b"Hello: 123"], list(resp.response)) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) PyramidInstrumentor().uninstrument_config(self.config) # Need to remake the WSGI app export self._common_initialization(self.config) resp = self.client.get("/hello/123") self.assertEqual(200, resp.status_code) self.assertEqual([b"Hello: 123"], list(resp.response)) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) def test_simple(self): expected_attrs = expected_attributes({ "http.target": "/hello/123", "http.route": "/hello/{helloid}" }) self.client.get("/hello/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.assertEqual(span_list[0].name, "/hello/{helloid}") self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) self.assertEqual(span_list[0].attributes, expected_attrs) def test_not_recording(self): mock_tracer = Mock() mock_span = Mock() mock_span.is_recording.return_value = False mock_tracer.start_span.return_value = mock_span mock_tracer.use_span.return_value.__enter__ = mock_span mock_tracer.use_span.return_value.__exit__ = mock_span with patch("opentelemetry.trace.get_tracer"): self.client.get("/hello/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called) def test_404(self): expected_attrs = expected_attributes({ "http.method": "POST", "http.target": "/bye", "http.status_text": "Not Found", "http.status_code": 404, }) resp = self.client.post("/bye") self.assertEqual(404, resp.status_code) resp.close() span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.assertEqual(span_list[0].name, "HTTP POST") self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) self.assertEqual(span_list[0].attributes, expected_attrs) def test_internal_error(self): expected_attrs = expected_attributes({ "http.target": "/hello/500", "http.route": "/hello/{helloid}", "http.status_text": "Internal Server Error", "http.status_code": 500, }) resp = self.client.get("/hello/500") self.assertEqual(500, resp.status_code) resp.close() span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.assertEqual(span_list[0].name, "/hello/{helloid}") self.assertEqual(span_list[0].kind, trace.SpanKind.SERVER) self.assertEqual(span_list[0].attributes, expected_attrs) def test_tween_list(self): tween_list = "opentelemetry.instrumentation.pyramid.trace_tween_factory\npyramid.tweens.excview_tween_factory" config = Configurator(settings={"pyramid.tweens": tween_list}) PyramidInstrumentor().instrument_config(config) self._common_initialization(config) resp = self.client.get("/hello/123") self.assertEqual(200, resp.status_code) self.assertEqual([b"Hello: 123"], list(resp.response)) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) PyramidInstrumentor().uninstrument_config(config) # Need to remake the WSGI app export self._common_initialization(config) resp = self.client.get("/hello/123") self.assertEqual(200, resp.status_code) self.assertEqual([b"Hello: 123"], list(resp.response)) span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) @patch("opentelemetry.instrumentation.pyramid.callbacks._logger") def test_warnings(self, mock_logger): tween_list = "pyramid.tweens.excview_tween_factory" config = Configurator(settings={"pyramid.tweens": tween_list}) PyramidInstrumentor().instrument_config(config) self._common_initialization(config) self.client.get("/hello/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) self.assertEqual(mock_logger.warning.called, True) mock_logger.warning.called = False tween_list = ( "opentelemetry.instrumentation.pyramid.trace_tween_factory") config = Configurator(settings={"pyramid.tweens": tween_list}) self._common_initialization(config) self.client.get("/hello/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) self.assertEqual(mock_logger.warning.called, True) @patch( "opentelemetry.instrumentation.pyramid.callbacks._excluded_urls", ExcludeList(["http://localhost/excluded_arg/123", "excluded_noarg"]), ) def test_exclude_lists(self): self.client.get("/excluded_arg/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) self.client.get("/excluded_arg/125") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.client.get("/excluded_noarg") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) self.client.get("/excluded_noarg2") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1)
class TestMiddleware(WsgiTestBase): @classmethod def setUpClass(cls): super().setUpClass() settings.configure(ROOT_URLCONF=modules[__name__]) def setUp(self): super().setUp() setup_test_environment() _django_instrumentor.instrument() Configuration._reset() # pylint: disable=protected-access def tearDown(self): super().tearDown() teardown_test_environment() _django_instrumentor.uninstrument() def test_traced_get(self): Client().get("/traced/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual(span.attributes["http.url"], "http://testserver/traced/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") def test_not_recording(self): mock_tracer = Mock() mock_span = Mock() mock_span.is_recording.return_value = False mock_tracer.start_span.return_value = mock_span mock_tracer.use_span.return_value.__enter__ = mock_span mock_tracer.use_span.return_value.__exit__ = True with patch("opentelemetry.trace.get_tracer") as tracer: tracer.return_value = mock_tracer Client().get("/traced/") self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called) def test_traced_post(self): Client().post("/traced/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assertEqual(span.attributes["http.method"], "POST") self.assertEqual(span.attributes["http.url"], "http://testserver/traced/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") def test_error(self): with self.assertRaises(ValueError): Client().get("/error/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "^error/" if DJANGO_2_2 else "tests.views.error") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.UNKNOWN) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual(span.attributes["http.url"], "http://testserver/error/") self.assertEqual(span.attributes["http.scheme"], "http") @patch( "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._excluded_urls", ExcludeList(["http://testserver/excluded_arg/123", "excluded_noarg"]), ) def test_exclude_lists(self): client = Client() client.get("/excluded_arg/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) client.get("/excluded_arg/125") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) client.get("/excluded_noarg/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) client.get("/excluded_noarg2/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) def test_span_name(self): Client().get("/span_name/1234/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual( span.name, "^span_name/([0-9]{4})/$" if DJANGO_2_2 else "tests.views.route_span_name", ) def test_span_name_404(self): Client().get("/span_name/1234567890/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual(span.name, "HTTP GET") def test_traced_request_attrs(self): with patch( "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._traced_request_attrs", [], ): Client().get("/span_name/1234/", CONTENT_TYPE="test/ct") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertNotIn("path_info", span.attributes) self.assertNotIn("content_type", span.attributes) self.memory_exporter.clear() with patch( "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._traced_request_attrs", ["path_info", "content_type", "non_existing_variable"], ): Client().get("/span_name/1234/", CONTENT_TYPE="test/ct") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual(span.attributes["path_info"], "/span_name/1234/") self.assertEqual(span.attributes["content_type"], "test/ct") self.assertNotIn("non_existing_variable", span.attributes)
class _DjangoMiddleware(MiddlewareMixin): """Django Middleware for OpenTelemetry """ _environ_activation_key = ( "opentelemetry-instrumentor-django.activation_key") _environ_token = "opentelemetry-instrumentor-django.token" _environ_span_key = "opentelemetry-instrumentor-django.span_key" _excluded_urls = Configuration().DJANGO_EXCLUDED_URLS or [] if _excluded_urls: _excluded_urls = ExcludeList(str.split(_excluded_urls, ",")) else: _excluded_urls = ExcludeList(_excluded_urls) def process_view(self, request, view_func, view_args, view_kwargs): # pylint: disable=unused-argument # request.META is a dictionary containing all available HTTP headers # Read more about request.META here: # https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.META # environ = { # key.lower().replace('_', '-').replace("http-", "", 1): value # for key, value in request.META.items() # } if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return environ = request.META token = attach(extract(get_header_from_environ, environ)) tracer = get_tracer(__name__, __version__) attributes = collect_request_attributes(environ) span = tracer.start_span( view_func.__name__, kind=SpanKind.SERVER, attributes=attributes, start_time=environ.get( "opentelemetry-instrumentor-django.starttime_key"), ) activation = tracer.use_span(span, end_on_exit=True) activation.__enter__() request.META[self._environ_activation_key] = activation request.META[self._environ_span_key] = span request.META[self._environ_token] = token def process_exception(self, request, exception): # Django can call this method and process_response later. In order # to avoid __exit__ and detach from being called twice then, the # respective keys are being removed here. if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return if self._environ_activation_key in request.META.keys(): request.META[self._environ_activation_key].__exit__( type(exception), exception, getattr(exception, "__traceback__", None), ) request.META.pop(self._environ_activation_key) detach(request.environ[self._environ_token]) request.META.pop(self._environ_token, None) def process_response(self, request, response): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return response if (self._environ_activation_key in request.META.keys() and self._environ_span_key in request.META.keys()): add_response_attributes( request.META[self._environ_span_key], "{} {}".format(response.status_code, response.reason_phrase), response, ) request.META.pop(self._environ_span_key) request.META[self._environ_activation_key].__exit__( None, None, None) request.META.pop(self._environ_activation_key) if self._environ_token in request.META.keys(): detach(request.environ.get(self._environ_token)) request.META.pop(self._environ_token) return response
def test_basic(self): regexes = ExcludeList(["path/123", "http://site.com/other_path"]) self.assertTrue(regexes.url_disabled("http://site.com/path/123")) self.assertTrue(regexes.url_disabled("http://site.com/path/123/abc")) self.assertTrue( regexes.url_disabled("https://site.com/path/123?arg=other") ) self.assertFalse(regexes.url_disabled("https://site.com/path/abc/123")) self.assertFalse(regexes.url_disabled("https://site.com/path")) self.assertTrue(regexes.url_disabled("http://site.com/other_path")) self.assertTrue(regexes.url_disabled("http://site.com/other_path?abc")) self.assertFalse(regexes.url_disabled("https://site.com/other_path")) self.assertFalse( regexes.url_disabled("https://site.com/abc/other_path") )
class TestFalconInstrumentation(TestBase): def setUp(self): super().setUp() FalconInstrumentor().instrument() self.app = make_app() def client(self): return testing.TestClient(self.app) def tearDown(self): super().tearDown() with self.disable_logging(): FalconInstrumentor().uninstrument() def test_get(self): self._test_method("GET") def test_post(self): self._test_method("POST") def test_patch(self): self._test_method("PATCH") def test_put(self): self._test_method("PUT") def test_delete(self): self._test_method("DELETE") def test_head(self): self._test_method("HEAD") def _test_method(self, method): self.client().simulate_request(method=method, path="/hello") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "HelloWorldResource.on_{0}".format(method.lower())) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assert_span_has_attributes( span, { "component": "http", "http.method": method, "http.server_name": "falconframework.org", "http.scheme": "http", "host.port": 80, "http.host": "falconframework.org", "http.target": "/", "net.peer.ip": "127.0.0.1", "net.peer.port": "65133", "http.flavor": "1.1", "falcon.resource": "HelloWorldResource", "http.status_text": "Created", "http.status_code": 201, }, ) self.memory_exporter.clear() def test_404(self): self.client().simulate_get("/does-not-exist") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "HTTP GET") self.assertEqual(span.status.canonical_code, StatusCanonicalCode.NOT_FOUND) self.assert_span_has_attributes( span, { "component": "http", "http.method": "GET", "http.server_name": "falconframework.org", "http.scheme": "http", "host.port": 80, "http.host": "falconframework.org", "http.target": "/", "net.peer.ip": "127.0.0.1", "net.peer.port": "65133", "http.flavor": "1.1", "http.status_text": "Not Found", "http.status_code": 404, }, ) def test_500(self): try: self.client().simulate_get("/error") except NameError: pass spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "ErrorResource.on_get") self.assertFalse(span.status.is_ok) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.INTERNAL) self.assertEqual( span.status.description, "NameError: name 'non_existent_var' is not defined", ) self.assert_span_has_attributes( span, { "component": "http", "http.method": "GET", "http.server_name": "falconframework.org", "http.scheme": "http", "host.port": 80, "http.host": "falconframework.org", "http.target": "/", "net.peer.ip": "127.0.0.1", "net.peer.port": "65133", "http.flavor": "1.1", "http.status_code": 500, }, ) def test_uninstrument(self): self.client().simulate_get(path="/hello") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) self.memory_exporter.clear() FalconInstrumentor().uninstrument() self.app = make_app() self.client().simulate_get(path="/hello") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 0) @patch( "opentelemetry.instrumentation.falcon._excluded_urls", ExcludeList(["ping"]), ) def test_exclude_lists(self): self.client().simulate_get(path="/ping") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) self.client().simulate_get(path="/hello") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) def test_traced_request_attributes(self): self.client().simulate_get(path="/hello?q=abc") span = self.memory_exporter.get_finished_spans()[0] self.assertNotIn("query_string", span.attributes) self.memory_exporter.clear() middleware = self.app._middleware[0][ # pylint:disable=W0212 0].__self__ with patch.object(middleware, "_traced_request_attrs", ["query_string"]): self.client().simulate_get(path="/hello?q=abc") span = self.memory_exporter.get_finished_spans()[0] self.assertIn("query_string", span.attributes) self.assertEqual(span.attributes["query_string"], "q=abc") def test_traced_not_recording(self): mock_tracer = Mock() mock_span = Mock() mock_span.is_recording.return_value = False mock_tracer.start_span.return_value = mock_span mock_tracer.use_span.return_value.__enter__ = mock_span mock_tracer.use_span.return_value.__exit__ = mock_span with patch("opentelemetry.trace.get_tracer") as tracer: tracer.return_value = mock_tracer self.client().simulate_get(path="/hello?q=abc") self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called)
class _DjangoMiddleware(MiddlewareMixin): """Django Middleware for OpenTelemetry""" _environ_activation_key = ( "opentelemetry-instrumentor-django.activation_key") _environ_token = "opentelemetry-instrumentor-django.token" _environ_span_key = "opentelemetry-instrumentor-django.span_key" _environ_exception_key = "opentelemetry-instrumentor-django.exception_key" _excluded_urls = Configuration().DJANGO_EXCLUDED_URLS or [] if _excluded_urls: _excluded_urls = ExcludeList(str.split(_excluded_urls, ",")) else: _excluded_urls = ExcludeList(_excluded_urls) _traced_request_attrs = [ attr.strip() for attr in ( Configuration().DJANGO_TRACED_REQUEST_ATTRS or "").split(",") ] @staticmethod def _get_span_name(request): try: if getattr(request, "resolver_match"): match = request.resolver_match else: match = resolve(request.get_full_path()) if hasattr(match, "route"): return match.route # Instead of using `view_name`, better to use `_func_name` as some applications can use similar # view names in different modules if hasattr(match, "_func_name"): return match._func_name # pylint: disable=protected-access # Fallback for safety as `_func_name` private field return match.view_name except Resolver404: return "HTTP {}".format(request.method) @staticmethod def _get_metric_labels_from_attributes(attributes): labels = {} labels["http.method"] = attributes.get("http.method", "") for attrs in _attributes_by_preference: labels_from_attributes = { attr: attributes.get(attr, None) for attr in attrs } if set(attrs).issubset(attributes.keys()): labels.update(labels_from_attributes) break if attributes.get("http.flavor"): labels["http.flavor"] = attributes.get("http.flavor") return labels def process_request(self, request): # request.META is a dictionary containing all available HTTP headers # Read more about request.META here: # https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.META if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return # pylint:disable=W0212 request._otel_start_time = time.time() environ = request.META token = attach(extract(carrier_getter, environ)) tracer = get_tracer(__name__, __version__) span = tracer.start_span( self._get_span_name(request), kind=SpanKind.SERVER, start_time=environ.get( "opentelemetry-instrumentor-django.starttime_key"), ) attributes = collect_request_attributes(environ) # pylint:disable=W0212 request._otel_labels = self._get_metric_labels_from_attributes( attributes) if span.is_recording(): attributes = extract_attributes_from_object( request, self._traced_request_attrs, attributes) for key, value in attributes.items(): span.set_attribute(key, value) activation = tracer.use_span(span, end_on_exit=True) activation.__enter__() request.META[self._environ_activation_key] = activation request.META[self._environ_span_key] = span request.META[self._environ_token] = token # pylint: disable=unused-argument def process_view(self, request, view_func, *args, **kwargs): # Process view is executed before the view function, here we get the # route template from request.resolver_match. It is not set yet in process_request if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return if (self._environ_activation_key in request.META.keys() and self._environ_span_key in request.META.keys()): span = request.META[self._environ_span_key] if span.is_recording(): match = getattr(request, "resolver_match") if match: route = getattr(match, "route") if route: span.set_attribute("http.route", route) def process_exception(self, request, exception): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return if self._environ_activation_key in request.META.keys(): request.META[self._environ_exception_key] = exception def process_response(self, request, response): if self._excluded_urls.url_disabled(request.build_absolute_uri("?")): return response if (self._environ_activation_key in request.META.keys() and self._environ_span_key in request.META.keys()): add_response_attributes( request.META[self._environ_span_key], "{} {}".format(response.status_code, response.reason_phrase), response, ) # pylint:disable=W0212 request._otel_labels["http.status_code"] = str( response.status_code) request.META.pop(self._environ_span_key) exception = request.META.pop(self._environ_exception_key, None) if exception: request.META[self._environ_activation_key].__exit__( type(exception), exception, getattr(exception, "__traceback__", None), ) else: request.META[self._environ_activation_key].__exit__( None, None, None) request.META.pop(self._environ_activation_key) if self._environ_token in request.META.keys(): detach(request.environ.get(self._environ_token)) request.META.pop(self._environ_token) try: metric_recorder = getattr(settings, "OTEL_METRIC_RECORDER", None) if metric_recorder is not None: # pylint:disable=W0212 metric_recorder.record_server_duration_range( request._otel_start_time, time.time(), request._otel_labels) except Exception as ex: # pylint: disable=W0703 _logger.warning("Error recording duration metrics: %s", ex) return response
class TestMiddleware(WsgiTestBase): @classmethod def setUpClass(cls): super().setUpClass() settings.configure(ROOT_URLCONF=modules[__name__]) def setUp(self): super().setUp() setup_test_environment() _django_instrumentor.instrument() Configuration._reset() # pylint: disable=protected-access def tearDown(self): super().tearDown() teardown_test_environment() _django_instrumentor.uninstrument() def test_traced_get(self): Client().get("/traced/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "traced") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual(span.attributes["http.url"], "http://testserver/traced/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") def test_traced_post(self): Client().post("/traced/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "traced") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.OK) self.assertEqual(span.attributes["http.method"], "POST") self.assertEqual(span.attributes["http.url"], "http://testserver/traced/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") def test_error(self): with self.assertRaises(ValueError): Client().get("/error/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "error") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.canonical_code, StatusCanonicalCode.UNKNOWN) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual(span.attributes["http.url"], "http://testserver/error/") self.assertEqual(span.attributes["http.scheme"], "http") @patch( "opentelemetry.ext.django.middleware._DjangoMiddleware._excluded_urls", ExcludeList(["http://testserver/excluded_arg/123", "excluded_noarg"]), ) def test_exclude_lists(self): client = Client() client.get("/excluded_arg/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) client.get("/excluded_arg/125") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) client.get("/excluded_noarg/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) client.get("/excluded_noarg2/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1)
class TestMiddleware(TestBase, WsgiTestBase): @classmethod def setUpClass(cls): super().setUpClass() settings.configure(ROOT_URLCONF=modules[__name__]) def setUp(self): super().setUp() setup_test_environment() _django_instrumentor.instrument() Configuration._reset() # pylint: disable=protected-access def tearDown(self): super().tearDown() teardown_test_environment() _django_instrumentor.uninstrument() def test_templated_route_get(self): Client().get("/route/2020/template/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual( span.name, "^route/(?P<year>[0-9]{4})/template/$" if DJANGO_2_2 else "tests.views.traced", ) self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.status_code, StatusCode.UNSET) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual( span.attributes["http.url"], "http://testserver/route/2020/template/", ) self.assertEqual( span.attributes["http.route"], "^route/(?P<year>[0-9]{4})/template/$", ) self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") def test_traced_get(self): Client().get("/traced/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.status_code, StatusCode.UNSET) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual(span.attributes["http.url"], "http://testserver/traced/") self.assertEqual(span.attributes["http.route"], "^traced/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") self.assertIsNotNone(_django_instrumentor.meter) self.assertEqual(len(_django_instrumentor.meter.metrics), 1) recorder = _django_instrumentor.meter.metrics.pop() match_key = get_dict_as_key({ "http.flavor": "1.1", "http.method": "GET", "http.status_code": "200", "http.url": "http://testserver/traced/", }) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1) self.assertGreaterEqual(view_data.aggregator.current.sum, 0) def test_not_recording(self): mock_tracer = Mock() mock_span = Mock() mock_span.is_recording.return_value = False mock_tracer.start_span.return_value = mock_span mock_tracer.use_span.return_value.__enter__ = mock_span mock_tracer.use_span.return_value.__exit__ = True with patch("opentelemetry.trace.get_tracer") as tracer: tracer.return_value = mock_tracer Client().get("/traced/") self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called) def test_traced_post(self): Client().post("/traced/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "^traced/" if DJANGO_2_2 else "tests.views.traced") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.status_code, StatusCode.UNSET) self.assertEqual(span.attributes["http.method"], "POST") self.assertEqual(span.attributes["http.url"], "http://testserver/traced/") self.assertEqual(span.attributes["http.route"], "^traced/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 200) self.assertEqual(span.attributes["http.status_text"], "OK") def test_error(self): with self.assertRaises(ValueError): Client().get("/error/") spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.name, "^error/" if DJANGO_2_2 else "tests.views.error") self.assertEqual(span.kind, SpanKind.SERVER) self.assertEqual(span.status.status_code, StatusCode.ERROR) self.assertEqual(span.attributes["http.method"], "GET") self.assertEqual(span.attributes["http.url"], "http://testserver/error/") self.assertEqual(span.attributes["http.route"], "^error/") self.assertEqual(span.attributes["http.scheme"], "http") self.assertEqual(span.attributes["http.status_code"], 500) self.assertIsNotNone(_django_instrumentor.meter) self.assertEqual(len(_django_instrumentor.meter.metrics), 1) self.assertEqual(len(span.events), 1) event = span.events[0] self.assertEqual(event.name, "exception") self.assertEqual(event.attributes["exception.type"], "ValueError") self.assertEqual(event.attributes["exception.message"], "error") recorder = _django_instrumentor.meter.metrics.pop() match_key = get_dict_as_key({ "http.flavor": "1.1", "http.method": "GET", "http.status_code": "500", "http.url": "http://testserver/error/", }) for key in recorder.bound_instruments.keys(): self.assertEqual(key, match_key) # pylint: disable=protected-access bound = recorder.bound_instruments.get(key) for view_data in bound.view_datas: self.assertEqual(view_data.labels, key) self.assertEqual(view_data.aggregator.current.count, 1) @patch( "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._excluded_urls", ExcludeList(["http://testserver/excluded_arg/123", "excluded_noarg"]), ) def test_exclude_lists(self): client = Client() client.get("/excluded_arg/123") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0) client.get("/excluded_arg/125") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) client.get("/excluded_noarg/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) client.get("/excluded_noarg2/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) def test_span_name(self): # test no query_string Client().get("/span_name/1234/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual( span.name, "^span_name/([0-9]{4})/$" if DJANGO_2_2 else "tests.views.route_span_name", ) def test_span_name_for_query_string(self): """ request not have query string """ Client().get("/span_name/1234/?query=test") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual( span.name, "^span_name/([0-9]{4})/$" if DJANGO_2_2 else "tests.views.route_span_name", ) def test_span_name_404(self): Client().get("/span_name/1234567890/") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual(span.name, "HTTP GET") def test_traced_request_attrs(self): with patch( "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._traced_request_attrs", [], ): Client().get("/span_name/1234/", CONTENT_TYPE="test/ct") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertNotIn("path_info", span.attributes) self.assertNotIn("content_type", span.attributes) self.memory_exporter.clear() with patch( "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._traced_request_attrs", ["path_info", "content_type", "non_existing_variable"], ): Client().get("/span_name/1234/", CONTENT_TYPE="test/ct") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) span = span_list[0] self.assertEqual(span.attributes["path_info"], "/span_name/1234/") self.assertEqual(span.attributes["content_type"], "test/ct") self.assertNotIn("non_existing_variable", span.attributes)