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 ,
6970 excluded_noarg2 ,
7071 response_with_custom_header ,
7172 route_span_name ,
73+ route_span_name_custom_attributes ,
7274 traced ,
7375 traced_template ,
7476)
@@ -95,6 +97,10 @@ def path(path_argument, *args, **kwargs):
9597 re_path (r"^excluded_noarg/" , excluded_noarg ),
9698 re_path (r"^excluded_noarg2/" , excluded_noarg2 ),
9799 re_path (r"^span_name/([0-9]{4})/$" , route_span_name ),
100+ re_path (
101+ r"^span_name_custom_attrs/([0-9]{4})/$" ,
102+ route_span_name_custom_attributes ,
103+ ),
98104 path ("" , traced , name = "empty" ),
99105]
100106_django_instrumentor = DjangoInstrumentor ()
@@ -115,6 +121,7 @@ def setUpClass(cls):
115121
116122 def setUp (self ):
117123 super ().setUp ()
124+ clear_labeler ()
118125 setup_test_environment ()
119126 test_name = ""
120127 if hasattr (self , "_testMethodName" ):
@@ -770,6 +777,67 @@ def test_wsgi_metrics(self):
770777 )
771778 self .assertTrue (histrogram_data_point_seen and number_data_point_seen )
772779
780+ def test_wsgi_metrics_custom_attributes (self ):
781+ _expected_metric_names = [
782+ "http.server.active_requests" ,
783+ "http.server.duration" ,
784+ ]
785+ expected_duration_attributes = {
786+ "http.method" : "GET" ,
787+ "http.scheme" : "http" ,
788+ "http.flavor" : "1.1" ,
789+ "http.server_name" : "testserver" ,
790+ "net.host.port" : 80 ,
791+ "http.status_code" : 200 ,
792+ "http.target" : "^span_name_custom_attrs/([0-9]{4})/$" ,
793+ "custom_attr" : "test_value" ,
794+ "endpoint_type" : "test" ,
795+ "feature_flag" : True ,
796+ }
797+ expected_requests_count_attributes = {
798+ "http.method" : "GET" ,
799+ "http.scheme" : "http" ,
800+ "http.flavor" : "1.1" ,
801+ "http.server_name" : "testserver" ,
802+ }
803+ start = default_timer ()
804+ for _ in range (3 ):
805+ response = Client ().get ("/span_name_custom_attrs/1234/" )
806+ self .assertEqual (response .status_code , 200 )
807+ duration = max (round ((default_timer () - start ) * 1000 ), 0 )
808+ metrics_list = self .memory_metrics_reader .get_metrics_data ()
809+ number_data_point_seen = False
810+ histrogram_data_point_seen = False
811+
812+ self .assertTrue (len (metrics_list .resource_metrics ) != 0 )
813+ for resource_metric in metrics_list .resource_metrics :
814+ self .assertTrue (len (resource_metric .scope_metrics ) != 0 )
815+ for scope_metric in resource_metric .scope_metrics :
816+ self .assertTrue (len (scope_metric .metrics ) != 0 )
817+ for metric in scope_metric .metrics :
818+ self .assertIn (metric .name , _expected_metric_names )
819+ data_points = list (metric .data .data_points )
820+ self .assertEqual (len (data_points ), 1 )
821+ for point in data_points :
822+ if isinstance (point , HistogramDataPoint ):
823+ self .assertEqual (point .count , 3 )
824+ histrogram_data_point_seen = True
825+ self .assertAlmostEqual (
826+ duration , point .sum , delta = 100
827+ )
828+ self .assertDictEqual (
829+ expected_duration_attributes ,
830+ dict (point .attributes ),
831+ )
832+ if isinstance (point , NumberDataPoint ):
833+ number_data_point_seen = True
834+ self .assertEqual (point .value , 0 )
835+ self .assertDictEqual (
836+ expected_requests_count_attributes ,
837+ dict (point .attributes ),
838+ )
839+ self .assertTrue (histrogram_data_point_seen and number_data_point_seen )
840+
773841 # pylint: disable=too-many-locals
774842 def test_wsgi_metrics_new_semconv (self ):
775843 _expected_metric_names = [
@@ -829,6 +897,68 @@ def test_wsgi_metrics_new_semconv(self):
829897 )
830898 self .assertTrue (histrogram_data_point_seen and number_data_point_seen )
831899
900+ # pylint: disable=too-many-locals
901+ def test_wsgi_metrics_new_semconv_custom_attributes (self ):
902+ _expected_metric_names = [
903+ "http.server.active_requests" ,
904+ "http.server.request.duration" ,
905+ ]
906+ expected_duration_attributes = {
907+ "http.request.method" : "GET" ,
908+ "url.scheme" : "http" ,
909+ "network.protocol.version" : "1.1" ,
910+ "http.response.status_code" : 200 ,
911+ "http.route" : "^span_name_custom_attrs/([0-9]{4})/$" ,
912+ "custom_attr" : "test_value" ,
913+ "endpoint_type" : "test" ,
914+ "feature_flag" : True ,
915+ }
916+ expected_requests_count_attributes = {
917+ "http.request.method" : "GET" ,
918+ "url.scheme" : "http" ,
919+ }
920+ start = default_timer ()
921+ for _ in range (3 ):
922+ response = Client ().get ("/span_name_custom_attrs/1234/" )
923+ self .assertEqual (response .status_code , 200 )
924+ duration_s = default_timer () - start
925+ metrics_list = self .memory_metrics_reader .get_metrics_data ()
926+ number_data_point_seen = False
927+ histrogram_data_point_seen = False
928+
929+ self .assertTrue (len (metrics_list .resource_metrics ) != 0 )
930+ for resource_metric in metrics_list .resource_metrics :
931+ self .assertTrue (len (resource_metric .scope_metrics ) != 0 )
932+ for scope_metric in resource_metric .scope_metrics :
933+ self .assertTrue (len (scope_metric .metrics ) != 0 )
934+ for metric in scope_metric .metrics :
935+ self .assertIn (metric .name , _expected_metric_names )
936+ data_points = list (metric .data .data_points )
937+ self .assertEqual (len (data_points ), 1 )
938+ for point in data_points :
939+ if isinstance (point , HistogramDataPoint ):
940+ self .assertEqual (point .count , 3 )
941+ histrogram_data_point_seen = True
942+ self .assertAlmostEqual (
943+ duration_s , point .sum , places = 1
944+ )
945+ self .assertDictEqual (
946+ expected_duration_attributes ,
947+ dict (point .attributes ),
948+ )
949+ self .assertEqual (
950+ point .explicit_bounds ,
951+ HTTP_DURATION_HISTOGRAM_BUCKETS_NEW ,
952+ )
953+ if isinstance (point , NumberDataPoint ):
954+ number_data_point_seen = True
955+ self .assertEqual (point .value , 0 )
956+ self .assertDictEqual (
957+ expected_requests_count_attributes ,
958+ dict (point .attributes ),
959+ )
960+ self .assertTrue (histrogram_data_point_seen and number_data_point_seen )
961+
832962 # pylint: disable=too-many-locals
833963 # pylint: disable=too-many-nested-blocks
834964 def test_wsgi_metrics_both_semconv (self ):
@@ -917,6 +1047,100 @@ def test_wsgi_metrics_both_semconv(self):
9171047 )
9181048 self .assertTrue (histrogram_data_point_seen and number_data_point_seen )
9191049
1050+ # pylint: disable=too-many-locals
1051+ # pylint: disable=too-many-nested-blocks
1052+ def test_wsgi_metrics_both_semconv_custom_attributes (self ):
1053+ _expected_metric_names = [
1054+ "http.server.duration" ,
1055+ "http.server.active_requests" ,
1056+ "http.server.request.duration" ,
1057+ ]
1058+ expected_duration_attributes_old = {
1059+ "http.method" : "GET" ,
1060+ "http.scheme" : "http" ,
1061+ "http.flavor" : "1.1" ,
1062+ "http.server_name" : "testserver" ,
1063+ "net.host.port" : 80 ,
1064+ "http.status_code" : 200 ,
1065+ "http.target" : "^span_name_custom_attrs/([0-9]{4})/$" ,
1066+ "custom_attr" : "test_value" ,
1067+ "endpoint_type" : "test" ,
1068+ "feature_flag" : True ,
1069+ }
1070+ expected_duration_attributes_new = {
1071+ "http.request.method" : "GET" ,
1072+ "url.scheme" : "http" ,
1073+ "network.protocol.version" : "1.1" ,
1074+ "http.response.status_code" : 200 ,
1075+ "http.route" : "^span_name_custom_attrs/([0-9]{4})/$" ,
1076+ "custom_attr" : "test_value" ,
1077+ "endpoint_type" : "test" ,
1078+ "feature_flag" : True ,
1079+ }
1080+ expected_requests_count_attributes = {
1081+ "http.method" : "GET" ,
1082+ "http.scheme" : "http" ,
1083+ "http.flavor" : "1.1" ,
1084+ "http.server_name" : "testserver" ,
1085+ "http.request.method" : "GET" ,
1086+ "url.scheme" : "http" ,
1087+ }
1088+ start = default_timer ()
1089+ for _ in range (3 ):
1090+ response = Client ().get ("/span_name/1234/" )
1091+ self .assertEqual (response .status_code , 200 )
1092+ duration_s = max (default_timer () - start , 0 )
1093+ duration = max (round (duration_s * 1000 ), 0 )
1094+ metrics_list = self .memory_metrics_reader .get_metrics_data ()
1095+ number_data_point_seen = False
1096+ histrogram_data_point_seen = False
1097+
1098+ self .assertTrue (len (metrics_list .resource_metrics ) != 0 )
1099+ for resource_metric in metrics_list .resource_metrics :
1100+ self .assertTrue (len (resource_metric .scope_metrics ) != 0 )
1101+ for scope_metric in resource_metric .scope_metrics :
1102+ self .assertTrue (len (scope_metric .metrics ) != 0 )
1103+ for metric in scope_metric .metrics :
1104+ self .assertIn (metric .name , _expected_metric_names )
1105+ data_points = list (metric .data .data_points )
1106+ self .assertEqual (len (data_points ), 1 )
1107+ for point in data_points :
1108+ if isinstance (point , HistogramDataPoint ):
1109+ self .assertEqual (point .count , 3 )
1110+ histrogram_data_point_seen = True
1111+ if metric .name == "http.server.request.duration" :
1112+ self .assertAlmostEqual (
1113+ duration_s , point .sum , places = 1
1114+ )
1115+ self .assertDictEqual (
1116+ expected_duration_attributes_new ,
1117+ dict (point .attributes ),
1118+ )
1119+ self .assertEqual (
1120+ point .explicit_bounds ,
1121+ HTTP_DURATION_HISTOGRAM_BUCKETS_NEW ,
1122+ )
1123+ elif metric .name == "http.server.duration" :
1124+ self .assertAlmostEqual (
1125+ duration , point .sum , delta = 100
1126+ )
1127+ self .assertDictEqual (
1128+ expected_duration_attributes_old ,
1129+ dict (point .attributes ),
1130+ )
1131+ self .assertEqual (
1132+ point .explicit_bounds ,
1133+ HTTP_DURATION_HISTOGRAM_BUCKETS_OLD ,
1134+ )
1135+ if isinstance (point , NumberDataPoint ):
1136+ number_data_point_seen = True
1137+ self .assertEqual (point .value , 0 )
1138+ self .assertDictEqual (
1139+ expected_requests_count_attributes ,
1140+ dict (point .attributes ),
1141+ )
1142+ self .assertTrue (histrogram_data_point_seen and number_data_point_seen )
1143+
9201144 def test_wsgi_metrics_unistrument (self ):
9211145 Client ().get ("/span_name/1234/" )
9221146 _django_instrumentor .uninstrument ()
0 commit comments