Module: ActiveSanction::Instrumentation
- Extended by:
- T::Sig
- Defined in:
- lib/active_sanction/instrumentation.rb,
lib/active_sanction/instrumentation/event.rb,
lib/active_sanction/instrumentation/notifications.rb
Overview
Structured events from every stage, so a host can measure this library without monkeypatching it.
ActiveSanction.configure do |c|
c.instrumenter = ->(event) do
StatsD.timing("sanctions.#{event.name}", event.duration_ms, tags: ["source:#{event.source}"])
end
end
Six events, and they are the operational questions a compliance installation is actually asked: is the data fresh, did a fetch fail, how long did screening take, which source is degrading.
| Event | Emitted by | Asks |
|---|---|---|
:fetch |
Fetcher | Did bytes move, and what did the publisher answer? |
:parse |
Sources::Base | How many records came out, and how many rows could not be read? |
:store |
Sync, Client#import | Which list version was written, and how big is it? |
:"index.build" |
Matcher.build | What did building the index cost, and how much is resident? |
:screen |
Matcher | How long did a query take, and what did it consult? |
:sync |
Sync | What did a whole run do? |
The payload keys of each are enumerated in
docs/api_stability.md and are public API:
a dashboard built on them is held to the same promise as a method call, and
a key is not removed or repurposed without the deprecation path. Keys may
be added to an event, so a subscriber reads the keys it knows and
ignores the rest.
A subscriber is anything answering #call(event)
A lambda, a Method, an object with a call. There is no registry and no
base class to inherit, because the whole interface is one method and a
registry would be a second thing to configure. A host wanting several
subscribers composes them itself -- ->(event) { subscribers.each { |s| s.call(event) } } --
which is one line and is exactly what a fan-out registry here would be.
Rails hosts have one already: see Notifications, which republishes every
event into ActiveSupport::Notifications under <name>.active_sanction.
It is an adapter rather than a dependency -- nothing here requires
ActiveSupport, and the class refuses to build in a process that has not
loaded it.
Nothing is listening by default, and that costs nothing
instrumenter defaults to nil, and a nil instrumenter is not a no-op
object that gets called and returns -- it is a branch taken before
anything is allocated. .instrument with no instrumenter calls the block
with a payload that discards writes and returns, so a stage that fills in
fifteen fields allocates no Hash and builds no Event. This is what keeps
the #37 benchmarks where they were.
A subscriber must be safe to call from several threads
sync!(concurrency: 3) fetches from three publishers at once, and the
:fetch, :parse and :store events of those three arrive on three
threads. Nothing here serializes them -- a lock around a subscriber would
make instrumentation a source of contention in the one place this library
deliberately fans out. A subscriber that appends to a plain Array wants a
Mutex of its own; one that hands an event to a metrics client is already
fine, because those are.
The :screen event is the same statement from the other direction: a
Matcher is screened from every thread a host has, so a subscriber counting
queries is counting them concurrently.
A raising subscriber cannot break a sync
Instrumentation is a measurement of the work and is never part of it.
A subscriber that raises has its exception caught, reported once, and
dropped; the stage it was measuring carries on and returns what it was
going to return. The converse is equally deliberate: instrumentation
never swallows the library's own exceptions. An event is emitted for a
stage that raised, carrying error:, and then the exception continues
exactly as if nothing were listening.
Defined Under Namespace
Classes: Event, Notifications
Constant Summary collapse
- EVENTS =
Every event name this library emits. Enumerated so a host can assert against the list rather than discovering a name in production, and frozen because it is the published vocabulary -- see the class comment on what may and may not change about it.
T.let(%i[fetch parse store index.build screen sync].freeze, T::Array[Symbol])
Class Method Summary collapse
-
.reset! ⇒ void
Forget which subscriber failures have already been reported, so the next one is reported again.
Class Method Details
.reset! ⇒ void
This method returns an undefined value.
Forget which subscriber failures have already been reported, so the next one is reported again. For a spec that asserts a broken subscriber is reported; nothing in a running application should call it.
197 198 199 |
# File 'lib/active_sanction/instrumentation.rb', line 197 def reset! @mutex.synchronize { @failures.clear } end |