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)
Example #4
0
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
Example #5
0
def get_excluded_urls():
    urls = configuration.Configuration().FLASK_EXCLUDED_URLS or []
    if urls:
        urls = str.split(urls, ",")
    return ExcludeList(urls)
Example #6
0
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)
Example #9
0
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
Example #13
0
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)
Example #14
0
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)