Skip to content

Commit d2d4459

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 c381ef4 commit d2d4459

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
@@ -22,6 +22,7 @@
2222
ContainerResourceDetector,
2323
)
2424

25+
from elasticotel.distro import version
2526
from .utils import ElasticIntegrationTestCase, OTEL_INSTRUMENTATION_VERSION, ROOT_DIR
2627

2728

@@ -136,8 +137,7 @@ def test_metrics_with_system_metrics(self):
136137

137138
def test_log_events_are_sent(self):
138139
def send_event():
139-
from opentelemetry._events import Event
140-
from opentelemetry._events import get_event_logger
140+
from opentelemetry._events import Event, get_event_logger
141141

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

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

154186
@pytest.mark.integration
155187
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)