Skip to content

Commit dfdb2da

Browse files
beniwohlibasepi
authored andcommitted
introduce enabled/recording settings (elastic#790)
* introduce enabled/recording settings see elastic/apm#92 (comment) Co-authored-by: Colton Myers <colton.myers@gmail.com>
1 parent aec4efd commit dfdb2da

17 files changed

Lines changed: 199 additions & 74 deletions

File tree

docs/configuration.asciidoc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,37 @@ Your service name must only contain characters from the ASCII alphabet, numbers,
112112
The URL for your APM Server.
113113
The URL must be fully qualified, including protocol (`http` or `https`) and port.
114114

115+
[float]
116+
[[config-enabled]]
117+
=== `enabled`
118+
119+
[options="header"]
120+
|============
121+
| Environment | Django/Flask | Default
122+
| `ELASTIC_APM_ENABLED` | `ENABLED` | `true`
123+
|============
124+
125+
Enable or disable the agent.
126+
When set to false, the agent will not collect any data, nor start any background threads.
127+
128+
129+
[float]
130+
[[config-recording]]
131+
=== `recording`
132+
133+
<<dynamic-configuration, image:./images/dynamic-config.svg[] >>
134+
135+
[options="header"]
136+
|============
137+
| Environment | Django/Flask | Default
138+
| `ELASTIC_APM_RECORDING` | `RECORDING` | `false`
139+
|============
140+
141+
Enable or disable recording of events.
142+
If set to false, then the Python agent does not send any events to the Elastic APM server,
143+
and instrumentation overhead is minimized,
144+
but the agent will continue to poll the server for configuration changes.
145+
115146

116147
[float]
117148
[[config-transport-class]]

elasticapm/base.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,7 @@ def __init__(self, config=None, **inline):
187187
)
188188
self.include_paths_re = stacks.get_path_regex(self.config.include_paths) if self.config.include_paths else None
189189
self.exclude_paths_re = stacks.get_path_regex(self.config.exclude_paths) if self.config.exclude_paths else None
190-
self._metrics = MetricsRegistry(
191-
self.config.metrics_interval / 1000.0, self.queue, ignore_patterns=self.config.disable_metrics
192-
)
190+
self._metrics = MetricsRegistry(self)
193191
for path in self.config.metrics_sets:
194192
self._metrics.register(path)
195193
if self.config.breakdown_metrics:
@@ -200,15 +198,14 @@ def __init__(self, config=None, **inline):
200198
self._thread_managers["config"] = self.config
201199
else:
202200
self._config_updater = None
203-
204201
if self.config.use_elastic_excepthook:
205202
self.original_excepthook = sys.excepthook
206203
self.excepthook_overwritten_by_user = (
207204
self.original_excepthook is not sys.__excepthook__
208205
)
209206
sys.excepthook = self._excepthook
210-
211-
self.start_threads()
207+
if config.enabled:
208+
self.start_threads()
212209

213210
def start_threads(self):
214211
with self._thread_starter_lock:
@@ -217,7 +214,7 @@ def start_threads(self):
217214
self.logger.debug("Detected PID change from %r to %r, starting threads", self._pid, current_pid)
218215
for manager_type, manager in self._thread_managers.items():
219216
self.logger.debug("Starting %s thread", manager_type)
220-
manager.start_thread()
217+
manager.start_thread(pid=current_pid)
221218
self._pid = current_pid
222219

223220
def get_handler(self, name):
@@ -227,6 +224,8 @@ def capture(self, event_type, date=None, context=None, custom=None, stack=None,
227224
"""
228225
Captures and processes an event and pipes it off to Client.send.
229226
"""
227+
if not self.config.is_recording:
228+
return
230229
if event_type == "Exception":
231230
# never gather log stack for exceptions
232231
stack = False
@@ -281,7 +280,8 @@ def begin_transaction(self, transaction_type, trace_parent=None, start=None):
281280
:param start: override the start timestamp, mostly useful for testing
282281
:return: the started transaction object
283282
"""
284-
return self.tracer.begin_transaction(transaction_type, trace_parent=trace_parent, start=start)
283+
if self.config.is_recording:
284+
return self.tracer.begin_transaction(transaction_type, trace_parent=trace_parent, start=start)
285285

286286
def end_transaction(self, name=None, result="", duration=None):
287287
"""
@@ -296,9 +296,10 @@ def end_transaction(self, name=None, result="", duration=None):
296296
return transaction
297297

298298
def close(self):
299-
with self._thread_starter_lock:
300-
for _manager_type, manager in self._thread_managers.items():
301-
manager.stop_thread()
299+
if self.config.enabled:
300+
with self._thread_starter_lock:
301+
for _, manager in self._thread_managers.items():
302+
manager.stop_thread()
302303

303304
def get_service_info(self):
304305
if self._service_info:

elasticapm/conf/__init__.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ class Config(_ConfigBase):
337337
framework_version = _ConfigValue("FRAMEWORK_VERSION", default=None)
338338
global_labels = _DictConfigValue("GLOBAL_LABELS", default=None)
339339
disable_send = _BoolConfigValue("DISABLE_SEND", default=False)
340+
enabled = _BoolConfigValue("ENABLED", default=True)
341+
recording = _BoolConfigValue("RECORDING", default=True)
340342
instrument = _BoolConfigValue("INSTRUMENT", default=True)
341343
enable_distributed_tracing = _BoolConfigValue("ENABLE_DISTRIBUTED_TRACING", default=True)
342344
capture_headers = _BoolConfigValue("CAPTURE_HEADERS", default=True)
@@ -345,13 +347,29 @@ class Config(_ConfigBase):
345347
use_elastic_traceparent_header = _BoolConfigValue("USE_ELASTIC_TRACEPARENT_HEADER", default=True)
346348
use_elastic_excepthook = _BoolConfigValue("USE_ELASTIC_EXCEPTHOOK", default=False)
347349

350+
@property
351+
def is_recording(self):
352+
if not self.enabled:
353+
return False
354+
else:
355+
return self.recording
356+
348357

349358
class VersionedConfig(ThreadManager):
350359
"""
351360
A thin layer around Config that provides versioning
352361
"""
353362

354-
__slots__ = ("_config", "_version", "_first_config", "_first_version", "_lock", "transport", "_update_thread")
363+
__slots__ = (
364+
"_config",
365+
"_version",
366+
"_first_config",
367+
"_first_version",
368+
"_lock",
369+
"transport",
370+
"_update_thread",
371+
"pid",
372+
)
355373

356374
def __init__(self, config_object, version, transport=None):
357375
"""
@@ -364,6 +382,7 @@ def __init__(self, config_object, version, transport=None):
364382
self.transport = transport
365383
self._lock = threading.Lock()
366384
self._update_thread = None
385+
super(VersionedConfig, self).__init__()
367386

368387
def update(self, version, **config):
369388
"""
@@ -437,11 +456,12 @@ def update_config(self):
437456

438457
return next_run
439458

440-
def start_thread(self):
459+
def start_thread(self, pid=None):
441460
self._update_thread = IntervalTimer(
442461
self.update_config, 1, "eapm conf updater", daemon=True, evaluate_function_interval=True
443462
)
444463
self._update_thread.start()
464+
super(VersionedConfig, self).start_thread(pid=pid)
445465

446466
def stop_thread(self):
447467
if self._update_thread:

elasticapm/contrib/aiohttp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@ def install_tracing(self, app, client):
5252
from elasticapm.contrib.aiohttp.middleware import tracing_middleware
5353

5454
app.middlewares.insert(0, tracing_middleware(app))
55-
if client.config.instrument:
55+
if client.config.instrument and client.config.enabled:
5656
elasticapm.instrument()

elasticapm/contrib/django/apps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def ready(self):
6565
if self.client.config.autoinsert_django_middleware:
6666
self.insert_middleware(django_settings)
6767
register_handlers(self.client)
68-
if self.client.config.instrument:
68+
if self.client.config.instrument and self.client.config.enabled:
6969
instrument(self.client)
7070
else:
7171
self.client.logger.debug("Skipping instrumentation. INSTRUMENT is set to False.")

elasticapm/contrib/flask/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131

3232
from __future__ import absolute_import
33+
3334
import logging
3435

3536
import flask
@@ -145,7 +146,7 @@ def init_app(self, app, **defaults):
145146
pass
146147

147148
# Instrument to get spans
148-
if self.client.config.instrument:
149+
if self.client.config.instrument and self.client.config.enabled:
149150
elasticapm.instrumentation.control.instrument()
150151

151152
signals.request_started.connect(self.request_started, sender=app)

elasticapm/contrib/opentracing/tracer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __init__(self, client_instance=None, config=None, scope_manager=None):
5353
"Usage of other scope managers will lead to unpredictable results."
5454
)
5555
self._scope_manager = scope_manager or ThreadLocalScopeManager()
56-
if self._agent.config.instrument:
56+
if self._agent.config.instrument and self._agent.config.enabled:
5757
instrument()
5858

5959
def start_active_span(

elasticapm/contrib/tornado/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,9 @@ def __init__(self, app, client=None, **config):
6363
app.elasticapm_client = client
6464

6565
# Don't instrument if debug=True in tornado, unless client.config.debug is True
66-
if (not self.app.settings.get("debug") or client.config.debug) and client.config.instrument:
66+
if (
67+
(not self.app.settings.get("debug") or client.config.debug)
68+
and client.config.instrument
69+
and client.config.enabled
70+
):
6771
elasticapm.instrument()

elasticapm/metrics/base_metrics.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,18 @@
4444

4545

4646
class MetricsRegistry(ThreadManager):
47-
def __init__(self, collect_interval, queue_func, tags=None, ignore_patterns=None):
47+
def __init__(self, client, tags=None):
4848
"""
4949
Creates a new metric registry
5050
51-
:param collect_interval: the interval to collect metrics from registered metric sets
52-
:param queue_func: the function to call with the collected metrics
51+
:param client: client instance
5352
:param tags:
5453
"""
55-
self._collect_interval = collect_interval
56-
self._queue_func = queue_func
54+
self.client = client
5755
self._metricsets = {}
5856
self._tags = tags or {}
5957
self._collect_timer = None
60-
self._ignore_patterns = ignore_patterns or ()
58+
super(MetricsRegistry, self).__init__()
6159

6260
def register(self, class_path):
6361
"""
@@ -84,16 +82,18 @@ def collect(self):
8482
Collect metrics from all registered metric sets and queues them for sending
8583
:return:
8684
"""
87-
logger.debug("Collecting metrics")
85+
if self.client.config.is_recording:
86+
logger.debug("Collecting metrics")
8887

89-
for name, metricset in compat.iteritems(self._metricsets):
90-
for data in metricset.collect():
91-
self._queue_func(constants.METRICSET, data)
88+
for _, metricset in compat.iteritems(self._metricsets):
89+
for data in metricset.collect():
90+
self.client.queue(constants.METRICSET, data)
9291

93-
def start_thread(self):
94-
if self._collect_interval:
92+
def start_thread(self, pid=None):
93+
super(MetricsRegistry, self).start_thread(pid=pid)
94+
if self.client.config.metrics_interval:
9595
self._collect_timer = IntervalTimer(
96-
self.collect, self._collect_interval, name="eapm metrics collect timer", daemon=True
96+
self.collect, self.collect_interval, name="eapm metrics collect timer", daemon=True
9797
)
9898
logger.debug("Starting metrics collect timer")
9999
self._collect_timer.start()
@@ -104,6 +104,14 @@ def stop_thread(self):
104104
self._collect_timer.cancel()
105105
self._collect_timer = None
106106

107+
@property
108+
def collect_interval(self):
109+
return self.client.config.metrics_interval / 1000.0
110+
111+
@property
112+
def ignore_patterns(self):
113+
return self.client.config.disable_metrics or []
114+
107115

108116
class MetricsSet(object):
109117
def __init__(self, registry):
@@ -159,9 +167,7 @@ def _metric(self, container, metric_class, name, reset_on_collect, labels):
159167
key = (name, labels)
160168
with self._lock:
161169
if key not in container:
162-
if self._registry._ignore_patterns and any(
163-
pattern.match(name) for pattern in self._registry._ignore_patterns
164-
):
170+
if any(pattern.match(name) for pattern in self._registry.ignore_patterns):
165171
metric = noop_metric
166172
elif len(self._gauges) + len(self._counters) + len(self._timers) >= DISTINCT_LABEL_LIMIT:
167173
if not self._label_limit_logged:

elasticapm/transport/base.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def __init__(
9494
self._flushed = threading.Event()
9595
self._closed = False
9696
self._processors = processors if processors is not None else []
97+
super(Transport, self).__init__()
9798

9899
@property
99100
def _max_flush_time(self):
@@ -228,13 +229,13 @@ def _flush(self, buffer):
228229
except Exception as e:
229230
self.handle_transport_fail(e)
230231

231-
def start_thread(self):
232-
current_pid = os.getpid()
233-
if (not self._thread or current_pid != self._thread.pid) and not self._closed:
232+
def start_thread(self, pid=None):
233+
super(Transport, self).start_thread(pid=pid)
234+
if (not self._thread or self.pid != self._thread.pid) and not self._closed:
234235
try:
235236
self._thread = threading.Thread(target=self._process_queue, name="eapm event processor thread")
236237
self._thread.daemon = True
237-
self._thread.pid = current_pid
238+
self._thread.pid = self.pid
238239
self._thread.start()
239240
except RuntimeError:
240241
pass

0 commit comments

Comments
 (0)