2525from django .test .utils import setup_test_environment , teardown_test_environment
2626
2727from opentelemetry import trace
28+ from opentelemetry .instrumentation ._labeler import clear_labeler
2829from opentelemetry .instrumentation ._semconv import (
2930 HTTP_DURATION_HISTOGRAM_BUCKETS_NEW ,
3031 HTTP_DURATION_HISTOGRAM_BUCKETS_OLD ,
3536 DjangoInstrumentor ,
3637 _DjangoMiddleware ,
3738)
39+ from opentelemetry .instrumentation .django .middleware import (
40+ otel_middleware as django_otel_middleware ,
41+ )
3842from opentelemetry .instrumentation .propagators import (
3943 TraceResponsePropagator ,
4044 set_global_response_propagator ,
6973 excluded_noarg2 ,
7074 response_with_custom_header ,
7175 route_span_name ,
76+ route_span_name_custom_attributes ,
7277 traced ,
7378 traced_template ,
7479)
@@ -95,6 +100,10 @@ def path(path_argument, *args, **kwargs):
95100 re_path (r"^excluded_noarg/" , excluded_noarg ),
96101 re_path (r"^excluded_noarg2/" , excluded_noarg2 ),
97102 re_path (r"^span_name/([0-9]{4})/$" , route_span_name ),
103+ re_path (
104+ r"^span_name_custom_attrs/([0-9]{4})/$" ,
105+ route_span_name_custom_attributes ,
106+ ),
98107 path ("" , traced , name = "empty" ),
99108]
100109_django_instrumentor = DjangoInstrumentor ()
@@ -117,6 +126,7 @@ def setUpClass(cls):
117126
118127 def setUp (self ):
119128 super ().setUp ()
129+ clear_labeler ()
120130 setup_test_environment ()
121131 test_name = ""
122132 if hasattr (self , "_testMethodName" ):
@@ -766,6 +776,111 @@ def test_wsgi_metrics(self):
766776 )
767777 self .assertTrue (histrogram_data_point_seen and number_data_point_seen )
768778
779+ def test_wsgi_metrics_custom_attributes_skip_override (self ):
780+ expected_duration_attributes = {
781+ "http.method" : "GET" ,
782+ "http.scheme" : "http" ,
783+ "http.flavor" : "1.1" ,
784+ "http.server_name" : "testserver" ,
785+ "net.host.port" : 80 ,
786+ "http.status_code" : 200 ,
787+ "http.target" : "^span_name_custom_attrs/([0-9]{4})/$" ,
788+ "custom_attr" : "test_value" ,
789+ }
790+
791+ response = Client ().get ("/span_name_custom_attrs/1234/" )
792+ self .assertEqual (response .status_code , 200 )
793+
794+ metrics = self .get_sorted_metrics (SCOPE )
795+ active_requests_point_seen = False
796+ histogram_data_point_seen = False
797+ for metric in metrics :
798+ if metric .name == "http.server.active_requests" :
799+ data_points = list (metric .data .data_points )
800+ for point in data_points :
801+ self .assertIsInstance (point , NumberDataPoint )
802+ if point .attributes .get ("custom_attr" ) != "test_value" :
803+ continue
804+ self .assertEqual (point .attributes ["http.method" ], "GET" )
805+ active_requests_point_seen = True
806+ continue
807+
808+ if metric .name != "http.server.duration" :
809+ continue
810+ data_points = list (metric .data .data_points )
811+ self .assertEqual (len (data_points ), 1 )
812+ point = data_points [0 ]
813+ self .assertIsInstance (point , HistogramDataPoint )
814+ histogram_data_point_seen = True
815+ self .assertDictEqual (
816+ expected_duration_attributes , dict (point .attributes )
817+ )
818+
819+ self .assertTrue (active_requests_point_seen )
820+ self .assertTrue (histogram_data_point_seen )
821+
822+ def test_wsgi_active_requests_custom_attributes_new_semconv (self ):
823+ response = Client ().get ("/span_name_custom_attrs/1234/" )
824+ self .assertEqual (response .status_code , 200 )
825+
826+ metrics = self .get_sorted_metrics (SCOPE )
827+ active_requests_point_seen = False
828+ for metric in metrics :
829+ if metric .name != "http.server.active_requests" :
830+ continue
831+ data_points = list (metric .data .data_points )
832+ for point in data_points :
833+ self .assertIsInstance (point , NumberDataPoint )
834+ if point .attributes .get ("custom_attr" ) != "test_value" :
835+ continue
836+ self .assertEqual (
837+ point .attributes ["http.request.method" ], "GET"
838+ )
839+ active_requests_point_seen = True
840+
841+ self .assertTrue (active_requests_point_seen )
842+
843+ def test_wsgi_active_requests_attrs_use_enrich_old_semconv (self ):
844+ with patch (
845+ "opentelemetry.instrumentation.django.middleware.otel_middleware.enrich_metric_attributes" ,
846+ wraps = django_otel_middleware .enrich_metric_attributes ,
847+ ) as mock_enrich :
848+ response = Client ().get ("/span_name/1234/" )
849+ self .assertEqual (response .status_code , 200 )
850+
851+ enriched_active_attrs_seen = False
852+ for call in mock_enrich .call_args_list :
853+ if not call .args :
854+ continue
855+ attrs = call .args [0 ]
856+ if "http.method" in attrs and "http.status_code" not in attrs :
857+ enriched_active_attrs_seen = True
858+ break
859+
860+ self .assertTrue (enriched_active_attrs_seen )
861+
862+ def test_wsgi_active_requests_attrs_use_enrich_new_semconv (self ):
863+ with patch (
864+ "opentelemetry.instrumentation.django.middleware.otel_middleware.enrich_metric_attributes" ,
865+ wraps = django_otel_middleware .enrich_metric_attributes ,
866+ ) as mock_enrich :
867+ response = Client ().get ("/span_name/1234/" )
868+ self .assertEqual (response .status_code , 200 )
869+
870+ enriched_active_attrs_seen = False
871+ for call in mock_enrich .call_args_list :
872+ if not call .args :
873+ continue
874+ attrs = call .args [0 ]
875+ if (
876+ "http.request.method" in attrs
877+ and "http.response.status_code" not in attrs
878+ ):
879+ enriched_active_attrs_seen = True
880+ break
881+
882+ self .assertTrue (enriched_active_attrs_seen )
883+
769884 # pylint: disable=too-many-locals
770885 def test_wsgi_metrics_new_semconv (self ):
771886 _expected_metric_names = [
0 commit comments