66metrics that track the usage and performance of the Azure Monitor OpenTelemetry Exporter.
77"""
88
9- from typing import List , Dict , Any , Iterable
9+ from typing import List , Dict , Any , Iterable , Optional
1010import os
1111
1212from opentelemetry .metrics import CallbackOptions , Observation
1717 _APPLICATIONINSIGHTS_STATSBEAT_ENABLED_PREVIEW ,
1818 _DEFAULT_STATS_SHORT_EXPORT_INTERVAL ,
1919 CustomerStatsbeatProperties ,
20- #DropCode,
20+ DropCode ,
21+ DropCodeType ,
2122 #RetryCode,
2223 CustomerStatsbeatMetricName ,
2324 _CUSTOMER_STATSBEAT_LANGUAGE ,
2930 get_compute_type ,
3031)
3132
33+ from azure .monitor .opentelemetry .exporter .statsbeat ._utils import (
34+ categorize_status_code ,
35+ )
3236from azure .monitor .opentelemetry .exporter import VERSION
3337
3438class _CustomerStatsbeatTelemetryCounters :
3539 def __init__ (self ):
3640 self .total_item_success_count : Dict [str , Any ] = {}
41+ self .total_item_drop_count : Dict [str , Dict [DropCodeType , Dict [str , int ]]] = {}
3742
3843class CustomerStatsbeatMetrics (metaclass = Singleton ):
3944 def __init__ (self , options ):
@@ -66,20 +71,51 @@ def __init__(self, options):
6671 description = "Tracks successful telemetry items sent to Azure Monitor" ,
6772 callbacks = [self ._item_success_callback ]
6873 )
74+ self ._dropped_gauge = self ._customer_statsbeat_meter .create_observable_gauge (
75+ name = CustomerStatsbeatMetricName .ITEM_DROP_COUNT .value ,
76+ description = "Tracks dropped telemetry items sent to Azure Monitor" ,
77+ callbacks = [self ._item_drop_callback ]
78+ )
6979
7080 def count_successful_items (self , count : int , telemetry_type : str ) -> None :
7181 if not self ._is_enabled or count <= 0 :
7282 return
83+
7384 if telemetry_type in self ._counters .total_item_success_count :
7485 self ._counters .total_item_success_count [telemetry_type ] += count
7586 else :
7687 self ._counters .total_item_success_count [telemetry_type ] = count
7788
89+ def count_dropped_items (
90+ self , count : int , telemetry_type : str , drop_code : DropCodeType ,
91+ exception_message : Optional [str ] = None
92+ ) -> None :
93+ if not self ._is_enabled or count <= 0 :
94+ return
95+
96+ # Get or create the drop_code map for this telemetry_type
97+ if telemetry_type not in self ._counters .total_item_drop_count :
98+ self ._counters .total_item_drop_count [telemetry_type ] = {}
99+ drop_code_map = self ._counters .total_item_drop_count [telemetry_type ]
100+
101+ # Get or create the reason map for this drop_code
102+ if drop_code not in drop_code_map :
103+ drop_code_map [drop_code ] = {}
104+ reason_map = drop_code_map [drop_code ]
105+
106+ # Generate a low-cardinality, informative reason description
107+ reason = self ._get_drop_reason (drop_code , exception_message )
108+
109+ # Update the count for this reason
110+ current_count = reason_map .get (reason , 0 )
111+ reason_map [reason ] = current_count + count
112+
78113 def _item_success_callback (self , options : CallbackOptions ) -> Iterable [Observation ]: # pylint: disable=unused-argument
79114 if not getattr (self , "_is_enabled" , False ):
80115 return []
81116
82117 observations : List [Observation ] = []
118+
83119 for telemetry_type , count in self ._counters .total_item_success_count .items ():
84120 attributes = {
85121 "language" : self ._customer_properties .language ,
@@ -90,3 +126,37 @@ def _item_success_callback(self, options: CallbackOptions) -> Iterable[Observati
90126 observations .append (Observation (count , dict (attributes )))
91127
92128 return observations
129+
130+ def _item_drop_callback (self , options : CallbackOptions ) -> Iterable [Observation ]: # pylint: disable=unused-argument
131+ if not getattr (self , "_is_enabled" , False ):
132+ return []
133+ observations : List [Observation ] = []
134+ for telemetry_type , drop_code_map in self ._counters .total_item_drop_count .items ():
135+ for drop_code , reason_map in drop_code_map .items ():
136+ for reason , count in reason_map .items ():
137+ attributes = {
138+ "language" : self ._customer_properties .language ,
139+ "version" : self ._customer_properties .version ,
140+ "compute_type" : self ._customer_properties .compute_type ,
141+ "drop.code" : drop_code ,
142+ "drop.reason" : reason ,
143+ "telemetry_type" : telemetry_type
144+ }
145+ observations .append (Observation (count , dict (attributes )))
146+
147+ return observations
148+
149+ def _get_drop_reason (self , drop_code : DropCodeType , exception_message : Optional [str ] = None ) -> str :
150+ if isinstance (drop_code , int ):
151+ return categorize_status_code (drop_code )
152+
153+ if drop_code == DropCode .CLIENT_EXCEPTION :
154+ return exception_message if exception_message else "unknown_exception"
155+
156+ drop_code_reasons = {
157+ DropCode .CLIENT_READONLY : "readonly_mode" ,
158+ DropCode .CLIENT_STALE_DATA : "stale_data" ,
159+ DropCode .CLIENT_PERSISTENCE_CAPACITY : "persistence_full" ,
160+ }
161+
162+ return drop_code_reasons .get (drop_code , "unknown_reason" )
0 commit comments