apm_bro 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ require "rack"
4
4
 
5
5
  module ApmBro
6
6
  class ErrorMiddleware
7
- EVENT_NAME = "exception.uncaught".freeze
7
+ EVENT_NAME = "exception.uncaught"
8
8
 
9
9
  def initialize(app, client: Client.new)
10
10
  @app = app
@@ -20,7 +20,7 @@ module ApmBro
20
20
  event_name = exception.class.name.to_s
21
21
  event_name = EVENT_NAME if event_name.empty?
22
22
  @client.post_metric(event_name: event_name, payload: payload)
23
- rescue StandardError
23
+ rescue
24
24
  # Never let APM reporting interfere with the host app
25
25
  end
26
26
  raise
@@ -36,14 +36,13 @@ module ApmBro
36
36
  message: truncate(exception.message.to_s, 1000),
37
37
  backtrace: safe_backtrace(exception),
38
38
  occurred_at: Time.now.utc.to_i,
39
- user_email: extract_user_email_from_env(env),
40
39
  rack:
41
40
  {
42
- method: (req&.request_method),
43
- path: (req&.path),
44
- fullpath: (req&.fullpath),
45
- ip: (req&.ip),
46
- user_agent: truncate((req&.user_agent).to_s, 200),
41
+ method: req&.request_method,
42
+ path: req&.path,
43
+ fullpath: req&.fullpath,
44
+ ip: req&.ip,
45
+ user_agent: truncate(req&.user_agent.to_s, 200),
47
46
  params: safe_params(req),
48
47
  request_id: env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"],
49
48
  referer: truncate(env["HTTP_REFERER"].to_s, 500),
@@ -58,13 +57,13 @@ module ApmBro
58
57
 
59
58
  def rack_request(env)
60
59
  ::Rack::Request.new(env)
61
- rescue StandardError
60
+ rescue
62
61
  nil
63
62
  end
64
63
 
65
64
  def safe_backtrace(exception)
66
65
  Array(exception.backtrace).first(50)
67
- rescue StandardError
66
+ rescue
68
67
  []
69
68
  end
70
69
 
@@ -79,7 +78,7 @@ module ApmBro
79
78
  filtered.delete(k.to_sym)
80
79
  end
81
80
  JSON.parse(JSON.dump(filtered)) # ensure JSON-safe
82
- rescue StandardError
81
+ rescue
83
82
  {}
84
83
  end
85
84
 
@@ -94,33 +93,20 @@ module ApmBro
94
93
  else
95
94
  ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
96
95
  end
97
- rescue StandardError
96
+ rescue
98
97
  "development"
99
98
  end
100
99
 
101
100
  def safe_app_name
102
101
  if defined?(Rails) && Rails.respond_to?(:application)
103
- Rails.application.class.module_parent_name rescue ""
102
+ begin
103
+ Rails.application.class.module_parent_name
104
+ rescue
105
+ ""
106
+ end
104
107
  else
105
108
  ""
106
109
  end
107
110
  end
108
-
109
- def extract_user_email_from_env(env)
110
- return nil unless ApmBro.configuration.user_email_tracking_enabled
111
-
112
- # Try to get user email from various sources in the Rack environment
113
- request_data = {
114
- request: rack_request(env),
115
- session: env["rack.session"],
116
- params: env["action_dispatch.request.parameters"] || {}
117
- }
118
-
119
- ApmBro.configuration.extract_user_email(request_data)
120
- rescue StandardError
121
- nil
122
- end
123
111
  end
124
112
  end
125
-
126
-
@@ -5,12 +5,12 @@ require "net/http"
5
5
 
6
6
  module ApmBro
7
7
  module HttpInstrumentation
8
- EVENT_NAME = "outgoing.http".freeze
8
+ EVENT_NAME = "outgoing.http"
9
9
 
10
10
  def self.install!(client: Client.new)
11
11
  install_net_http!(client)
12
12
  install_typhoeus!(client) if defined?(::Typhoeus)
13
- rescue StandardError
13
+ rescue
14
14
  # Never raise from instrumentation install
15
15
  end
16
16
 
@@ -30,29 +30,33 @@ module ApmBro
30
30
  finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
31
31
  duration_ms = ((finish_time - start_time) * 1000.0).round(2)
32
32
  begin
33
- uri = URI.parse(req.uri ? req.uri.to_s : "http://#{@address}:#{@port}#{req.path}") rescue nil
33
+ uri = begin
34
+ URI.parse(req.uri ? req.uri.to_s : "http://#{@address}:#{@port}#{req.path}")
35
+ rescue
36
+ nil
37
+ end
34
38
 
35
39
  # Skip instrumentation for our own APM endpoint to prevent infinite loops,
36
40
  # but do NOT alter the original method's return value/control flow.
37
- skip_instrumentation = (uri && (uri.to_s.include?("localhost") || uri.to_s.include?("uptime.aberatii.com")))
41
+ skip_instrumentation = uri && (uri.to_s.include?("localhost") || uri.to_s.include?("uptime.aberatii.com"))
38
42
 
39
43
  unless skip_instrumentation
40
44
  payload = {
41
45
  library: "net_http",
42
46
  method: req.method,
43
- url: (uri && uri.to_s),
47
+ url: uri && uri.to_s,
44
48
  host: (uri && uri.host) || @address,
45
49
  path: (uri && uri.path) || req.path,
46
- status: (response && response.code.to_i),
50
+ status: response && response.code.to_i,
47
51
  duration_ms: duration_ms,
48
- exception: (error && error.class.name)
52
+ exception: error && error.class.name
49
53
  }
50
54
  # Accumulate per-request; only send with controller metric
51
55
  if Thread.current[:apm_bro_http_events]
52
56
  Thread.current[:apm_bro_http_events] << payload
53
57
  end
54
58
  end
55
- rescue StandardError
59
+ rescue
56
60
  end
57
61
  end
58
62
  end
@@ -73,18 +77,22 @@ module ApmBro
73
77
  finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
74
78
  duration_ms = ((finish_time - start_time) * 1000.0).round(2)
75
79
  begin
76
- req_url = respond_to?(:url) ? url : (respond_to?(:base_url) ? base_url : nil)
80
+ req_url = if respond_to?(:url)
81
+ url
82
+ else
83
+ (respond_to?(:base_url) ? base_url : nil)
84
+ end
77
85
 
78
86
  # Skip instrumentation for our own APM endpoint to prevent infinite loops,
79
87
  # but do NOT alter the original method's return value/control flow.
80
- skip_instrumentation = (req_url && req_url.include?("localhost:3100/apm/v1/metrics"))
88
+ skip_instrumentation = req_url && req_url.include?("localhost:3100/apm/v1/metrics")
81
89
 
82
90
  unless skip_instrumentation
83
91
  payload = {
84
92
  library: "typhoeus",
85
- method: respond_to?(:options) && options[:method] ? options[:method].to_s.upcase : nil,
93
+ method: (respond_to?(:options) && options[:method]) ? options[:method].to_s.upcase : nil,
86
94
  url: req_url,
87
- status: (response && response.code),
95
+ status: response && response.code,
88
96
  duration_ms: duration_ms
89
97
  }
90
98
  # Accumulate per-request; only send with controller metric
@@ -92,16 +100,14 @@ module ApmBro
92
100
  Thread.current[:apm_bro_http_events] << payload
93
101
  end
94
102
  end
95
- rescue StandardError
103
+ rescue
96
104
  end
97
105
  end
98
106
  end
99
107
  end
100
108
 
101
109
  ::Typhoeus::Request.prepend(mod) unless ::Typhoeus::Request.ancestors.include?(mod)
102
- rescue StandardError
110
+ rescue
103
111
  end
104
112
  end
105
113
  end
106
-
107
-
@@ -19,7 +19,7 @@ module ApmBro
19
19
  ApmBro::MemoryTrackingSubscriber.start_request_tracking
20
20
  end
21
21
  end
22
- rescue StandardError
22
+ rescue
23
23
  # Never raise from instrumentation install
24
24
  end
25
25
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/notifications"
3
+ begin
4
+ require "active_support/notifications"
5
+ rescue LoadError
6
+ # ActiveSupport not available
7
+ end
4
8
 
5
9
  module ApmBro
6
10
  class JobSubscriber
7
- JOB_EVENT_NAME = "perform.active_job".freeze
8
- JOB_EXCEPTION_EVENT_NAME = "exception.active_job".freeze
11
+ JOB_EVENT_NAME = "perform.active_job"
12
+ JOB_EXCEPTION_EVENT_NAME = "exception.active_job"
9
13
 
10
14
  def self.subscribe!(client: Client.new)
11
15
  # Track job execution
@@ -15,14 +19,14 @@ module ApmBro
15
19
  if ApmBro.configuration.excluded_job?(job_class_name)
16
20
  next
17
21
  end
18
- rescue StandardError
22
+ rescue
19
23
  end
20
24
 
21
25
  duration_ms = ((finished - started) * 1000.0).round(2)
22
-
26
+
23
27
  # Get SQL queries executed during this job
24
28
  sql_queries = ApmBro::SqlSubscriber.stop_request_tracking
25
-
29
+
26
30
  # Stop memory tracking and get collected memory data
27
31
  if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
28
32
  detailed_memory = ApmBro::MemoryTrackingSubscriber.stop_request_tracking
@@ -50,7 +54,7 @@ module ApmBro
50
54
  duration_seconds: lightweight_memory[:duration_seconds]
51
55
  }
52
56
  end
53
-
57
+
54
58
  payload = {
55
59
  job_class: data[:job].class.name,
56
60
  job_id: data[:job].job_id,
@@ -67,7 +71,7 @@ module ApmBro
67
71
  memory_performance: memory_performance,
68
72
  logs: ApmBro.logger.logs
69
73
  }
70
-
74
+
71
75
  client.post_metric(event_name: name, payload: payload)
72
76
  end
73
77
 
@@ -78,15 +82,15 @@ module ApmBro
78
82
  if ApmBro.configuration.excluded_job?(job_class_name)
79
83
  next
80
84
  end
81
- rescue StandardError
85
+ rescue
82
86
  end
83
87
 
84
88
  duration_ms = ((finished - started) * 1000.0).round(2)
85
89
  exception = data[:exception_object]
86
-
90
+
87
91
  # Get SQL queries executed during this job
88
92
  sql_queries = ApmBro::SqlSubscriber.stop_request_tracking
89
-
93
+
90
94
  # Stop memory tracking and get collected memory data
91
95
  if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
92
96
  detailed_memory = ApmBro::MemoryTrackingSubscriber.stop_request_tracking
@@ -114,7 +118,7 @@ module ApmBro
114
118
  duration_seconds: lightweight_memory[:duration_seconds]
115
119
  }
116
120
  end
117
-
121
+
118
122
  payload = {
119
123
  job_class: data[:job].class.name,
120
124
  job_id: data[:job].job_id,
@@ -134,11 +138,11 @@ module ApmBro
134
138
  memory_performance: memory_performance,
135
139
  logs: ApmBro.logger.logs
136
140
  }
137
-
141
+
138
142
  event_name = exception&.class&.name || "ActiveJob::Exception"
139
143
  client.post_metric(event_name: event_name, payload: payload, error: true)
140
144
  end
141
- rescue StandardError
145
+ rescue
142
146
  # Never raise from instrumentation install
143
147
  end
144
148
 
@@ -146,27 +150,31 @@ module ApmBro
146
150
 
147
151
  def self.safe_arguments(arguments)
148
152
  return [] unless arguments.is_a?(Array)
149
-
153
+
150
154
  # Limit and sanitize job arguments
151
155
  arguments.first(10).map do |arg|
152
156
  case arg
153
157
  when String
154
- arg.length > 200 ? arg[0, 200] + "..." : arg
158
+ (arg.length > 200) ? arg[0, 200] + "..." : arg
155
159
  when Hash
156
160
  # Filter sensitive keys and limit size
157
161
  filtered = arg.except(*%w[password token secret key])
158
- filtered.keys.size > 20 ? filtered.first(20).to_h : filtered
162
+ (filtered.keys.size > 20) ? filtered.first(20).to_h : filtered
159
163
  when Array
160
164
  arg.first(5)
161
165
  when ActiveRecord::Base
162
166
  # Handle ActiveRecord objects safely
163
- "#{arg.class.name}##{arg.id rescue 'unknown'}"
167
+ "#{arg.class.name}##{begin
168
+ arg.id
169
+ rescue
170
+ "unknown"
171
+ end}"
164
172
  else
165
173
  # Convert to string and truncate, but avoid object inspection
166
- arg.to_s.length > 200 ? arg.to_s[0, 200] + "..." : arg.to_s
174
+ (arg.to_s.length > 200) ? arg.to_s[0, 200] + "..." : arg.to_s
167
175
  end
168
176
  end
169
- rescue StandardError
177
+ rescue
170
178
  []
171
179
  end
172
180
 
@@ -176,13 +184,17 @@ module ApmBro
176
184
  else
177
185
  ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
178
186
  end
179
- rescue StandardError
187
+ rescue
180
188
  "development"
181
189
  end
182
190
 
183
191
  def self.safe_host
184
192
  if defined?(Rails) && Rails.respond_to?(:application)
185
- Rails.application.class.module_parent_name rescue ""
193
+ begin
194
+ Rails.application.class.module_parent_name
195
+ rescue
196
+ ""
197
+ end
186
198
  else
187
199
  ""
188
200
  end
@@ -191,12 +203,16 @@ module ApmBro
191
203
  def self.memory_usage_mb
192
204
  if defined?(GC) && GC.respond_to?(:stat)
193
205
  # Get memory usage in MB
194
- memory_kb = `ps -o rss= -p #{Process.pid}`.to_i rescue 0
206
+ memory_kb = begin
207
+ `ps -o rss= -p #{Process.pid}`.to_i
208
+ rescue
209
+ 0
210
+ end
195
211
  (memory_kb / 1024.0).round(2)
196
212
  else
197
213
  0
198
214
  end
199
- rescue StandardError
215
+ rescue
200
216
  0
201
217
  end
202
218
 
@@ -212,7 +228,7 @@ module ApmBro
212
228
  else
213
229
  {}
214
230
  end
215
- rescue StandardError
231
+ rescue
216
232
  {}
217
233
  end
218
234
  end
@@ -7,7 +7,7 @@ module ApmBro
7
7
 
8
8
  def self.start_request_tracking
9
9
  return unless ApmBro.configuration.memory_tracking_enabled
10
-
10
+
11
11
  # Only track essential metrics to minimize overhead
12
12
  Thread.current[THREAD_LOCAL_KEY] = {
13
13
  gc_before: lightweight_gc_stats,
@@ -19,13 +19,13 @@ module ApmBro
19
19
  def self.stop_request_tracking
20
20
  events = Thread.current[THREAD_LOCAL_KEY]
21
21
  Thread.current[THREAD_LOCAL_KEY] = nil
22
-
22
+
23
23
  return {} unless events
24
-
24
+
25
25
  # Calculate only essential metrics
26
26
  gc_after = lightweight_gc_stats
27
27
  memory_after = lightweight_memory_usage
28
-
28
+
29
29
  {
30
30
  memory_growth_mb: (memory_after - events[:memory_before]).round(2),
31
31
  gc_count_increase: (gc_after[:count] || 0) - (events[:gc_before][:count] || 0),
@@ -39,24 +39,24 @@ module ApmBro
39
39
  def self.lightweight_memory_usage
40
40
  # Use only GC stats for memory estimation (no system calls)
41
41
  return 0 unless defined?(GC) && GC.respond_to?(:stat)
42
-
42
+
43
43
  gc_stats = GC.stat
44
44
  heap_pages = gc_stats[:heap_allocated_pages] || 0
45
45
  # Rough estimation: 4KB per page
46
46
  (heap_pages * 4) / 1024.0 # Convert to MB
47
- rescue StandardError
47
+ rescue
48
48
  0
49
49
  end
50
50
 
51
51
  def self.lightweight_gc_stats
52
52
  return {} unless defined?(GC) && GC.respond_to?(:stat)
53
-
53
+
54
54
  stats = GC.stat
55
55
  {
56
56
  count: stats[:count] || 0,
57
57
  heap_allocated_pages: stats[:heap_allocated_pages] || 0
58
58
  }
59
- rescue StandardError
59
+ rescue
60
60
  {}
61
61
  end
62
62
  end
@@ -11,12 +11,12 @@ module ApmBro
11
11
  }.freeze
12
12
 
13
13
  # ANSI color codes
14
- COLOR_RESET = "\033[0m".freeze
15
- COLOR_DEBUG = "\033[36m".freeze # Cyan
16
- COLOR_INFO = "\033[32m".freeze # Green
17
- COLOR_WARN = "\033[33m".freeze # Yellow
18
- COLOR_ERROR = "\033[31m".freeze # Red
19
- COLOR_FATAL = "\033[35m".freeze # Magenta
14
+ COLOR_RESET = "\033[0m"
15
+ COLOR_DEBUG = "\033[36m" # Cyan
16
+ COLOR_INFO = "\033[32m" # Green
17
+ COLOR_WARN = "\033[33m" # Yellow
18
+ COLOR_ERROR = "\033[31m" # Red
19
+ COLOR_FATAL = "\033[35m" # Magenta
20
20
 
21
21
  def initialize
22
22
  @thread_logs_key = :apm_bro_logs
@@ -72,7 +72,7 @@ module ApmBro
72
72
 
73
73
  def print_log(severity, message, timestamp)
74
74
  formatted_message = format_log_message(severity, message, timestamp)
75
-
75
+
76
76
  if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
77
77
  # Use Rails logger if available (Rails handles its own color formatting)
78
78
  case severity
@@ -92,7 +92,7 @@ module ApmBro
92
92
  colored_message = format_log_message_with_color(severity, message, timestamp)
93
93
  $stdout.puts(colored_message)
94
94
  end
95
- rescue StandardError
95
+ rescue
96
96
  # Never let logging break the application
97
97
  $stdout.puts("[ApmBro] #{severity.to_s.upcase}: #{message}")
98
98
  end
@@ -125,4 +125,3 @@ module ApmBro
125
125
  end
126
126
  end
127
127
  end
128
-
@@ -3,17 +3,17 @@
3
3
  module ApmBro
4
4
  module MemoryHelpers
5
5
  # Helper methods for memory tracking and leak detection
6
-
6
+
7
7
  # Take a memory snapshot with a custom label
8
8
  def self.snapshot(label)
9
9
  ApmBro::MemoryTrackingSubscriber.take_memory_snapshot(label)
10
10
  end
11
-
11
+
12
12
  # Get current memory analysis
13
13
  def self.analyze_memory
14
14
  ApmBro::MemoryLeakDetector.get_memory_analysis
15
15
  end
16
-
16
+
17
17
  # Check for memory leaks
18
18
  def self.check_for_leaks
19
19
  analysis = analyze_memory
@@ -23,26 +23,26 @@ module ApmBro
23
23
  puts " - Growth: #{alert[:memory_growth_mb]}MB"
24
24
  puts " - Rate: #{alert[:growth_rate_mb_per_second]}MB/sec"
25
25
  puts " - Confidence: #{(alert[:confidence] * 100).round(1)}%"
26
- puts " - Recent controllers: #{alert[:recent_controllers].join(', ')}"
26
+ puts " - Recent controllers: #{alert[:recent_controllers].join(", ")}"
27
27
  end
28
28
  else
29
29
  puts "✅ No memory leaks detected"
30
30
  end
31
31
  analysis
32
32
  end
33
-
33
+
34
34
  # Get memory usage summary
35
35
  def self.memory_summary
36
36
  analysis = analyze_memory
37
37
  return "Insufficient data" if analysis[:status] == "insufficient_data"
38
-
38
+
39
39
  memory_stats = analysis[:memory_stats]
40
40
  puts "📊 Memory Summary:"
41
41
  puts " - Current: #{memory_stats[:mean]}MB (avg)"
42
42
  puts " - Range: #{memory_stats[:min]}MB - #{memory_stats[:max]}MB"
43
43
  puts " - Volatility: #{memory_stats[:std_dev]}MB"
44
44
  puts " - Samples: #{analysis[:sample_count]}"
45
-
45
+
46
46
  if analysis[:memory_trend][:slope] > 0
47
47
  puts " - Trend: ↗️ Growing at #{analysis[:memory_trend][:slope].round(3)}MB/sec"
48
48
  elsif analysis[:memory_trend][:slope] < 0
@@ -50,16 +50,16 @@ module ApmBro
50
50
  else
51
51
  puts " - Trend: ➡️ Stable"
52
52
  end
53
-
53
+
54
54
  analysis
55
55
  end
56
-
56
+
57
57
  # Monitor memory during a block execution
58
58
  def self.monitor_memory(label, &block)
59
59
  snapshot("before_#{label}")
60
60
  result = yield
61
61
  snapshot("after_#{label}")
62
-
62
+
63
63
  # Get the difference
64
64
  analysis = analyze_memory
65
65
  if analysis[:memory_stats]
@@ -67,15 +67,15 @@ module ApmBro
67
67
  puts " - Memory change: #{analysis[:memory_stats][:max] - analysis[:memory_stats][:min]}MB"
68
68
  puts " - Peak usage: #{analysis[:memory_stats][:max]}MB"
69
69
  end
70
-
70
+
71
71
  result
72
72
  end
73
-
73
+
74
74
  # Clear memory history (useful for testing)
75
75
  def self.clear_history
76
76
  ApmBro::MemoryLeakDetector.clear_history
77
77
  end
78
-
78
+
79
79
  # Get top memory allocating classes
80
80
  def self.top_allocators
81
81
  # This would need to be called from within a request context