Module: Asherah::Hooks

Defined in:
lib/asherah/hooks.rb

Overview

Log + metrics observability hooks.

The C ABI accepts a single function pointer per hook. We marshal each invocation into a Ruby Hash with symbol keys and yield it to the user-provided Proc. Exceptions raised by the user’s callback are caught and silently swallowed — propagating an exception across the FFI boundary would be undefined behavior (and since Rust 1.81 aborts the process).

The user’s callback may fire from any thread (Rust tokio worker threads, database driver threads). Implementations must be thread-safe and should not block; expensive forwarding (e.g. to a logging framework) should be done by enqueueing work onto a background thread you own.

Class Method Summary collapse

Class Method Details

.clear_log_hookObject

Remove the active log hook. Idempotent.



125
126
127
128
129
130
131
132
# File 'lib/asherah/hooks.rb', line 125

def clear_log_hook
  @mutex.synchronize do
    Native.asherah_clear_log_hook
    @log_callback = nil
    @log_trampoline = nil
  end
  nil
end

.clear_metrics_hookObject

Remove the active metrics hook and disable the metrics gate. Idempotent.



169
170
171
172
173
174
175
176
# File 'lib/asherah/hooks.rb', line 169

def clear_metrics_hook
  @mutex.synchronize do
    Native.asherah_clear_metrics_hook
    @metrics_callback = nil
    @metrics_trampoline = nil
  end
  nil
end

.set_log_hook(callback = nil, &block) ⇒ Object

Install a log hook. Three forms are supported:

  1. A stdlib Logger instance (or any Logger-compatible object that responds to #add, #debug, #info, #warn, #error):

    Asherah.set_log_hook(Logger.new($stdout))
    

    Each Asherah record is forwarded via Logger#add(severity, message, target) so the logger’s own filter rules and formatters apply. The target argument is passed as progname for routing.

  2. A Proc or block, yielded a Hash:

    Asherah.set_log_hook do |event|
      # event[:level]    => :debug | :info | :warn | :error  (symbol)
      # event[:severity] => Logger::DEBUG ...  (Logger::Severity int)
      # event[:target]   => "asherah::session"
      # event[:message]  => "..."
    end
    
  3. nil to clear (equivalent to clear_log_hook).

Replaces any previously installed log hook. Exceptions raised from the callback are caught and silently swallowed.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/asherah/hooks.rb', line 97

def set_log_hook(callback = nil, &block)
  callback ||= block
  if callback.nil?
    clear_log_hook
    return
  end
  callback = logger_to_callback(callback) if logger_like?(callback)
  unless callback.respond_to?(:call)
    raise ArgumentError, "log hook must be a Logger, Proc, or block"
  end

  @mutex.synchronize do
    @log_callback = callback
    # Allocate the trampoline OUTSIDE the user block so a slow user
    # callback can't hold the mutex.
    @log_trampoline = FFI::Function.new(
      :void,
      [:pointer, :int, :string, :string]
    ) do |_user_data, level, target, message|
      dispatch_log(level, target, message)
    end
    rc = Native.asherah_set_log_hook(@log_trampoline, FFI::Pointer::NULL)
    raise Error, "asherah_set_log_hook failed: rc=#{rc}" if rc != 0
  end
  nil
end

.set_metrics_hook(callback = nil, &block) ⇒ Object

Install a metrics hook. block receives a Hash:

# Timing event:
{ type: :encrypt|:decrypt|:store|:load, duration_ns: Integer, name: nil }
# Cache event:
{ type: :cache_hit|:cache_miss|:cache_stale, duration_ns: 0, name: String }

Installing a hook implicitly enables the global metrics gate; clearing it disables the gate. Replaces any previously installed metrics hook. Pass nil to clear.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/asherah/hooks.rb', line 144

def set_metrics_hook(callback = nil, &block)
  callback ||= block
  if callback.nil?
    clear_metrics_hook
    return
  end
  unless callback.respond_to?(:call)
    raise ArgumentError, "metrics hook must be callable (Proc or block)"
  end

  @mutex.synchronize do
    @metrics_callback = callback
    @metrics_trampoline = FFI::Function.new(
      :void,
      [:pointer, :int, :uint64, :string]
    ) do |_user_data, type, duration_ns, name|
      dispatch_metric(type, duration_ns, name)
    end
    rc = Native.asherah_set_metrics_hook(@metrics_trampoline, FFI::Pointer::NULL)
    raise Error, "asherah_set_metrics_hook failed: rc=#{rc}" if rc != 0
  end
  nil
end