Skip to content

Commit 2ceab7c

Browse files
authored
Add metrics namespace opt-in (#34463)
1 parent c028477 commit 2ceab7c

File tree

5 files changed

+53
-2
lines changed

5 files changed

+53
-2
lines changed

sdk/monitor/azure-monitor-opentelemetry-exporter/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
([#34141](https://github.com/Azure/azure-sdk-for-python/pull/34141))
1111
- Add application.ver to part A fields
1212
([#34401](https://github.com/Azure/azure-sdk-for-python/pull/34401))
13+
- Add `APPLICATIONINSIGHTS_METRIC_NAMESPACE_OPT_IN`
14+
([#34463](https://github.com/Azure/azure-sdk-for-python/pull/34463))
1315

1416
### Breaking Changes
1517

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/_constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
_APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL = "APPLICATIONINSIGHTS_STATSBEAT_DISABLED_ALL"
1010
_APPLICATIONINSIGHTS_OPENTELEMETRY_RESOURCE_METRIC_DISABLED = \
1111
"APPLICATIONINSIGHTS_OPENTELEMETRY_RESOURCE_METRIC_DISABLED"
12+
_APPLICATIONINSIGHTS_METRIC_NAMESPACE_OPT_IN = "APPLICATIONINSIGHTS_METRIC_NAMESPACE_OPT_IN"
1213
_WEBSITE_SITE_NAME = "WEBSITE_SITE_NAME"
1314
_WEBSITE_HOME_STAMPNAME = "WEBSITE_HOME_STAMPNAME"
1415
_WEBSITE_HOSTNAME = "WEBSITE_HOSTNAME"

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/export/metrics/_exporter.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33
import logging
4+
import os
45

56
from typing import Dict, Optional, Union, Any
67

@@ -23,8 +24,10 @@
2324
NumberDataPoint,
2425
)
2526
from opentelemetry.sdk.resources import Resource
27+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
2628

2729
from azure.monitor.opentelemetry.exporter._constants import (
30+
_APPLICATIONINSIGHTS_METRIC_NAMESPACE_OPT_IN,
2831
_AUTOCOLLECTED_INSTRUMENT_NAMES,
2932
_METRIC_ENVELOPE_NAME,
3033
)
@@ -94,6 +97,7 @@ def export(
9497
point,
9598
metric.name,
9699
resource_metric.resource,
100+
scope_metric.scope,
97101
)
98102
if envelope is not None:
99103
envelopes.append(envelope)
@@ -133,8 +137,9 @@ def _point_to_envelope(
133137
point: DataPointT,
134138
name: str,
135139
resource: Optional[Resource] = None,
140+
scope: Optional[InstrumentationScope] = None,
136141
) -> Optional[TelemetryItem]:
137-
envelope = _convert_point_to_envelope(point, name, resource)
142+
envelope = _convert_point_to_envelope(point, name, resource, scope)
138143
if name in _AUTOCOLLECTED_INSTRUMENT_NAMES:
139144
envelope = _handle_std_metric_envelope(envelope, name, point.attributes) # type: ignore
140145
if envelope is not None:
@@ -168,10 +173,14 @@ def _convert_point_to_envelope(
168173
point: DataPointT,
169174
name: str,
170175
resource: Optional[Resource] = None,
176+
scope: Optional[InstrumentationScope] = None
171177
) -> TelemetryItem:
172178
envelope = _utils._create_telemetry_item(point.time_unix_nano)
173179
envelope.name = _METRIC_ENVELOPE_NAME
174180
envelope.tags.update(_utils._populate_part_a_fields(resource)) # type: ignore
181+
namespace = None
182+
if scope is not None and _is_metric_namespace_opted_in():
183+
namespace = str(scope.name)[:256]
175184
value: Union[int, float] = 0
176185
count = 1
177186
min_ = None
@@ -191,6 +200,7 @@ def _convert_point_to_envelope(
191200

192201
data_point = MetricDataPoint(
193202
name=str(name)[:1024],
203+
namespace=namespace,
194204
value=value,
195205
count=count,
196206
min=min_,
@@ -266,6 +276,8 @@ def _handle_std_metric_envelope(
266276
def _is_status_code_success(status_code: Optional[str], threshold: int) -> bool:
267277
return status_code is not None and int(status_code) < threshold
268278

279+
def _is_metric_namespace_opted_in() -> bool:
280+
return os.environ.get(_APPLICATIONINSIGHTS_METRIC_NAMESPACE_OPT_IN, "False").lower() == "true"
269281

270282
def _get_metric_export_result(result: ExportResult) -> MetricExportResult:
271283
if result == ExportResult.SUCCESS:

sdk/monitor/azure-monitor-opentelemetry-exporter/azure/monitor/opentelemetry/exporter/statsbeat/_exporter.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Optional
44
from opentelemetry.sdk.metrics.export import DataPointT
55
from opentelemetry.sdk.resources import Resource
6+
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
67

78
from azure.monitor.opentelemetry.exporter._generated.models import TelemetryItem
89
from azure.monitor.opentelemetry.exporter import AzureMonitorMetricExporter
@@ -17,11 +18,13 @@ def _point_to_envelope(
1718
point: DataPointT,
1819
name: str,
1920
resource: Optional[Resource] = None,
21+
scope: Optional[InstrumentationScope] = None
2022
) -> Optional[TelemetryItem]:
2123
# map statsbeat name from OpenTelemetry name
2224
name = _STATSBEAT_METRIC_NAME_MAPPINGS[name]
2325
return super()._point_to_envelope(
2426
point,
2527
name,
2628
resource,
29+
None,
2730
)

sdk/monitor/azure-monitor-opentelemetry-exporter/tests/metrics/test_metrics.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def test_point_to_envelope_partA_default(self):
213213
def test_point_to_envelope_number(self):
214214
exporter = self._exporter
215215
resource = Resource.create(attributes={"asd":"test_resource"})
216+
scope = InstrumentationScope("test_scope")
216217
point=NumberDataPoint(
217218
attributes={
218219
"test": "attribute",
@@ -221,7 +222,7 @@ def test_point_to_envelope_number(self):
221222
time_unix_nano=1646865018558419457,
222223
value=10,
223224
)
224-
envelope = exporter._point_to_envelope(point, "test name", resource)
225+
envelope = exporter._point_to_envelope(point, "test name", resource, scope)
225226
self.assertEqual(envelope.instrumentation_key, exporter._instrumentation_key)
226227
self.assertEqual(envelope.name, 'Microsoft.ApplicationInsights.Metric')
227228
self.assertEqual(envelope.time, ns_to_iso_str(point.time_unix_nano))
@@ -230,6 +231,7 @@ def test_point_to_envelope_number(self):
230231
self.assertEqual(envelope.data.base_data.properties['test'], 'attribute')
231232
self.assertEqual(len(envelope.data.base_data.metrics), 1)
232233
self.assertEqual(envelope.data.base_data.metrics[0].name, "test name")
234+
self.assertEqual(envelope.data.base_data.metrics[0].namespace, None)
233235
self.assertEqual(envelope.data.base_data.metrics[0].value, 10)
234236
self.assertEqual(envelope.data.base_data.metrics[0].count, 1)
235237

@@ -261,6 +263,37 @@ def test_point_to_envelope_histogram(self):
261263
self.assertEqual(envelope.data.base_data.metrics[0].value, 31)
262264
self.assertEqual(envelope.data.base_data.metrics[0].count, 7)
263265

266+
@mock.patch.dict(
267+
"os.environ",
268+
{
269+
"APPLICATIONINSIGHTS_METRIC_NAMESPACE_OPT_IN": "True",
270+
},
271+
)
272+
def test_point_to_envelope_metric_namespace(self):
273+
exporter = self._exporter
274+
resource = Resource.create(attributes={"asd":"test_resource"})
275+
scope = InstrumentationScope("test_scope")
276+
point=NumberDataPoint(
277+
attributes={
278+
"test": "attribute",
279+
},
280+
start_time_unix_nano=1646865018558419456,
281+
time_unix_nano=1646865018558419457,
282+
value=10,
283+
)
284+
envelope = exporter._point_to_envelope(point, "test name", resource, scope)
285+
self.assertEqual(envelope.instrumentation_key, exporter._instrumentation_key)
286+
self.assertEqual(envelope.name, 'Microsoft.ApplicationInsights.Metric')
287+
self.assertEqual(envelope.time, ns_to_iso_str(point.time_unix_nano))
288+
self.assertEqual(envelope.data.base_type, 'MetricData')
289+
self.assertEqual(len(envelope.data.base_data.properties), 1)
290+
self.assertEqual(envelope.data.base_data.properties['test'], 'attribute')
291+
self.assertEqual(len(envelope.data.base_data.metrics), 1)
292+
self.assertEqual(envelope.data.base_data.metrics[0].name, "test name")
293+
self.assertEqual(envelope.data.base_data.metrics[0].namespace, "test_scope")
294+
self.assertEqual(envelope.data.base_data.metrics[0].value, 10)
295+
self.assertEqual(envelope.data.base_data.metrics[0].count, 1)
296+
264297
def test_point_to_envelope_std_metric_client_duration(self):
265298
exporter = self._exporter
266299
resource = Resource(

0 commit comments

Comments
 (0)