2222
2323import opentelemetry .instrumentation .wsgi as otel_wsgi
2424from opentelemetry import trace as trace_api
25+ from opentelemetry .instrumentation ._labeler import (
26+ clear_labeler ,
27+ get_labeler ,
28+ )
2529from opentelemetry .instrumentation ._semconv import (
2630 HTTP_DURATION_HISTOGRAM_BUCKETS_NEW ,
2731 OTEL_SEMCONV_STABILITY_OPT_IN ,
@@ -139,6 +143,14 @@ def error_wsgi_unhandled(environ, start_response):
139143 raise ValueError
140144
141145
146+ def error_wsgi_unhandled_custom_attrs (environ , start_response ):
147+ labeler = get_labeler ()
148+ labeler .add ("custom_attr" , "test_value" )
149+ labeler .add_attributes ({"endpoint_type" : "test" , "feature_flag" : True })
150+ assert isinstance (environ , dict )
151+ raise ValueError
152+
153+
142154def wsgi_with_custom_response_headers (environ , start_response ):
143155 assert isinstance (environ , dict )
144156 start_response (
@@ -201,6 +213,28 @@ def wsgi_with_repeat_custom_response_headers(environ, start_response):
201213 "http.server.request.duration" : _server_duration_attrs_new ,
202214}
203215
216+ _custom_attributes = ["custom_attr" , "endpoint_type" , "feature_flag" ]
217+ _server_duration_attrs_old_with_custom = _server_duration_attrs_old .copy ()
218+ _server_duration_attrs_old_with_custom .append ("http.target" )
219+ _server_duration_attrs_old_with_custom .extend (_custom_attributes )
220+ _server_duration_attrs_new_with_custom = _server_duration_attrs_new .copy ()
221+ _server_duration_attrs_new_with_custom .append ("http.route" )
222+ _server_duration_attrs_new_with_custom .extend (_custom_attributes )
223+
224+ _recommended_metrics_attrs_old_with_custom = {
225+ "http.server.active_requests" : _server_active_requests_count_attrs_old ,
226+ "http.server.duration" : _server_duration_attrs_old_with_custom ,
227+ }
228+ _recommended_metrics_attrs_new_with_custom = {
229+ "http.server.active_requests" : _server_active_requests_count_attrs_new ,
230+ "http.server.request.duration" : _server_duration_attrs_new_with_custom ,
231+ }
232+ _recommended_metrics_attrs_both_with_custom = {
233+ "http.server.active_requests" : _server_active_requests_count_attrs_both ,
234+ "http.server.duration" : _server_duration_attrs_old_with_custom ,
235+ "http.server.request.duration" : _server_duration_attrs_new_with_custom ,
236+ }
237+
204238
205239class TestWsgiApplication (WsgiTestBase ):
206240 def setUp (self ):
@@ -221,6 +255,8 @@ def setUp(self):
221255 },
222256 )
223257
258+ clear_labeler ()
259+
224260 _OpenTelemetrySemanticConventionStability ._initialized = False
225261
226262 self .env_patch .start ()
@@ -415,6 +451,41 @@ def test_wsgi_metrics(self):
415451 )
416452 self .assertTrue (number_data_point_seen and histogram_data_point_seen )
417453
454+ def test_wsgi_metrics_custom_attributes (self ):
455+ app = otel_wsgi .OpenTelemetryMiddleware (
456+ error_wsgi_unhandled_custom_attrs
457+ )
458+ self .assertRaises (ValueError , app , self .environ , self .start_response )
459+ self .assertRaises (ValueError , app , self .environ , self .start_response )
460+ self .assertRaises (ValueError , app , self .environ , self .start_response )
461+ metrics_list = self .memory_metrics_reader .get_metrics_data ()
462+ number_data_point_seen = False
463+ histogram_data_point_seen = False
464+
465+ self .assertTrue (len (metrics_list .resource_metrics ) != 0 )
466+ for resource_metric in metrics_list .resource_metrics :
467+ self .assertTrue (len (resource_metric .scope_metrics ) != 0 )
468+ for scope_metric in resource_metric .scope_metrics :
469+ self .assertTrue (len (scope_metric .metrics ) != 0 )
470+ for metric in scope_metric .metrics :
471+ self .assertIn (metric .name , _expected_metric_names_old )
472+ data_points = list (metric .data .data_points )
473+ self .assertEqual (len (data_points ), 1 )
474+ for point in data_points :
475+ if isinstance (point , HistogramDataPoint ):
476+ self .assertEqual (point .count , 3 )
477+ histogram_data_point_seen = True
478+ if isinstance (point , NumberDataPoint ):
479+ number_data_point_seen = True
480+ for attr in point .attributes :
481+ self .assertIn (
482+ attr ,
483+ _recommended_metrics_attrs_old_with_custom [
484+ metric .name
485+ ],
486+ )
487+ self .assertTrue (number_data_point_seen and histogram_data_point_seen )
488+
418489 def test_wsgi_metrics_new_semconv (self ):
419490 # pylint: disable=too-many-nested-blocks
420491 app = otel_wsgi .OpenTelemetryMiddleware (error_wsgi_unhandled )
@@ -452,6 +523,45 @@ def test_wsgi_metrics_new_semconv(self):
452523 )
453524 self .assertTrue (number_data_point_seen and histogram_data_point_seen )
454525
526+ def test_wsgi_metrics_new_semconv_custom_attributes (self ):
527+ # pylint: disable=too-many-nested-blocks
528+ app = otel_wsgi .OpenTelemetryMiddleware (error_wsgi_unhandled )
529+ self .assertRaises (ValueError , app , self .environ , self .start_response )
530+ self .assertRaises (ValueError , app , self .environ , self .start_response )
531+ self .assertRaises (ValueError , app , self .environ , self .start_response )
532+ metrics_list = self .memory_metrics_reader .get_metrics_data ()
533+ number_data_point_seen = False
534+ histogram_data_point_seen = False
535+
536+ self .assertTrue (len (metrics_list .resource_metrics ) != 0 )
537+ for resource_metric in metrics_list .resource_metrics :
538+ self .assertTrue (len (resource_metric .scope_metrics ) != 0 )
539+ for scope_metric in resource_metric .scope_metrics :
540+ self .assertTrue (len (scope_metric .metrics ) != 0 )
541+ for metric in scope_metric .metrics :
542+ self .assertIn (metric .name , _expected_metric_names_new )
543+ data_points = list (metric .data .data_points )
544+ self .assertEqual (len (data_points ), 1 )
545+ for point in data_points :
546+ if isinstance (point , HistogramDataPoint ):
547+ self .assertEqual (point .count , 3 )
548+ if metric .name == "http.server.request.duration" :
549+ self .assertEqual (
550+ point .explicit_bounds ,
551+ HTTP_DURATION_HISTOGRAM_BUCKETS_NEW ,
552+ )
553+ histogram_data_point_seen = True
554+ if isinstance (point , NumberDataPoint ):
555+ number_data_point_seen = True
556+ for attr in point .attributes :
557+ self .assertIn (
558+ attr ,
559+ _recommended_metrics_attrs_new_with_custom [
560+ metric .name
561+ ],
562+ )
563+ self .assertTrue (number_data_point_seen and histogram_data_point_seen )
564+
455565 def test_wsgi_metrics_both_semconv (self ):
456566 # pylint: disable=too-many-nested-blocks
457567 app = otel_wsgi .OpenTelemetryMiddleware (error_wsgi_unhandled )
@@ -496,6 +606,52 @@ def test_wsgi_metrics_both_semconv(self):
496606 )
497607 self .assertTrue (number_data_point_seen and histogram_data_point_seen )
498608
609+ def test_wsgi_metrics_both_semconv_custom_attributes (self ):
610+ # pylint: disable=too-many-nested-blocks
611+ app = otel_wsgi .OpenTelemetryMiddleware (error_wsgi_unhandled )
612+ self .assertRaises (ValueError , app , self .environ , self .start_response )
613+ metrics_list = self .memory_metrics_reader .get_metrics_data ()
614+ number_data_point_seen = False
615+ histogram_data_point_seen = False
616+
617+ self .assertTrue (len (metrics_list .resource_metrics ) != 0 )
618+ for resource_metric in metrics_list .resource_metrics :
619+ self .assertTrue (len (resource_metric .scope_metrics ) != 0 )
620+ for scope_metric in resource_metric .scope_metrics :
621+ self .assertTrue (len (scope_metric .metrics ) != 0 )
622+ for metric in scope_metric .metrics :
623+ if metric .unit == "ms" :
624+ self .assertEqual (metric .name , "http.server.duration" )
625+ elif metric .unit == "s" :
626+ self .assertEqual (
627+ metric .name , "http.server.request.duration"
628+ )
629+ else :
630+ self .assertEqual (
631+ metric .name , "http.server.active_requests"
632+ )
633+ data_points = list (metric .data .data_points )
634+ self .assertEqual (len (data_points ), 1 )
635+ for point in data_points :
636+ if isinstance (point , HistogramDataPoint ):
637+ self .assertEqual (point .count , 1 )
638+ if metric .name == "http.server.request.duration" :
639+ self .assertEqual (
640+ point .explicit_bounds ,
641+ HTTP_DURATION_HISTOGRAM_BUCKETS_NEW ,
642+ )
643+ histogram_data_point_seen = True
644+ if isinstance (point , NumberDataPoint ):
645+ number_data_point_seen = True
646+ for attr in point .attributes :
647+ self .assertIn (
648+ attr ,
649+ _recommended_metrics_attrs_both_with_custom [
650+ metric .name
651+ ],
652+ )
653+ self .assertTrue (number_data_point_seen and histogram_data_point_seen )
654+
499655 def test_nonstandard_http_method (self ):
500656 self .environ ["REQUEST_METHOD" ] = "NONSTANDARD"
501657 app = otel_wsgi .OpenTelemetryMiddleware (simple_wsgi )
0 commit comments