Skip to content

Commit 7fab34d

Browse files
officialasishkumarMikeGoldsmithxrmx
authored
fix(config): allow deflate for OTLP HTTP exporters (#5075)
* fix(config): allow deflate for OTLP HTTP exporters Signed-off-by: Asish Kumar <officialasishkumar@gmail.com> * chore: move changelog entry to towncrier fragment * update changelog prefix Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com> * chore: rename changelog fragment * style: format configuration changes --------- Signed-off-by: Asish Kumar <officialasishkumar@gmail.com> Co-authored-by: Mike Goldsmith <goldsmith.mike@gmail.com> Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
1 parent 0b690d2 commit 7fab34d

9 files changed

Lines changed: 187 additions & 43 deletions

File tree

.changelog/5075.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`opentelemetry-sdk`: Allow declarative OTLP HTTP exporters to map `compression: deflate` instead of rejecting it as unsupported.

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_common.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,33 @@ def _parse_headers(
102102
else:
103103
result[pair.name] = pair.value or ""
104104
return result
105+
106+
107+
def _map_compression(
108+
value: str | None,
109+
compression_enum: type,
110+
*,
111+
allow_deflate: bool = False,
112+
) -> object | None:
113+
"""Map a compression string to the given Compression enum value."""
114+
if value is None:
115+
return None
116+
117+
value_lower = value.lower()
118+
supports_deflate = hasattr(compression_enum, "Deflate")
119+
120+
if value_lower == "none":
121+
return None
122+
if value_lower == "gzip":
123+
return compression_enum.Gzip # type: ignore[attr-defined]
124+
if value_lower == "deflate" and allow_deflate and supports_deflate:
125+
return compression_enum.Deflate # type: ignore[attr-defined]
126+
127+
supported_values = ["'gzip'", "'none'"]
128+
if allow_deflate and supports_deflate:
129+
supported_values.insert(1, "'deflate'")
130+
131+
raise ConfigurationError(
132+
f"Unsupported compression value '{value}'. Supported values: "
133+
f"{', '.join(supported_values)}."
134+
)

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_logger_provider.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from opentelemetry._logs import set_logger_provider
99
from opentelemetry.sdk._configuration._common import (
10+
_map_compression,
1011
_parse_headers,
1112
load_entry_point,
1213
)
@@ -50,19 +51,6 @@
5051
_DEFAULT_MAX_EXPORT_BATCH_SIZE = 512
5152

5253

53-
def _map_compression(
54-
value: str | None, compression_enum: type
55-
) -> object | None:
56-
"""Map a compression string to the given Compression enum value."""
57-
if value is None or value.lower() == "none":
58-
return None
59-
if value.lower() == "gzip":
60-
return compression_enum.Gzip # type: ignore[attr-defined]
61-
raise ConfigurationError(
62-
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
63-
)
64-
65-
6654
def _create_console_log_exporter() -> ConsoleLogRecordExporter:
6755
"""Create a ConsoleLogRecordExporter."""
6856
return ConsoleLogRecordExporter()
@@ -86,7 +74,9 @@ def _create_otlp_http_log_exporter(
8674
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
8775
) from exc
8876

89-
compression = _map_compression(config.compression, Compression)
77+
compression = _map_compression(
78+
config.compression, Compression, allow_deflate=True
79+
)
9080
headers = _parse_headers(config.headers, config.headers_list)
9181
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
9282

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_meter_provider.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from opentelemetry import metrics
99
from opentelemetry.sdk._configuration._common import (
10+
_map_compression,
1011
_parse_headers,
1112
load_entry_point,
1213
)
@@ -256,19 +257,6 @@ def _create_console_metric_exporter(
256257
)
257258

258259

259-
def _map_compression_metric(
260-
value: str | None, compression_enum: type
261-
) -> object | None:
262-
"""Map a compression string to the given Compression enum value."""
263-
if value is None or value.lower() == "none":
264-
return None
265-
if value.lower() == "gzip":
266-
return compression_enum.Gzip # type: ignore[attr-defined]
267-
raise ConfigurationError(
268-
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
269-
)
270-
271-
272260
def _create_otlp_http_metric_exporter(
273261
config: OtlpHttpMetricExporterConfig,
274262
) -> MetricExporter:
@@ -287,7 +275,9 @@ def _create_otlp_http_metric_exporter(
287275
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
288276
) from exc
289277

290-
compression = _map_compression_metric(config.compression, Compression)
278+
compression = _map_compression(
279+
config.compression, Compression, allow_deflate=True
280+
)
291281
headers = _parse_headers(config.headers, config.headers_list)
292282
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
293283
preferred_temporality = _map_temporality(config.temporality_preference)
@@ -322,7 +312,7 @@ def _create_otlp_grpc_metric_exporter(
322312
"Install it with: pip install opentelemetry-exporter-otlp-proto-grpc"
323313
) from exc
324314

325-
compression = _map_compression_metric(config.compression, grpc.Compression)
315+
compression = _map_compression(config.compression, grpc.Compression)
326316
headers = _parse_headers(config.headers, config.headers_list)
327317
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
328318
preferred_temporality = _map_temporality(config.temporality_preference)

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_tracer_provider.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from opentelemetry import trace
99
from opentelemetry.sdk._configuration._common import (
10+
_map_compression,
1011
_parse_headers,
1112
load_entry_point,
1213
)
@@ -83,7 +84,9 @@ def _create_otlp_http_span_exporter(
8384
"Install it with: pip install opentelemetry-exporter-otlp-proto-http"
8485
) from exc
8586

86-
compression = _map_compression(config.compression, Compression)
87+
compression = _map_compression(
88+
config.compression, Compression, allow_deflate=True
89+
)
8790
headers = _parse_headers(config.headers, config.headers_list)
8891
timeout = (config.timeout / 1000.0) if config.timeout is not None else None
8992

@@ -95,19 +98,6 @@ def _create_otlp_http_span_exporter(
9598
)
9699

97100

98-
def _map_compression(
99-
value: str | None, compression_enum: type
100-
) -> object | None:
101-
"""Map a compression string to the given Compression enum value."""
102-
if value is None or value.lower() == "none":
103-
return None
104-
if value.lower() == "gzip":
105-
return compression_enum.Gzip # type: ignore[attr-defined]
106-
raise ConfigurationError(
107-
f"Unsupported compression value '{value}'. Supported values: 'gzip', 'none'."
108-
)
109-
110-
111101
def _create_otlp_grpc_span_exporter(
112102
config: OtlpGrpcExporterConfig,
113103
) -> SpanExporter:

opentelemetry-sdk/tests/_configuration/test_common.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from opentelemetry.sdk._configuration._common import (
1212
_additional_properties,
13+
_map_compression,
1314
_parse_headers,
1415
load_entry_point,
1516
)
@@ -140,6 +141,70 @@ def test_instantiation_error_not_wrapped(self):
140141
cls()
141142

142143

144+
class _CompressionWithDeflate:
145+
Gzip = "gzip"
146+
Deflate = "deflate"
147+
148+
149+
class _CompressionWithoutDeflate:
150+
Gzip = "gzip"
151+
152+
153+
class TestMapCompression(unittest.TestCase):
154+
def test_none_returns_none(self):
155+
self.assertIsNone(_map_compression(None, _CompressionWithDeflate))
156+
157+
def test_none_string_returns_none(self):
158+
self.assertIsNone(_map_compression("none", _CompressionWithDeflate))
159+
160+
def test_gzip_maps_to_gzip(self):
161+
self.assertEqual(
162+
_map_compression("gzip", _CompressionWithDeflate), "gzip"
163+
)
164+
165+
def test_deflate_maps_when_enabled(self):
166+
self.assertEqual(
167+
_map_compression(
168+
"deflate", _CompressionWithDeflate, allow_deflate=True
169+
),
170+
"deflate",
171+
)
172+
173+
def test_deflate_raises_by_default(self):
174+
with self.assertRaises(ConfigurationError) as ctx:
175+
_map_compression("deflate", _CompressionWithDeflate)
176+
177+
self.assertEqual(
178+
str(ctx.exception),
179+
"Unsupported compression value 'deflate'. Supported values: "
180+
"'gzip', 'none'.",
181+
)
182+
183+
def test_deflate_raises_when_http_enum_lacks_support(self):
184+
with self.assertRaises(ConfigurationError) as ctx:
185+
_map_compression(
186+
"deflate", _CompressionWithoutDeflate, allow_deflate=True
187+
)
188+
189+
self.assertEqual(
190+
str(ctx.exception),
191+
"Unsupported compression value 'deflate'. Supported values: "
192+
"'gzip', 'none'.",
193+
)
194+
195+
def test_http_error_message_includes_deflate(self):
196+
with self.assertRaises(ConfigurationError) as ctx:
197+
_map_compression(
198+
"brotli", _CompressionWithDeflate, allow_deflate=True
199+
)
200+
201+
self.assertEqual(
202+
str(ctx.exception),
203+
"Unsupported compression value 'brotli'. Supported values: "
204+
"'gzip', 'deflate', 'none'.",
205+
)
206+
207+
143208
class TestAdditionalPropertiesSupport(unittest.TestCase):
144209
def setUp(self):
145210
@_additional_properties

opentelemetry-sdk/tests/_configuration/test_logger_provider.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,30 @@ def test_otlp_http_exporter_headers(self):
331331
call_kwargs = mock_exporter_cls.call_args.kwargs
332332
self.assertEqual(call_kwargs["headers"], {"x-api-key": "secret"})
333333

334+
def test_otlp_http_exporter_deflate_compression(self):
335+
mock_exporter_cls = MagicMock()
336+
mock_compression_cls = MagicMock()
337+
mock_compression_cls.Deflate = "deflate"
338+
mock_module = MagicMock()
339+
mock_module.Compression = mock_compression_cls
340+
mock_log_module = MagicMock()
341+
mock_log_module.OTLPLogExporter = mock_exporter_cls
342+
343+
with patch.dict(
344+
sys.modules,
345+
{
346+
"opentelemetry.exporter.otlp.proto.http": mock_module,
347+
"opentelemetry.exporter.otlp.proto.http._log_exporter": mock_log_module,
348+
},
349+
):
350+
config = LogRecordExporterConfig(
351+
otlp_http=OtlpHttpExporterConfig(compression="deflate")
352+
)
353+
_create_log_record_exporter(config)
354+
355+
call_kwargs = mock_exporter_cls.call_args.kwargs
356+
self.assertEqual(call_kwargs["compression"], "deflate")
357+
334358
def test_otlp_grpc_exporter_endpoint(self):
335359
mock_exporter_cls = MagicMock()
336360
mock_grpc = MagicMock()

opentelemetry-sdk/tests/_configuration/test_meter_provider.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,34 @@ def test_otlp_http_created_with_endpoint(self):
263263
self.assertIsNone(kwargs["timeout"])
264264
self.assertIsNone(kwargs["compression"])
265265

266+
def test_otlp_http_created_with_deflate_compression(self):
267+
mock_exporter_cls = MagicMock()
268+
mock_compression_cls = MagicMock()
269+
mock_compression_cls.Deflate = "deflate_val"
270+
mock_http_module = MagicMock()
271+
mock_http_module.Compression = mock_compression_cls
272+
mock_module = MagicMock()
273+
mock_module.OTLPMetricExporter = mock_exporter_cls
274+
275+
with patch.dict(
276+
sys.modules,
277+
{
278+
"opentelemetry.exporter.otlp.proto.http.metric_exporter": mock_module,
279+
"opentelemetry.exporter.otlp.proto.http": mock_http_module,
280+
},
281+
):
282+
config = self._make_periodic_config(
283+
PushMetricExporterConfig(
284+
otlp_http=OtlpHttpMetricExporterConfig(
285+
compression="deflate"
286+
)
287+
)
288+
)
289+
create_meter_provider(config)
290+
291+
_, kwargs = mock_exporter_cls.call_args
292+
self.assertEqual(kwargs["compression"], "deflate_val")
293+
266294
def test_otlp_grpc_missing_package_raises(self):
267295
config = self._make_periodic_config(
268296
PushMetricExporterConfig(otlp_grpc=OtlpGrpcMetricExporterConfig())

opentelemetry-sdk/tests/_configuration/test_tracer_provider.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,32 @@ def test_otlp_http_created_with_endpoint(self):
317317
compression=None,
318318
)
319319

320+
def test_otlp_http_created_with_deflate_compression(self):
321+
mock_exporter_cls = MagicMock()
322+
mock_compression_cls = MagicMock()
323+
mock_compression_cls.Deflate = "deflate_val"
324+
mock_module = MagicMock()
325+
mock_module.OTLPSpanExporter = mock_exporter_cls
326+
mock_http_module = MagicMock()
327+
mock_http_module.Compression = mock_compression_cls
328+
329+
with patch.dict(
330+
sys.modules,
331+
{
332+
"opentelemetry.exporter.otlp.proto.http.trace_exporter": mock_module,
333+
"opentelemetry.exporter.otlp.proto.http": mock_http_module,
334+
},
335+
):
336+
config = self._make_batch_config(
337+
SpanExporterConfig(
338+
otlp_http=OtlpHttpExporterConfig(compression="deflate")
339+
)
340+
)
341+
create_tracer_provider(config)
342+
343+
_, kwargs = mock_exporter_cls.call_args
344+
self.assertEqual(kwargs["compression"], "deflate_val")
345+
320346
def test_otlp_http_headers_list(self):
321347
mock_exporter_cls = MagicMock()
322348
mock_http_module = MagicMock()

0 commit comments

Comments
 (0)