Skip to content
Open
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
1 change: 1 addition & 0 deletions .changelog/5389.changed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-exporter-otlp-proto-http`: refactor to use shared opentelemetry-exporter-otlp-common and opentelemetry-exporter-http-transport packages
2 changes: 1 addition & 1 deletion docs-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sphinx-jekyll-builder==0.3.0
./shim/opentelemetry-opencensus-shim
./shim/opentelemetry-opentracing-shim
./exporter/opentelemetry-exporter-otlp-proto-common
./exporter/opentelemetry-exporter-otlp-proto-http
./exporter/opentelemetry-exporter-otlp-proto-http[requests]
./exporter/opentelemetry-exporter-otlp-proto-grpc

# Required by instrumentation and exporter packages
Expand Down
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"wrapt": ("https://wrapt.readthedocs.io/en/latest/", None),
"pymongo": ("https://pymongo.readthedocs.io/en/stable/", None),
"grpc": ("https://grpc.github.io/grpc/python/", None),
"requests": ("https://requests.readthedocs.io/en/latest/", None),
}

# http://www.sphinx-doc.org/en/master/config.html#confval-nitpicky
Expand Down Expand Up @@ -194,6 +195,8 @@
# ``from os import PathLike`` renders as the bare name ``PathLike`` in the
# file exporter type hints, which sphinx cannot resolve to os.PathLike.
("py:class", "PathLike"),
("py:class", "BaseHTTPTransport"),
("py:class", "opentelemetry.exporter.otlp.common._http.Compression"),
]

# Add any paths that contain templates here, relative to this directory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ dependencies = [
"opentelemetry-proto == 1.44.0.dev",
"opentelemetry-sdk ~= 1.44.0.dev",
"opentelemetry-exporter-otlp-proto-common == 1.44.0.dev",
"requests ~= 2.7",
"opentelemetry-exporter-otlp-common == 0.65b0.dev",
"opentelemetry-exporter-http-transport[urllib3] == 0.65b0.dev",
"typing-extensions >= 4.5.0",
]

Expand All @@ -52,6 +53,10 @@ Repository = "https://github.com/open-telemetry/opentelemetry-python"
gcp-auth = [
"opentelemetry-exporter-credential-provider-gcp >= 0.59b0",
]
requests = [
"requests ~= 2.7",
"opentelemetry-exporter-http-transport[requests] == 0.65b0.dev",
]

[tool.hatch.version]
path = "src/opentelemetry/exporter/otlp/proto/http/version/__init__.py"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,65 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

from os import environ
from typing import Literal
from __future__ import annotations

import requests
import logging
import os
from collections.abc import Mapping
from os import environ
from typing import TYPE_CHECKING, Literal

from opentelemetry.exporter.http.transport._requests import (
RequestsHTTPTransport,
)
from opentelemetry.exporter.http.transport._urllib3 import (
Urllib3HTTPTransport,
)
from opentelemetry.exporter.otlp.common import _http
from opentelemetry.exporter.otlp.proto.http import (
_OTLP_HTTP_HEADERS,
Compression,
)
from opentelemetry.sdk.environment_variables import (
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER,
OTEL_EXPORTER_OTLP_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_KEY,
OTEL_EXPORTER_OTLP_COMPRESSION,
OTEL_EXPORTER_OTLP_ENDPOINT,
OTEL_EXPORTER_OTLP_HEADERS,
OTEL_EXPORTER_OTLP_TIMEOUT,
)
from opentelemetry.util._importlib_metadata import entry_points
from opentelemetry.util.re import parse_env_headers

if TYPE_CHECKING:
import requests

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

from opentelemetry.exporter.http.transport._base import BaseHTTPTransport

def _load_session_from_envvar(
cred_envvar: Literal[
_CredentialEnvVar = 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",
],
]

_logger = logging.getLogger(__name__)

_DEFAULT_ENDPOINT = "http://localhost:4318"
_DEFAULT_TIMEOUT = 10


def _load_session_from_envvar(
cred_envvar: _CredentialEnvVar,
) -> requests.Session | None:
_credential_env = environ.get(
_OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER
) or environ.get(cred_envvar)
if _credential_env:
# pylint: disable-next=import-outside-toplevel
import requests # noqa: PLC0415

try:
maybe_session = next(
iter(
Expand All @@ -53,3 +82,126 @@ def _load_session_from_envvar(
f" must be of type `requests.Session`."
)
return None


def _normalize_compression(
compression: Compression | _http.Compression | None,
) -> _http.Compression | None:
match compression:
case Compression.NoCompression:
return _http.Compression.NONE
case Compression.Deflate:
return _http.Compression.DEFLATE
case Compression.Gzip:
return _http.Compression.GZIP
case _:
return compression


def _resolve_endpoint(
endpoint_env_var: str,
default_path: Literal["v1/traces", "v1/metrics", "v1/logs"],
) -> str:
if endpoint := os.environ.get(endpoint_env_var):
return endpoint

base_endpoint = (
os.environ.get(OTEL_EXPORTER_OTLP_ENDPOINT) or _DEFAULT_ENDPOINT
)

return f"{base_endpoint.removesuffix('/')}/{default_path}"


def _resolve_headers(
headers: Mapping[str, str] | None,
headers_env_var: str,
) -> dict[str, str]:
headers_ = {k.lower(): v for k, v in _OTLP_HTTP_HEADERS.items()}
env_headers = parse_env_headers(
os.environ.get(headers_env_var)
or os.environ.get(OTEL_EXPORTER_OTLP_HEADERS, ""),
liberal=True,
)
headers_.update(env_headers)
if headers:
headers_.update({key.lower(): value for key, value in headers.items()})
return headers_


def _resolve_timeout(
timeout_env_var: str,
) -> float:
raw = (
os.environ.get(timeout_env_var)
or os.environ.get(OTEL_EXPORTER_OTLP_TIMEOUT)
or _DEFAULT_TIMEOUT
)

try:
return float(raw)
except ValueError:
_logger.warning(
"Invalid timeout value %r, using default of %s seconds",
raw,
_DEFAULT_TIMEOUT,
)
return float(_DEFAULT_TIMEOUT)


def _resolve_compression(compression_env_var: str) -> _http.Compression:
value = (
(
os.environ.get(compression_env_var)
or os.environ.get(OTEL_EXPORTER_OTLP_COMPRESSION)
or "none"
)
.lower()
.strip()
)

try:
return _http.Compression.from_str(value)
except ValueError:
_logger.warning("Unsupported compression type: %s", value)
return _http.Compression.NONE


def _build_transport(
certificate_file: str | bool | None,
client_key_file: str | None,
client_certificate_file: str | None,
certificate_env_var: str,
client_key_env_var: str,
client_certificate_env_var: str,
session: requests.Session | None,
) -> BaseHTTPTransport:
verify: bool | str
if certificate_file is None:
verify = (
os.environ.get(certificate_env_var)
or os.environ.get(OTEL_EXPORTER_OTLP_CERTIFICATE)
or True
)
else:
verify = certificate_file
client_key_file = (
client_key_file
or os.environ.get(client_key_env_var)
or os.environ.get(OTEL_EXPORTER_OTLP_CLIENT_KEY)
)
client_certificate_file = (
client_certificate_file
or os.environ.get(client_certificate_env_var)
or os.environ.get(OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE)
)
cert = (
(client_certificate_file, client_key_file)
if client_certificate_file and client_key_file
else client_certificate_file
)

return (
RequestsHTTPTransport(verify=verify, cert=cert, session=session)
if session
else Urllib3HTTPTransport(verify=verify, cert=cert)

@lzchen lzchen Jul 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't the default (if no passed in session or credential env var) requests.Session?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it was, I was thinking we can change the default here to use urllib3. This shouldn't be a breaking change since from the user's perspective exporting is still performed normally over Http respecting all of the parameters passed to the constructor.

)
Loading
Loading