Module: Familia::Instrumentation

Defined in:
lib/familia/instrumentation.rb

Overview

Provides instrumentation hooks for observability into Familia operations.

This module allows applications to register callbacks for various events in Familia's lifecycle, enabling audit trails, performance monitoring, and operational observability.

Examples:

Basic usage

Familia.on_command do |cmd, duration, context|
  puts "Redis command: #{cmd} (#{duration}μs)"
end

Audit trail for secrets service

Familia.on_lifecycle do |event, instance, context|
  case event
  when :save
    AuditLog.create!(
      event: 'secret_saved',
      secret_id: instance.identifier,
      user_id: RequestContext.current_user_id
    )
  end
end

Class Method Summary collapse

Class Method Details

.hooks?(type) ⇒ Boolean

Check whether any hooks are registered for the given event type.

Because Familia::Instrumentation is always loaded, a defined?(Familia::Instrumentation) check is always true and cannot be used to gate fast paths. This predicate answers the question that actually matters: is anyone listening? Middleware uses it to decide whether timing must be measured so that observability hooks keep firing at full rate even when command capture is disabled.

Examples:

Familia::Instrumentation.hooks?(:command) # => false
Familia.on_command { |cmd, dur, ctx| ... }
Familia::Instrumentation.hooks?(:command) # => true

Parameters:

  • type (Symbol)

    The hook type (:command, :pipeline, :lifecycle, :error)

Returns:

  • (Boolean)

    true if at least one hook is registered for the type



132
133
134
135
# File 'lib/familia/instrumentation.rb', line 132

def hooks?(type)
  hooks = @hooks[type]
  !hooks.nil? && !hooks.empty?
end

.notify_command(cmd, duration, context = {}) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Notify all registered command hooks.



139
140
141
142
143
144
145
# File 'lib/familia/instrumentation.rb', line 139

def notify_command(cmd, duration, context = {})
  @hooks[:command].each do |hook|
    hook.call(cmd, duration, context)
  rescue => e
    Familia.error("Instrumentation hook failed", error: e.message, hook_type: :command)
  end
end

.notify_error(error, context = {}) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Notify all registered error hooks.



169
170
171
172
173
174
175
# File 'lib/familia/instrumentation.rb', line 169

def notify_error(error, context = {})
  @hooks[:error].each do |hook|
    hook.call(error, context)
  rescue => e
    # Don't recurse on hook failures - just silently skip
  end
end

.notify_lifecycle(event, instance, context = {}) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Notify all registered lifecycle hooks.



159
160
161
162
163
164
165
# File 'lib/familia/instrumentation.rb', line 159

def notify_lifecycle(event, instance, context = {})
  @hooks[:lifecycle].each do |hook|
    hook.call(event, instance, context)
  rescue => e
    Familia.error("Instrumentation hook failed", error: e.message, hook_type: :lifecycle)
  end
end

.notify_pipeline(command_count, duration, context = {}) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Notify all registered pipeline hooks.



149
150
151
152
153
154
155
# File 'lib/familia/instrumentation.rb', line 149

def notify_pipeline(command_count, duration, context = {})
  @hooks[:pipeline].each do |hook|
    hook.call(command_count, duration, context)
  rescue => e
    Familia.error("Instrumentation hook failed", error: e.message, hook_type: :pipeline)
  end
end

.on_command {|cmd, duration, context| ... } ⇒ Object

Register a callback for Redis command execution.

Examples:

Familia.on_command do |cmd, duration, ctx|
  StatsD.timing("familia.command.#{cmd.downcase}", duration / 1000.0)
end

Yields:

  • (cmd, duration, context)

    Callback block

Yield Parameters:

  • cmd (String)

    The Redis command name (e.g., "SET", "ZADD")

  • duration (Integer)

    Command execution duration in microseconds

  • context (Hash)

    Additional context including:

    • :full_command [Array] Complete command with arguments
    • :db [Integer] Database number
    • :connection_id [String] Connection identifier


55
56
57
# File 'lib/familia/instrumentation.rb', line 55

def on_command(&block)
  @hooks[:command] << block
end

.on_error {|error, context| ... } ⇒ Object

Register a callback for error conditions.

Examples:

Familia.on_error do |error, ctx|
  Sentry.capture_exception(error, extra: ctx)
end

Yields:

  • (error, context)

    Callback block

Yield Parameters:

  • error (Exception)

    The error that occurred

  • context (Hash)

    Additional context including:

    • :operation [Symbol] Operation that failed (:serialization, etc.)
    • :field [Symbol] Field name (for serialization errors)
    • :object_class [String] Class name of the object


111
112
113
# File 'lib/familia/instrumentation.rb', line 111

def on_error(&block)
  @hooks[:error] << block
end

.on_lifecycle {|event, instance, context| ... } ⇒ Object

Register a callback for Horreum lifecycle events.

Examples:

Familia.on_lifecycle do |event, instance, ctx|
  case event
  when :destroy
    Rails.logger.info("Destroyed #{instance.class}:#{instance.identifier}")
  end
end

Yields:

  • (event, instance, context)

    Callback block

Yield Parameters:

  • event (Symbol)

    Lifecycle event (:initialize, :save, :destroy)

  • instance (Familia::Horreum)

    The object instance

  • context (Hash)

    Additional context including:

    • :duration [Integer] Operation duration in microseconds (for initialize/save)
    • :update_expiration [Boolean] Whether TTL was updated (for save)


93
94
95
# File 'lib/familia/instrumentation.rb', line 93

def on_lifecycle(&block)
  @hooks[:lifecycle] << block
end

.on_pipeline {|command_count, duration, context| ... } ⇒ Object

Register a callback for pipelined Redis operations.

Examples:

Familia.on_pipeline do |count, duration, ctx|
  StatsD.timing("familia.pipeline", duration / 1000.0)
  StatsD.gauge("familia.pipeline.commands", count)
end

Yields:

  • (command_count, duration, context)

    Callback block

Yield Parameters:

  • command_count (Integer)

    Number of commands in the pipeline

  • duration (Integer)

    Pipeline execution duration in microseconds

  • context (Hash)

    Additional context



72
73
74
# File 'lib/familia/instrumentation.rb', line 72

def on_pipeline(&block)
  @hooks[:pipeline] << block
end