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/5273.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-exporter-otlp-proto-http`: auto-append signal path when a base URL (no path or path `/`) is passed as `endpoint` to HTTP OTLP exporters
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from os import environ
from typing import Literal
from urllib.parse import urlparse, urlunparse

import requests

Expand All @@ -12,6 +13,19 @@
from opentelemetry.util._importlib_metadata import entry_points


def _resolve_endpoint_to_signal(endpoint: str, signal_path: str) -> str:
"""Append signal_path to endpoint if it has no signal-specific path.

Uses proper URL manipulation so query strings and fragments are preserved.
If the endpoint already has a path other than '/', it is returned unchanged.
"""
parsed = urlparse(endpoint)

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.

Should probably embed this in a try-catch to avoid crashes.

if not parsed.path or parsed.path == "/":

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.

Could use hasattr to check if path exists or not.

base = parsed.path.rstrip("/")
return urlunparse(parsed._replace(path=f"{base}/{signal_path}"))
return endpoint


def _is_retryable(resp: requests.Response) -> bool:
if resp.status_code == 408:
return True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from opentelemetry.exporter.otlp.proto.http._common import (
_is_retryable,
_load_session_from_envvar,
_resolve_endpoint_to_signal,
)
from opentelemetry.metrics import MeterProvider
from opentelemetry.sdk._logs import ReadableLogRecord
Expand Down Expand Up @@ -88,12 +89,17 @@ def __init__(
meter_provider: MeterProvider | None = None,
):
self._shutdown_is_occuring = threading.Event()
self._endpoint = endpoint or environ.get(
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
_append_logs_path(
environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT)
),
)
if endpoint is not None:
self._endpoint = _resolve_endpoint_to_signal(
endpoint, DEFAULT_LOGS_EXPORT_PATH
)
else:
self._endpoint = environ.get(
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
_append_logs_path(
environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT)
),
)
# Keeping these as instance variables because they are used in tests
self._certificate_file = certificate_file or environ.get(
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from opentelemetry.exporter.otlp.proto.http._common import (
_is_retryable,
_load_session_from_envvar,
_resolve_endpoint_to_signal,
)
from opentelemetry.metrics import MeterProvider
from opentelemetry.proto.collector.metrics.v1.metrics_service_pb2 import ( # noqa: F401
Expand Down Expand Up @@ -147,12 +148,17 @@ def __init__(
If it is set and the number of data points exceeds the max, the request will be split.
"""
self._shutdown_in_progress = threading.Event()
self._endpoint = endpoint or environ.get(
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
_append_metrics_path(
environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT)
),
)
if endpoint is not None:
self._endpoint = _resolve_endpoint_to_signal(
endpoint, DEFAULT_METRICS_EXPORT_PATH
)
else:
self._endpoint = environ.get(
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
_append_metrics_path(
environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT)
),
)
self._certificate_file = certificate_file or environ.get(
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
environ.get(OTEL_EXPORTER_OTLP_CERTIFICATE, True),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from opentelemetry.exporter.otlp.proto.http._common import (
_is_retryable,
_load_session_from_envvar,
_resolve_endpoint_to_signal,
)
from opentelemetry.metrics import MeterProvider
from opentelemetry.sdk.environment_variables import (
Expand Down Expand Up @@ -84,12 +85,17 @@ def __init__(
meter_provider: MeterProvider | None = None,
):
self._shutdown_in_progress = threading.Event()
self._endpoint = endpoint or environ.get(
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
_append_trace_path(
environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT)
),
)
if endpoint is not None:
self._endpoint = _resolve_endpoint_to_signal(
endpoint, DEFAULT_TRACES_EXPORT_PATH
)
else:
self._endpoint = environ.get(
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
_append_trace_path(
environ.get(OTEL_EXPORTER_OTLP_ENDPOINT, DEFAULT_ENDPOINT)
),
)
self._certificate_file = certificate_file or environ.get(
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
environ.get(OTEL_EXPORTER_OTLP_CERTIFICATE, True),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,30 @@ def test_exporter_env_endpoint_with_slash(self):
OS_ENV_ENDPOINT + f"/{DEFAULT_METRICS_EXPORT_PATH}",
)

def test_endpoint_base_url_no_path(self):
exporter = OTLPMetricExporter(endpoint="http://collector:4318")
self.assertEqual(
exporter._endpoint, "http://collector:4318/v1/metrics"
)

def test_endpoint_base_url_trailing_slash(self):
exporter = OTLPMetricExporter(endpoint="http://collector:4318/")
self.assertEqual(
exporter._endpoint, "http://collector:4318/v1/metrics"
)

def test_endpoint_full_url_unchanged(self):
exporter = OTLPMetricExporter(
endpoint="http://collector:4318/v1/metrics"
)
self.assertEqual(
exporter._endpoint, "http://collector:4318/v1/metrics"
)

def test_endpoint_custom_path_unchanged(self):
exporter = OTLPMetricExporter(endpoint="http://collector:4318/custom")
self.assertEqual(exporter._endpoint, "http://collector:4318/custom")

@patch.dict(
"os.environ",
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,22 @@ def test_exporter_env(self):
)
self.assertIsInstance(exporter._session, requests.Session)

def test_endpoint_base_url_no_path(self):

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.

Should add tests for some malformed, invalid urls

exporter = OTLPLogExporter(endpoint="http://collector:4318")
self.assertEqual(exporter._endpoint, "http://collector:4318/v1/logs")

def test_endpoint_base_url_trailing_slash(self):
exporter = OTLPLogExporter(endpoint="http://collector:4318/")
self.assertEqual(exporter._endpoint, "http://collector:4318/v1/logs")

def test_endpoint_full_url_unchanged(self):
exporter = OTLPLogExporter(endpoint="http://collector:4318/v1/logs")
self.assertEqual(exporter._endpoint, "http://collector:4318/v1/logs")

def test_endpoint_custom_path_unchanged(self):
exporter = OTLPLogExporter(endpoint="http://collector:4318/custom")
self.assertEqual(exporter._endpoint, "http://collector:4318/custom")

@staticmethod
def export_log_and_deserialize(log):
with patch("requests.Session.post") as mock_post:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,31 @@ def test_exporter_env_endpoint_with_slash(self):
OS_ENV_ENDPOINT + f"/{DEFAULT_TRACES_EXPORT_PATH}",
)

def test_endpoint_base_url_no_path(self):
exporter = OTLPSpanExporter(endpoint="http://collector:4318")
self.assertEqual(exporter._endpoint, "http://collector:4318/v1/traces")

def test_endpoint_base_url_trailing_slash(self):
exporter = OTLPSpanExporter(endpoint="http://collector:4318/")
self.assertEqual(exporter._endpoint, "http://collector:4318/v1/traces")

def test_endpoint_full_url_unchanged(self):
exporter = OTLPSpanExporter(endpoint="http://collector:4318/v1/traces")
self.assertEqual(exporter._endpoint, "http://collector:4318/v1/traces")

def test_endpoint_custom_path_unchanged(self):
exporter = OTLPSpanExporter(endpoint="http://collector:4318/custom")
self.assertEqual(exporter._endpoint, "http://collector:4318/custom")

def test_endpoint_base_url_with_query_string(self):
exporter = OTLPSpanExporter(
endpoint="http://collector:4318?tenant=acme"
)
self.assertEqual(
exporter._endpoint,
"http://collector:4318/v1/traces?tenant=acme",
)

@patch.dict(
"os.environ",
{
Expand Down