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
-
.app ⇒ Object
Returns the value of attribute app.
-
.ar_stats ⇒ Object
readonly
Returns the value of attribute ar_stats.
-
.auth_configs ⇒ Object
readonly
Returns the value of attribute auth_configs.
-
.caches ⇒ Object
readonly
Returns the value of attribute caches.
-
.concurrent_promises ⇒ Object
readonly
Returns the value of attribute concurrent_promises.
-
.error_buffer ⇒ Object
readonly
Returns the value of attribute error_buffer.
-
.faraday_connections ⇒ Object
readonly
Returns the value of attribute faraday_connections.
-
.health_checks ⇒ Object
readonly
Returns the value of attribute health_checks.
-
.job_history ⇒ Object
readonly
Returns the value of attribute job_history.
-
.loggers ⇒ Object
readonly
Returns the value of attribute loggers.
-
.outbound_stats ⇒ Object
readonly
Returns the value of attribute outbound_stats.
-
.redis_clients ⇒ Object
readonly
Returns the value of attribute redis_clients.
-
.scheduled_jobs ⇒ Object
readonly
Returns the value of attribute scheduled_jobs.
-
.session_stores ⇒ Object
readonly
Returns the value of attribute session_stores.
-
.sidekiq_queues ⇒ Object
readonly
Returns the value of attribute sidekiq_queues.
-
.ws_connections ⇒ Object
readonly
Returns the value of attribute ws_connections.
-
.ws_message_log ⇒ Object
readonly
Returns the value of attribute ws_message_log.
-
.ws_servers ⇒ Object
readonly
Returns the value of attribute ws_servers.
Class Method Summary collapse
-
.ar_fingerprint(sql) ⇒ Object
Normalize SQL into a fingerprint for grouping / N+1 detection.
-
.best_effort_cache_stats(cache) ⇒ Object
Extract hit/miss and size where the cache exposes them (e.g. Dalli).
- .cache_keys_for(cache) ⇒ Object
- .cache_size_for(cache) ⇒ Object
-
.cache_stats_for(cache) ⇒ Object
Introspect a cache object, supporting ActiveSupport::Cache::MemoryStore, generic ActiveSupport::Cache::Store, plain Hashes, and custom caches.
-
.capture_log(severity, args) ⇒ Object
Invoked by the wrapped Logger#add to push an entry into the ring buffer.
- .executor_info(executor) ⇒ Object
- .faraday_conn_info(name, conn) ⇒ Object
- .faraday_handler_name(handler) ⇒ Object
-
.format_uptime(seconds) ⇒ Object
Helper method for formatting uptime.
-
.install_ar_tracker ⇒ Object
Subscribe to sql.active_record notifications (idempotent).
-
.install_log_capture ⇒ Object
Wrap the standard Logger#add / << so all log output flows into the ring buffer.
-
.install_outbound_tracker ⇒ Object
Wrap Net::HTTP once to capture outbound request metrics.
-
.introspect_session_store(store_name, store) ⇒ Object
— Helpers —.
-
.log_ws_message(conn_id, direction, data, size = nil) ⇒ Object
Log a WebSocket message.
-
.prometheus_metric_value(metric) ⇒ Object
Safely read a metric’s value(s).
-
.prometheus_registry ⇒ Object
Resolve the Prometheus registry to inspect.
- .promise_info(name, promise) ⇒ Object
- .record_ar_query(started, finished, payload) ⇒ Object
- .record_error(error, context = {}) ⇒ Object
-
.record_job_execution(name, duration_ms, success: true, error: nil) ⇒ Object
Record a job execution for history tracking.
- .record_outbound(http, req, latency_ms, error) ⇒ Object
- .register_auth_config(name, config) ⇒ Object
- .register_cache(name, cache) ⇒ Object
- .register_concurrent(name, promise) ⇒ Object
- .register_faraday(name, conn) ⇒ Object
- .register_health_check(name, &block) ⇒ Object
- .register_logger(name, logger) ⇒ Object
- .register_redis_client(name, client) ⇒ Object
- .register_scheduled_job(name, schedule, **opts) ⇒ Object
- .register_session_store(name, store) ⇒ Object
- .register_sidekiq_queue(name, queue) ⇒ Object
- .register_ws_server(name, server) ⇒ Object
-
.severity_label(severity) ⇒ Object
Map a Logger severity integer to a human-readable label.
- .track_http_connect(http) ⇒ Object
- .track_http_disconnect(http) ⇒ Object
-
.track_ws_connection(conn_id, remote_addr, channel = nil) ⇒ Object
Track a WebSocket connection.
-
.untrack_ws_connection(conn_id) ⇒ Object
Remove a tracked WebSocket connection.
-
.with_redis(name = nil) ⇒ Object
Resolve a registered Redis client.
Methods included from ClassMethods
Class Attribute Details
.app ⇒ Object
Returns the value of attribute app.
49 50 51 |
# File 'lib/debug_agent.rb', line 49 def app @app end |
.ar_stats ⇒ Object (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_configs ⇒ Object (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 |
.caches ⇒ Object (readonly)
Returns the value of attribute caches.
9 10 11 |
# File 'lib/debug_agent/inspectors/cache.rb', line 9 def caches @caches end |
.concurrent_promises ⇒ Object (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_buffer ⇒ Object (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_connections ⇒ Object (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_checks ⇒ Object (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_history ⇒ Object (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 |
.loggers ⇒ Object (readonly)
Returns the value of attribute loggers.
16 17 18 |
# File 'lib/debug_agent/inspectors/logging.rb', line 16 def loggers @loggers end |
.outbound_stats ⇒ Object (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_clients ⇒ Object (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_jobs ⇒ Object (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_stores ⇒ Object (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_queues ⇒ Object (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_connections ⇒ Object (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_log ⇒ Object (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 end |
.ws_servers ⇒ Object (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. } 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. } 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_tracker ⇒ Object
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_capture ⇒ Object
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_tracker ⇒ Object
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 (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. } end end |
.prometheus_registry ⇒ Object
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. : reason.inspect unless reason.nil? end info rescue => e { name: name, error: e. } 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., 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 |