11# Copyright (c) Microsoft Corporation. All rights reserved.
22# Licensed under the MIT License.
33# cSpell:disable
4- from typing import Any
4+ from typing import Any , Dict , List , Optional
55
66import logging
77import platform
3939 _QuickpulseMetricReader ,
4040)
4141from azure .monitor .opentelemetry .exporter ._quickpulse ._filter import (
42+ _check_filters ,
4243 _check_metric_filters ,
4344)
4445from azure .monitor .opentelemetry .exporter ._quickpulse ._generated .models import (
46+ DerivedMetricInfo ,
47+ FilterConjunctionGroupInfo ,
4548 MonitoringDataPoint ,
4649 TelemetryType ,
4750)
5356 _is_post_state ,
5457 _append_quickpulse_document ,
5558 _get_quickpulse_derived_metric_infos ,
59+ _get_quickpulse_doc_stream_infos ,
5660 _set_global_quickpulse_state ,
5761)
5862from azure .monitor .opentelemetry .exporter ._quickpulse ._types import (
@@ -174,8 +178,6 @@ def _record_span(self, span: ReadableSpan) -> None:
174178 # Only record if in post state
175179 if _is_post_state ():
176180 try :
177- document = _get_span_document (span )
178- _append_quickpulse_document (document )
179181 duration_ms = 0
180182 if span .end_time and span .start_time :
181183 duration_ms = (span .end_time - span .start_time ) / 1e9 # type: ignore
@@ -195,13 +197,14 @@ def _record_span(self, span: ReadableSpan) -> None:
195197 self ._dependency_failure_rate_counter .add (1 )
196198 self ._dependency_duration .record (duration_ms )
197199
198- metric_infos_dict = _get_quickpulse_derived_metric_infos ()
199- # check if filtering is enabled
200- if metric_infos_dict :
201- # Derive metrics for quickpulse filtering
202- data = _TelemetryData ._from_span (span )
203- _derive_metrics_from_telemetry_data (data )
204- # TODO: derive exception metrics from span events
200+ # Derive metrics for quickpulse filtering
201+ data = _TelemetryData ._from_span (span )
202+ _derive_metrics_from_telemetry_data (data )
203+
204+ # Process docs for quickpulse filtering
205+ _apply_document_filters_from_telemetry_data (data )
206+
207+ # TODO: derive exception metrics from span events
205208 except Exception : # pylint: disable=broad-except
206209 _logger .exception ("Exception occurred while recording span." )
207210
@@ -210,31 +213,33 @@ def _record_log_record(self, log_data: LogData) -> None:
210213 if _is_post_state ():
211214 try :
212215 if log_data .log_record :
216+ exc_type = None
213217 log_record = log_data .log_record
214218 if log_record .attributes :
215- document = _get_log_record_document (log_data )
216- _append_quickpulse_document (document )
217219 exc_type = log_record .attributes .get (SpanAttributes .EXCEPTION_TYPE )
218220 exc_message = log_record .attributes .get (SpanAttributes .EXCEPTION_MESSAGE )
219221 if exc_type is not None or exc_message is not None :
220222 self ._exception_rate_counter .add (1 )
221223
222- metric_infos_dict = _get_quickpulse_derived_metric_infos ()
223- # check if filtering is enabled
224- if metric_infos_dict :
225- # Derive metrics for quickpulse filtering
226- data = _TelemetryData . _from_log_record ( log_record )
227- _derive_metrics_from_telemetry_data ( data )
224+ # Derive metrics for quickpulse filtering
225+ data = _TelemetryData . _from_log_record ( log_record )
226+ _derive_metrics_from_telemetry_data ( data )
227+
228+ # Process docs for quickpulse filtering
229+ _apply_document_filters_from_telemetry_data ( data , exc_type ) # type: ignore
228230 except Exception : # pylint: disable=broad-except
229231 _logger .exception ("Exception occurred while recording log record." )
230232
231233
232234# Filtering
233235
234- # Called by record_span/record_log when processing a span/log_record
236+ # Called by record_span/record_log when processing a span/log_record for metrics filtering
235237# Derives metrics from projections if applicable to current filters in config
236238def _derive_metrics_from_telemetry_data (data : _TelemetryData ):
237- metric_infos_dict = _get_quickpulse_derived_metric_infos ()
239+ metric_infos_dict : Dict [TelemetryType , List [DerivedMetricInfo ]] = _get_quickpulse_derived_metric_infos ()
240+ # if empty, filtering was not configured
241+ if not metric_infos_dict :
242+ return
238243 metric_infos = [] # type: ignore
239244 if isinstance (data , _RequestData ):
240245 metric_infos = metric_infos_dict .get (TelemetryType .REQUEST ) # type: ignore
@@ -249,4 +254,44 @@ def _derive_metrics_from_telemetry_data(data: _TelemetryData):
249254 # generate filtered metrics
250255 _create_projections (metric_infos , data )
251256
257+
258+ # Called by record_span/record_log when processing a span/log_record for docs filtering
259+ # Finds doc stream Ids and their doc filter configurations
260+ def _apply_document_filters_from_telemetry_data (data : _TelemetryData , exc_type : Optional [str ] = None ):
261+ doc_config_dict : Dict [TelemetryType , Dict [str , List [FilterConjunctionGroupInfo ]]] = _get_quickpulse_doc_stream_infos () # pylint: disable=C0301
262+ stream_ids = set ()
263+ doc_config = {} # type: ignore
264+ if isinstance (data , _RequestData ):
265+ doc_config = doc_config_dict .get (TelemetryType .REQUEST , {}) # type: ignore
266+ elif isinstance (data , _DependencyData ):
267+ doc_config = doc_config_dict .get (TelemetryType .DEPENDENCY , {}) # type: ignore
268+ elif isinstance (data , _ExceptionData ):
269+ doc_config = doc_config_dict .get (TelemetryType .EXCEPTION , {}) # type: ignore
270+ elif isinstance (data , _TraceData ):
271+ doc_config = doc_config_dict .get (TelemetryType .TRACE , {}) # type: ignore
272+ for stream_id , filter_groups in doc_config .items ():
273+ for filter_group in filter_groups :
274+ if _check_filters (filter_group .filters , data ):
275+ stream_ids .add (stream_id )
276+ break
277+
278+ # We only append and send the document if either:
279+ # 1. The document matched the filtering for a specific streamId
280+ # 2. Filtering was not enabled for this telemetry type (empty doc_config)
281+ if len (stream_ids ) > 0 or not doc_config :
282+ if type (data ) in (_DependencyData , _RequestData ):
283+ document = _get_span_document (data ) # type: ignore
284+ else :
285+ document = _get_log_record_document (data , exc_type ) # type: ignore
286+ # A stream (with a unique streamId) is relevant if there are multiple sources sending to the same
287+ # ApplicationInsights instace with live metrics enabled
288+ # Modify the document's streamIds to determine which stream to send to in post
289+ # Note that the default case is that the list of document_stream_ids is empty, in which
290+ # case no filtering is done for the telemetry type and it is sent to all streams
291+ if stream_ids :
292+ document .document_stream_ids = list (stream_ids )
293+
294+ # Add the generated document to be sent to quickpulse
295+ _append_quickpulse_document (document )
296+
252297# cSpell:enable
0 commit comments