Skip to content

Commit 2c76546

Browse files
committed
feat(ddtrace/settings/_env): add new EnvConfig class to wrap std os.environ
1 parent 7b21c07 commit 2c76546

File tree

6 files changed

+126
-28
lines changed

6 files changed

+126
-28
lines changed

ddtrace/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
import sys
32

43

@@ -14,14 +13,14 @@
1413

1514
# Enable telemetry writer and excepthook as early as possible to ensure we capture any exceptions from initialization
1615
import ddtrace.internal.telemetry # noqa: E402
17-
from .settings._config import config, _get_env
1816
from ddtrace.vendor import debtcollector
1917

2018
from ._monkey import patch # noqa: E402
2119
from ._monkey import patch_all # noqa: E402
2220
from .internal.compat import PYTHON_VERSION_INFO # noqa: E402
2321
from .internal.utils.deprecations import DDTraceDeprecationWarning # noqa: E402
2422
from .settings._config import config
23+
from .settings._config import _get_env
2524
from .version import get_version # noqa: E402
2625

2726

ddtrace/internal/telemetry/__init__.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@
44
``DD_INSTRUMENTATION_TELEMETRY_ENABLED`` variable to ``False``.
55
"""
66

7-
import os
87
import typing as t
98

109
from ddtrace.internal.logger import get_logger
1110
from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE
1211
from ddtrace.internal.utils.formats import asbool
1312
from ddtrace.settings._agent import config as agent_config
13+
from ddtrace.settings._core import ENV_CONFIG
1414
from ddtrace.settings._core import FLEET_CONFIG
1515
from ddtrace.settings._core import FLEET_CONFIG_IDS
1616
from ddtrace.settings._core import LOCAL_CONFIG
1717
from ddtrace.settings._core import DDConfig
18+
from ddtrace.settings._env import environ as _environ
19+
from ddtrace.settings._env import get_env as _get_env
1820
from ddtrace.settings._otel_remapper import ENV_VAR_MAPPINGS
1921
from ddtrace.settings._otel_remapper import SUPPORTED_OTEL_ENV_VARS
2022
from ddtrace.settings._otel_remapper import parse_otel_env
@@ -25,13 +27,6 @@
2527
__all__ = ["telemetry_writer"]
2628

2729

28-
def get_env(env_name: str, default: t.Any = None) -> t.Any:
29-
if env_name.startswith(("DD_", "OTEL_", "_DD")):
30-
return get_config(env_name, default)
31-
32-
return os.environ.get(env_name, default)
33-
34-
3530
def get_config(
3631
envs: t.Union[str, t.List[str]],
3732
default: t.Any = None,
@@ -67,7 +62,7 @@ def get_config(
6762
effective_val = val
6863
break
6964

70-
if otel_env is not None and otel_env in os.environ:
65+
if otel_env is not None and otel_env in ENV_CONFIG:
7166
raw_val, parsed_val = parse_otel_env(otel_env)
7267
if parsed_val is not None:
7368
val = parsed_val
@@ -82,14 +77,14 @@ def get_config(
8277
_invalid_otel_config(otel_env)
8378

8479
for env in envs:
85-
if env in os.environ:
86-
val = os.environ[env]
80+
if env in ENV_CONFIG:
81+
val = ENV_CONFIG[env]
8782
if modifier:
8883
val = modifier(val)
8984

9085
if report_telemetry:
9186
telemetry_writer.add_configuration(telemetry_name, val, "env_var")
92-
if otel_env is not None and otel_env in os.environ:
87+
if otel_env is not None and otel_env in ENV_CONFIG:
9388
_hiding_otel_config(otel_env, env)
9489
effective_val = val
9590
break
@@ -103,7 +98,7 @@ def get_config(
10398

10499
if report_telemetry:
105100
telemetry_writer.add_configuration(telemetry_name, val, "fleet_stable_config", config_id)
106-
if otel_env is not None and otel_env in os.environ:
101+
if otel_env is not None and otel_env in ENV_CONFIG:
107102
_hiding_otel_config(otel_env, env)
108103
effective_val = val
109104
break
@@ -139,7 +134,7 @@ def _invalid_otel_config(otel_env):
139134
log.warning(
140135
"Setting %s to %s is not supported by ddtrace, this configuration will be ignored.",
141136
otel_env,
142-
os.environ.get(otel_env, ""),
137+
_get_env(otel_env, ""),
143138
)
144139
telemetry_writer.add_count_metric(
145140
TELEMETRY_NAMESPACE.TRACERS,
@@ -160,7 +155,7 @@ def _unsupported_otel_config(otel_env):
160155

161156

162157
def validate_otel_envs():
163-
user_envs = {key.upper(): value for key, value in os.environ.items()}
158+
user_envs = {key.upper(): value for key, value in ENV_CONFIG.items()}
164159
for otel_env, _ in user_envs.items():
165160
if (
166161
otel_env not in ENV_VAR_MAPPINGS
@@ -170,7 +165,7 @@ def validate_otel_envs():
170165
_unsupported_otel_config(otel_env)
171166
elif otel_env == "OTEL_LOGS_EXPORTER":
172167
# check for invalid values
173-
otel_value = os.environ.get(otel_env, "none").lower()
168+
otel_value = _get_env(otel_env, "none").lower()
174169
if otel_value != "none":
175170
_invalid_otel_config(otel_env)
176171
# TODO: Separate from validation
@@ -182,9 +177,8 @@ def validate_otel_envs():
182177

183178
def validate_and_report_otel_metrics_exporter_enabled():
184179
metrics_exporter_enabled = True
185-
user_envs = {key.upper(): value for key, value in os.environ.items()}
186-
if "OTEL_METRICS_EXPORTER" in user_envs:
187-
otel_value = os.environ.get("OTEL_METRICS_EXPORTER", "otlp").lower()
180+
if "OTEL_METRICS_EXPORTER" in _environ:
181+
otel_value = _get_env("OTEL_METRICS_EXPORTER", "otlp").lower()
188182
if otel_value == "none":
189183
metrics_exporter_enabled = False
190184
elif otel_value != "otlp":
@@ -201,7 +195,7 @@ def _hiding_otel_config(otel_env, dd_env):
201195
"Datadog configuration %s is already set. OpenTelemetry configuration will be ignored: %s=%s",
202196
dd_env,
203197
otel_env,
204-
os.environ[otel_env],
198+
ENV_CONFIG[otel_env],
205199
)
206200
telemetry_writer.add_count_metric(
207201
TELEMETRY_NAMESPACE.TRACERS,

ddtrace/settings/_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
from ..internal.schema import DEFAULT_SPAN_SERVICE_NAME
3636
from ..internal.serverless import in_aws_lambda
3737
from ..internal.telemetry import get_config as _get_config
38-
from ..internal.telemetry import get_env as _get_env # noqa:F401
3938
from ..internal.utils.formats import asbool
4039
from ..internal.utils.formats import parse_tags_str
40+
from ._env import get_env as _get_env # noqa:F401
4141
from ._inferred_base_service import detect_service
4242
from .endpoint_config import fetch_config_from_endpoint
4343
from .http import HttpConfig

ddtrace/settings/_core.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
from collections import ChainMap
22
from enum import Enum
3-
import os
43
from typing import Dict # noqa:F401
54
from typing import Optional # noqa:F401
65
from typing import Union # noqa:F401
76

87
from envier import Env
98

109
from ddtrace.internal.native import get_configuration_from_disk
10+
from ddtrace.settings._env import environ
1111

1212

1313
FLEET_CONFIG, LOCAL_CONFIG, FLEET_CONFIG_IDS = get_configuration_from_disk()
14+
ENV_CONFIG = environ
1415

1516

1617
class ValueSource(str, Enum):
@@ -34,7 +35,7 @@ def __init__(
3435
) -> None:
3536
self.fleet_source = FLEET_CONFIG
3637
self.local_source = LOCAL_CONFIG
37-
self.env_source = os.environ
38+
self.env_source = ENV_CONFIG
3839

3940
# Order of precedence: provided source < local stable config < environment variables < fleet stable config
4041
full_source = ChainMap(self.fleet_source, self.env_source, self.local_source, source or {})

ddtrace/settings/_env.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from collections.abc import MutableMapping
2+
import os
3+
import typing as t
4+
5+
6+
class EnvConfig(MutableMapping):
7+
"""
8+
Wrapper around os.environ that validates the variable against list of allowed variables.
9+
10+
It is used to get and set environment variables in a safe way mimicking the behavior of os.environ.
11+
"""
12+
13+
def __init__(self):
14+
self._env = os.environ
15+
16+
def __getitem__(self, key):
17+
"""Handles: environ[key]"""
18+
# TODO: Check will go here to validate the variable against list of allowed variables
19+
20+
return self._env[key]
21+
22+
def get(self, key, default=None):
23+
"""
24+
Get an environment variable.
25+
If the variable is a DD_ or OTEL_ or _DD prefixed variable, it will be validated against list
26+
of allowed variables.
27+
28+
This is a wrapper around os.environ.get(key, default). as direct access to os.environ is not allowed.
29+
"""
30+
# TODO: Check will go here to validate the variable against list of allowed variables
31+
32+
return self._env.get(key, default)
33+
34+
def __setitem__(self, key, value):
35+
"""Handles: environ[key] = value"""
36+
self._env[key] = value
37+
38+
def __delitem__(self, key):
39+
"""Handles: del environ[key]"""
40+
del self._env[key]
41+
42+
def __contains__(self, key):
43+
"""Handles: key in environ"""
44+
# TODO: Check will go here to validate the variable against list of allowed variables
45+
46+
return key in self._env
47+
48+
def __iter__(self):
49+
"""Handles: for key in environ"""
50+
# TODO: Check will go here to filter out unknown variables against list of allowed variables
51+
52+
return iter(self._env)
53+
54+
def __len__(self):
55+
"""Handles: len(environ)"""
56+
return len(self._env)
57+
58+
def items(self):
59+
"""Handles: environ.items()"""
60+
# TODO: Check will go here to filter out unknown variables against list of allowed variables
61+
62+
return self._env.items()
63+
64+
def keys(self):
65+
"""Handles: environ.keys()"""
66+
# TODO: Check will go here to filter out unknown variables against list of allowed variables
67+
68+
return self._env.keys()
69+
70+
def values(self):
71+
"""
72+
Get all the values of the environment variables.
73+
If the variable is a DD_ or OTEL_ or _DD prefixed variable, it will be validated against list of allowed
74+
variables.
75+
76+
This is a wrapper around os.environ.values(). as direct access to os.environ is not allowed.
77+
"""
78+
# TODO: Check will go here to filter out unknown variables against list of allowed variables
79+
80+
return self._env.values()
81+
82+
83+
environ = EnvConfig()
84+
"""
85+
Equivalent to os.environ using the EnvConfig wrapper-class.
86+
"""
87+
88+
89+
def get_env(env_name: str, default: t.Any = None) -> t.Any:
90+
"""
91+
Get an environment variable.
92+
If the variable is a DD_ or OTEL_ or _DD prefixed variable, it will be validated against list of allowed variables.
93+
94+
This is a wrapper around os.environ.get(env_name, default). as direct access to os.environ is not allowed.
95+
"""
96+
return environ.get(env_name, default)
97+
98+
99+
def set_env(env_name: str, value: t.Any) -> None:
100+
"""
101+
Set an environment variable.
102+
This is a wrapper around os.environ[env_name] = value. as direct access to os.environ is not allowed.
103+
"""
104+
os.environ[env_name] = value

ddtrace/settings/_otel_remapper.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from typing import Callable
32
from typing import Dict
43
from typing import List
@@ -8,6 +7,7 @@
87
from ..constants import ENV_KEY
98
from ..constants import VERSION_KEY
109
from ..internal.logger import get_logger
10+
from ._env import get_env as _get_env
1111

1212

1313
log = get_logger(__name__)
@@ -55,7 +55,7 @@ def _remap_traces_sampler(otel_value: str) -> Optional[str]:
5555
elif otel_value == "parentbased_always_off":
5656
rate = "0.0"
5757
elif otel_value == "parentbased_traceidratio":
58-
rate = os.environ.get("OTEL_TRACES_SAMPLER_ARG", "1")
58+
rate = _get_env("OTEL_TRACES_SAMPLER_ARG", "1")
5959

6060
if rate is not None:
6161
return f'[{{"sample_rate":{rate}}}]'
@@ -165,7 +165,7 @@ def _remap_default(otel_value: str) -> Optional[str]:
165165

166166
def parse_otel_env(otel_env: str) -> Tuple[str, Optional[str]]:
167167
_, otel_config_validator = ENV_VAR_MAPPINGS[otel_env]
168-
raw_value = os.environ.get(otel_env, "")
168+
raw_value = _get_env(otel_env, "")
169169
if otel_env not in ("OTEL_RESOURCE_ATTRIBUTES", "OTEL_SERVICE_NAME"):
170170
# Resource attributes and service name are case-insensitive
171171
raw_value = raw_value.lower()

0 commit comments

Comments
 (0)