Repository: open-telemetry/opentelemetry-python-contrib

Issue:
[instrumentation-django] - empty span name
**Describe your environment** 
opentelemetry-instrumentation-django==0.30b1
django==4.0.4


**Steps to reproduce**
Having the following URL patterns:
`from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('list', views.list, name='list'),
]`
Please note that the path route has an empty value.

**What is the expected behavior?**
The expected behavior is not having an empty span name for this endpoint.
Also, consider returning http.route  attribute with value '/'

**What is the actual behavior?**
* Got an empty span name
* No http.route attribute returned




Discussion:
What do you expect the span name if not empty?
if an empty route has the same meaning of a route with a value of '/' then I would consider setting both the route and the name to '/'



Resolving patch:
diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py
--- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py
+++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py
@@ -172,9 +172,12 @@ def _get_span_name(request):
             else:
                 match = resolve(request.path)
 
-            if hasattr(match, "route"):
+            if hasattr(match, "route") and match.route:
                 return f"{request.method} {match.route}"
 
+            if hasattr(match, "url_name") and match.url_name:
+                return f"{request.method} {match.url_name}"
+
             return request.method
 
         except Resolver404:


Test patch:
diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py
--- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py
+++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py
@@ -74,10 +74,14 @@
 DJANGO_3_0 = VERSION >= (3, 0)
 
 if DJANGO_2_0:
-    from django.urls import re_path
+    from django.urls import path, re_path
 else:
     from django.conf.urls import url as re_path
 
+    def path(path_argument, *args, **kwargs):
+        return re_path(rf"^{path_argument}$", *args, **kwargs)
+
+
 urlpatterns = [
     re_path(r"^traced/", traced),
     re_path(r"^traced_custom_header/", response_with_custom_header),
@@ -87,6 +91,7 @@
     re_path(r"^excluded_noarg/", excluded_noarg),
     re_path(r"^excluded_noarg2/", excluded_noarg2),
     re_path(r"^span_name/([0-9]{4})/$", route_span_name),
+    path("", traced, name="empty"),
 ]
 _django_instrumentor = DjangoInstrumentor()
 
@@ -205,6 +210,16 @@ def test_not_recording(self):
             self.assertFalse(mock_span.set_attribute.called)
             self.assertFalse(mock_span.set_status.called)
 
+    def test_empty_path(self):
+        Client().get("/")
+
+        spans = self.memory_exporter.get_finished_spans()
+        self.assertEqual(len(spans), 1)
+
+        span = spans[0]
+
+        self.assertEqual(span.name, "GET empty")
+
     def test_traced_post(self):
         Client().post("/traced/")