Skip to content

Commit 06929bc

Browse files
fix(UNTRACKED): support "none" value for OTEL exporters to disable telemetry (#64)
## Problem When `OTEL_TRACES_EXPORTER=none` or `OTEL_METRICS_EXPORTER=none` is set, plugin pods crash on startup with: ### Real-world scenario In in-vpc or single-node clusters, you might notice this issue in plugin pods (created by the job-execution-operator) when you set those environment variables in the `JOB_ENV_VARS` block. https://github.com/Unstructured-IO/platform-applications-cd/blob/9cf7335f3fe620106c61da208c12d44539d743bc/apps/dataplane/etl-operator/values.yaml#L52 ``` NotImplementedError: none implementation not supported yet ``` **Root cause:** The code splits the env var by comma but doesn't filter "none", then tries to initialize exporters for it, hitting the `NotImplementedError` in `_add_trace_exporter()` and `_get_metrics_reader()`. ## Solution ### 1. Filter "none" from exporter lists (lines 33, 37) ```python trace_exporters = [e for e in trace_exporters if e != "none"] metric_exporters = [e for e in metric_exporters if e != "none"] ``` ### 2. Return None when no exporters configured (lines 45-49, 59-63) ```python def get_trace_provider() -> TracerProvider | None: settings = get_settings() if not settings["trace_exporters"]: return None # Let OTEL SDK use default no-op ... ``` ## Compatibility `FastAPIInstrumentor.instrument_app()` explicitly supports `None` values for `tracer_provider` and `meter_provider` parameters (defaults to None). When None, OTEL SDK uses its built-in no-op providers. ## Testing **Before:** Plugin crashes with `NotImplementedError` **After:** Plugin starts successfully with telemetry disabled ## Changes - Filter "none" from OTEL_TRACES_EXPORTER and OTEL_METRICS_EXPORTER env vars - Return None from get_trace_provider/get_metric_provider when exporters list empty - Bump version 0.0.41 → 0.0.42 - Update CHANGELOG.md ## Deployment 1. After merge, we need to rebuild platform-plugins images (auto-picks latest version) 2. Cut a new platform release
1 parent 56f376b commit 06929bc

File tree

4 files changed

+83
-3
lines changed

4 files changed

+83
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.0.42
2+
3+
* **Support "none" value for OTEL_TRACES_EXPORTER and OTEL_METRICS_EXPORTER** - Filter "none" from exporter lists and return None when no exporters configured to properly disable OpenTelemetry instrumentation
4+
15
## 0.0.39
26

37
* **Remove wrap_error logic as exceptions are categorized in unstructured-ingest**

test/test_otel.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from opentelemetry.environment_variables import OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER
2+
from opentelemetry.sdk.metrics import MeterProvider
3+
from opentelemetry.sdk.trace import TracerProvider
4+
5+
from unstructured_platform_plugins.etl_uvicorn.otel import (
6+
get_metric_provider,
7+
get_settings,
8+
get_trace_provider,
9+
)
10+
11+
12+
def test_get_settings_filters_none_from_traces(monkeypatch):
13+
monkeypatch.setenv(OTEL_TRACES_EXPORTER, "none")
14+
settings = get_settings()
15+
assert settings["trace_exporters"] == []
16+
17+
18+
def test_get_settings_filters_none_from_metrics(monkeypatch):
19+
monkeypatch.setenv(OTEL_METRICS_EXPORTER, "none")
20+
settings = get_settings()
21+
assert settings["metric_exporters"] == []
22+
23+
24+
def test_get_settings_filters_none_from_combined_trace_exporters(monkeypatch):
25+
monkeypatch.setenv(OTEL_TRACES_EXPORTER, "none,console")
26+
settings = get_settings()
27+
assert settings["trace_exporters"] == ["console"]
28+
29+
30+
def test_get_settings_filters_none_from_combined_metric_exporters(monkeypatch):
31+
monkeypatch.setenv(OTEL_METRICS_EXPORTER, "none,console")
32+
settings = get_settings()
33+
assert settings["metric_exporters"] == ["console"]
34+
35+
36+
def test_get_trace_provider_returns_none_when_exporter_is_none(monkeypatch):
37+
monkeypatch.setenv(OTEL_TRACES_EXPORTER, "none")
38+
assert get_trace_provider() is None
39+
40+
41+
def test_get_metric_provider_returns_none_when_exporter_is_none(monkeypatch):
42+
monkeypatch.setenv(OTEL_METRICS_EXPORTER, "none")
43+
assert get_metric_provider() is None
44+
45+
46+
def test_get_trace_provider_returns_provider_when_exporter_set(monkeypatch):
47+
monkeypatch.setenv(OTEL_TRACES_EXPORTER, "console")
48+
provider = get_trace_provider()
49+
assert isinstance(provider, TracerProvider)
50+
51+
52+
def test_get_metric_provider_returns_provider_when_exporter_set(monkeypatch):
53+
monkeypatch.setenv(OTEL_METRICS_EXPORTER, "console")
54+
provider = get_metric_provider()
55+
assert isinstance(provider, MeterProvider)
56+
57+
58+
def test_wrap_in_fastapi_does_not_crash_with_none_otel_exporters(monkeypatch):
59+
"""Regression: previously crashed with NotImplementedError when none was set."""
60+
monkeypatch.setenv(OTEL_TRACES_EXPORTER, "none")
61+
monkeypatch.setenv(OTEL_METRICS_EXPORTER, "none")
62+
63+
from test.assets.async_typed_dict_response import async_sample_function
64+
from unstructured_platform_plugins.etl_uvicorn.api_generator import wrap_in_fastapi
65+
66+
# Should not raise NotImplementedError
67+
app = wrap_in_fastapi(func=async_sample_function, plugin_id="test_plugin")
68+
assert app is not None
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.41" # pragma: no cover
1+
__version__ = "0.0.42" # pragma: no cover

unstructured_platform_plugins/etl_uvicorn/otel.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,23 @@ def get_settings() -> OtelSettings:
3030
service_name = os.environ.get(OTEL_SERVICE_NAME, "unknown_service")
3131
trace_exporters = os.environ.get(OTEL_TRACES_EXPORTER)
3232
trace_exporters = trace_exporters.split(",") if trace_exporters else []
33+
trace_exporters = [e for e in trace_exporters if e != "none"]
3334

3435
metric_exporters = os.environ.get(OTEL_METRICS_EXPORTER)
3536
metric_exporters = metric_exporters.split(",") if metric_exporters else []
37+
metric_exporters = [e for e in metric_exporters if e != "none"]
3638
return OtelSettings(
3739
service_name=service_name,
3840
trace_exporters=trace_exporters,
3941
metric_exporters=metric_exporters,
4042
)
4143

4244

43-
def get_trace_provider() -> TracerProvider:
45+
def get_trace_provider() -> TracerProvider | None:
4446
settings = get_settings()
47+
if not settings["trace_exporters"]:
48+
return None
49+
4550
provider = TracerProvider(resource=Resource({SERVICE_NAME: settings["service_name"]}))
4651

4752
for trace_exporter_type in settings["trace_exporters"]:
@@ -50,8 +55,11 @@ def get_trace_provider() -> TracerProvider:
5055
return provider
5156

5257

53-
def get_metric_provider() -> MeterProvider:
58+
def get_metric_provider() -> MeterProvider | None:
5459
settings = get_settings()
60+
if not settings["metric_exporters"]:
61+
return None
62+
5563
readers = []
5664
for metric_exporter_type in settings["metric_exporters"]:
5765
readers.append(_get_metrics_reader(exporter_type=metric_exporter_type))

0 commit comments

Comments
 (0)