Module: DebugAgent

Extended by:
ClassMethods
Defined in:
lib/debug_agent.rb,
lib/debug_agent/config.rb,
lib/debug_agent/engine.rb,
lib/debug_agent/version.rb,
lib/debug_agent/chat_page.rb,
lib/debug_agent/llm_client.rb,
lib/debug_agent/middleware.rb,
lib/debug_agent/chat_session.rb,
lib/debug_agent/inspectors/gc.rb,
lib/debug_agent/tool_registry.rb,
lib/debug_agent/inspectors/puma.rb,
lib/debug_agent/inspectors/cache.rb,
lib/debug_agent/inspectors/rails.rb,
lib/debug_agent/inspectors/redis.rb,
lib/debug_agent/inspectors/health.rb,
lib/debug_agent/inspectors/routes.rb,
lib/debug_agent/inspectors/system.rb,
lib/debug_agent/context_compressor.rb,
lib/debug_agent/inspectors/faraday.rb,
lib/debug_agent/inspectors/logging.rb,
lib/debug_agent/inspectors/metrics.rb,
lib/debug_agent/inspectors/runtime.rb,
lib/debug_agent/inspectors/sidekiq.rb,
lib/debug_agent/inspectors/threads.rb,
lib/debug_agent/inspectors/core_ext.rb,
lib/debug_agent/inspectors/security.rb,
lib/debug_agent/inspectors/scheduler.rb,
lib/debug_agent/inspectors/websocket.rb,
lib/debug_agent/inspectors/concurrent.rb,
lib/debug_agent/system_prompt_builder.rb,
lib/debug_agent/inspectors/http_client.rb,
lib/debug_agent/inspectors/http_tracker.rb,
lib/debug_agent/inspectors/object_space.rb,
lib/debug_agent/inspectors/process_info.rb,
lib/debug_agent/inspectors/error_tracking.rb,
lib/debug_agent/inspectors/active_record_stats.rb

Defined Under Namespace

Modules: ChatPage, ClassMethods, HttpRequestTracker, Middleware, MiddlewareCore, OutboundHttpTracker, ToolDefinitionExt Classes: ChatCallback, ChatSession, CompressionResult, Config, ContextCompressor, DebugEngine, EngineStreamHandler, Error, LLMClient, LLMConfig, RackMiddleware, RetriableError, SseCallback, StreamHandler, SystemPromptBuilder, ToolDefinition, ToolParam, ToolRegistry

Constant Summary collapse

PROCESS_START_TIME =

Process start time for uptime tracking

Time.now
VERSION =
'0.5.1'.freeze
REGISTRY =

Global registry singleton

ToolRegistry.new
MAX_LOGS =

Ring buffer of recent log entries and a registry of named loggers.

DebugAgent.register_logger(:app, Rails.logger)
100
LEVEL_MAP =
{
  'debug' => defined?(::Logger) ? ::Logger::DEBUG : 0,
  'info'  => defined?(::Logger) ? ::Logger::INFO  : 1,
  'warn'  => defined?(::Logger) ? ::Logger::WARN  : 2,
  'error' => defined?(::Logger) ? ::Logger::ERROR : 3,
  'fatal' => defined?(::Logger) ? ::Logger::FATAL : 4
}.freeze
CATEGORY_MAP =
{
  'gc'            => 'Memory & GC',
  'object_space'  => 'Memory & GC',
  'memory'        => 'Memory & GC',
  'object_count'  => 'Memory & GC',
  'allocations'   => 'Memory & GC',
  'force_gc'      => 'Memory & GC',
  'trigger_gc'    => 'Memory & GC',
  'process'       => 'Process Info',
  'cpu'           => 'Process Info',
  'uptime'        => 'Process Info',
  'thread'        => 'Threads',
  'system'        => 'System Info',
  'disk'          => 'System Info',
  'environment'   => 'System Info',
  'routes'        => 'Framework & Routes',
  'middleware'    => 'Framework & Routes',
  'runtime'       => 'Runtime Info',
  'recent'        => 'HTTP Requests',
  'slow'          => 'HTTP Requests',
  'error'         => 'HTTP Requests',
  'request'       => 'HTTP Requests',
  'gem'           => 'Dependencies',
  'module'        => 'Module Info',
}.freeze
MAX_REQUESTS =
500
MAX_ERRORS =
50

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from ClassMethods

register_tool, registry

Class Attribute Details

.appObject

Returns the value of attribute app.



49
50
51
# File 'lib/debug_agent.rb', line 49

def app
  @app
end

.ar_statsObject (readonly)

Returns the value of attribute ar_stats.



18
19
20
# File 'lib/debug_agent/inspectors/active_record_stats.rb', line 18

def ar_stats
  @ar_stats
end

.auth_configsObject (readonly)

Returns the value of attribute auth_configs.



13
14
15
# File 'lib/debug_agent/inspectors/security.rb', line 13

def auth_configs
  @auth_configs
end

.cachesObject (readonly)

Returns the value of attribute caches.



9
10
11
# File 'lib/debug_agent/inspectors/cache.rb', line 9

def caches
  @caches
end

.concurrent_promisesObject (readonly)

Returns the value of attribute concurrent_promises.



9
10
11
# File 'lib/debug_agent/inspectors/concurrent.rb', line 9

def concurrent_promises
  @concurrent_promises
end

.error_bufferObject (readonly)

Returns the value of attribute error_buffer.



10
11
12
# File 'lib/debug_agent/inspectors/error_tracking.rb', line 10

def error_buffer
  @error_buffer
end

.faraday_connectionsObject (readonly)

Returns the value of attribute faraday_connections.



8
9
10
# File 'lib/debug_agent/inspectors/faraday.rb', line 8

def faraday_connections
  @faraday_connections
end

.health_checksObject (readonly)

Returns the value of attribute health_checks.



9
10
11
# File 'lib/debug_agent/inspectors/health.rb', line 9

def health_checks
  @health_checks
end

.job_historyObject (readonly)

Returns the value of attribute job_history.



12
13
14
# File 'lib/debug_agent/inspectors/scheduler.rb', line 12

def job_history
  @job_history
end

.loggersObject (readonly)

Returns the value of attribute loggers.



16
17
18
# File 'lib/debug_agent/inspectors/logging.rb', line 16

def loggers
  @loggers
end

.outbound_statsObject (readonly)

Returns the value of attribute outbound_stats.



12
13
14
# File 'lib/debug_agent/inspectors/http_client.rb', line 12

def outbound_stats
  @outbound_stats
end

.redis_clientsObject (readonly)

Returns the value of attribute redis_clients.



9
10
11
# File 'lib/debug_agent/inspectors/redis.rb', line 9

def redis_clients
  @redis_clients
end

.scheduled_jobsObject (readonly)

Returns the value of attribute scheduled_jobs.



12
13
14
# File 'lib/debug_agent/inspectors/scheduler.rb', line 12

def scheduled_jobs
  @scheduled_jobs
end

.session_storesObject (readonly)

Returns the value of attribute session_stores.



13
14
15
# File 'lib/debug_agent/inspectors/security.rb', line 13

def session_stores
  @session_stores
end

.sidekiq_queuesObject (readonly)

Returns the value of attribute sidekiq_queues.



9
10
11
# File 'lib/debug_agent/inspectors/sidekiq.rb', line 9

def sidekiq_queues
  @sidekiq_queues
end

.ws_connectionsObject (readonly)

Returns the value of attribute ws_connections.



15
16
17
# File 'lib/debug_agent/inspectors/websocket.rb', line 15

def ws_connections
  @ws_connections
end

.ws_message_logObject (readonly)

Returns the value of attribute ws_message_log.



15
16
17
# File 'lib/debug_agent/inspectors/websocket.rb', line 15

def ws_message_log
  @ws_message_log
end

.ws_serversObject (readonly)

Returns the value of attribute ws_servers.



15
16
17
# File 'lib/debug_agent/inspectors/websocket.rb', line 15

def ws_servers
  @ws_servers
end

Class Method Details

.ar_fingerprint(sql) ⇒ Object

Normalize SQL into a fingerprint for grouping / N+1 detection.



61
62
63
64
65
66
67
# File 'lib/debug_agent/inspectors/active_record_stats.rb', line 61

def ar_fingerprint(sql)
  sql
    .gsub(/'[^']*'/, '?')
    .gsub(/\b\d+\b/, '?')
    .gsub(/\s+/, ' ')
    .strip[0..200]
end

.best_effort_cache_stats(cache) ⇒ Object

Extract hit/miss and size where the cache exposes them (e.g. Dalli).



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/debug_agent/inspectors/cache.rb', line 45

def best_effort_cache_stats(cache)
  result = {}
  result[:size] = cache_size_for(cache)

  if cache.respond_to?(:stats)
    raw =
      begin
        cache.stats
      rescue
        {}
      end
    if raw.is_a?(Hash)
      result[:raw_stats] = raw
      hits = raw['get_hits'] || raw[:get_hits]
      misses = raw['get_misses'] || raw[:get_misses]
      if hits && misses
        total = hits.to_i + misses.to_i
        result[:hits] = hits.to_i
        result[:misses] = misses.to_i
        result[:hit_rate] = total.zero? ? nil : format('%.1f%%', hits.to_f / total * 100)
      end
    end
  end
  result
end

.cache_keys_for(cache) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/debug_agent/inspectors/cache.rb', line 71

def cache_keys_for(cache)
  if defined?(::ActiveSupport::Cache::MemoryStore) && cache.is_a?(::ActiveSupport::Cache::MemoryStore)
    (cache.instance_variable_get(:@data) || {}).keys
  elsif cache.is_a?(Hash)
    cache.keys
  elsif cache.respond_to?(:keys)
    begin
      cache.keys
    rescue
      []
    end
  else
    []
  end
end

.cache_size_for(cache) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/debug_agent/inspectors/cache.rb', line 87

def cache_size_for(cache)
  if defined?(::ActiveSupport::Cache::MemoryStore) && cache.is_a?(::ActiveSupport::Cache::MemoryStore)
    (cache.instance_variable_get(:@data) || {}).size
  elsif cache.respond_to?(:size)
    begin
      cache.size
    rescue
      nil
    end
  elsif cache.respond_to?(:length)
    begin
      cache.length
    rescue
      nil
    end
  end
end

.cache_stats_for(cache) ⇒ Object

Introspect a cache object, supporting ActiveSupport::Cache::MemoryStore, generic ActiveSupport::Cache::Store, plain Hashes, and custom caches.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/debug_agent/inspectors/cache.rb', line 17

def cache_stats_for(cache)
  if defined?(::ActiveSupport::Cache::MemoryStore) && cache.is_a?(::ActiveSupport::Cache::MemoryStore)
    data = cache.instance_variable_get(:@data) || {}
    key_access = cache.instance_variable_get(:@key_access) || {}
    {
      type: 'ActiveSupport::Cache::MemoryStore',
      size: data.size,
      max_size: cache.instance_variable_get(:@max_size),
      tracked_keys: key_access.size,
      sample_keys: data.keys.first(50)
    }
  elsif defined?(::ActiveSupport::Cache::Store) && defined?(::ActiveSupport::Cache) &&
        cache.is_a?(::ActiveSupport::Cache::Store)
    stats = best_effort_cache_stats(cache)
    stats.merge(type: cache.class.name)
  elsif cache.is_a?(Hash)
    {
      type: 'Hash',
      size: cache.size,
      sample_keys: cache.keys.first(50)
    }
  else
    stats = best_effort_cache_stats(cache)
    stats.merge(type: cache.class.name)
  end
end

.capture_log(severity, args) ⇒ Object

Invoked by the wrapped Logger#add to push an entry into the ring buffer.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/debug_agent/inspectors/logging.rb', line 23

def capture_log(severity, args)
  args = args.is_a?(Array) ? args : [args]
  # Logger passes (message, progname); pick the meaningful value.
  msg = args.compact.first
  entry = {
    timestamp: Time.now.iso8601,
    severity: severity_label(severity),
    message: msg.respond_to?(:to_str) ? msg.to_s : msg.inspect
  }
  @log_buffer_lock.synchronize do
    @log_buffer << entry
    @log_buffer.shift if @log_buffer.size > MAX_LOGS
  end
end

.executor_info(executor) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/debug_agent/inspectors/concurrent.rb', line 15

def executor_info(executor)
  return nil unless executor
  info = { class: executor.class.name }
  %i[running?].each do |m|
    info[m] = executor.public_send(m) if executor.respond_to?(m)
  end
  %i[length largest_length queue_length scheduled_task_count completed_task_count
     max_threads min_threads idletime max_queue].each do |m|
    info[m] = (executor.public_send(m) rescue nil) if executor.respond_to?(m)
  end
  info
rescue => e
  { class: executor&.class&.name, error: e.message }
end

.faraday_conn_info(name, conn) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/debug_agent/inspectors/faraday.rb', line 14

def faraday_conn_info(name, conn)
  info = { name: name, class: conn.class.name }

  if conn.respond_to?(:url_prefix)
    info[:url] = conn.url_prefix.to_s
    info[:host] = conn.url_prefix.host
    info[:port] = conn.url_prefix.port
    info[:scheme] = conn.url_prefix.scheme
  end

  builder = conn.respond_to?(:builder) ? conn.builder : nil
  if builder
    handlers =
      if builder.respond_to?(:handlers)
        builder.handlers.map { |h| faraday_handler_name(h) }
      else
        []
      end
    info[:middleware] = handlers

    adapter =
      if builder.respond_to?(:adapter)
        faraday_handler_name(builder.adapter)
      end
    info[:adapter] = adapter if adapter
  end

  info[:headers] = conn.headers.to_h if conn.respond_to?(:headers) && conn.headers.respond_to?(:to_h)

  info
rescue => e
  { name: name, error: e.message }
end

.faraday_handler_name(handler) ⇒ Object



48
49
50
51
52
# File 'lib/debug_agent/inspectors/faraday.rb', line 48

def faraday_handler_name(handler)
  return handler.name if handler.respond_to?(:name)
  return handler.class.name if handler.respond_to?(:class)
  handler.to_s
end

.format_uptime(seconds) ⇒ Object

Helper method for formatting uptime



48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/debug_agent/inspectors/process_info.rb', line 48

def self.format_uptime(seconds)
  days = (seconds / 86400).to_i
  hours = ((seconds % 86400) / 3600).to_i
  minutes = ((seconds % 3600) / 60).to_i
  secs = (seconds % 60).to_i

  parts = []
  parts << "#{days}d" if days > 0
  parts << "#{hours}h" if hours > 0 || days > 0
  parts << "#{minutes}m" if minutes > 0 || hours > 0 || days > 0
  parts << "#{secs}s"
  parts.join(' ')
end

.install_ar_trackerObject

Subscribe to sql.active_record notifications (idempotent).



21
22
23
24
25
26
27
28
29
30
# File 'lib/debug_agent/inspectors/active_record_stats.rb', line 21

def install_ar_tracker
  return true if @ar_tracker_installed
  return false unless defined?(::ActiveSupport::Notifications)

  ::ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, started, finished, _id, payload|
    DebugAgent.record_ar_query(started, finished, payload)
  end
  @ar_tracker_installed = true
  true
end

.install_log_captureObject

Wrap the standard Logger#add / << so all log output flows into the ring buffer. Only wraps once — guarded by checking for the aliased method.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/debug_agent/inspectors/logging.rb', line 40

def install_log_capture
  return false unless defined?(::Logger)
  return true if ::Logger.method_defined?(:_original_add)

  ::Logger.class_eval do
    alias_method :_original_add, :add
    alias_method :_original_lshift, :<<

    def add(severity, *args, &block)
      if block
        msg = args[0]
        msg = block.call if msg.nil?
        DebugAgent.capture_log(severity, [msg]) rescue nil
        _original_add(severity, msg, *args[1..-1])
      else
        DebugAgent.capture_log(severity, args) rescue nil
        _original_add(severity, *args)
      end
    end

    def <<(msg)
      DebugAgent.capture_log(nil, [msg]) rescue nil
      _original_lshift(msg)
    end
  end
  true
end

.install_outbound_trackerObject

Wrap Net::HTTP once to capture outbound request metrics.



53
54
55
56
57
58
59
# File 'lib/debug_agent/inspectors/http_client.rb', line 53

def install_outbound_tracker
  return false unless defined?(::Net::HTTP)
  return true if ::Net::HTTP.include?(OutboundHttpTracker)

  ::Net::HTTP.prepend(OutboundHttpTracker)
  true
end

.introspect_session_store(store_name, store) ⇒ Object

— Helpers —



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/debug_agent/inspectors/security.rb', line 152

def self.introspect_session_store(store_name, store)
  info = { name: store_name, type: store.class.name }

  # Rack::Session::Abstract::SessionHash or similar
  if store.respond_to?(:to_hash)
    begin
      data = store.to_hash
      info[:session_count] = data.size
      info[:keys] = data.keys.first(50)
      info[:has_user] = data.key?('user_id') || data.key?(:user_id) || data.key?('user')
    rescue
      info[:session_count] = 'unable to read'
    end
  end

  # ActiveRecord::SessionStore
  if store.respond_to?(:all)
    begin
      sessions = store.all.to_a
      info[:session_count] = sessions.size
      info[:sessions] = sessions.first(20).map do |sess|
        sess_data = begin
          sess.respond_to?(:data) ? sess.data.keys : []
        rescue
          []
        end
        {
          session_id: sess.respond_to?(:session_id) ? sess.session_id : sess.id,
          data: sess_data,
          created_at: sess.respond_to?(:created_at) ? sess.created_at : nil,
          updated_at: sess.respond_to?(:updated_at) ? sess.updated_at : nil
        }
      end
    rescue
      info[:session_count] = 'unable to read'
    end
  end

  # Generic session store with session_id method
  if store.respond_to?(:session_id)
    begin
      info[:session_id] = store.session_id
    rescue
      # ignore
    end
  end

  info
end

.log_ws_message(conn_id, direction, data, size = nil) ⇒ Object

Log a WebSocket message



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/debug_agent/inspectors/websocket.rb', line 44

def log_ws_message(conn_id, direction, data, size = nil)
  @ws_lock.synchronize do
    @ws_message_log << {
      timestamp: Time.now.iso8601,
      connection_id: conn_id,
      direction: direction, # 'sent' or 'received'
      size: size || data.to_s.bytesize,
      preview: data.to_s[0...200]
    }
    @ws_message_log.shift if @ws_message_log.size > 200

    conn = @ws_connections.find { |c| c[:id] == conn_id }
    if conn
      if direction == 'sent'
        conn[:messages_sent] += 1
      else
        conn[:messages_received] += 1
      end
    end
  end
end

.prometheus_metric_value(metric) ⇒ Object

Safely read a metric’s value(s). Different metric types return different shapes from #get.



14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/debug_agent/inspectors/metrics.rb', line 14

def prometheus_metric_value(metric)
  begin
    value = metric.get({})
    # Counter/Gauge return a Hash of {labels => value}; unwrap the unlabeled value.
    if value.is_a?(Hash) && value.size == 1 && value.key?({})
      value[{}]
    else
      value
    end
  rescue => e
    { error: e.message }
  end
end

.prometheus_registryObject

Resolve the Prometheus registry to inspect.



7
8
9
10
# File 'lib/debug_agent/inspectors/metrics.rb', line 7

def prometheus_registry
  return nil unless defined?(::Prometheus) && defined?(::Prometheus::Client)
  ::Prometheus::Client.respond_to?(:registry) ? ::Prometheus::Client.registry : nil
end

.promise_info(name, promise) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/debug_agent/inspectors/concurrent.rb', line 30

def promise_info(name, promise)
  info = { name: name, class: promise.class.name }
  info[:state] = promise.state if promise.respond_to?(:state)
  if promise.respond_to?(:fulfilled?)
    info[:fulfilled] = promise.fulfilled?
    info[:rejected] = promise.rejected? if promise.respond_to?(:rejected?)
    info[:pending] = promise.pending? if promise.respond_to?(:pending?)
  end
  if promise.respond_to?(:reason)
    reason = promise.reason
    info[:reason] = reason.is_a?(Exception) ? reason.message : reason.inspect unless reason.nil?
  end
  info
rescue => e
  { name: name, error: e.message }
end

.record_ar_query(started, finished, payload) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/debug_agent/inspectors/active_record_stats.rb', line 32

def record_ar_query(started, finished, payload)
  duration_ms = ((finished - started) * 1000.0)
  sql = payload[:sql].to_s.strip
  return if sql.empty?

  fp = ar_fingerprint(sql)
  name = payload[:name]

  @ar_lock.synchronize do
    s = @ar_stats
    s[:total_queries] += 1
    s[:total_time_ms] += duration_ms
    next unless fp
    s[:query_counts][fp] += 1
    s[:query_times][fp] += duration_ms
    if duration_ms >= s[:slow_threshold_ms] && s[:slow_queries].size < 200
      s[:slow_queries] << {
        sql: sql[0..500],
        name: name,
        duration_ms: duration_ms.round(2),
        timestamp: finished.to_s
      }
    end
  end
rescue
  nil
end

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



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/debug_agent/inspectors/error_tracking.rb', line 12

def record_error(error, context = {})
  @error_buffer_lock.synchronize do
    @error_buffer << {
      timestamp: Time.now.iso8601,
      class: error.class.name,
      message: error.message,
      backtrace: (error.backtrace || []).first(10),
      context: context
    }
    @error_buffer.shift if @error_buffer.size > MAX_ERRORS
  end
end

.record_job_execution(name, duration_ms, success: true, error: nil) ⇒ Object

Record a job execution for history tracking.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/debug_agent/inspectors/scheduler.rb', line 28

def record_job_execution(name, duration_ms, success: true, error: nil)
  @scheduler_lock.synchronize do
    history = (@job_history[name.to_s] ||= [])
    history << {
      timestamp: Time.now.iso8601,
      duration_ms: duration_ms.round(2),
      success: success,
      error: error
    }
    history.shift if history.size > 100

    # Update last_run on the job itself
    if @scheduled_jobs[name.to_s]
      @scheduled_jobs[name.to_s][:last_run] = Time.now
    end
  end
end

.record_outbound(http, req, latency_ms, error) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/debug_agent/inspectors/http_client.rb', line 14

def record_outbound(http, req, latency_ms, error)
  @outbound_lock.synchronize do
    s = @outbound_stats
    s[:total] += 1
    s[:latencies] << latency_ms
    s[:latencies].shift if s[:latencies].size > 1000

    host_key = "#{http.address}:#{http.port}"
    h = (s[:hosts][host_key] ||= { count: 0, latencies: [], errors: 0 })
    h[:count] += 1
    h[:latencies] << latency_ms
    h[:latencies].shift if h[:latencies].size > 200
    if error
      s[:errors] += 1
      h[:errors] += 1
    end
  end
end

.register_auth_config(name, config) ⇒ Object



15
16
17
# File 'lib/debug_agent/inspectors/security.rb', line 15

def register_auth_config(name, config)
  @auth_configs[name.to_s] = config
end

.register_cache(name, cache) ⇒ Object



11
12
13
# File 'lib/debug_agent/inspectors/cache.rb', line 11

def register_cache(name, cache)
  @caches[name.to_s] = cache
end

.register_concurrent(name, promise) ⇒ Object



11
12
13
# File 'lib/debug_agent/inspectors/concurrent.rb', line 11

def register_concurrent(name, promise)
  @concurrent_promises[name.to_s] = promise
end

.register_faraday(name, conn) ⇒ Object



10
11
12
# File 'lib/debug_agent/inspectors/faraday.rb', line 10

def register_faraday(name, conn)
  @faraday_connections[name.to_s] = conn
end

.register_health_check(name, &block) ⇒ Object



11
12
13
# File 'lib/debug_agent/inspectors/health.rb', line 11

def register_health_check(name, &block)
  @health_checks[name.to_s] = block
end

.register_logger(name, logger) ⇒ Object



18
19
20
# File 'lib/debug_agent/inspectors/logging.rb', line 18

def register_logger(name, logger)
  @loggers[name.to_s] = logger
end

.register_redis_client(name, client) ⇒ Object



11
12
13
# File 'lib/debug_agent/inspectors/redis.rb', line 11

def register_redis_client(name, client)
  @redis_clients[name.to_s] = client
end

.register_scheduled_job(name, schedule, **opts) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/debug_agent/inspectors/scheduler.rb', line 14

def register_scheduled_job(name, schedule, **opts)
  @scheduled_jobs[name.to_s] = {
    schedule: schedule,
    name: name.to_s,
    class: opts[:class] || name.to_s,
    queue: opts[:queue],
    enabled: opts.key?(:enabled) ? opts[:enabled] : true,
    last_run: opts[:last_run],
    next_run: opts[:next_run],
    registered_at: Time.now
  }
end

.register_session_store(name, store) ⇒ Object



19
20
21
# File 'lib/debug_agent/inspectors/security.rb', line 19

def register_session_store(name, store)
  @session_stores[name.to_s] = store
end

.register_sidekiq_queue(name, queue) ⇒ Object



11
12
13
# File 'lib/debug_agent/inspectors/sidekiq.rb', line 11

def register_sidekiq_queue(name, queue)
  @sidekiq_queues[name.to_s] = queue
end

.register_ws_server(name, server) ⇒ Object



17
18
19
# File 'lib/debug_agent/inspectors/websocket.rb', line 17

def register_ws_server(name, server)
  @ws_servers[name.to_s] = server
end

.severity_label(severity) ⇒ Object

Map a Logger severity integer to a human-readable label.



69
70
71
72
73
# File 'lib/debug_agent/inspectors/logging.rb', line 69

def severity_label(severity)
  labels = %w[DEBUG INFO WARN ERROR FATAL ANY]
  idx = severity.is_a?(Integer) ? severity : (defined?(::Logger) ? ::Logger::UNKNOWN : 5)
  labels[idx] || 'UNKNOWN'
end

.track_http_connect(http) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/debug_agent/inspectors/http_client.rb', line 33

def track_http_connect(http)
  @outbound_lock.synchronize do
    @http_connections[http.object_id] = {
      host: http.address,
      port: http.port,
      use_ssl: http.use_ssl?,
      started_at: Time.now.iso8601,
      active: true
    }
  end
end

.track_http_disconnect(http) ⇒ Object



45
46
47
48
49
50
# File 'lib/debug_agent/inspectors/http_client.rb', line 45

def track_http_disconnect(http)
  @outbound_lock.synchronize do
    conn = @http_connections[http.object_id]
    conn[:active] = false if conn
  end
end

.track_ws_connection(conn_id, remote_addr, channel = nil) ⇒ Object

Track a WebSocket connection



22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/debug_agent/inspectors/websocket.rb', line 22

def track_ws_connection(conn_id, remote_addr, channel = nil)
  @ws_lock.synchronize do
    @ws_connections << {
      id: conn_id,
      remote_addr: remote_addr,
      channel: channel,
      connected_since: Time.now.iso8601,
      messages_sent: 0,
      messages_received: 0
    }
  end
  conn_id
end

.untrack_ws_connection(conn_id) ⇒ Object

Remove a tracked WebSocket connection



37
38
39
40
41
# File 'lib/debug_agent/inspectors/websocket.rb', line 37

def untrack_ws_connection(conn_id)
  @ws_lock.synchronize do
    @ws_connections.reject! { |c| c[:id] == conn_id }
  end
end

.with_redis(name = nil) ⇒ Object

Resolve a registered Redis client. Accepts a bare Redis object or a ConnectionPool (redis-rb ships ConnectionPool support). We yield a usable connection object to the block.



19
20
21
22
23
24
25
26
27
28
29
# File 'lib/debug_agent/inspectors/redis.rb', line 19

def self.with_redis(name = nil)
  name, client = if name
    [name.to_s, redis_clients[name.to_s]]
  else
    redis_clients.first
  end

  return [nil, nil] unless client

  [name, client]
end