diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d3121668e..c6d6055392b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4494](https://github.com/open-telemetry/opentelemetry-python/pull/4494)) - Improve CI by cancelling stale runs and setting timeouts ([#4498](https://github.com/open-telemetry/opentelemetry-python/pull/4498)) +- Fix intermittent `Connection aborted` error when using otlp/http exporters + ([#4477](https://github.com/open-telemetry/opentelemetry-python/pull/4477)) ## Version 1.31.0/0.52b0 (2025-03-12) diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py index 21b877380c8..f86f0113833 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/_log_exporter/__init__.py @@ -21,6 +21,7 @@ from typing import Dict, Optional, Sequence import requests +from requests.exceptions import ConnectionError from opentelemetry.exporter.otlp.proto.common._internal import ( _create_exp_backoff_generator, @@ -133,13 +134,27 @@ def _export(self, serialized_data: bytes): elif self._compression == Compression.Deflate: data = zlib.compress(serialized_data) - return self._session.post( - url=self._endpoint, - data=data, - verify=self._certificate_file, - timeout=self._timeout, - cert=self._client_cert, - ) + # By default, keep-alive is enabled in Session's request + # headers. Backends may choose to close the connection + # while a post happens which causes an unhandled + # exception. This try/except will retry the post on such exceptions + try: + resp = self._session.post( + url=self._endpoint, + data=data, + verify=self._certificate_file, + timeout=self._timeout, + cert=self._client_cert, + ) + except ConnectionError: + resp = self._session.post( + url=self._endpoint, + data=data, + verify=self._certificate_file, + timeout=self._timeout, + cert=self._client_cert, + ) + return resp @staticmethod def _retryable(resp: requests.Response) -> bool: diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py index 00f429e4c97..4feea8d4302 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/metric_exporter/__init__.py @@ -29,6 +29,7 @@ import requests from deprecated import deprecated +from requests.exceptions import ConnectionError from opentelemetry.exporter.otlp.proto.common._internal import ( _create_exp_backoff_generator, @@ -175,13 +176,27 @@ def _export(self, serialized_data: bytes): elif self._compression == Compression.Deflate: data = zlib.compress(serialized_data) - return self._session.post( - url=self._endpoint, - data=data, - verify=self._certificate_file, - timeout=self._timeout, - cert=self._client_cert, - ) + # By default, keep-alive is enabled in Session's request + # headers. Backends may choose to close the connection + # while a post happens which causes an unhandled + # exception. This try/except will retry the post on such exceptions + try: + resp = self._session.post( + url=self._endpoint, + data=data, + verify=self._certificate_file, + timeout=self._timeout, + cert=self._client_cert, + ) + except ConnectionError: + resp = self._session.post( + url=self._endpoint, + data=data, + verify=self._certificate_file, + timeout=self._timeout, + cert=self._client_cert, + ) + return resp @staticmethod def _retryable(resp: requests.Response) -> bool: diff --git a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py index 7bcf4b4ced1..1841e5210a4 100644 --- a/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py +++ b/exporter/opentelemetry-exporter-otlp-proto-http/src/opentelemetry/exporter/otlp/proto/http/trace_exporter/__init__.py @@ -21,6 +21,7 @@ from typing import Dict, Optional import requests +from requests.exceptions import ConnectionError from opentelemetry.exporter.otlp.proto.common._internal import ( _create_exp_backoff_generator, @@ -130,13 +131,27 @@ def _export(self, serialized_data: bytes): elif self._compression == Compression.Deflate: data = zlib.compress(serialized_data) - return self._session.post( - url=self._endpoint, - data=data, - verify=self._certificate_file, - timeout=self._timeout, - cert=self._client_cert, - ) + # By default, keep-alive is enabled in Session's request + # headers. Backends may choose to close the connection + # while a post happens which causes an unhandled + # exception. This try/except will retry the post on such exceptions + try: + resp = self._session.post( + url=self._endpoint, + data=data, + verify=self._certificate_file, + timeout=self._timeout, + cert=self._client_cert, + ) + except ConnectionError: + resp = self._session.post( + url=self._endpoint, + data=data, + verify=self._certificate_file, + timeout=self._timeout, + cert=self._client_cert, + ) + return resp @staticmethod def _retryable(resp: requests.Response) -> bool: