Skip to content

Commit 7ca4f0d

Browse files
committed
distro: override otlp exporters user agent headers
For GRPC we leverage the recently added channel_options, for HTTP we temporarily need to monkey patch the exporter default headers in the meantime we have an sdk where we can override them.
1 parent 2e09f44 commit 7ca4f0d

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

src/elasticotel/distro/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@
2323
OTEL_METRICS_EXPORTER,
2424
OTEL_TRACES_EXPORTER,
2525
)
26+
from opentelemetry.exporter.otlp.proto.grpc import _USER_AGENT_HEADER_VALUE
27+
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter as GRPCOTLPLogExporter
28+
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter as GRPCOTLPMetricExporter
29+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GRPCOTLPSpanExporter
30+
from opentelemetry.exporter.otlp.proto.http import _OTLP_HTTP_HEADERS
31+
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter as HTTPOTLPLogExporter
32+
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter as HTTPOTLPMetricExporter
33+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPOTLPSpanExporter
2634
from opentelemetry.instrumentation.distro import BaseDistro
2735
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
2836
from opentelemetry.instrumentation.system_metrics import (
@@ -44,16 +52,43 @@
4452
from opentelemetry._opamp.client import OpAMPClient
4553
from opentelemetry._opamp.proto import opamp_pb2 as opamp_pb2
4654

55+
from elasticotel.distro import version
4756
from elasticotel.distro.environment_variables import ELASTIC_OTEL_OPAMP_ENDPOINT, ELASTIC_OTEL_SYSTEM_METRICS_ENABLED
4857
from elasticotel.distro.resource_detectors import get_cloud_resource_detectors
4958
from elasticotel.distro.config import opamp_handler
5059

5160

5261
logger = logging.getLogger(__name__)
5362

63+
EDOT_GRPC_USER_AGENT_HEADER_VALUE = "elastic-otlp-grpc-python/" + version.__version__
64+
EDOT_HTTP_USER_AGENT_HEADER_VALUE = "elastic-otlp-http-python/" + version.__version__
65+
5466

5567
class ElasticOpenTelemetryConfigurator(_OTelSDKConfigurator):
5668
def _configure(self, **kwargs):
69+
# override GRPC and HTTP user agent headers, GRPC works since OTel SDK 1.35.0, HTTP currently requires an hack
70+
otlp_grpc_exporter_options = {
71+
"channel_options": (
72+
("grpc.primary_user_agent", f"{EDOT_GRPC_USER_AGENT_HEADER_VALUE} {_USER_AGENT_HEADER_VALUE}"),
73+
)
74+
}
75+
otlp_http_exporter_options = {
76+
"headers": {
77+
**_OTLP_HTTP_HEADERS,
78+
"User-Agent": f"{EDOT_HTTP_USER_AGENT_HEADER_VALUE} {_OTLP_HTTP_HEADERS['User-Agent']}",
79+
}
80+
}
81+
kwargs["exporter_args_map"] = {
82+
GRPCOTLPLogExporter: otlp_grpc_exporter_options,
83+
GRPCOTLPMetricExporter: otlp_grpc_exporter_options,
84+
GRPCOTLPSpanExporter: otlp_grpc_exporter_options,
85+
HTTPOTLPLogExporter: otlp_http_exporter_options,
86+
HTTPOTLPMetricExporter: otlp_http_exporter_options,
87+
HTTPOTLPSpanExporter: otlp_http_exporter_options,
88+
}
89+
# TODO: Remove the following line after rebasing on top of upstream 1.37.0
90+
_OTLP_HTTP_HEADERS["User-Agent"] = otlp_http_exporter_options["headers"]["User-Agent"]
91+
5792
super()._configure(**kwargs)
5893

5994
enable_opamp = False

tests/integration/test_integration.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import pytest
2020

21+
from elasticotel.distro import version
2122
from .utils import ElasticIntegrationTestCase, OTEL_INSTRUMENTATION_VERSION, ROOT_DIR
2223

2324

@@ -128,8 +129,7 @@ def test_metrics_with_system_metrics(self):
128129

129130
def test_log_events_are_sent(self):
130131
def send_event():
131-
from opentelemetry._events import Event
132-
from opentelemetry._events import get_event_logger
132+
from opentelemetry._events import Event, get_event_logger
133133

134134
event = Event(name="test.event", attributes={}, body={"key": "value", "dict": {"nestedkey": "nestedvalue"}})
135135
event_logger = get_event_logger(__name__)
@@ -142,6 +142,38 @@ def send_event():
142142
self.assertEqual(log["attributes"]["event.name"], "test.event")
143143
self.assertEqual(log["body"], {"key": "value", "dict": {"nestedkey": "nestedvalue"}})
144144

145+
def test_edot_user_agent_is_used_in_otlp_grpc_exporter(self):
146+
def test_script():
147+
import sqlite3
148+
149+
from opentelemetry._events import Event, get_event_logger
150+
151+
connection = sqlite3.connect(":memory:")
152+
cursor = connection.cursor()
153+
cursor.execute("CREATE TABLE movie(title, year, score)")
154+
155+
event = Event(name="test.event", attributes={}, body={"key": "value"})
156+
event_logger = get_event_logger(__name__)
157+
event_logger.emit(event)
158+
159+
stdout, stderr, returncode = self.run_script(test_script, wrapper_script="opentelemetry-instrument")
160+
161+
telemetry = self.get_telemetry()
162+
(metrics_headers, logs_headers, traces_headers) = (
163+
telemetry["metrics_headers"],
164+
telemetry["logs_headers"],
165+
telemetry["traces_headers"],
166+
)
167+
168+
assert metrics_headers
169+
assert traces_headers
170+
assert logs_headers
171+
172+
edot_user_agent = "elastic-otlp-grpc-python/" + version.__version__
173+
self.assertIn(edot_user_agent, metrics_headers[0]["user-agent"])
174+
self.assertIn(edot_user_agent, traces_headers[0]["user-agent"])
175+
self.assertIn(edot_user_agent, logs_headers[0]["user-agent"])
176+
145177

146178
@pytest.mark.integration
147179
class OperatorTestCase(ElasticIntegrationTestCase):

tests/integration/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def normalize_kvlist(body) -> dict:
156156
return dict_values
157157

158158
metrics = []
159+
metrics_headers = []
159160
for request in telemetry["metric_requests"]:
160161
elems = []
161162
for proto_elem in request["pbreq"]["resourceMetrics"]:
@@ -176,7 +177,10 @@ def normalize_kvlist(body) -> dict:
176177
metric = {"resourceMetrics": elems}
177178
metrics.append(metric)
178179

180+
metrics_headers.append(request["headers"])
181+
179182
traces = []
183+
traces_headers = []
180184
for request in telemetry["trace_requests"]:
181185
for resource_span in request["pbreq"]["resourceSpans"]:
182186
resource_attributes = normalize_attributes(resource_span["resource"]["attributes"])
@@ -188,8 +192,10 @@ def normalize_kvlist(body) -> dict:
188192
span["spanId"] = decode_id(span["spanId"])
189193
span["traceId"] = decode_id(span["traceId"])
190194
traces.append(span)
195+
traces_headers.append(request["headers"])
191196

192197
logs = []
198+
logs_headers = []
193199
for request in telemetry["log_requests"]:
194200
for resource_log in request["pbreq"]["resourceLogs"]:
195201
resource_attributes = normalize_attributes(resource_log["resource"]["attributes"])
@@ -200,11 +206,15 @@ def normalize_kvlist(body) -> dict:
200206
log["body"] = normalize_kvlist(log["body"])
201207
log["resource"] = resource_attributes
202208
logs.append(log)
209+
logs_headers.append(request["headers"])
203210

204211
return {
205212
"logs": logs,
213+
"logs_headers": logs_headers,
206214
"metrics": metrics,
215+
"metrics_headers": metrics_headers,
207216
"traces": traces,
217+
"traces_headers": traces_headers,
208218
}
209219

210220
def run_script(

0 commit comments

Comments
 (0)