Skip to content

Commit 27e6131

Browse files
Adding OpenTelemetry exporter code (Azure#14784)
* Adding OpenTelemetry exporter code * Adding OT requires * Update ci.yml * Update README.md * rename samples * Addressing comments * Updating generated client name * changelog fix * ignore 27 * oops * 3.5 * Update sdk/monitor/opentelemetry-exporter-azuremonitor/opentelemetry/exporter/azuremonitor/export/trace/__init__.py * Fixing lint * Adding check for storage test path * Adding check in tearDown * Adding file check in tests * Add check in setUp * Disable put test * Adding ignore_errors = true for rmtree method * Renaming import update * fix set up * Updating swagger readme Movign clean up and setup code in tests * Adding exist_ok=True to makedirs method Co-authored-by: Rakshith Bhyravabhotla <rakshith.bhyravabhotla@gmail.com>
1 parent c29191a commit 27e6131

File tree

23 files changed

+2910
-26
lines changed

23 files changed

+2910
-26
lines changed
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# Release History
22

3-
## Unreleased
3+
## 1.0.0b1 (Unreleased)
44

5-
## 0.5b.0
6-
Released 2020-09-24
5+
**Breaking Changes**
6+
- This library is renamed to `microsoft-opentelemetry-exporter-azuremonitor`.
7+
8+
## 0.5b.0 (2020-09-24)
79

810
- Change epoch for live metrics
911
([#115](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/115))
1012
- Dropping support for Python 3.4
1113
([#117](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/117))
1214

13-
## 0.4b.0
14-
Released 2020-06-29
15+
## 0.4b.0 (2020-06-29)
1516

1617
- Added live metrics
1718
([#96](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/96))
@@ -24,14 +25,12 @@ Released 2020-06-29
2425
- Remove request failed per second metrics from auto-collection
2526
([#102](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/102))
2627

27-
## 0.3b.1
28-
Released 2020-05-21
28+
## 0.3b.1 (2020-05-21)
2929

3030
- Fix metrics exporter serialization bug
3131
([#92](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/92))
3232

33-
## 0.3b.0
34-
Released 2020-05-19
33+
## 0.3b.0 (2020-05-19)
3534

3635
- Implement max size logic for local storage
3736
([#74](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/74))
@@ -44,12 +43,10 @@ Released 2020-05-19
4443
- Fix breaking changes from OT release 0.7b.0
4544
([#86](https://github.com/microsoft/opentelemetry-azure-monitor-python/pull/86))
4645

47-
## 0.2b.0
48-
Released 2020-03-31
46+
## 0.2b.0 (2020-03-31)
4947

5048
- Initial beta release
5149

52-
## 0.1a.0
53-
Released 2019-11-06
50+
## 0.1a.0 (2019-11-06)
5451

5552
- Initial alpha release
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
include *.md
2-
include opentelemetry/__init__.py
3-
include opentelemetry/exporter/__init__.py
2+
include microsoft/opentelemetry/__init__.py
3+
include microsoft/opentelemetry/exporter/__init__.py
44
include LICENSE.txt
55
recursive-include tests *.py
66
recursive-include samples *.py *.md

sdk/monitor/microsoft-opentelemetry-exporter-azuremonitor/microsoft/opentelemetry/exporter/azuremonitor/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# Licensed under the MIT License. See License.txt in the project root for license information.
55
# --------------------------------------------------------------------------
66

7-
from ._generated._azure_monitor_exporter import AzureMonitorExporter
7+
from microsoft.opentelemetry.exporter.azuremonitor.export.trace import AzureMonitorSpanExporter
88
from ._version import VERSION
99

10-
__all__ = ['AzureMonitorExporter']
10+
__all__ = ["AzureMonitorSpanExporter"]
1111
__version__ = VERSION
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
import logging
4+
import typing
5+
from enum import Enum
6+
7+
from azure.core.exceptions import HttpResponseError
8+
from azure.core.pipeline.policies import ProxyPolicy, RetryPolicy
9+
10+
from opentelemetry.sdk.metrics.export import MetricsExportResult
11+
from opentelemetry.sdk.trace.export import SpanExportResult
12+
from microsoft.opentelemetry.exporter.azuremonitor._generated import AzureMonitorClient
13+
from microsoft.opentelemetry.exporter.azuremonitor._generated.models import TelemetryItem
14+
from microsoft.opentelemetry.exporter.azuremonitor.options import ExporterOptions
15+
from microsoft.opentelemetry.exporter.azuremonitor.storage import LocalFileStorage
16+
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
class ExportResult(Enum):
22+
SUCCESS = 0
23+
FAILED_RETRYABLE = 1
24+
FAILED_NOT_RETRYABLE = 2
25+
26+
27+
# pylint: disable=broad-except
28+
class BaseExporter:
29+
"""Azure Monitor base exporter for OpenTelemetry.
30+
31+
Args:
32+
options: :doc:`export.options` to allow configuration for the exporter
33+
"""
34+
35+
def __init__(self, **options):
36+
self._telemetry_processors = []
37+
self.options = ExporterOptions(**options)
38+
retry_policy = RetryPolicy(timeout=self.options.timeout)
39+
proxy_policy = ProxyPolicy(proxies=self.options.proxies)
40+
self.client = AzureMonitorClient(
41+
self.options.endpoint, proxy_policy=proxy_policy, retry_policy=retry_policy)
42+
self.storage = LocalFileStorage(
43+
path=self.options.storage_path,
44+
max_size=self.options.storage_max_size,
45+
maintenance_period=self.options.storage_maintenance_period,
46+
retention_period=self.options.storage_retention_period,
47+
)
48+
49+
def add_telemetry_processor(
50+
self, processor: typing.Callable[..., any]
51+
) -> None:
52+
"""Adds telemetry processor to the collection.
53+
54+
Telemetry processors will be called one by one before telemetry
55+
item is pushed for sending and in the order they were added.
56+
57+
Args:
58+
processor: Processor to add
59+
"""
60+
self._telemetry_processors.append(processor)
61+
62+
def clear_telemetry_processors(self) -> None:
63+
"""Removes all telemetry processors"""
64+
self._telemetry_processors = []
65+
66+
def _apply_telemetry_processors(
67+
self, envelopes: typing.List[TelemetryItem]
68+
) -> typing.List[TelemetryItem]:
69+
"""Applies all telemetry processors in the order they were added.
70+
71+
This function will return the list of envelopes to be exported after
72+
each processor has been run sequentially. Individual processors can
73+
throw exceptions and fail, but the applying of all telemetry processors
74+
will proceed (not fast fail). Processors also return True if envelope
75+
should be included for exporting, False otherwise.
76+
77+
Args:
78+
envelopes: The envelopes to apply each processor to.
79+
"""
80+
filtered_envelopes = []
81+
for envelope in envelopes:
82+
accepted = True
83+
for processor in self._telemetry_processors:
84+
try:
85+
if processor(envelope) is False:
86+
accepted = False
87+
break
88+
except Exception as ex:
89+
logger.warning("Telemetry processor failed with: %s.", ex)
90+
if accepted:
91+
filtered_envelopes.append(envelope)
92+
return filtered_envelopes
93+
94+
def _transmit_from_storage(self) -> None:
95+
for blob in self.storage.gets():
96+
# give a few more seconds for blob lease operation
97+
# to reduce the chance of race (for perf consideration)
98+
if blob.lease(self.options.timeout + 5):
99+
envelopes = blob.get()
100+
result = self._transmit(envelopes)
101+
if result == ExportResult.FAILED_RETRYABLE:
102+
blob.lease(1)
103+
else:
104+
blob.delete()
105+
106+
# pylint: disable=too-many-branches
107+
# pylint: disable=too-many-nested-blocks
108+
# pylint: disable=too-many-return-statements
109+
def _transmit(self, envelopes: typing.List[TelemetryItem]) -> ExportResult:
110+
"""
111+
Transmit the data envelopes to the ingestion service.
112+
113+
Returns an ExportResult, this function should never
114+
throw an exception.
115+
"""
116+
if len(envelopes) > 0:
117+
try:
118+
track_response = self.client.track(envelopes)
119+
if not track_response.errors:
120+
logger.info("Transmission succeeded: Item received: %s. Items accepted: %s",
121+
track_response.items_received, track_response.items_accepted)
122+
return ExportResult.SUCCESS
123+
resend_envelopes = []
124+
for error in track_response.errors:
125+
if is_retryable_code(error.statusCode):
126+
resend_envelopes.append(
127+
envelopes[error.index]
128+
)
129+
else:
130+
logger.error(
131+
"Data drop %s: %s %s.",
132+
error.statusCode,
133+
error.message,
134+
envelopes[error.index],
135+
)
136+
if resend_envelopes:
137+
self.storage.put(resend_envelopes)
138+
139+
except HttpResponseError as response_error:
140+
if is_retryable_code(response_error.status_code):
141+
return ExportResult.FAILED_RETRYABLE
142+
return ExportResult.FAILED_NOT_RETRYABLE
143+
except Exception as ex:
144+
logger.warning(
145+
"Retrying due to transient client side error %s.", ex
146+
)
147+
# client side error (retryable)
148+
return ExportResult.FAILED_RETRYABLE
149+
return ExportResult.FAILED_NOT_RETRYABLE
150+
# No spans to export
151+
return ExportResult.SUCCESS
152+
153+
154+
def is_retryable_code(response_code: int) -> bool:
155+
"""
156+
Determine if response is retryable
157+
"""
158+
return bool(response_code in (
159+
206, # Retriable
160+
408, # Timeout
161+
429, # Throttle, too Many Requests
162+
439, # Quota, too Many Requests over extended time
163+
500, # Internal Server Error
164+
503, # Service Unavailable
165+
))
166+
167+
168+
def get_trace_export_result(result: ExportResult) -> SpanExportResult:
169+
if result == ExportResult.SUCCESS:
170+
return SpanExportResult.SUCCESS
171+
if result in (
172+
ExportResult.FAILED_RETRYABLE,
173+
ExportResult.FAILED_NOT_RETRYABLE,
174+
):
175+
return SpanExportResult.FAILURE
176+
return None
177+
178+
179+
def get_metrics_export_result(result: ExportResult) -> MetricsExportResult:
180+
if result == ExportResult.SUCCESS:
181+
return MetricsExportResult.SUCCESS
182+
if result in (
183+
ExportResult.FAILED_RETRYABLE,
184+
ExportResult.FAILED_NOT_RETRYABLE,
185+
):
186+
return MetricsExportResult.FAILURE
187+
return None

0 commit comments

Comments
 (0)