Skip to content

Commit bed8002

Browse files
committed
opentelemetry-exporter-otlp-proto-grpc: set user agent properly
It looks like metadata is ignored and instead we should set the grpc.primary_user_agent channel option instead. User-agent will change from: grpc-python/1.71.0 grpc-c/46.0.0 (linux; chttp2) to: OTel-OTLP-Exporter-Python/1.34.1 grpc-python/1.71.0 grpc-c/46.0.0 (linux; chttp2)
1 parent 2a0282c commit bed8002

File tree

9 files changed

+118
-2
lines changed

9 files changed

+118
-2
lines changed

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,9 @@
7373
from .version import __version__
7474

7575
_USER_AGENT_HEADER_VALUE = "OTel-OTLP-Exporter-Python/" + __version__
76+
# these will be sent as grpc metadata
7677
_OTLP_GRPC_HEADERS = [("user-agent", _USER_AGENT_HEADER_VALUE)]
78+
_OTLP_GRPC_CHANNEL_OPTIONS = [
79+
# this will appear in the http User-Agent header
80+
("grpc.primary_user_agent", _USER_AGENT_HEADER_VALUE)
81+
]

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/_log_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def __init__(
6060
] = None,
6161
timeout: Optional[float] = None,
6262
compression: Optional[Compression] = None,
63+
channel_options: Optional[TypingSequence[Tuple[str, str]]] = None,
6364
):
6465
if insecure is None:
6566
insecure = environ.get(OTEL_EXPORTER_OTLP_LOGS_INSECURE)
@@ -99,6 +100,7 @@ def __init__(
99100
"headers": headers,
100101
"timeout": timeout or environ_timeout,
101102
"compression": compression,
103+
"channel_options": channel_options,
102104
}
103105
)
104106

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/exporter.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
_get_resource_data,
5252
)
5353
from opentelemetry.exporter.otlp.proto.grpc import (
54+
_OTLP_GRPC_CHANNEL_OPTIONS,
5455
_OTLP_GRPC_HEADERS,
5556
)
5657
from opentelemetry.proto.common.v1.common_pb2 import ( # noqa: F401
@@ -196,6 +197,7 @@ class OTLPExporterMixin(
196197
headers: Headers to send when exporting
197198
timeout: Backend request timeout in seconds
198199
compression: gRPC compression method to use
200+
channel_options: gRPC channel options
199201
"""
200202

201203
def __init__(
@@ -208,6 +210,7 @@ def __init__(
208210
] = None,
209211
timeout: Optional[float] = None,
210212
compression: Optional[Compression] = None,
213+
channel_options: Optional[TypingSequence[Tuple[str, str]]] = None,
211214
):
212215
super().__init__()
213216

@@ -243,6 +246,10 @@ def __init__(
243246
else:
244247
self._headers = tuple(self._headers) + tuple(_OTLP_GRPC_HEADERS)
245248

249+
self._channel_options = channel_options or tuple(
250+
_OTLP_GRPC_CHANNEL_OPTIONS
251+
)
252+
246253
self._timeout = timeout or float(
247254
environ.get(OTEL_EXPORTER_OTLP_TIMEOUT, 10)
248255
)
@@ -258,6 +265,7 @@ def __init__(
258265
self._channel = insecure_channel(
259266
self._endpoint,
260267
compression=compression,
268+
options=self._channel_options,
261269
)
262270
else:
263271
credentials = _get_credentials(
@@ -270,6 +278,7 @@ def __init__(
270278
self._endpoint,
271279
credentials,
272280
compression=compression,
281+
options=self._channel_options,
273282
)
274283
self._client = self._stub(self._channel)
275284

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/metric_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ def __init__(
105105
| None = None,
106106
preferred_aggregation: dict[type, Aggregation] | None = None,
107107
max_export_batch_size: int | None = None,
108+
channel_options: TypingSequence[Tuple[str, str]] | None = None,
108109
):
109110
if insecure is None:
110111
insecure = environ.get(OTEL_EXPORTER_OTLP_METRICS_INSECURE)
@@ -146,6 +147,7 @@ def __init__(
146147
headers=headers or environ.get(OTEL_EXPORTER_OTLP_METRICS_HEADERS),
147148
timeout=timeout or environ_timeout,
148149
compression=compression,
150+
channel_options=channel_options,
149151
)
150152

151153
self._max_export_batch_size: int | None = max_export_batch_size

exporter/opentelemetry-exporter-otlp-proto-grpc/src/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def __init__(
9393
] = None,
9494
timeout: Optional[float] = None,
9595
compression: Optional[Compression] = None,
96+
channel_options: Optional[TypingSequence[Tuple[str, str]]] = None,
9697
):
9798
if insecure is None:
9899
insecure = environ.get(OTEL_EXPORTER_OTLP_TRACES_INSECURE)
@@ -131,6 +132,7 @@ def __init__(
131132
or environ.get(OTEL_EXPORTER_OTLP_TRACES_HEADERS),
132133
"timeout": timeout or environ_timeout,
133134
"compression": compression,
135+
"channel_options": channel_options,
134136
}
135137
)
136138

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/logs/test_otlp_logs_exporter.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import time
1818
from os.path import dirname
1919
from unittest import TestCase
20-
from unittest.mock import patch
20+
from unittest.mock import Mock, patch
2121

2222
from google.protobuf.json_format import MessageToDict
2323
from grpc import ChannelCredentials, Compression
@@ -226,6 +226,45 @@ def test_env_variables_with_only_certificate(
226226

227227
mock_logger_error.assert_not_called()
228228

229+
@patch.dict(
230+
"os.environ",
231+
{
232+
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317",
233+
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: THIS_DIR
234+
+ "/../fixtures/test.cert",
235+
OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2",
236+
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10",
237+
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip",
238+
},
239+
)
240+
@patch(
241+
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
242+
)
243+
@patch("logging.Logger.error")
244+
def test_kwargs_have_precedence_over_env_variables(
245+
self, mock_logger_error, mock_exporter_mixin
246+
):
247+
credentials_mock = Mock()
248+
OTLPLogExporter(
249+
endpoint="logs:4318",
250+
headers=(("an", "header"),),
251+
timeout=20,
252+
credentials=credentials_mock,
253+
compression=Compression.NoCompression,
254+
channel_options=(("some", "options"),),
255+
)
256+
257+
self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
258+
_, kwargs = mock_exporter_mixin.call_args_list[0]
259+
self.assertEqual(kwargs["endpoint"], "logs:4318")
260+
self.assertEqual(kwargs["headers"], (("an", "header"),))
261+
self.assertEqual(kwargs["timeout"], 20)
262+
self.assertEqual(kwargs["compression"], Compression.NoCompression)
263+
self.assertEqual(kwargs["credentials"], credentials_mock)
264+
self.assertEqual(kwargs["channel_options"], (("some", "options"),))
265+
266+
mock_logger_error.assert_not_called()
267+
229268
def export_log_and_deserialize(self, log_data):
230269
# pylint: disable=protected-access
231270
translated_data = self.exporter._translate_data([log_data])

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_exporter_mixin.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,12 @@ def test_otlp_exporter_otlp_compression_unspecified(
268268
mock_insecure_channel.assert_called_once_with(
269269
"localhost:4317",
270270
compression=Compression.NoCompression,
271+
options=(
272+
(
273+
"grpc.primary_user_agent",
274+
"OTel-OTLP-Exporter-Python/" + __version__,
275+
),
276+
),
271277
)
272278

273279
# pylint: disable=no-self-use, disable=unused-argument
@@ -291,7 +297,14 @@ def test_otlp_exporter_otlp_compression_envvar(
291297
"""Just OTEL_EXPORTER_OTLP_COMPRESSION should work"""
292298
OTLPSpanExporterForTesting(insecure=True)
293299
mock_insecure_channel.assert_called_once_with(
294-
"localhost:4317", compression=Compression.Gzip
300+
"localhost:4317",
301+
compression=Compression.Gzip,
302+
options=(
303+
(
304+
"grpc.primary_user_agent",
305+
"OTel-OTLP-Exporter-Python/" + __version__,
306+
),
307+
),
295308
)
296309

297310
def test_shutdown(self):

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_metrics_exporter.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,26 @@ def test_otlp_exporter_otlp_compression_kwarg(self, mock_insecure_channel):
299299
mock_insecure_channel.assert_called_once_with(
300300
"localhost:4317",
301301
compression=Compression.NoCompression,
302+
options=(
303+
(
304+
"grpc.primary_user_agent",
305+
"OTel-OTLP-Exporter-Python/" + __version__,
306+
),
307+
),
308+
)
309+
310+
# pylint: disable=no-self-use
311+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.insecure_channel")
312+
def test_otlp_exporter_otlp_channel_options_kwarg(
313+
self, mock_insecure_channel
314+
):
315+
OTLPMetricExporter(
316+
insecure=True, channel_options=(("some", "options"),)
317+
)
318+
mock_insecure_channel.assert_called_once_with(
319+
"localhost:4317",
320+
compression=Compression.NoCompression,
321+
options=(("some", "options"),),
302322
)
303323

304324
def test_split_metrics_data_many_data_points(self):

exporter/opentelemetry-exporter-otlp-proto-grpc/tests/test_otlp_trace_exporter.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,12 @@ def test_otlp_exporter_otlp_compression_kwarg(self, mock_insecure_channel):
335335
mock_insecure_channel.assert_called_once_with(
336336
"localhost:4317",
337337
compression=Compression.NoCompression,
338+
options=(
339+
(
340+
"grpc.primary_user_agent",
341+
"OTel-OTLP-Exporter-Python/" + __version__,
342+
),
343+
),
338344
)
339345

340346
# pylint: disable=no-self-use
@@ -353,6 +359,24 @@ def test_otlp_exporter_otlp_compression_precendence(
353359
mock_insecure_channel.assert_called_once_with(
354360
"localhost:4317",
355361
compression=Compression.Gzip,
362+
options=(
363+
(
364+
"grpc.primary_user_agent",
365+
"OTel-OTLP-Exporter-Python/" + __version__,
366+
),
367+
),
368+
)
369+
370+
# pylint: disable=no-self-use
371+
@patch("opentelemetry.exporter.otlp.proto.grpc.exporter.insecure_channel")
372+
def test_otlp_exporter_otlp_channel_options_kwarg(
373+
self, mock_insecure_channel
374+
):
375+
OTLPSpanExporter(insecure=True, channel_options=(("some", "options"),))
376+
mock_insecure_channel.assert_called_once_with(
377+
"localhost:4317",
378+
compression=Compression.NoCompression,
379+
options=(("some", "options"),),
356380
)
357381

358382
def test_translate_spans(self):

0 commit comments

Comments
 (0)