-
Notifications
You must be signed in to change notification settings - Fork 657
/
__init__.py
198 lines (153 loc) · 7.2 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Simple configuration manager
This is a configuration manager for OpenTelemetry. It reads configuration
values from environment variables prefixed with ``OTEL_`` (for environment
variables that apply to any OpenTelemetry implementation) or with
``OTEL_PYTHON_`` (for environment variables that are specific to the Python
implementation of OpenTelemetry) whose characters are only alphanumeric
characters and unserscores, except for the first character after ``OTEL_`` or
``OTEL_PYTHON_`` which must not be a number.
For example, these environment variables will be read:
1. ``OTEL_SOMETHING``
2. ``OTEL_SOMETHING_ELSE_``
3. ``OTEL_SOMETHING_ELSE_AND__ELSE``
4. ``OTEL_SOMETHING_ELSE_AND_else``
5. ``OTEL_SOMETHING_ELSE_AND_else2``
These won't:
1. ``OPENTELEMETRY_PYTH_SOMETHING``
2. ``OTEL_2_SOMETHING_AND__ELSE``
3. ``OTEL_SOMETHING_%_ELSE``
The values stored in the environment variables can be found in an instance of
``opentelemetry.configuration.Configuration``. This class can be instantiated
freely because instantiating it returns always the same object.
For example, if the environment variable
``OTEL_PYTHON_METER_PROVIDER`` value is ``my_meter_provider``, then
``Configuration().METER_PROVIDER == "my_meter_provider"`` would be ``True``.
Non defined attributes will always return ``None``. This is intended to make it
easier to use the ``Configuration`` object in actual code, because it won't be
necessary to check for the attribute to be defined first.
Environment variables used by OpenTelemetry
-------------------------------------------
1. OTEL_PYTHON_METER_PROVIDER
2. OTEL_PYTHON_TRACER_PROVIDER
The value of these environment variables should be the name of the entry point
that points to the class that implements either provider. This OpenTelemetry
API package provides one entry point for each, which can be found in the
setup.py file::
entry_points={
...
"opentelemetry_meter_provider": [
"default_meter_provider = "
"opentelemetry.metrics:DefaultMeterProvider"
],
"opentelemetry_tracer_provider": [
"default_tracer_provider = "
"opentelemetry.trace:DefaultTracerProvider"
],
}
To use the meter provider above, then the
``OTEL_PYTHON_METER_PROVIDER`` should be set to
``"default_meter_provider"`` (this is not actually necessary since the
OpenTelemetry API provided providers are the default ones used if no
configuration is found in the environment variables).
Configuration values that are exactly ``"True"`` or ``"False"`` will be
converted to its boolean values of ``True`` and ``False`` respectively.
Configuration values that can be casted to integers or floats will be casted.
This object can be used by any OpenTelemetry component, native or external.
For that reason, the ``Configuration`` object is designed to be immutable.
If a component would change the value of one of the ``Configuration`` object
attributes then another component that relied on that value may break, leading
to bugs that are very hard to debug. To avoid this situation, the preferred
approach for components that need a different value than the one provided by
the ``Configuration`` object is to implement a mechanism that allows the user
to override this value instead of changing it.
"""
import re
from os import environ
from typing import ClassVar, Dict, List, Optional, Sequence, TypeVar, Union
ConfigValue = Union[str, bool, int, float]
_T = TypeVar("_T", ConfigValue, Optional[ConfigValue])
class ExcludeList:
"""Class to exclude certain paths (given as a list of regexes) from tracing requests"""
def __init__(self, excluded_urls: Sequence[str]):
self._non_empty = len(excluded_urls) > 0
if self._non_empty:
self._regex = re.compile("|".join(excluded_urls))
def url_disabled(self, url: str) -> bool:
return bool(self._non_empty and re.search(self._regex, url))
class Configuration:
_instance = None # type: ClassVar[Optional[Configuration]]
_config_map = {} # type: ClassVar[Dict[str, ConfigValue]]
def __new__(cls) -> "Configuration":
if cls._instance is not None:
instance = cls._instance
else:
instance = super().__new__(cls)
for key, value_str in environ.items():
match = re.fullmatch(r"OTEL_(PYTHON_)?([A-Za-z_][\w_]*)", key)
if match is not None:
key = match.group(2)
value = value_str # type: ConfigValue
if value_str == "True":
value = True
elif value_str == "False":
value = False
else:
try:
value = int(value_str)
except ValueError:
try:
value = float(value_str)
except ValueError:
pass
instance._config_map[key] = value
cls._instance = instance
return instance
def __getattr__(self, name: str) -> Optional[ConfigValue]:
return self._config_map.get(name)
def __setattr__(self, name: str, value: ConfigValue) -> None:
if name not in self._config_map.keys():
self._config_map[name] = value
else:
raise AttributeError(name)
def get(self, name: str, default: _T) -> _T:
"""Use this typed method for dynamic access instead of `getattr`
:rtype: str or bool or int or float or None
"""
return self._config_map.get(name, default)
@classmethod
def _reset(cls) -> None:
"""
This method "resets" the global configuration attributes
It is not intended to be used by production code but by testing code
only.
"""
if cls._instance:
cls._instance._config_map.clear() # pylint: disable=protected-access
cls._instance = None
def _traced_request_attrs(self, instrumentation: str) -> List[str]:
"""Returns list of traced request attributes for instrumentation."""
key = "{}_TRACED_REQUEST_ATTRS".format(instrumentation.upper())
value = self._config_map.get(key, "")
request_attrs = (
[attr.strip() for attr in str.split(value, ",")] if value else [] # type: ignore
)
return request_attrs
def _excluded_urls(self, instrumentation: str) -> ExcludeList:
key = "{}_EXCLUDED_URLS".format(instrumentation.upper())
value = self._config_map.get(key, "")
urls = str.split(value, ",") if value else [] # type: ignore
return ExcludeList(urls)