Class: ActiveSanction::Instrumentation::Notifications

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/active_sanction/instrumentation/notifications.rb

Overview

Republishes every event into ActiveSupport::Notifications, for a host that already has subscribers, log tags and a dashboard pointed there.

# config/initializers/active_sanction.rb
ActiveSanction.configure do |c|
c.instrumenter = ActiveSanction::Instrumentation::Notifications.new
end

ActiveSupport::Notifications.subscribe("screen.active_sanction") do |event|
Rails.logger.info("screened in #{event.duration.round(1)}ms: #{event.payload[:results]} hits")
end

Names are <event>.active_sanction -- fetch.active_sanction, index.build.active_sanction, and so on -- which is the namespacing convention every ActiveSupport subscriber already expects, so subscribe(/\.active_sanction\z/) picks up all six.

It is an adapter, not a dependency

Nothing in this gem requires ActiveSupport, and this file does not either -- it names ::ActiveSupport::Notifications and never loads it. Zero required runtime dependencies is a promise this library keeps for the API container it is going to run in, and a notification adapter is not a reason to break it. Building one in a process that has not loaded ActiveSupport raises ConfigurationError rather than quietly instrumenting nothing, because a subscriber that is not recording is a dashboard that is wrong rather than missing.

Events arrive finished

publish rather than instrument: this library has already done the work and timed it, and re-wrapping a finished event in a block would put an ActiveSupport subscriber around a stage it cannot influence while reporting a duration measured somewhere else. Subscribers see a normal ActiveSupport::Notifications::Event with real start and finish times.

A stage that raised carries the two keys ActiveSupport's own subscribers look for -- :exception, the [class, message] pair, and :exception_object -- beside this library's :error, so a Rails host's existing error reporting sees it without being taught anything.

Constant Summary collapse

NAMESPACE =

The suffix every published name carries.

T.let("active_sanction", String)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(namespace: NAMESPACE, notifier: nil) ⇒ void

Parameters:

  • namespace (String) (defaults to: NAMESPACE) —

    the suffix published names carry. Change it only to keep two installations of this gem apart in one process.

  • notifier (T.untyped) (defaults to: nil) —

    defaults to ActiveSupport::Notifications, resolved now rather than per event so that a process without it fails here, at the line that configured it.

Raises:



70
71
72
73
74
75
76
77
# File 'lib/active_sanction/instrumentation/notifications.rb', line 70

def initialize(namespace: NAMESPACE, notifier: nil)
  @namespace = T.let(namespace.to_s, String)
  @notifier = T.let(notifier || default_notifier, T.untyped)
  return if @notifier.respond_to?(:publish)

  raise ConfigurationError, "an ActiveSupport::Notifications adapter needs a notifier answering " \
                            "#publish, got #{@notifier.class}"
end

Instance Attribute Details

#namespace ⇒ String (readonly)

Returns:

  • (String)


57
58
59
# File 'lib/active_sanction/instrumentation/notifications.rb', line 57

def namespace
  @namespace
end

#notifier ⇒ T.untyped (readonly)

Whatever the events are published through -- ActiveSupport::Notifications itself unless a host named its own notifier.

Returns:

  • (T.untyped)


62
63
64
# File 'lib/active_sanction/instrumentation/notifications.rb', line 62

def notifier
  @notifier
end

Instance Method Details

#call(event) ⇒ T.untyped

Publishes one finished event. Called by Instrumentation, which has already isolated it: an exception raised in here is reported and dropped rather than reaching the sync that emitted the event.

Parameters:

Returns:

  • (T.untyped)


83
84
85
86
# File 'lib/active_sanction/instrumentation/notifications.rb', line 83

def call(event)
  notifier.publish("#{event.name}.#{namespace}", event.started_at, event.finished_at, event_id,
                   payload_for(event))
end

#inspect ⇒ String

Returns:

  • (String)


89
# File 'lib/active_sanction/instrumentation/notifications.rb', line 89

def inspect = "#<#{self.class} #{notifier.class} *.#{namespace}>"