Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ dependencies = [
"opentelemetry-api ~= 1.12",
"opentelemetry-sdk ~= 1.12",
"opentelemetry-exporter-otlp-pyproto-common == 1.43.0.dev",
"requests ~= 2.7",
]

[project.entry-points.opentelemetry_traces_exporter]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,66 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

import ssl
from os import environ
from typing import Literal

from requests import Response, Session
from urllib.error import HTTPError
from urllib.request import Request, urlopen

from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER,
)
from opentelemetry.util._importlib_metadata import entry_points


def _is_retryable(resp: Response) -> bool:
if resp.status_code == 408:
def _is_retryable(status_code: int) -> bool:
if status_code == 408:
return True
if 500 <= resp.status_code <= 599:
if 500 <= status_code <= 599:
return True
return False


def _build_ssl_context(
certificate_file: str | bool,
client_cert: str | tuple[str, str | None] | None,
) -> ssl.SSLContext:
context = ssl.create_default_context(
cafile=certificate_file if isinstance(certificate_file, str) else None
)
if certificate_file is False:
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
if client_cert:
certfile, keyfile = (
client_cert if isinstance(client_cert, tuple) else (client_cert, None)
)
context.load_cert_chain(certfile, keyfile)
return context


def _post(
url: str,
data: bytes,
headers: dict[str, str],
timeout_sec: float,
ssl_context: ssl.SSLContext,
) -> tuple[int, str]:
request = Request(url, data=data, headers=headers, method="POST")
try:
with urlopen(request, timeout=timeout_sec, context=ssl_context) as response:
return response.status, response.reason
except HTTPError as error:
return error.code, error.reason


def _load_session_from_envvar(
cred_envvar: Literal[
"OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER",
"OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER",
"OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER",
],
) -> Session | None:
):
_credential_env = environ.get(
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER
) or environ.get(cred_envvar)
Expand All @@ -45,11 +79,15 @@ def _load_session_from_envvar(
f"Requested component '{_credential_env}' not found in "
f"entry point 'opentelemetry_otlp_credential_provider'"
)
if isinstance(maybe_session, Session):
return maybe_session
else:
raise RuntimeError(
f"Requested component '{_credential_env}' is of type {type(maybe_session)}"
f" must be of type `Session`."
)
# `requests` is no longer a dependency of this package, so the
# provider's return value can no longer be verified here.
# from requests import Session
# if isinstance(maybe_session, Session):
# return maybe_session
# else:
# raise RuntimeError(
# f"Requested component '{_credential_env}' is of type {type(maybe_session)}"
# f" must be of type `Session`."
# )
return maybe_session
return None
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
from random import uniform
from threading import Event
from time import time
from urllib.error import URLError
from urllib.parse import urlparse
from zlib import compress

from requests import Session
from requests.exceptions import ConnectionError, RequestException

from opentelemetry.exporter.otlp.pyproto.common._exporter_metrics import (
create_exporter_metrics,
)
Expand All @@ -27,8 +25,9 @@
Compression,
)
from opentelemetry.exporter.otlp.pyproto.http._common import (
_build_ssl_context,
_is_retryable,
_load_session_from_envvar,
_post,
)
from opentelemetry.metrics import MeterProvider
from opentelemetry.sdk._logs import ReadableLogRecord
Expand All @@ -38,7 +37,6 @@
)
from opentelemetry.sdk._shared_internal import DuplicateFilter
from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER,
OTEL_EXPORTER_OTLP_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_KEY,
Expand Down Expand Up @@ -82,7 +80,6 @@ def __init__(
headers: dict[str, str] | None = None,
timeout: float | None = None,
compression: Compression | None = None,
session: Session | None = None,
*,
meter_provider: MeterProvider | None = None,
):
Expand Down Expand Up @@ -122,20 +119,12 @@ def __init__(
)
)
self._compression = compression or _compression_from_env()
self._session = (
session
or _load_session_from_envvar(
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER
)
or Session()
)
self._session.headers.update(self._headers)
self._session.headers.update(_OTLP_HTTP_HEADERS)
self._session.headers.update(self._headers)
self._request_headers = {**_OTLP_HTTP_HEADERS, **self._headers}
if self._compression is not Compression.NoCompression:
self._session.headers.update(
{"Content-Encoding": self._compression.value}
)
self._request_headers["Content-Encoding"] = self._compression.value
self._ssl_context = _build_ssl_context(
self._certificate_file, self._client_cert
)
self._shutdown = False

self._metrics = create_exporter_metrics(
Expand All @@ -161,22 +150,21 @@ def _export(self, serialized_data: bytes, timeout_sec: float | None = None):
if timeout_sec is None:
timeout_sec = self._timeout
try:
resp = self._session.post(
url=self._endpoint,
data=data,
verify=self._certificate_file,
timeout=timeout_sec,
cert=self._client_cert,
return _post(
self._endpoint,
data,
self._request_headers,
timeout_sec,
self._ssl_context,
)
except ConnectionError:
resp = self._session.post(
url=self._endpoint,
data=data,
verify=self._certificate_file,
timeout=timeout_sec,
cert=self._client_cert,
except URLError:
return _post(
self._endpoint,
data,
self._request_headers,
timeout_sec,
self._ssl_context,
)
return resp

def export(self, batch: Sequence[ReadableLogRecord]) -> LogRecordExportResult:
if self._shutdown:
Expand All @@ -190,18 +178,17 @@ def export(self, batch: Sequence[ReadableLogRecord]) -> LogRecordExportResult:
backoff_seconds = 2**retry_num * uniform(0.8, 1.2)
export_error: Exception | None = None
try:
resp = self._export(serialized_data, deadline_sec - time())
if resp.ok:
status_code, reason = self._export(
serialized_data, deadline_sec - time()
)
if status_code < 400:
return LogRecordExportResult.SUCCESS
except RequestException as error:
reason = error
retryable = _is_retryable(status_code)
except URLError as error:
reason = error.reason
export_error = error
retryable = isinstance(error, ConnectionError)
retryable = True
status_code = None
else:
reason = resp.reason
retryable = _is_retryable(resp)
status_code = resp.status_code

if not retryable:
_logger.error(
Expand Down Expand Up @@ -254,7 +241,6 @@ def shutdown(self):
return
self._shutdown = True
self._shutdown_is_occuring.set()
self._session.close()


def _compression_from_env() -> Compression:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@
from random import uniform
from threading import Event
from time import time
from urllib.error import URLError
from urllib.parse import urlparse
from zlib import compress

from requests import Session
from requests.exceptions import ConnectionError, RequestException

from opentelemetry.exporter.otlp.pyproto.common._exporter_metrics import (
create_exporter_metrics,
)
Expand All @@ -29,8 +27,9 @@
Compression,
)
from opentelemetry.exporter.otlp.pyproto.http._common import (
_build_ssl_context,
_is_retryable,
_load_session_from_envvar,
_post,
)
from opentelemetry.metrics import MeterProvider
from opentelemetry.pyproto.collector.metrics.v1.metrics_service_pypb2 import (
Expand All @@ -47,7 +46,6 @@
Summary,
)
from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER,
OTEL_EXPORTER_OTLP_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_KEY,
Expand Down Expand Up @@ -97,7 +95,6 @@ def __init__(
headers: dict[str, str] | None = None,
timeout: float | None = None,
compression: Compression | None = None,
session: Session | None = None,
preferred_temporality: dict[type, AggregationTemporality] | None = None,
preferred_aggregation: dict[type, Aggregation] | None = None,
max_export_batch_size: int | None = None,
Expand Down Expand Up @@ -140,20 +137,12 @@ def __init__(
)
)
self._compression = compression or _compression_from_env()
self._session = (
session
or _load_session_from_envvar(
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER
)
or Session()
)
self._session.headers.update(self._headers)
self._session.headers.update(_OTLP_HTTP_HEADERS)
self._session.headers.update(self._headers)
self._request_headers = {**_OTLP_HTTP_HEADERS, **self._headers}
if self._compression is not Compression.NoCompression:
self._session.headers.update(
{"Content-Encoding": self._compression.value}
)
self._request_headers["Content-Encoding"] = self._compression.value
self._ssl_context = _build_ssl_context(
self._certificate_file, self._client_cert
)
self._common_configuration(preferred_temporality, preferred_aggregation)
self._max_export_batch_size = max_export_batch_size
self._shutdown = False
Expand Down Expand Up @@ -181,22 +170,21 @@ def _export(self, serialized_data: bytes, timeout_sec: float | None = None):
if timeout_sec is None:
timeout_sec = self._timeout
try:
resp = self._session.post(
url=self._endpoint,
data=data,
verify=self._certificate_file,
timeout=timeout_sec,
cert=self._client_cert,
return _post(
self._endpoint,
data,
self._request_headers,
timeout_sec,
self._ssl_context,
)
except ConnectionError:
resp = self._session.post(
url=self._endpoint,
data=data,
verify=self._certificate_file,
timeout=timeout_sec,
cert=self._client_cert,
except URLError:
return _post(
self._endpoint,
data,
self._request_headers,
timeout_sec,
self._ssl_context,
)
return resp

def _export_with_retries(
self,
Expand All @@ -210,18 +198,17 @@ def _export_with_retries(
backoff_seconds = 2**retry_num * uniform(0.8, 1.2)
export_error: Exception | None = None
try:
resp = self._export(serialized_data, deadline_sec - time())
if resp.ok:
status_code, reason = self._export(
serialized_data, deadline_sec - time()
)
if status_code < 400:
return MetricExportResult.SUCCESS
except RequestException as error:
reason = error
retryable = _is_retryable(status_code)
except URLError as error:
reason = error.reason
export_error = error
retryable = isinstance(error, ConnectionError)
retryable = True
status_code = None
else:
reason = resp.reason
retryable = _is_retryable(resp)
status_code = resp.status_code

if not retryable:
_logger.error(
Expand Down Expand Up @@ -299,7 +286,6 @@ def shutdown(self, timeout_millis: float = 30_000, **kwargs) -> None:
return
self._shutdown = True
self._shutdown_in_progress.set()
self._session.close()

def force_flush(self, timeout_millis: float = 10_000) -> bool:
return True
Expand Down
Loading
Loading