diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md index a663d989571e..d582b5831159 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md @@ -11,6 +11,9 @@ ### Bugs Fixed +- Fixed sampleRate field in ApplicationInsightsSampler, changed attribute to `_MS.sampleRate` + ([#26771](https://github.com/Azure/azure-sdk-for-python/pull/26771)) + ### Other Changes - Update `README.md` diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py index 21daa7a1cdb1..7efe6f7b32f5 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py @@ -98,3 +98,7 @@ ] # cSpell:enable _INSTRUMENTATIONS_BIT_MAP = {_INSTRUMENTATIONS_LIST[i]: _BASE**i for i in range(len(_INSTRUMENTATIONS_LIST))} + +# sampleRate + +_SAMPLE_RATE_KEY = "_MS.sampleRate" diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/_base.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/_base.py index 68d8ab141883..d1fa6dff0d73 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/_base.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/_base.py @@ -58,6 +58,9 @@ def __init__(self, **kwargs: Any) -> None: """Azure Monitor base exporter for OpenTelemetry. :keyword str api_version: The service API version used. Defaults to latest. + :keyword str connection_string: The connection string used for your Application Insights resource. + :keyword bool enable_local_storage: Determines whether to store failed telemetry records for retry. Defaults to `True`. + :keyword str storage_path: Storage path in which to store retry files. Defaults to `/opentelemetry-python-`. :rtype: None """ parsed_connection_string = ConnectionStringParser(kwargs.get('connection_string')) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py index 31bd8b0ee3b8..543dce28e0e3 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_exporter.py @@ -11,6 +11,7 @@ from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from opentelemetry.trace import SpanKind +from azure.monitor.opentelemetry.exporter._constants import _SAMPLE_RATE_KEY from azure.monitor.opentelemetry.exporter import _utils from azure.monitor.opentelemetry.exporter._generated.models import ( MessageData, @@ -45,6 +46,10 @@ "code.", ] +_STANDARD_AZURE_MONITOR_ATTRIBUTES = [ + _SAMPLE_RATE_KEY, +] + class AzureMonitorTraceExporter(BaseExporter, SpanExporter): """Azure Monitor Trace exporter for OpenTelemetry.""" @@ -420,9 +425,13 @@ def _convert_span_to_envelope(span: ReadableSpan) -> TelemetryItem: if target: data.target = str(target)[:1024] + # sampleRate + if _SAMPLE_RATE_KEY in span.attributes: + envelope.sample_rate = span.attributes[_SAMPLE_RATE_KEY] + data.properties = _utils._filter_custom_properties( span.attributes, - lambda key, val: not _is_opentelemetry_standard_attribute(key) + lambda key, val: not _is_standard_attribute(key) ) if span.links: # Max length for value is 8192 @@ -450,7 +459,7 @@ def _convert_span_events_to_envelopes(span: ReadableSpan) -> Sequence[TelemetryI ) properties = _utils._filter_custom_properties( event.attributes, - lambda key, val: not _is_opentelemetry_standard_attribute(key) + lambda key, val: not _is_standard_attribute(key) ) if event.name == "exception": envelope.name = 'Microsoft.ApplicationInsights.Exception' @@ -545,11 +554,12 @@ def _check_instrumentation_span(span: ReadableSpan) -> None: _utils.add_instrumentation(name) -def _is_opentelemetry_standard_attribute(key: str) -> bool: +def _is_standard_attribute(key: str) -> bool: for prefix in _STANDARD_OPENTELEMETRY_ATTRIBUTE_PREFIXES: if key.startswith(prefix): return True - return False + return key in _STANDARD_AZURE_MONITOR_ATTRIBUTES + def _get_azure_sdk_target_source(attributes: Attributes) -> Optional[str]: # Currently logic only works for ServiceBus and EventHub diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_sampling.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_sampling.py index e431d81eaeb8..64e09dcdf455 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_sampling.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/trace/_sampling.py @@ -15,6 +15,8 @@ from opentelemetry.trace.span import TraceState from opentelemetry.util.types import Attributes +from azure.monitor.opentelemetry.exporter._constants import _SAMPLE_RATE_KEY + _HASH = 5381 _INTEGER_MAX = Int32.maxval @@ -62,7 +64,7 @@ def should_sample( # Add sample rate as span attribute if attributes is None: attributes = {} - attributes["sampleRate"] = self._sample_rate + attributes[_SAMPLE_RATE_KEY] = self._sample_rate return SamplingResult( decision, attributes, diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/README.md b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/README.md index b17872abcc8a..79a17ff3ebd6 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/README.md +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/README.md @@ -10,7 +10,9 @@ products: These code samples show common champion scenario operations with the AzureMonitorMetricExporter. +* Attributes: [sample_attributes.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_attributes.py) * Instruments: [sample_instruments.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_instruments.py) +* Views: [sample_views.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_views.py) ## Installation @@ -20,6 +22,17 @@ $ pip install azure-monitor-opentelemetry-exporter --pre ## Run the Applications +### Metrics with attributes + +* Update `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable + +* Run the sample + +```sh +$ # from this directory +$ python sample_attributes.py +``` + ### Instrument usage * Update `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable @@ -31,6 +44,17 @@ $ # from this directory $ python sample_instruments.py ``` +### Configuring metrics with views + +* Update `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable + +* Run the sample + +```sh +$ # from this directory +$ python sample_views.py +``` + ## Explore the data After running the applications, data would be available in [Azure]( diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_attributes.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_attributes.py index b8304d292013..294fcb5944e5 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_attributes.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_attributes.py @@ -3,7 +3,7 @@ """ An example to show an application using different attributes with instruments in the OpenTelemetry SDK. Metrics created and recorded using the sdk are tracked and telemetry is exported to application insights -with the AzureMonitorMetricsExporter. +with the AzureMonitorMetricExporter. """ import os @@ -16,7 +16,8 @@ exporter = AzureMonitorMetricExporter.from_connection_string( os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"] ) -reader = PeriodicExportingMetricReader(exporter, export_interval_millis=5000) +# Metrics are reported every 1 minute +reader = PeriodicExportingMetricReader(exporter) metrics.set_meter_provider(MeterProvider(metric_readers=[reader])) attribute_set1 = { diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_instruments.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_instruments.py index 396e72e4bc40..6f4c9bfa3053 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_instruments.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_instruments.py @@ -1,9 +1,10 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +# cSpell:disable """ An example to show an application using all instruments in the OpenTelemetry SDK. Metrics created and recorded using the sdk are tracked and telemetry is exported to application insights with the -AzureMonitorMetricsExporter. +AzureMonitorMetricExporter. """ import os from typing import Iterable @@ -18,7 +19,8 @@ exporter = AzureMonitorMetricExporter.from_connection_string( os.environ["APPLICATIONINSIGHTS_CONNECTION_STRING"] ) -reader = PeriodicExportingMetricReader(exporter, export_interval_millis=5000) +# Metrics are reported every 1 minute +reader = PeriodicExportingMetricReader(exporter) metrics.set_meter_provider(MeterProvider(metric_readers=[reader])) # Create a namespaced meter @@ -63,3 +65,5 @@ def observable_gauge_func(options: CallbackOptions) -> Iterable[Observation]: # Async Gauge gauge = meter.create_observable_gauge("gauge", [observable_gauge_func]) + +# cSpell:disable \ No newline at end of file diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_views.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_views.py index 305d2808015f..aaa4758193aa 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_views.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/metrics/sample_views.py @@ -3,7 +3,7 @@ """ This example shows how to customize the metrics that are output by the SDK using Views. Metrics created and recorded using the sdk are tracked and telemetry is exported to application insights with the -AzureMonitorMetricsExporter. +AzureMonitorMetricExporter. """ import os @@ -25,7 +25,8 @@ name="my.counter.total", ) -reader = PeriodicExportingMetricReader(exporter, export_interval_millis=5000) +# Metrics are reported every 1 minute +reader = PeriodicExportingMetricReader(exporter) provider = MeterProvider( metric_readers=[ reader, diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/traces/sample_sampling.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/traces/sample_sampling.py index 95ece50d6e88..b9c44a9e63f5 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/traces/sample_sampling.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/samples/traces/sample_sampling.py @@ -29,3 +29,5 @@ # Approximately 25% of these spans should be sampled out with tracer.start_as_current_span("hello"): print("Hello, World!") + +input() diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_sampling.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_sampling.py index 71563851289f..195cd9a438c2 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_sampling.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_sampling.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. import unittest +from unittest import mock from azure.monitor.opentelemetry.exporter.export.trace._sampling import ( ApplicationInsightsSampler, @@ -21,3 +22,19 @@ def test_invalid_ratio(self): self.assertRaises( ValueError, lambda: ApplicationInsightsSampler(-0.01) ) + + @mock.patch.object(ApplicationInsightsSampler, '_get_DJB2_sample_score') + def test_should_sample(self, score_mock): + sampler = ApplicationInsightsSampler(0.75) + score_mock.return_value = 0.7 + result = sampler.should_sample(None, 0, "test") + self.assertEqual(result.attributes["_MS.sampleRate"], 75) + self.assertTrue(result.decision.is_sampled()) + + @mock.patch.object(ApplicationInsightsSampler, '_get_DJB2_sample_score') + def test_should_sample_not_sampled(self, score_mock): + sampler = ApplicationInsightsSampler(0.5) + score_mock.return_value = 0.7 + result = sampler.should_sample(None, 0, "test") + self.assertEqual(result.attributes["_MS.sampleRate"], 50) + self.assertFalse(result.decision.is_sampled()) diff --git a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py index 83a844a841b3..c60dd7c6f62d 100644 --- a/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py +++ b/sdk/monitor/azure-monitor-opentelemetry-exporter/tests/trace/test_trace.py @@ -976,6 +976,34 @@ def test_span_to_envelope_success_error(self): envelope = exporter._span_to_envelope(span) self.assertFalse(envelope.data.base_data.success) + def test_span_to_envelope_sample_rate(self): + exporter = self._exporter + start_time = 1575494316027613500 + end_time = start_time + 1001000000 + + span = trace._Span( + name="test", + context=SpanContext( + trace_id=36873507687745823477771305566750195431, + span_id=12030755672171557337, + is_remote=False, + ), + attributes={ + "test": "asd", + "http.method": "GET", + "http.url": "https://www.wikipedia.org/wiki/Rabbit", + "http.status_code": 200, + "_MS.sampleRate": 50, + }, + kind=SpanKind.CLIENT, + ) + span._status = Status(status_code=StatusCode.OK) + span.start(start_time=start_time) + span.end(end_time=end_time) + envelope = exporter._span_to_envelope(span) + self.assertEqual(envelope.sample_rate, 50) + self.assertIsNone(envelope.data.base_data.properties.get("_MS.sampleRate")) + def test_span_to_envelope_properties(self): exporter = self._exporter start_time = 1575494316027613500