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
1 change: 1 addition & 0 deletions .changelog/5668.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-exporter-otlp-proto-grpc`: Fix incorrect default port for OTLP gRPC exporter self-metrics
1 change: 1 addition & 0 deletions docs/getting_started/tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ wrapt==1.15.0
-e opentelemetry-semantic-conventions
-e opentelemetry-proto
-e exporter/opentelemetry-exporter-otlp-proto-common
-e exporter/opentelemetry-exporter-otlp-common
-e exporter/opentelemetry-exporter-otlp-proto-grpc
-e opentelemetry-api
-e opentelemetry-sdk
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from dataclasses import dataclass
from time import perf_counter
from typing import TYPE_CHECKING, Protocol
from urllib.parse import urlparse

from opentelemetry.metrics import MeterProvider, get_meter_provider
from opentelemetry.semconv._incubating.attributes.otel_attributes import (
Expand All @@ -33,12 +34,19 @@

if TYPE_CHECKING:
from typing import Literal
from urllib.parse import ParseResult as UrlParseResult

from opentelemetry.util.types import AnyValue, Attributes

_component_counter = Counter()

_GRPC_COMPONENT_TYPES = frozenset(
{
OtelComponentTypeValues.OTLP_GRPC_SPAN_EXPORTER,
OtelComponentTypeValues.OTLP_GRPC_LOG_EXPORTER,
OtelComponentTypeValues.OTLP_GRPC_METRIC_EXPORTER,
}
)


@dataclass
class ExportResult:
Expand All @@ -52,6 +60,7 @@ def export_operation(self, num_items: int) -> AbstractContextManager[ExportResul

class NoOpExporterMetrics:
@contextmanager
# pylint: disable-next=no-self-use
def export_operation(self, num_items: int) -> Iterator[ExportResult]:
yield ExportResult()

Expand All @@ -61,7 +70,7 @@ def __init__(
self,
component_type: OtelComponentTypeValues | None,
signal: Literal["traces", "metrics", "logs"],
endpoint: UrlParseResult,
endpoint: str,
meter_provider: MeterProvider | None,
) -> None:
if signal == "traces":
Expand All @@ -74,11 +83,15 @@ def __init__(
create_exported = create_otel_sdk_exporter_metric_data_point_exported
create_inflight = create_otel_sdk_exporter_metric_data_point_inflight

port = endpoint.port
if not endpoint.startswith("//") and "://" not in endpoint:
endpoint = f"//{endpoint}"
parsed_endpoint = urlparse(endpoint)

port = parsed_endpoint.port
if port is None:
if endpoint.scheme == "https":
if component_type in _GRPC_COMPONENT_TYPES or parsed_endpoint.scheme == "https":
port = 443
elif endpoint.scheme == "http":
elif parsed_endpoint.scheme == "http":
port = 80

component_type_value = component_type.value if component_type else "unknown_otlp_exporter"
Expand All @@ -88,8 +101,8 @@ def __init__(
OTEL_COMPONENT_TYPE: component_type_value,
OTEL_COMPONENT_NAME: f"{component_type_value}/{count}",
}
if endpoint.hostname:
self._standard_attrs[SERVER_ADDRESS] = endpoint.hostname
if parsed_endpoint.hostname:
self._standard_attrs[SERVER_ADDRESS] = parsed_endpoint.hostname
if port is not None:
self._standard_attrs[SERVER_PORT] = port

Expand Down Expand Up @@ -124,7 +137,7 @@ def export_operation(self, num_items: int) -> Iterator[ExportResult]:
def create_exporter_metrics(
component_type: OtelComponentTypeValues | None,
signal: Literal["traces", "metrics", "logs"],
endpoint: UrlParseResult,
endpoint: str,
meter_provider: MeterProvider | None,
enabled: bool,
) -> ExporterMetricsT:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import unittest
from unittest.mock import Mock, patch

from opentelemetry.exporter.otlp.common._exporter_metrics import (
ExporterMetrics,
NoOpExporterMetrics,
create_exporter_metrics,
)
from opentelemetry.semconv._incubating.attributes.otel_attributes import (
OtelComponentTypeValues,
)
from opentelemetry.semconv.attributes.server_attributes import (
SERVER_ADDRESS,
SERVER_PORT,
)


class TestExporterMetrics(unittest.TestCase):
def test_factory_returns_noop_when_disabled(self):
meter_provider = Mock()

with patch("opentelemetry.exporter.otlp.common._exporter_metrics.get_meter_provider") as get_meter_provider:
metrics = create_exporter_metrics(
OtelComponentTypeValues.OTLP_HTTP_SPAN_EXPORTER,
"traces",
"http://localhost:4318/v1/traces",
meter_provider,
False,
)

self.assertIsInstance(metrics, NoOpExporterMetrics)
meter_provider.get_meter.assert_not_called()
get_meter_provider.assert_not_called()

def test_factory_returns_exporter_metrics_when_enabled(self):
meter_provider = Mock()
meter_provider.get_meter.return_value = Mock()

metrics = create_exporter_metrics(
OtelComponentTypeValues.OTLP_HTTP_SPAN_EXPORTER,
"traces",
"http://localhost:4318/v1/traces",
meter_provider,
True,
)

self.assertIsInstance(metrics, ExporterMetrics)
meter_provider.get_meter.assert_called_once_with("opentelemetry-sdk")

def test_noop_export_operation_yields_result(self):
metrics = NoOpExporterMetrics()

with metrics.export_operation(1) as result:
result.error = RuntimeError("error")

self.assertIsInstance(result.error, RuntimeError)

def test_grpc_uses_static_port(self):
meter_provider = Mock()
meter_provider.get_meter.return_value = Mock()

cases: list[tuple[OtelComponentTypeValues, str, str, int]] = [
(
OtelComponentTypeValues.OTLP_GRPC_SPAN_EXPORTER,
"http://otlp.example.com",
"otlp.example.com",
443,
),
(
OtelComponentTypeValues.OTLP_GRPC_LOG_EXPORTER,
"https://otlp.example.com",
"otlp.example.com",
443,
),
(
OtelComponentTypeValues.OTLP_GRPC_METRIC_EXPORTER,
"otlp.example.com:4317",
"otlp.example.com",
4317,
),
(
OtelComponentTypeValues.OTLP_GRPC_SPAN_EXPORTER,
"http://localhost:4317",
"localhost",
4317,
),
]

for component_type, endpoint, expected_address, expected_port in cases:
with self.subTest(component_type=component_type, endpoint=endpoint):
metrics = ExporterMetrics(component_type, "traces", endpoint, meter_provider)
# pylint: disable-next=protected-access
self.assertEqual(metrics._standard_attrs[SERVER_ADDRESS], expected_address)
# pylint: disable-next=protected-access
self.assertEqual(metrics._standard_attrs[SERVER_PORT], expected_port)

def test_http_port_defaults_by_scheme(self):
meter_provider = Mock()
meter_provider.get_meter.return_value = Mock()

cases: list[tuple[str, int]] = [
("http://otlp.example.com", 80),
("https://otlp.example.com", 443),
]

for endpoint, expected_port in cases:
with self.subTest(endpoint=endpoint):
metrics = ExporterMetrics(
OtelComponentTypeValues.OTLP_HTTP_SPAN_EXPORTER,
"traces",
endpoint,
meter_provider,
)
# pylint: disable-next=protected-access
self.assertEqual(metrics._standard_attrs[SERVER_ADDRESS], "otlp.example.com")
# pylint: disable-next=protected-access
self.assertEqual(metrics._standard_attrs[SERVER_PORT], expected_port)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies = [
"opentelemetry-proto == 1.45.0.dev",
"opentelemetry-sdk ~= 1.45.0.dev",
"opentelemetry-exporter-otlp-proto-common == 1.45.0.dev",
"opentelemetry-exporter-otlp-common == 0.66b0.dev",
"typing-extensions >= 4.6.0",
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
secure_channel,
ssl_channel_credentials,
)
from opentelemetry.exporter.otlp.proto.common._exporter_metrics import (
from opentelemetry.exporter.otlp.common._exporter_metrics import (
create_exporter_metrics,
)
from opentelemetry.exporter.otlp.proto.common._internal import (
Expand Down Expand Up @@ -358,11 +358,10 @@ def __init__(

self._component_type = component_type
self._signal: Literal["traces", "metrics", "logs"] = signal
self._parsed_url = parsed_url
self._metrics = create_exporter_metrics(
self._component_type,
signal,
parsed_url,
self._endpoint,
meter_provider,
os.environ.get(OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, "").strip().lower() == "true",
)
Expand Down Expand Up @@ -518,7 +517,7 @@ def _set_meter_provider(self, meter_provider: MeterProvider) -> None:
self._metrics = create_exporter_metrics(
self._component_type,
self._signal,
self._parsed_url,
self._endpoint,
meter_provider,
os.environ.get(OTEL_PYTHON_SDK_INTERNAL_METRICS_ENABLED, "").strip().lower() == "true",
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pytest>=9.0.3
-e opentelemetry-api
-e tests/opentelemetry-test-utils
-e exporter/opentelemetry-exporter-otlp-proto-common
-e exporter/opentelemetry-exporter-otlp-common
-e opentelemetry-proto
-e opentelemetry-sdk
-e opentelemetry-semantic-conventions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --python-version=3.10 --universal --constraints=dev-requirements.txt exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in --output-file=exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.latest.txt
-e exporter/opentelemetry-exporter-otlp-common
# via
# -r exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in
# opentelemetry-exporter-otlp-proto-grpc
-e exporter/opentelemetry-exporter-otlp-proto-common
# via
# -r exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in
Expand All @@ -21,6 +25,7 @@
-e opentelemetry-sdk
# via
# -r exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in
# opentelemetry-exporter-otlp-common
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-test-utils
-e opentelemetry-semantic-conventions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# This file was autogenerated by uv via the following command:
# uv pip compile --python-version=3.10 --universal --resolution=lowest --constraints=dev-requirements.txt exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in --output-file=exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.oldest.txt
-e exporter/opentelemetry-exporter-otlp-common
# via
# -r exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in
# opentelemetry-exporter-otlp-proto-grpc
-e exporter/opentelemetry-exporter-otlp-proto-common
# via
# -r exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in
Expand All @@ -21,6 +25,7 @@
-e opentelemetry-sdk
# via
# -r exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in
# opentelemetry-exporter-otlp-common
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-test-utils
-e opentelemetry-semantic-conventions
Expand All @@ -33,7 +38,7 @@ asgiref==3.7.2
# via
# -c dev-requirements.txt
# opentelemetry-test-utils
certifi==2017.4.17
certifi==2023.5.7
# via requests
charset-normalizer==2.0.0
# via requests
Expand Down Expand Up @@ -76,7 +81,7 @@ pytest==9.0.3
# via
# -c dev-requirements.txt
# -r exporter/opentelemetry-exporter-otlp-proto-grpc/test-requirements.in
requests==2.32.3
requests==2.33.0
# via
# -c dev-requirements.txt
# opentelemetry-test-utils
Expand All @@ -96,5 +101,5 @@ typing-extensions==4.12.0 ; python_full_version >= '3.14'
# opentelemetry-exporter-otlp-proto-grpc
# opentelemetry-sdk
# opentelemetry-semantic-conventions
urllib3==1.21.1
urllib3==1.26.0
# via requests
Loading
Loading