|
22 | 22 |
|
23 | 23 | from elasticotel.distro.sanitization import _sanitize_headers_env_vars |
24 | 24 | from elasticotel.sdk.sampler import DefaultSampler |
| 25 | +from elasticotel.sdk.trace import tracer_configurator |
25 | 26 | from opentelemetry import trace |
26 | 27 | from opentelemetry._opamp import messages |
27 | 28 | from opentelemetry._opamp.agent import OpAMPAgent |
|
33 | 34 | from opentelemetry._opamp.proto import opamp_pb2 as opamp_pb2 |
34 | 35 | from opentelemetry.sdk._logs import LoggingHandler |
35 | 36 | from opentelemetry.sdk.environment_variables import OTEL_LOG_LEVEL, OTEL_TRACES_SAMPLER_ARG |
| 37 | +from opentelemetry.sdk.trace import _TracerConfig, _TracerConfiguratorRulesT, _scope_name_matches_glob |
36 | 38 |
|
37 | 39 |
|
38 | 40 | logger = logging.getLogger(__name__) |
|
51 | 53 | DEFAULT_SAMPLING_RATE = 1.0 |
52 | 54 | DEFAULT_LOGGING_LEVEL = "warn" |
53 | 55 |
|
| 56 | +DEACTIVATE_INSTRUMENTATIONS_CONFIG_KEY = "deactivate_instrumentations" |
54 | 57 | LOGGING_LEVEL_CONFIG_KEY = "logging_level" |
55 | 58 | SAMPLING_RATE_CONFIG_KEY = "sampling_rate" |
56 | 59 |
|
@@ -90,9 +93,14 @@ class Config: |
90 | 93 | sampling_rate = ConfigItem(default=str(DEFAULT_SAMPLING_RATE), from_env_var=OTEL_TRACES_SAMPLER_ARG) |
91 | 94 | # currently the sdk does not handle OTEL_LOG_LEVEL, so we use handle it on our own |
92 | 95 | logging_level = ConfigItem(default=DEFAULT_LOGGING_LEVEL, from_env_var=OTEL_LOG_LEVEL) |
| 96 | + deactivate_instrumentations = ConfigItem(default="") |
93 | 97 |
|
94 | 98 | def to_dict(self): |
95 | | - return {LOGGING_LEVEL_CONFIG_KEY: self.logging_level.value, SAMPLING_RATE_CONFIG_KEY: self.sampling_rate.value} |
| 99 | + return { |
| 100 | + LOGGING_LEVEL_CONFIG_KEY: self.logging_level.value, |
| 101 | + SAMPLING_RATE_CONFIG_KEY: self.sampling_rate.value, |
| 102 | + DEACTIVATE_INSTRUMENTATIONS_CONFIG_KEY: self.deactivate_instrumentations.value, |
| 103 | + } |
96 | 104 |
|
97 | 105 | def log_env_vars(self): |
98 | 106 | # log all the environment variables that starts with OTEL_ or ELASTIC_OTEL_ to ease troubleshooting |
@@ -142,6 +150,7 @@ def __post_init__(self): |
142 | 150 | # we need to initialize each config item when we instantiate the Config and not at declaration time |
143 | 151 | self.sampling_rate.init() |
144 | 152 | self.logging_level.init() |
| 153 | + self.deactivate_instrumentations.init() |
145 | 154 |
|
146 | 155 | self._setup_logging() |
147 | 156 |
|
@@ -192,6 +201,42 @@ def _handle_sampling_rate(remote_config) -> ConfigUpdate: |
192 | 201 | return ConfigUpdate() |
193 | 202 |
|
194 | 203 |
|
| 204 | +def _rules_from_deactivate_instrumentations(csv: str) -> _TracerConfiguratorRulesT: |
| 205 | + patterns = [pattern.strip() for pattern in csv.split(",") if pattern.strip()] |
| 206 | + if not patterns: |
| 207 | + return [] |
| 208 | + |
| 209 | + tracer_off_config = _TracerConfig(is_enabled=False) |
| 210 | + # remember that python instrumentations scope name are in the form opentelemetry.instrumentation.<module> |
| 211 | + return [(_scope_name_matches_glob(pattern), tracer_off_config) for pattern in patterns] |
| 212 | + |
| 213 | + |
| 214 | +def _handle_deactivate_instrumentations(remote_config) -> ConfigUpdate: |
| 215 | + tracer_provider = trace.get_tracer_provider() |
| 216 | + set_tracer_configurator = getattr(tracer_provider, "_set_tracer_configurator", None) |
| 217 | + if set_tracer_configurator is None: |
| 218 | + logger.debug("Cannot get _set_tracer_configurator from tracer provider.") |
| 219 | + return ConfigUpdate() |
| 220 | + |
| 221 | + config_deactivate_instrumentations = remote_config.get(DEACTIVATE_INSTRUMENTATIONS_CONFIG_KEY, "") |
| 222 | + |
| 223 | + rules = _rules_from_deactivate_instrumentations(config_deactivate_instrumentations) |
| 224 | + current_tracer_configurator = tracer_configurator._get_tracer_configurator() |
| 225 | + rules_updated = current_tracer_configurator.update_rules(rules) |
| 226 | + # if the rules did not change we are fine |
| 227 | + if not rules_updated: |
| 228 | + return ConfigUpdate() |
| 229 | + # when rules are updated we need to clear the cache of the tracer_configurator function |
| 230 | + tracer_configurator._updatable_tracer_configurator.cache_clear() |
| 231 | + |
| 232 | + set_tracer_configurator(tracer_configurator=tracer_configurator._updatable_tracer_configurator) |
| 233 | + logger.debug('Updated deactivate instrumentations to "%s".', config_deactivate_instrumentations) |
| 234 | + _config = _get_config() |
| 235 | + if _config: |
| 236 | + _config.deactivate_instrumentations.update(value=config_deactivate_instrumentations) |
| 237 | + return ConfigUpdate() |
| 238 | + |
| 239 | + |
195 | 240 | def _report_full_state(message: opamp_pb2.ServerToAgent): |
196 | 241 | return message.flags & opamp_pb2.ServerToAgentFlags_ReportFullState |
197 | 242 |
|
@@ -234,6 +279,10 @@ def opamp_handler(agent: OpAMPAgent, client: OpAMPClient, message: opamp_pb2.Ser |
234 | 279 | config_update = _handle_sampling_rate(remote_config) |
235 | 280 | if config_update.error_message: |
236 | 281 | error_messages.append(config_update.error_message) |
| 282 | + |
| 283 | + config_update = _handle_deactivate_instrumentations(remote_config) |
| 284 | + if config_update.error_message: |
| 285 | + error_messages.append(config_update.error_message) |
237 | 286 | except (OpAMPRemoteConfigParseException, OpAMPRemoteConfigDecodeException) as exc: |
238 | 287 | logger.error(str(exc)) |
239 | 288 | error_messages.append(str(exc)) |
|
0 commit comments