Class: ActionMCP::LogSubscriber
- Inherits:
-
ActiveSupport::LogSubscriber
- Object
- ActiveSupport::LogSubscriber
- ActionMCP::LogSubscriber
- Defined in:
- lib/action_mcp/log_subscriber.rb
Class Attribute Summary collapse
-
.custom_metrics ⇒ Object
Returns the value of attribute custom_metrics.
-
.formatters ⇒ Object
Returns the value of attribute formatters.
-
.metric_groups ⇒ Object
Returns the value of attribute metric_groups.
-
.subscribed_events ⇒ Object
Returns the value of attribute subscribed_events.
Class Method Summary collapse
-
.add_metric(name, value) ⇒ Object
Add a custom metric to be included in logs.
-
.define_metric_group(group_name, metrics) ⇒ Object
Define a group of related metrics.
-
.format_metrics ⇒ Object
Format metrics for display in logs.
-
.measure_metric(name) ⇒ Object
Measure execution time of a block and add as metric.
-
.register_formatter(metric_name, &block) ⇒ Object
Register a custom formatter for a specific metric.
-
.reset_metrics ⇒ Object
Reset all custom metrics.
- .reset_runtime ⇒ Object
-
.subscribe_event(pattern, metric_name, options = {}) ⇒ Object
Subscribe to a Rails event to capture metrics.
Instance Method Summary collapse
-
#process_action(event) ⇒ Object
Enhance process_action to include our custom metrics.
- #prompt_call(event) ⇒ Object
- #tool_call(event) ⇒ Object
Class Attribute Details
.custom_metrics ⇒ Object
Returns the value of attribute custom_metrics.
7 8 9 |
# File 'lib/action_mcp/log_subscriber.rb', line 7 def custom_metrics @custom_metrics end |
.formatters ⇒ Object
Returns the value of attribute formatters.
7 8 9 |
# File 'lib/action_mcp/log_subscriber.rb', line 7 def formatters @formatters end |
.metric_groups ⇒ Object
Returns the value of attribute metric_groups.
7 8 9 |
# File 'lib/action_mcp/log_subscriber.rb', line 7 def metric_groups @metric_groups end |
.subscribed_events ⇒ Object
Returns the value of attribute subscribed_events.
7 8 9 |
# File 'lib/action_mcp/log_subscriber.rb', line 7 def subscribed_events @subscribed_events end |
Class Method Details
.add_metric(name, value) ⇒ Object
Add a custom metric to be included in logs
35 36 37 38 |
# File 'lib/action_mcp/log_subscriber.rb', line 35 def self.add_metric(name, value) self.custom_metrics ||= {} self.custom_metrics[name] = value end |
.define_metric_group(group_name, metrics) ⇒ Object
Define a group of related metrics
173 174 175 176 |
# File 'lib/action_mcp/log_subscriber.rb', line 173 def self.define_metric_group(group_name, metrics) self.metric_groups ||= {} self.metric_groups[group_name] = metrics end |
.format_metrics ⇒ Object
Format metrics for display in logs
93 94 95 96 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/action_mcp/log_subscriber.rb', line 93 def self.format_metrics return nil if custom_metrics.nil? || custom_metrics.empty? # If grouping is enabled, organize metrics by groups if metric_groups.present? grouped_metrics = {} # Initialize groups with empty arrays metric_groups.each_key do |group_name| grouped_metrics[group_name] = [] end # Add "other" group for ungrouped metrics grouped_metrics[:other] = [] # Assign metrics to their groups custom_metrics.each do |key, value| group = nil # Find which group this metric belongs to metric_groups.each do |group_name, metrics| if metrics.include?(key) group = group_name break end end # Format the metric formatter = formatters&.dig(key) formatted_value = if formatter.respond_to?(:call) formatter.call(value) elsif value.is_a?(Float) format("%.1fms", value) else value.to_s end formatted_metric = "#{key}: #{formatted_value}" # Add to appropriate group (or "other") if group grouped_metrics[group] << formatted_metric else grouped_metrics[:other] << formatted_metric end end # Join metrics within groups, then join groups grouped_metrics.map do |_group, metrics| next if metrics.empty? metrics.join(" | ") end.compact.join(" | ") else # No grouping, just format all metrics custom_metrics.map do |key, value| formatter = formatters&.dig(key) formatted_value = if formatter.respond_to?(:call) formatter.call(value) elsif value.is_a?(Float) format("%.1fms", value) else value.to_s end "#{key}: #{formatted_value}" end.join(" | ") end end |
.measure_metric(name) ⇒ Object
Measure execution time of a block and add as metric
41 42 43 44 45 46 47 48 |
# File 'lib/action_mcp/log_subscriber.rb', line 41 def self.measure_metric(name) start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) result = yield duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000.0 add_metric(name, duration) result end |
.register_formatter(metric_name, &block) ⇒ Object
Register a custom formatter for a specific metric
165 166 167 168 |
# File 'lib/action_mcp/log_subscriber.rb', line 165 def self.register_formatter(metric_name, &block) self.formatters ||= {} self.formatters[metric_name] = block end |
.reset_metrics ⇒ Object
Reset all custom metrics
51 52 53 |
# File 'lib/action_mcp/log_subscriber.rb', line 51 def self.reset_metrics self.custom_metrics = nil end |
.reset_runtime ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/action_mcp/log_subscriber.rb', line 10 def self.reset_runtime # Get the combined runtime from both tool and prompt operations tool_rt = Thread.current[:mcp_tool_runtime] || 0 prompt_rt = Thread.current[:mcp_prompt_runtime] || 0 total_rt = tool_rt + prompt_rt # Reset both counters Thread.current[:mcp_tool_runtime] = 0 Thread.current[:mcp_prompt_runtime] = 0 # Return the total runtime total_rt end |
.subscribe_event(pattern, metric_name, options = {}) ⇒ Object
Subscribe to a Rails event to capture metrics
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/action_mcp/log_subscriber.rb', line 59 def self.subscribe_event(pattern, metric_name, = {}) self.subscribed_events ||= {} # Store subscription info self.subscribed_events[pattern] = { metric_name: metric_name, options: } # Create the actual subscription ActiveSupport::Notifications.subscribe(pattern) do |*args| event = ActiveSupport::Notifications::Event.new(*args) # Extract value based on options value = if [:duration] event.duration elsif [:extract_value].respond_to?(:call) [:extract_value].call(event) else 1 # Default to count end # Accumulate or set the metric if [:accumulate] self.custom_metrics ||= {} self.custom_metrics[metric_name] ||= 0 self.custom_metrics[metric_name] += value else add_metric(metric_name, value) end end end |
Instance Method Details
#process_action(event) ⇒ Object
Enhance process_action to include our custom metrics
179 180 181 182 183 184 185 186 187 |
# File 'lib/action_mcp/log_subscriber.rb', line 179 def process_action(event) return unless logger.info? return unless self.class.custom_metrics.present? metrics_msg = self.class.format_metrics event.payload[:message] = "#{event.payload[:message]} | #{metrics_msg}" if metrics_msg self.class.reset_metrics end |
#prompt_call(event) ⇒ Object
29 30 31 32 |
# File 'lib/action_mcp/log_subscriber.rb', line 29 def prompt_call(event) Thread.current[:mcp_prompt_runtime] ||= 0 Thread.current[:mcp_prompt_runtime] += event.duration end |
#tool_call(event) ⇒ Object
24 25 26 27 |
# File 'lib/action_mcp/log_subscriber.rb', line 24 def tool_call(event) Thread.current[:mcp_tool_runtime] ||= 0 Thread.current[:mcp_tool_runtime] += event.duration end |