solnlib 8.0.0__tar.gz → 8.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {solnlib-8.0.0 → solnlib-8.1.0}/PKG-INFO +1 -1
- {solnlib-8.0.0 → solnlib-8.1.0}/pyproject.toml +1 -1
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/__init__.py +1 -1
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/observability.py +290 -28
- {solnlib-8.0.0 → solnlib-8.1.0}/LICENSE +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/_settings.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/_utils.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/acl.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/alerts_rest_client.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/bulletin_rest_client.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/concurrent/concurrent_executor.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/concurrent/process_pool.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/concurrent/thread_pool.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/conf_manager.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/credentials.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/file_monitor.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/hec_config.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/log.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/modular_input/__init__.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/modular_input/checkpointer.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/modular_input/event.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/modular_input/event_writer.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/modular_input/modinput.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/modular_input/modular_input.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/net_utils.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/orphan_process_monitor.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/pattern.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/rest.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/schedule/job.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/schedule/scheduler.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/server_info.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/soln_exceptions.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/splunk_rest_client.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/splunkenv.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/time_parser.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/timer_queue.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/user_access.py +0 -0
- {solnlib-8.0.0 → solnlib-8.1.0}/solnlib/utils.py +0 -0
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
#
|
|
16
16
|
"""OpenTelemetry observability utilities for Splunk add-ons.
|
|
17
17
|
|
|
18
|
-
This module provides
|
|
18
|
+
This module provides three public components:
|
|
19
19
|
|
|
20
20
|
- :class:`LoggerMetricExporter` — an OpenTelemetry ``MetricExporter`` that
|
|
21
21
|
writes every exported data point to a standard Python logger. It is useful
|
|
@@ -28,39 +28,30 @@ This module provides two public components:
|
|
|
28
28
|
Splunk Spotlight OTLP collector and falls back silently when it is not
|
|
29
29
|
reachable, so callers never have to handle observability failures themselves.
|
|
30
30
|
|
|
31
|
+
- :class:`StanzaObservabilityRecorder` — a stanza-scoped recorder that wraps
|
|
32
|
+
``ObservabilityService`` with a per-process singleton cache. Bind it to a
|
|
33
|
+
single stanza name and call :meth:`~StanzaObservabilityRecorder.record` after
|
|
34
|
+
each batch of ingested events. Use as a context manager for automatic flush
|
|
35
|
+
on exit.
|
|
36
|
+
|
|
31
37
|
Typical usage::
|
|
32
38
|
|
|
33
39
|
import logging
|
|
34
|
-
from solnlib.observability import
|
|
40
|
+
from solnlib.observability import StanzaObservabilityRecorder
|
|
35
41
|
|
|
36
42
|
logger = logging.getLogger(__name__)
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
logger=logger,
|
|
41
|
-
ta_name="my_ta",
|
|
42
|
-
ta_version="1.0.0",
|
|
43
|
-
extra_exporters=[LoggerMetricExporter(logger)],
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
# In your event collection loop:
|
|
47
|
-
if obs.event_count_counter:
|
|
48
|
-
obs.event_count_counter.add(
|
|
49
|
-
len(events), {ATTR_MODINPUT_NAME: stanza_name}
|
|
50
|
-
)
|
|
51
|
-
if obs.event_bytes_counter:
|
|
52
|
-
obs.event_bytes_counter.add(
|
|
53
|
-
total_bytes, {ATTR_MODINPUT_NAME: stanza_name}
|
|
54
|
-
)
|
|
44
|
+
with StanzaObservabilityRecorder("my-input", logger, stanza_name) as obs:
|
|
45
|
+
obs.record(len(events), total_bytes)
|
|
55
46
|
"""
|
|
56
47
|
|
|
57
48
|
import json
|
|
58
49
|
import logging
|
|
59
50
|
import os
|
|
60
51
|
import ssl
|
|
52
|
+
import threading
|
|
61
53
|
import urllib.request
|
|
62
|
-
from typing import Callable, Optional, Union
|
|
63
|
-
import grpc
|
|
54
|
+
from typing import Callable, ClassVar, Optional, Union
|
|
64
55
|
from .splunkenv import get_conf_stanzas
|
|
65
56
|
from opentelemetry.metrics import Instrument, Meter
|
|
66
57
|
from opentelemetry.sdk.metrics import MeterProvider, Counter, Histogram
|
|
@@ -72,7 +63,6 @@ from opentelemetry.sdk.metrics.export import (
|
|
|
72
63
|
AggregationTemporality,
|
|
73
64
|
)
|
|
74
65
|
from opentelemetry.sdk.resources import Resource
|
|
75
|
-
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
|
|
76
66
|
|
|
77
67
|
_Logger = Union[logging.Logger, logging.LoggerAdapter]
|
|
78
68
|
|
|
@@ -283,6 +273,7 @@ class ObservabilityService:
|
|
|
283
273
|
self.event_count_counter: Optional[Counter] = None
|
|
284
274
|
self.event_bytes_counter: Optional[Counter] = None
|
|
285
275
|
self._meter: Optional[Meter] = None
|
|
276
|
+
self._provider: Optional[MeterProvider] = None
|
|
286
277
|
|
|
287
278
|
try:
|
|
288
279
|
if ta_name is None or ta_version is None:
|
|
@@ -313,8 +304,10 @@ class ObservabilityService:
|
|
|
313
304
|
for exporter in extra_exporters or []:
|
|
314
305
|
metric_readers.append(PeriodicExportingMetricReader(exporter))
|
|
315
306
|
|
|
316
|
-
|
|
317
|
-
|
|
307
|
+
self._provider = MeterProvider(
|
|
308
|
+
resource=resource, metric_readers=metric_readers
|
|
309
|
+
)
|
|
310
|
+
self._meter = self._provider.get_meter(ta_name, ta_version)
|
|
318
311
|
|
|
319
312
|
self.event_count_counter = self._meter.create_counter(
|
|
320
313
|
name="splunk.addon.events",
|
|
@@ -435,20 +428,34 @@ class ObservabilityService:
|
|
|
435
428
|
return self._discover_otlp_port_via_ipc_broker()
|
|
436
429
|
|
|
437
430
|
def _create_otlp_exporter(self) -> Optional[MetricExporter]:
|
|
438
|
-
"""Create a TLS-secured OTLP gRPC exporter targeting the Spotlight
|
|
439
|
-
|
|
431
|
+
"""Create a TLS-secured OTLP gRPC exporter targeting the Spotlight collector.
|
|
432
|
+
|
|
433
|
+
``grpc`` and ``OTLPMetricExporter`` are imported lazily inside this
|
|
434
|
+
method so that ``import solnlib.observability`` succeeds in environments
|
|
435
|
+
where ``grpcio`` is not installed. The import only fails when OTLP
|
|
436
|
+
export is actually attempted.
|
|
440
437
|
|
|
441
438
|
The collector's server certificate is read from
|
|
442
439
|
``$SPLUNK_HOME/var/packages/data/spotlight-collector/server.crt``
|
|
443
440
|
(defaults to ``/opt/splunk`` when ``SPLUNK_HOME`` is not set).
|
|
444
441
|
|
|
442
|
+
Both ``Counter`` and ``Histogram`` instruments are configured with
|
|
443
|
+
``AggregationTemporality.DELTA`` so that each export interval reports
|
|
444
|
+
only the change since the previous interval.
|
|
445
|
+
|
|
445
446
|
Returns the configured exporter, or ``None`` when:
|
|
446
447
|
|
|
447
448
|
- The OTLP port cannot be resolved (see :meth:`_resolve_otlp_port`).
|
|
448
449
|
- The certificate file does not exist.
|
|
449
|
-
- Any other exception occurs during exporter construction
|
|
450
|
+
- Any other exception occurs during exporter construction (including a
|
|
451
|
+
missing ``grpcio`` package).
|
|
450
452
|
"""
|
|
451
453
|
try:
|
|
454
|
+
import grpc
|
|
455
|
+
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
|
|
456
|
+
OTLPMetricExporter,
|
|
457
|
+
)
|
|
458
|
+
|
|
452
459
|
splunk_home = os.environ.get("SPLUNK_HOME", "/opt/splunk")
|
|
453
460
|
otel_port = self._resolve_otlp_port()
|
|
454
461
|
|
|
@@ -484,7 +491,14 @@ class ObservabilityService:
|
|
|
484
491
|
server_cert = f.read()
|
|
485
492
|
|
|
486
493
|
credentials = grpc.ssl_channel_credentials(root_certificates=server_cert)
|
|
487
|
-
exporter = OTLPMetricExporter(
|
|
494
|
+
exporter = OTLPMetricExporter(
|
|
495
|
+
endpoint=endpoint,
|
|
496
|
+
credentials=credentials,
|
|
497
|
+
preferred_temporality={
|
|
498
|
+
Counter: AggregationTemporality.DELTA,
|
|
499
|
+
Histogram: AggregationTemporality.DELTA,
|
|
500
|
+
},
|
|
501
|
+
)
|
|
488
502
|
self._logger.info("OTLP gRPC exporter configured with TLS for %s", endpoint)
|
|
489
503
|
return exporter
|
|
490
504
|
|
|
@@ -531,3 +545,251 @@ class ObservabilityService:
|
|
|
531
545
|
if self._meter is None:
|
|
532
546
|
return None
|
|
533
547
|
return callback(self._meter)
|
|
548
|
+
|
|
549
|
+
def flush(self, timeout_millis: float = 30_000) -> None:
|
|
550
|
+
"""Force-flush all metric readers.
|
|
551
|
+
|
|
552
|
+
Blocks until all buffered data points have been handed off to their
|
|
553
|
+
exporters or *timeout_millis* elapses. Call this before the modular
|
|
554
|
+
input process exits to avoid dropping the last batch of metrics.
|
|
555
|
+
|
|
556
|
+
Prefer using :class:`StanzaObservabilityRecorder` as a context manager
|
|
557
|
+
rather than calling this method directly — it calls
|
|
558
|
+
:meth:`StanzaObservabilityRecorder.flush` on exit automatically.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
timeout_millis: Maximum time to wait for exporters to drain, in
|
|
562
|
+
milliseconds. Defaults to 30 seconds.
|
|
563
|
+
"""
|
|
564
|
+
if self._provider is None:
|
|
565
|
+
return
|
|
566
|
+
try:
|
|
567
|
+
self._provider.force_flush(timeout_millis=int(timeout_millis))
|
|
568
|
+
except Exception as e:
|
|
569
|
+
self._logger.warning("Failed to flush metrics: %s", e)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
class StanzaObservabilityRecorder:
|
|
573
|
+
"""Stanza-scoped observability recorder backed by a shared ``ObservabilityService``.
|
|
574
|
+
|
|
575
|
+
One ``ObservabilityService`` is created per *modinput_type* per process and
|
|
576
|
+
cached in :attr:`_instances` for the lifetime of the process. Every
|
|
577
|
+
``StanzaObservabilityRecorder`` for the same *modinput_type* shares that
|
|
578
|
+
service regardless of how many stanzas are active, so the OTLP connection
|
|
579
|
+
and ``MeterProvider`` are only initialised once.
|
|
580
|
+
|
|
581
|
+
Each recorder instance is bound to a single *stanza_name*, which is
|
|
582
|
+
automatically attached as the ``"splunk.modinput.name"`` attribute on every
|
|
583
|
+
recorded data point.
|
|
584
|
+
|
|
585
|
+
**Best practices:**
|
|
586
|
+
|
|
587
|
+
- Use as a context manager (``with`` statement) so that :meth:`flush` is
|
|
588
|
+
always called when the stanza collection loop exits, even on exceptions.
|
|
589
|
+
- Pass the same *modinput_type* string for all stanzas of the same input
|
|
590
|
+
type. The string should be lowercase, hyphenated, and stable across
|
|
591
|
+
restarts (e.g. ``"event-hub"``).
|
|
592
|
+
- Do not store the recorder beyond the lifetime of a single stanza
|
|
593
|
+
collection cycle — create a new instance for each run.
|
|
594
|
+
- Register custom instruments via :meth:`register_instrument` on the
|
|
595
|
+
recorder instance rather than accessing the underlying service directly.
|
|
596
|
+
- :class:`StanzaObservabilityRecorder` is **thread-safe** at the singleton
|
|
597
|
+
level (``_lock`` protects ``_instances``), but individual recorder
|
|
598
|
+
instances are not meant to be shared across threads.
|
|
599
|
+
|
|
600
|
+
**Typical usage**::
|
|
601
|
+
|
|
602
|
+
import logging
|
|
603
|
+
from solnlib.observability import StanzaObservabilityRecorder
|
|
604
|
+
|
|
605
|
+
logger = logging.getLogger(__name__)
|
|
606
|
+
|
|
607
|
+
def collect(stanza_name: str) -> None:
|
|
608
|
+
with StanzaObservabilityRecorder("my-input", logger, stanza_name) as obs:
|
|
609
|
+
events = fetch_events()
|
|
610
|
+
obs.record(len(events), sum(len(e) for e in events))
|
|
611
|
+
|
|
612
|
+
**Custom instrument** (e.g. latency histogram)::
|
|
613
|
+
|
|
614
|
+
from solnlib.observability import StanzaObservabilityRecorder, ATTR_MODINPUT_NAME
|
|
615
|
+
|
|
616
|
+
with StanzaObservabilityRecorder("my-input", logger, stanza_name) as obs:
|
|
617
|
+
latency_histogram = obs.register_instrument(
|
|
618
|
+
lambda meter: meter.create_histogram(
|
|
619
|
+
name="my_ta.request.latency",
|
|
620
|
+
description="Latency of outbound API requests",
|
|
621
|
+
unit="s",
|
|
622
|
+
)
|
|
623
|
+
)
|
|
624
|
+
# ... collect events ...
|
|
625
|
+
if latency_histogram:
|
|
626
|
+
latency_histogram.record(elapsed, {ATTR_MODINPUT_NAME: stanza_name})
|
|
627
|
+
"""
|
|
628
|
+
|
|
629
|
+
_instances: ClassVar[dict[str, ObservabilityService]] = {}
|
|
630
|
+
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
631
|
+
|
|
632
|
+
def __init__(
|
|
633
|
+
self,
|
|
634
|
+
modinput_type: str,
|
|
635
|
+
logger: _Logger,
|
|
636
|
+
stanza_name: str,
|
|
637
|
+
) -> None:
|
|
638
|
+
"""Initialise a stanza-scoped recorder.
|
|
639
|
+
|
|
640
|
+
Gets or creates the shared :class:`ObservabilityService` for
|
|
641
|
+
*modinput_type* (singleton per process), then emits a zero baseline
|
|
642
|
+
on both built-in counters so that the metric series is visible in
|
|
643
|
+
dashboards from the very first collection cycle even when no events
|
|
644
|
+
were ingested.
|
|
645
|
+
|
|
646
|
+
Args:
|
|
647
|
+
modinput_type: Low-cardinality identifier for the input type,
|
|
648
|
+
e.g. ``"event-hub"``. All recorders for the same input type
|
|
649
|
+
share a single ``ObservabilityService``.
|
|
650
|
+
logger: Python logger used both for ``ObservabilityService``
|
|
651
|
+
diagnostics and for the :class:`LoggerMetricExporter` that
|
|
652
|
+
is automatically added as an extra exporter.
|
|
653
|
+
stanza_name: The name of the input stanza being collected (e.g.
|
|
654
|
+
``"my_stanza"``). Attached as ``"splunk.modinput.name"``
|
|
655
|
+
on every recorded data point.
|
|
656
|
+
"""
|
|
657
|
+
self._stanza_name = stanza_name
|
|
658
|
+
self._service = self._get_or_create_service(modinput_type, logger)
|
|
659
|
+
self._emit_zero_baseline()
|
|
660
|
+
|
|
661
|
+
@classmethod
|
|
662
|
+
def _get_or_create_service(
|
|
663
|
+
cls, modinput_type: str, logger: _Logger
|
|
664
|
+
) -> ObservabilityService:
|
|
665
|
+
"""Return the cached service for *modinput_type*, creating it if needed.
|
|
666
|
+
|
|
667
|
+
Thread-safe: uses ``_lock`` to ensure exactly one
|
|
668
|
+
``ObservabilityService`` is created per *modinput_type* even when
|
|
669
|
+
multiple stanzas are initialised concurrently at process start.
|
|
670
|
+
"""
|
|
671
|
+
with cls._lock:
|
|
672
|
+
if modinput_type not in cls._instances:
|
|
673
|
+
cls._instances[modinput_type] = ObservabilityService(
|
|
674
|
+
modinput_type=modinput_type,
|
|
675
|
+
logger=logger,
|
|
676
|
+
extra_exporters=[LoggerMetricExporter(logger)],
|
|
677
|
+
)
|
|
678
|
+
return cls._instances[modinput_type]
|
|
679
|
+
|
|
680
|
+
def register_instrument(
|
|
681
|
+
self, callback: Callable[[Meter], Instrument]
|
|
682
|
+
) -> Optional[Instrument]:
|
|
683
|
+
"""Create a custom instrument on the shared meter.
|
|
684
|
+
|
|
685
|
+
Delegates to :meth:`ObservabilityService.register_instrument`. The
|
|
686
|
+
instrument is registered on the process-wide ``MeterProvider``, so it
|
|
687
|
+
is shared across all recorders for the same *modinput_type*. Calling
|
|
688
|
+
this method on any recorder instance for a given *modinput_type* is
|
|
689
|
+
equivalent — register each instrument only once.
|
|
690
|
+
|
|
691
|
+
Returns ``None`` when :class:`ObservabilityService` failed to
|
|
692
|
+
initialise (e.g. because ``ta_name`` could not be determined). Always
|
|
693
|
+
guard the returned value before recording::
|
|
694
|
+
|
|
695
|
+
latency_histogram = obs.register_instrument(
|
|
696
|
+
lambda meter: meter.create_histogram(
|
|
697
|
+
name="my_ta.request.latency",
|
|
698
|
+
description="Latency of outbound API requests",
|
|
699
|
+
unit="s",
|
|
700
|
+
)
|
|
701
|
+
)
|
|
702
|
+
if latency_histogram:
|
|
703
|
+
latency_histogram.record(elapsed, {ATTR_MODINPUT_NAME: self._stanza_name})
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
callback: Callable that receives the ``Meter`` and returns a new
|
|
707
|
+
instrument (Counter, Histogram, Gauge, etc.).
|
|
708
|
+
|
|
709
|
+
Returns:
|
|
710
|
+
The instrument created by *callback*, or ``None``.
|
|
711
|
+
"""
|
|
712
|
+
return self._service.register_instrument(callback)
|
|
713
|
+
|
|
714
|
+
def record(
|
|
715
|
+
self,
|
|
716
|
+
event_count: int,
|
|
717
|
+
byte_count: int,
|
|
718
|
+
extra_attrs: Optional[dict] = None,
|
|
719
|
+
) -> None:
|
|
720
|
+
"""Add *event_count* and *byte_count* to the built-in counters.
|
|
721
|
+
|
|
722
|
+
The ``"splunk.modinput.name"`` attribute is always set to the
|
|
723
|
+
*stanza_name* supplied at construction time and cannot be overridden by
|
|
724
|
+
*extra_attrs*. This preserves the stanza-scoped guarantee — every data
|
|
725
|
+
point is unambiguously attributed to the stanza that recorded it.
|
|
726
|
+
|
|
727
|
+
Silently no-ops if either counter is ``None`` (i.e.
|
|
728
|
+
:class:`ObservabilityService` failed to initialise).
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
event_count: Number of events ingested in this batch. Pass ``0``
|
|
732
|
+
for an explicit "no events" observation.
|
|
733
|
+
byte_count: Total size of the ingested events in bytes.
|
|
734
|
+
extra_attrs: Optional dict of additional OpenTelemetry attributes
|
|
735
|
+
to attach to both data points. Keys must be strings; values
|
|
736
|
+
must be strings, booleans, or numbers. Avoid high-cardinality
|
|
737
|
+
keys such as user IDs or GUIDs.
|
|
738
|
+
|
|
739
|
+
Example::
|
|
740
|
+
|
|
741
|
+
obs.record(
|
|
742
|
+
event_count=len(events),
|
|
743
|
+
byte_count=sum(len(e) for e in events),
|
|
744
|
+
extra_attrs={"my_ta.partition": partition_id},
|
|
745
|
+
)
|
|
746
|
+
"""
|
|
747
|
+
attrs = dict(extra_attrs) if extra_attrs else {}
|
|
748
|
+
attrs[ATTR_MODINPUT_NAME] = self._stanza_name
|
|
749
|
+
if self._service.event_count_counter:
|
|
750
|
+
self._service.event_count_counter.add(event_count, attributes=attrs)
|
|
751
|
+
if self._service.event_bytes_counter:
|
|
752
|
+
self._service.event_bytes_counter.add(byte_count, attributes=attrs)
|
|
753
|
+
|
|
754
|
+
def flush(self) -> None:
|
|
755
|
+
"""Force-flush all metric readers.
|
|
756
|
+
|
|
757
|
+
Delegates to :meth:`ObservabilityService.flush` (which calls
|
|
758
|
+
``MeterProvider.force_flush()`` internally). Called automatically by
|
|
759
|
+
``__exit__`` when the recorder is used as a context manager, so you
|
|
760
|
+
rarely need to call this directly.
|
|
761
|
+
|
|
762
|
+
Call it explicitly only when you are not using the context manager and
|
|
763
|
+
need to guarantee delivery before the process exits::
|
|
764
|
+
|
|
765
|
+
obs = StanzaObservabilityRecorder("my-input", logger, stanza_name)
|
|
766
|
+
try:
|
|
767
|
+
obs.record(len(events), total_bytes)
|
|
768
|
+
finally:
|
|
769
|
+
obs.flush()
|
|
770
|
+
"""
|
|
771
|
+
self._service.flush()
|
|
772
|
+
|
|
773
|
+
def __enter__(self) -> "StanzaObservabilityRecorder":
|
|
774
|
+
"""Return *self* to support the ``with`` statement."""
|
|
775
|
+
return self
|
|
776
|
+
|
|
777
|
+
def __exit__(self, *_) -> bool:
|
|
778
|
+
"""Flush all metric readers and allow exceptions to propagate.
|
|
779
|
+
|
|
780
|
+
Returns ``False`` so any exception raised inside the ``with`` block
|
|
781
|
+
is re-raised after flushing.
|
|
782
|
+
"""
|
|
783
|
+
self.flush()
|
|
784
|
+
return False
|
|
785
|
+
|
|
786
|
+
def _emit_zero_baseline(self) -> None:
|
|
787
|
+
"""Emit ``add(0)`` on both built-in counters.
|
|
788
|
+
|
|
789
|
+
Called once from ``__init__``. Ensures that the metric series for this
|
|
790
|
+
stanza appears in dashboards and alerting rules from the very first
|
|
791
|
+
collection cycle, even when no events were ingested. Without this
|
|
792
|
+
baseline, a stanza that has never produced data is indistinguishable
|
|
793
|
+
from a stanza that has never been seen at all.
|
|
794
|
+
"""
|
|
795
|
+
self.record(0, 0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|