Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#4649](https://github.com/open-telemetry/opentelemetry-python/pull/4649))
- proto: relax protobuf version requirement to support v6
([#4620](https://github.com/open-telemetry/opentelemetry-python/pull/4620))
- Set expected User-Agent in HTTP headers for grpc OTLP exporter
([#4658](https://github.com/open-telemetry/opentelemetry-python/pull/4658))

## Version 1.34.0/0.55b0 (2025-06-04)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,7 @@
from .version import __version__

_USER_AGENT_HEADER_VALUE = "OTel-OTLP-Exporter-Python/" + __version__
_OTLP_GRPC_HEADERS = [("user-agent", _USER_AGENT_HEADER_VALUE)]
_OTLP_GRPC_CHANNEL_OPTIONS = [
# this will appear in the http User-Agent header
("grpc.primary_user_agent", _USER_AGENT_HEADER_VALUE)
]
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def __init__(
] = None,
timeout: Optional[float] = None,
compression: Optional[Compression] = None,
channel_options: Optional[TypingSequence[Tuple[str, str]]] = None,
):
if insecure is None:
insecure = environ.get(OTEL_EXPORTER_OTLP_LOGS_INSECURE)
Expand Down Expand Up @@ -99,6 +100,7 @@ def __init__(
"headers": headers,
"timeout": timeout or environ_timeout,
"compression": compression,
"channel_options": channel_options,
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
_get_resource_data,
)
from opentelemetry.exporter.otlp.proto.grpc import (
_OTLP_GRPC_HEADERS,
_OTLP_GRPC_CHANNEL_OPTIONS,
)
from opentelemetry.proto.common.v1.common_pb2 import ( # noqa: F401
AnyValue,
Expand Down Expand Up @@ -196,6 +196,7 @@ class OTLPExporterMixin(
headers: Headers to send when exporting
timeout: Backend request timeout in seconds
compression: gRPC compression method to use
channel_options: gRPC channel options
"""

def __init__(
Expand All @@ -208,6 +209,7 @@ def __init__(
] = None,
timeout: Optional[float] = None,
compression: Optional[Compression] = None,
channel_options: Optional[TypingSequence[Tuple[str, str]]] = None,
):
super().__init__()

Expand Down Expand Up @@ -239,9 +241,11 @@ def __init__(
elif isinstance(self._headers, dict):
self._headers = tuple(self._headers.items())
if self._headers is None:
self._headers = tuple(_OTLP_GRPC_HEADERS)
else:
self._headers = tuple(self._headers) + tuple(_OTLP_GRPC_HEADERS)
self._headers = tuple()

self._channel_options = channel_options or tuple(
_OTLP_GRPC_CHANNEL_OPTIONS
)

self._timeout = timeout or float(
environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, 10)
Expand All @@ -258,6 +262,7 @@ def __init__(
self._channel = insecure_channel(
self._endpoint,
compression=compression,
options=self._channel_options,
)
else:
credentials = _get_credentials(
Expand All @@ -270,6 +275,7 @@ def __init__(
self._endpoint,
credentials,
compression=compression,
options=self._channel_options,
)
self._client = self._stub(self._channel)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def __init__(
| None = None,
preferred_aggregation: dict[type, Aggregation] | None = None,
max_export_batch_size: int | None = None,
channel_options: TypingSequence[Tuple[str, str]] | None = None,
):
if insecure is None:
insecure = environ.get(OTEL_EXPORTER_OTLP_METRICS_INSECURE)
Expand Down Expand Up @@ -146,6 +147,7 @@ def __init__(
headers=headers or environ.get(OTEL_EXPORTER_OTLP_METRICS_HEADERS),
timeout=timeout or environ_timeout,
compression=compression,
channel_options=channel_options,
)

self._max_export_batch_size: int | None = max_export_batch_size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def __init__(
] = None,
timeout: Optional[float] = None,
compression: Optional[Compression] = None,
channel_options: Optional[TypingSequence[Tuple[str, str]]] = None,
):
if insecure is None:
insecure = environ.get(OTEL_EXPORTER_OTLP_TRACES_INSECURE)
Expand Down Expand Up @@ -131,6 +132,7 @@ def __init__(
or environ.get(OTEL_EXPORTER_OTLP_TRACES_HEADERS),
"timeout": timeout or environ_timeout,
"compression": compression,
"channel_options": channel_options,
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import time
from os.path import dirname
from unittest import TestCase
from unittest.mock import patch
from unittest.mock import Mock, patch

from google.protobuf.json_format import MessageToDict
from grpc import ChannelCredentials, Compression
Expand Down Expand Up @@ -266,6 +266,45 @@ def test_env_variables_with_only_certificate(

mock_logger_error.assert_not_called()

@patch.dict(
"os.environ",
{
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317",
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: THIS_DIR
+ "/../fixtures/test.cert",
OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2",
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10",
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip",
},
)
@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
)
@patch("logging.Logger.error")
def test_kwargs_have_precedence_over_env_variables(
self, mock_logger_error, mock_exporter_mixin
):
credentials_mock = Mock()
OTLPLogExporter(
endpoint="logs:4318",
headers=(("an", "header"),),
timeout=20,
credentials=credentials_mock,
compression=Compression.NoCompression,
channel_options=(("some", "options"),),
)

self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
_, kwargs = mock_exporter_mixin.call_args_list[0]
self.assertEqual(kwargs["endpoint"], "logs:4318")
self.assertEqual(kwargs["headers"], (("an", "header"),))
self.assertEqual(kwargs["timeout"], 20)
self.assertEqual(kwargs["compression"], Compression.NoCompression)
self.assertEqual(kwargs["credentials"], credentials_mock)
self.assertEqual(kwargs["channel_options"], (("some", "options"),))

mock_logger_error.assert_not_called()

def export_log_and_deserialize(self, log_data):
# pylint: disable=protected-access
translated_data = self.exporter._translate_data([log_data])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ def test_otlp_exporter_otlp_compression_unspecified(
mock_insecure_channel.assert_called_once_with(
"localhost:4317",
compression=Compression.NoCompression,
options=(
(
"grpc.primary_user_agent",
"OTel-OTLP-Exporter-Python/" + __version__,
),
),
)

# pylint: disable=no-self-use, disable=unused-argument
Expand All @@ -291,7 +297,14 @@ def test_otlp_exporter_otlp_compression_envvar(
"""Just OTEL_EXPORTER_OTLP_COMPRESSION should work"""
OTLPSpanExporterForTesting(insecure=True)
mock_insecure_channel.assert_called_once_with(
"localhost:4317", compression=Compression.Gzip
"localhost:4317",
compression=Compression.Gzip,
options=(
(
"grpc.primary_user_agent",
"OTel-OTLP-Exporter-Python/" + __version__,
),
),
)

def test_shutdown(self):
Expand Down Expand Up @@ -457,7 +470,7 @@ def test_otlp_headers_from_env(self):
# This ensures that there is no other header than standard user-agent.
self.assertEqual(
self.exporter._headers,
(("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),),
(),
)

def test_permanent_failure(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure):
(
("key1", "value1"),
("key2", "VALUE=2"),
("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),
),
)
exporter = OTLPMetricExporter(
Expand All @@ -268,7 +267,6 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure):
(
("key3", "value3"),
("key4", "value4"),
("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),
),
)

Expand Down Expand Up @@ -299,6 +297,26 @@ def test_otlp_exporter_otlp_compression_kwarg(self, mock_insecure_channel):
mock_insecure_channel.assert_called_once_with(
"localhost:4317",
compression=Compression.NoCompression,
options=(
(
"grpc.primary_user_agent",
"OTel-OTLP-Exporter-Python/" + __version__,
),
),
)

# pylint: disable=no-self-use
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.insecure_channel")
def test_otlp_exporter_otlp_channel_options_kwarg(
self, mock_insecure_channel
):
OTLPMetricExporter(
insecure=True, channel_options=(("some", "options"),)
)
mock_insecure_channel.assert_called_once_with(
"localhost:4317",
compression=Compression.NoCompression,
options=(("some", "options"),),
)

def test_split_metrics_data_many_data_points(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure):
(
("key1", "value1"),
("key2", "VALUE=2"),
("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),
),
)
exporter = OTLPSpanExporter(
Expand All @@ -294,7 +293,6 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure):
(
("key3", "value3"),
("key4", "value4"),
("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),
),
)
exporter = OTLPSpanExporter(
Expand All @@ -306,7 +304,6 @@ def test_otlp_headers_from_env(self, mock_ssl_channel, mock_secure):
(
("key5", "value5"),
("key6", "value6"),
("user-agent", "OTel-OTLP-Exporter-Python/" + __version__),
),
)

Expand Down Expand Up @@ -335,6 +332,12 @@ def test_otlp_exporter_otlp_compression_kwarg(self, mock_insecure_channel):
mock_insecure_channel.assert_called_once_with(
"localhost:4317",
compression=Compression.NoCompression,
options=(
(
"grpc.primary_user_agent",
"OTel-OTLP-Exporter-Python/" + __version__,
),
),
)

# pylint: disable=no-self-use
Expand All @@ -353,6 +356,24 @@ def test_otlp_exporter_otlp_compression_precendence(
mock_insecure_channel.assert_called_once_with(
"localhost:4317",
compression=Compression.Gzip,
options=(
(
"grpc.primary_user_agent",
"OTel-OTLP-Exporter-Python/" + __version__,
),
),
)

# pylint: disable=no-self-use
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.insecure_channel")
def test_otlp_exporter_otlp_channel_options_kwarg(
self, mock_insecure_channel
):
OTLPSpanExporter(insecure=True, channel_options=(("some", "options"),))
mock_insecure_channel.assert_called_once_with(
"localhost:4317",
compression=Compression.NoCompression,
options=(("some", "options"),),
)

def test_translate_spans(self):
Expand Down
Loading