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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d47832a9c956542a7b0e88d048d2f8760f7971df069a7e06fec92822ad79e67e
4
- data.tar.gz: 2f536dfe0c55b9b46fae2a4f5b77f5c873b39c59ef71ec06052951d02192a4c7
3
+ metadata.gz: 7bf4fc110108b143a2f2e608090ae8c75a5882f8634ef1c01d0ddb949d41e54a
4
+ data.tar.gz: 7c5bd6915f805032ca63b73ab9325ce64b262d8489c2842b45c28a02e84b961d
5
5
  SHA512:
6
- metadata.gz: 1e288923b2a58952d927cee3c85e3e242fe22baf79acccefd79ce0933a3b6c7db786faae7b4e96bd9f5687f9aed48c78e9bc493440809a65d27eaf9a31cb51d2
7
- data.tar.gz: 02cd07fa51270bdda5a7ada77463435491cd99e7999ea4162864547454f99ce9846ed78b9cfc9c1df0e5095e7145da43d9822bd790786e1c77701f94a9705d34
6
+ metadata.gz: 601ed7213bb2dc1b551ba8d8e47944492bd67b727b0747aaad9c60b5ce13fb2b728b218a43eb0775e1479f5684ea1721dd0b9310bd25d2ccfcfcfad8cfc9e439
7
+ data.tar.gz: b8a2e25e5c0369d7bd58fe058ef7d524bdc42c7cc22739d30f9ad0956338370f63578aa10688869c817148451b46d7b9e1dab8835c4e7f33531cd9146a04ed88
data/README.md CHANGED
@@ -28,41 +28,6 @@ ApmBro.configure do |cfg|
28
28
  end
29
29
  ```
30
30
 
31
- ## User Email Tracking
32
-
33
- ApmBro can track the email of the user making requests, which is useful for debugging user-specific issues and understanding user behavior patterns.
34
-
35
- ### Configuration
36
-
37
- Enable user email tracking in your Rails configuration:
38
-
39
- ```ruby
40
- # In config/application.rb or environments/*.rb
41
- ApmBro.configure do |config|
42
- config.user_email_tracking_enabled = true
43
- end
44
- ```
45
-
46
- ### Default Email Extraction
47
-
48
- By default, ApmBro will try to extract user email from these sources (in order of priority):
49
-
50
- 1. **`current_user.email`** - Most common in Rails apps with authentication
51
- 2. **Request parameters** - `user_email` or `email` in params
52
- 3. **HTTP headers** - `X-User-Email` or `HTTP_X_USER_EMAIL`
53
- 4. **Session data** - `user_email` in session
54
-
55
- ### Custom Email Extractor
56
-
57
- In progress
58
-
59
- ### Security Considerations
60
-
61
- - User email tracking is **disabled by default** for privacy
62
- - Only enable when necessary for debugging or analytics
63
- - Consider your data privacy requirements and regulations
64
- - The email is included in all request payloads sent to our APM endpoint
65
-
66
31
  ## Request Sampling
67
32
 
68
33
  ApmBro supports configurable request sampling to reduce the volume of metrics sent to your APM endpoint, which is useful for high-traffic applications.
@@ -143,6 +108,54 @@ ApmBro automatically tracks SQL queries executed during each request and job. Ea
143
108
  - `cached` - Whether the query was cached
144
109
  - `connection_id` - Database connection ID
145
110
  - `trace` - Call stack showing where the query was executed
111
+ - `explain_plan` - Query execution plan (when EXPLAIN ANALYZE is enabled, see below)
112
+
113
+ ## Automatic EXPLAIN ANALYZE for Slow Queries
114
+
115
+ ApmBro can automatically run `EXPLAIN ANALYZE` on slow SQL queries to help you understand query performance and identify optimization opportunities. This feature runs in the background and doesn't block your application requests.
116
+
117
+ ### How It Works
118
+
119
+ - **Automatic Detection**: When a query exceeds the configured threshold, ApmBro automatically captures its execution plan
120
+ - **Background Execution**: EXPLAIN ANALYZE runs in a separate thread using a dedicated database connection, so it never blocks your application
121
+ - **Database Support**: Works with PostgreSQL, MySQL, SQLite, and other databases
122
+ - **Smart Filtering**: Automatically skips transaction queries (BEGIN, COMMIT, ROLLBACK) and other queries that don't benefit from EXPLAIN
123
+
124
+ ### Configuration
125
+
126
+ - **`explain_analyze_enabled`** (default: `false`) - Set to `true` to enable automatic EXPLAIN ANALYZE
127
+ - **`slow_query_threshold_ms`** (default: `500`) - Queries taking longer than this threshold will have their execution plan captured
128
+
129
+ ### Example Configuration
130
+
131
+ ```ruby
132
+ ApmBro.configure do |config|
133
+ config.api_key = ENV['APM_BRO_API_KEY']
134
+ config.enabled = true
135
+
136
+ # Enable EXPLAIN ANALYZE for queries slower than 500ms
137
+ config.explain_analyze_enabled = true
138
+ config.slow_query_threshold_ms = 500
139
+
140
+ # Or use a higher threshold for production
141
+ # config.slow_query_threshold_ms = 1000 # Only explain queries > 1 second
142
+ end
143
+ ```
144
+
145
+ ### What You Get
146
+
147
+ When a slow query is detected, the `explain_plan` field in the SQL query data will contain:
148
+ - **PostgreSQL**: Full EXPLAIN ANALYZE output with buffer usage statistics
149
+ - **MySQL**: EXPLAIN ANALYZE output showing actual execution times
150
+ - **SQLite**: EXPLAIN QUERY PLAN output
151
+ - **Other databases**: Standard EXPLAIN output
152
+
153
+ This execution plan helps you:
154
+ - Identify missing indexes
155
+ - Understand query execution order
156
+ - Spot full table scans
157
+ - Optimize JOIN operations
158
+ - Analyze buffer and cache usage (PostgreSQL)
146
159
 
147
160
  ## View Rendering Tracking
148
161
 
@@ -19,18 +19,16 @@ module ApmBro
19
19
 
20
20
  def self.subscribe!
21
21
  EVENTS.each do |event_name|
22
- begin
23
- ActiveSupport::Notifications.subscribe(event_name) do |name, started, finished, _unique_id, data|
24
- next unless Thread.current[THREAD_LOCAL_KEY]
22
+ ActiveSupport::Notifications.subscribe(event_name) do |name, started, finished, _unique_id, data|
23
+ next unless Thread.current[THREAD_LOCAL_KEY]
25
24
 
26
- duration_ms = ((finished - started) * 1000.0).round(2)
27
- event = build_event(name, data, duration_ms)
28
- Thread.current[THREAD_LOCAL_KEY] << event if event
29
- end
30
- rescue StandardError
25
+ duration_ms = ((finished - started) * 1000.0).round(2)
26
+ event = build_event(name, data, duration_ms)
27
+ Thread.current[THREAD_LOCAL_KEY] << event if event
31
28
  end
29
+ rescue
32
30
  end
33
- rescue StandardError
31
+ rescue
34
32
  # Never raise from instrumentation install
35
33
  end
36
34
 
@@ -47,7 +45,7 @@ module ApmBro
47
45
  def self.build_event(name, data, duration_ms)
48
46
  return nil unless data.is_a?(Hash)
49
47
 
50
- base = {
48
+ {
51
49
  event: name,
52
50
  duration_ms: duration_ms,
53
51
  key: safe_key(data[:key]),
@@ -57,27 +55,23 @@ module ApmBro
57
55
  namespace: safe_namespace(data[:namespace]),
58
56
  at: Time.now.utc.to_i
59
57
  }
60
-
61
- base
62
- rescue StandardError
58
+ rescue
63
59
  nil
64
60
  end
65
61
 
66
62
  def self.safe_key(key)
67
63
  return nil if key.nil?
68
64
  s = key.to_s
69
- s.length > 200 ? s[0, 200] + "…" : s
70
- rescue StandardError
65
+ (s.length > 200) ? s[0, 200] + "…" : s
66
+ rescue
71
67
  nil
72
68
  end
73
69
 
74
70
  def self.safe_keys_count(keys)
75
71
  if keys.respond_to?(:size)
76
72
  keys.size
77
- else
78
- nil
79
73
  end
80
- rescue StandardError
74
+ rescue
81
75
  nil
82
76
  end
83
77
 
@@ -88,13 +82,13 @@ module ApmBro
88
82
  else
89
83
  store.class.name
90
84
  end
91
- rescue StandardError
85
+ rescue
92
86
  nil
93
87
  end
94
88
 
95
89
  def self.safe_namespace(ns)
96
90
  ns.to_s[0, 100]
97
- rescue StandardError
91
+ rescue
98
92
  nil
99
93
  end
100
94
 
@@ -104,13 +98,9 @@ module ApmBro
104
98
  true
105
99
  when "cache_read.active_support"
106
100
  !!data[:hit]
107
- else
108
- nil
109
101
  end
110
- rescue StandardError
102
+ rescue
111
103
  nil
112
104
  end
113
105
  end
114
106
  end
115
-
116
-
@@ -20,7 +20,7 @@ module ApmBro
20
20
  @failure_threshold = failure_threshold
21
21
  @recovery_timeout = recovery_timeout
22
22
  @retry_timeout = retry_timeout
23
-
23
+
24
24
  @state = CLOSED
25
25
  @failure_count = 0
26
26
  @last_failure_time = nil
@@ -43,21 +43,13 @@ module ApmBro
43
43
  end
44
44
  end
45
45
 
46
- def state
47
- @state
48
- end
46
+ attr_reader :state
49
47
 
50
- def failure_count
51
- @failure_count
52
- end
48
+ attr_reader :failure_count
53
49
 
54
- def last_failure_time
55
- @last_failure_time
56
- end
50
+ attr_reader :last_failure_time
57
51
 
58
- def last_success_time
59
- @last_success_time
60
- end
52
+ attr_reader :last_success_time
61
53
 
62
54
  def reset!
63
55
  @state = CLOSED
@@ -76,7 +68,7 @@ module ApmBro
76
68
 
77
69
  def should_attempt_reset?
78
70
  return false unless @last_failure_time
79
-
71
+
80
72
  # Try to reset after recovery timeout
81
73
  elapsed = Time.now - @last_failure_time
82
74
  elapsed >= @recovery_timeout
@@ -86,7 +78,7 @@ module ApmBro
86
78
 
87
79
  def execute_with_monitoring(&block)
88
80
  result = block.call
89
-
81
+
90
82
  if success?(result)
91
83
  on_success
92
84
  result
@@ -94,7 +86,7 @@ module ApmBro
94
86
  on_failure
95
87
  result
96
88
  end
97
- rescue StandardError => e
89
+ rescue => e
98
90
  on_failure
99
91
  raise e
100
92
  end
@@ -113,7 +105,7 @@ module ApmBro
113
105
  def on_failure
114
106
  @failure_count += 1
115
107
  @last_failure_time = Time.now
116
-
108
+
117
109
  # If we're in half-open state and get a failure, go back to open
118
110
  if @state == HALF_OPEN
119
111
  @state = OPEN
@@ -23,7 +23,7 @@ module ApmBro
23
23
  end
24
24
 
25
25
  api_key = @configuration.resolve_api_key
26
-
26
+
27
27
  if api_key.nil?
28
28
  return
29
29
  end
@@ -50,7 +50,7 @@ module ApmBro
50
50
 
51
51
  def create_circuit_breaker
52
52
  return nil unless @configuration.circuit_breaker_enabled
53
-
53
+
54
54
  CircuitBreaker.new(
55
55
  failure_threshold: @configuration.circuit_breaker_failure_threshold,
56
56
  recovery_timeout: @configuration.circuit_breaker_recovery_timeout,
@@ -59,8 +59,8 @@ module ApmBro
59
59
  end
60
60
 
61
61
  def make_http_request(event_name, payload, api_key)
62
- endpoint_url = @configuration.respond_to?(:ruby_dev) && @configuration.ruby_dev ?
63
- 'http://localhost:3100/apm/v1/metrics' :
62
+ endpoint_url = (@configuration.respond_to?(:ruby_dev) && @configuration.ruby_dev) ?
63
+ "http://localhost:3100/apm/v1/metrics" :
64
64
  "https://www.deadbro.com/apm/v1/metrics"
65
65
 
66
66
  uri = URI.parse(endpoint_url)
@@ -72,43 +72,37 @@ module ApmBro
72
72
  request = Net::HTTP::Post.new(uri.request_uri)
73
73
  request["Content-Type"] = "application/json"
74
74
  request["Authorization"] = "Bearer #{api_key}"
75
- body = { event: event_name, payload: payload, sent_at: Time.now.utc.iso8601, revision: @configuration.resolve_deploy_id }
75
+ body = {event: event_name, payload: payload, sent_at: Time.now.utc.iso8601, revision: @configuration.resolve_deploy_id}
76
76
  request.body = JSON.dump(body)
77
77
 
78
78
  # Fire-and-forget using a short-lived thread to avoid blocking the request cycle.
79
79
  Thread.new do
80
- begin
81
- response = http.request(request)
82
-
83
- if response
84
- # Update circuit breaker based on response
85
- if @circuit_breaker && @configuration.circuit_breaker_enabled
86
- if response.is_a?(Net::HTTPSuccess)
87
- @circuit_breaker.send(:on_success)
88
- else
89
- @circuit_breaker.send(:on_failure)
90
- end
91
- end
92
- else
93
- # Treat nil response as failure for circuit breaker
94
- if @circuit_breaker && @configuration.circuit_breaker_enabled
80
+ response = http.request(request)
81
+
82
+ if response
83
+ # Update circuit breaker based on response
84
+ if @circuit_breaker && @configuration.circuit_breaker_enabled
85
+ if response.is_a?(Net::HTTPSuccess)
86
+ @circuit_breaker.send(:on_success)
87
+ else
95
88
  @circuit_breaker.send(:on_failure)
96
89
  end
97
90
  end
98
-
99
- response
100
- rescue Timeout::Error => e
101
-
102
- # Update circuit breaker on timeout
103
- if @circuit_breaker && @configuration.circuit_breaker_enabled
104
- @circuit_breaker.send(:on_failure)
105
- end
106
- rescue StandardError => e
107
-
108
- # Update circuit breaker on exception
109
- if @circuit_breaker && @configuration.circuit_breaker_enabled
110
- @circuit_breaker.send(:on_failure)
111
- end
91
+ elsif @circuit_breaker && @configuration.circuit_breaker_enabled
92
+ # Treat nil response as failure for circuit breaker
93
+ @circuit_breaker.send(:on_failure)
94
+ end
95
+
96
+ response
97
+ rescue Timeout::Error
98
+ # Update circuit breaker on timeout
99
+ if @circuit_breaker && @configuration.circuit_breaker_enabled
100
+ @circuit_breaker.send(:on_failure)
101
+ end
102
+ rescue
103
+ # Update circuit breaker on exception
104
+ if @circuit_breaker && @configuration.circuit_breaker_enabled
105
+ @circuit_breaker.send(:on_failure)
112
106
  end
113
107
  end
114
108
 
@@ -124,5 +118,3 @@ module ApmBro
124
118
  end
125
119
  end
126
120
  end
127
-
128
-
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ApmBro
4
4
  class Configuration
5
- DEFAULT_ENDPOINT_PATH = "/v1/metrics".freeze
5
+ DEFAULT_ENDPOINT_PATH = "/v1/metrics"
6
6
 
7
- attr_accessor :api_key, :endpoint_url, :open_timeout, :read_timeout, :enabled, :ruby_dev, :memory_tracking_enabled, :allocation_tracking_enabled, :circuit_breaker_enabled, :circuit_breaker_failure_threshold, :circuit_breaker_recovery_timeout, :circuit_breaker_retry_timeout, :user_email_tracking_enabled, :user_email_extractor, :sample_rate, :excluded_controllers, :excluded_jobs, :excluded_controller_actions, :deploy_id
7
+ attr_accessor :api_key, :endpoint_url, :open_timeout, :read_timeout, :enabled, :ruby_dev, :memory_tracking_enabled, :allocation_tracking_enabled, :circuit_breaker_enabled, :circuit_breaker_failure_threshold, :circuit_breaker_recovery_timeout, :circuit_breaker_retry_timeout, :sample_rate, :excluded_controllers, :excluded_jobs, :excluded_controller_actions, :deploy_id, :slow_query_threshold_ms, :explain_analyze_enabled
8
8
 
9
9
  def initialize
10
10
  @api_key = nil
@@ -19,13 +19,13 @@ module ApmBro
19
19
  @circuit_breaker_failure_threshold = 3
20
20
  @circuit_breaker_recovery_timeout = 60 # seconds
21
21
  @circuit_breaker_retry_timeout = 300 # seconds
22
- @user_email_tracking_enabled = false
23
- @user_email_extractor = nil
24
22
  @sample_rate = 100 # 100% sampling by default
25
23
  @excluded_controllers = []
26
24
  @excluded_jobs = []
27
25
  @excluded_controller_actions = []
28
26
  @deploy_id = resolve_deploy_id
27
+ @slow_query_threshold_ms = 500 # Default: 500ms
28
+ @explain_analyze_enabled = false # Enable EXPLAIN ANALYZE for slow queries by default
29
29
  end
30
30
 
31
31
  def resolve_api_key
@@ -81,6 +81,9 @@ module ApmBro
81
81
  end
82
82
 
83
83
  # Prefer explicit env var, then common platform-specific var
84
+ apm_bro_deploy_id = ENV["APM_BRO_DEPLOY_ID"]
85
+ return apm_bro_deploy_id if present?(apm_bro_deploy_id)
86
+
84
87
  env_val = ENV["GIT_REV"]
85
88
  return env_val if present?(env_val)
86
89
 
@@ -91,6 +94,12 @@ module ApmBro
91
94
  ApmBro.process_deploy_id
92
95
  end
93
96
 
97
+ def excluded_controller?(controller_name)
98
+ list = resolve_excluded_controllers
99
+ return false if list.nil? || list.empty?
100
+ list.any? { |pat| match_name_or_pattern?(controller_name, pat) }
101
+ end
102
+
94
103
  def excluded_job?(job_class_name)
95
104
  list = resolve_excluded_jobs
96
105
  return false if list.nil? || list.empty?
@@ -105,14 +114,58 @@ module ApmBro
105
114
  end
106
115
 
107
116
  def resolve_excluded_controller_actions
108
- return @excluded_controller_actions if @excluded_controller_actions && !@excluded_controller_actions.empty?
117
+ # Collect patterns from @excluded_controller_actions
118
+ patterns = []
119
+ if @excluded_controller_actions && !@excluded_controller_actions.empty?
120
+ patterns.concat(Array(@excluded_controller_actions))
121
+ end
122
+
123
+ # Also check @excluded_controllers for patterns containing '#' (controller action patterns)
124
+ if @excluded_controllers && !@excluded_controllers.empty?
125
+ action_patterns = Array(@excluded_controllers).select { |pat| pat.to_s.include?("#") }
126
+ patterns.concat(action_patterns)
127
+ end
128
+
129
+ return patterns if !patterns.empty?
109
130
 
110
131
  if defined?(Rails)
111
132
  list = fetch_from_rails_settings(%w[apm_bro excluded_controller_actions])
112
- return Array(list) if list
133
+ if list
134
+ rails_patterns = Array(list)
135
+ # Also check excluded_controllers from Rails settings for action patterns
136
+ controllers_list = fetch_from_rails_settings(%w[apm_bro excluded_controllers])
137
+ if controllers_list
138
+ action_patterns = Array(controllers_list).select { |pat| pat.to_s.include?("#") }
139
+ rails_patterns.concat(action_patterns)
140
+ end
141
+ return rails_patterns if !rails_patterns.empty?
142
+ end
113
143
  end
114
144
 
115
145
  env = ENV["APM_BRO_EXCLUDED_CONTROLLER_ACTIONS"]
146
+ if env && !env.strip.empty?
147
+ env_patterns = env.split(",").map(&:strip)
148
+ # Also check excluded_controllers env var for action patterns
149
+ controllers_env = ENV["APM_BRO_EXCLUDED_CONTROLLERS"]
150
+ if controllers_env && !controllers_env.strip.empty?
151
+ action_patterns = controllers_env.split(",").map(&:strip).select { |pat| pat.include?("#") }
152
+ env_patterns.concat(action_patterns)
153
+ end
154
+ return env_patterns if !env_patterns.empty?
155
+ end
156
+
157
+ []
158
+ end
159
+
160
+ def resolve_excluded_controllers
161
+ return @excluded_controllers if @excluded_controllers && !@excluded_controllers.empty?
162
+
163
+ if defined?(Rails)
164
+ list = fetch_from_rails_settings(%w[apm_bro excluded_controllers])
165
+ return Array(list) if list
166
+ end
167
+
168
+ env = ENV["APM_BRO_EXCLUDED_CONTROLLERS"]
116
169
  return env.split(",").map(&:strip) if env && !env.strip.empty?
117
170
 
118
171
  []
@@ -136,29 +189,23 @@ module ApmBro
136
189
  sample_rate = resolve_sample_rate
137
190
  return true if sample_rate >= 100
138
191
  return false if sample_rate <= 0
139
-
192
+
140
193
  # Generate random number 1-100 and check if it's within sample rate
141
194
  rand(1..100) <= sample_rate
142
195
  end
143
196
 
144
197
  def sample_rate=(value)
145
- unless value.is_a?(Integer) && value >= 1 && value <= 100
146
- raise ArgumentError, "Sample rate must be an integer between 1 and 100, got: #{value.inspect}"
198
+ # Allow nil to use default/resolved value
199
+ if value.nil?
200
+ @sample_rate = nil
201
+ return
147
202
  end
148
- @sample_rate = value
149
- end
150
-
151
- def extract_user_email(request_data)
152
- ap request_data[:headers].class
153
- return nil unless @user_email_tracking_enabled
154
203
 
155
- # If a custom extractor is provided, use it
156
- if @user_email_extractor.respond_to?(:call)
157
- return @user_email_extractor.call(request_data)
204
+ # Allow 0 to disable sampling, or 1-100 for percentage
205
+ unless value.is_a?(Integer) && value >= 0 && value <= 100
206
+ raise ArgumentError, "Sample rate must be an integer between 0 and 100, got: #{value.inspect}"
158
207
  end
159
-
160
- # Default extraction logic
161
- extract_user_email_from_request(request_data)
208
+ @sample_rate = value
162
209
  end
163
210
 
164
211
  private
@@ -171,10 +218,18 @@ module ApmBro
171
218
  return false if name.nil? || pattern.nil?
172
219
  pat = pattern.to_s
173
220
  return !!(name.to_s == pat) unless pat.include?("*")
174
- # Convert simple wildcard pattern (e.g., "Admin::*") to regex
175
- regex = Regexp.new("^" + Regexp.escape(pat).gsub(/\\\*/,'[^:]*') + "$")
221
+
222
+ # For controller action patterns (containing '#'), use .* to match any characters including colons
223
+ # For controller-only patterns, use [^:]* to match namespace segments
224
+ if pat.include?("#")
225
+ # Controller action pattern: allow * to match any characters including colons
226
+ regex = Regexp.new("^" + Regexp.escape(pat).gsub("\\*", ".*") + "$")
227
+ else
228
+ # Controller-only pattern: use [^:]* to match namespace segments
229
+ regex = Regexp.new("^" + Regexp.escape(pat).gsub("\\*", "[^:]*") + "$")
230
+ end
176
231
  !!(name.to_s =~ regex)
177
- rescue StandardError
232
+ rescue
178
233
  false
179
234
  end
180
235
 
@@ -182,12 +237,16 @@ module ApmBro
182
237
  # Try Rails.application.config_for(:apm_bro)
183
238
  begin
184
239
  if Rails.respond_to?(:application) && Rails.application.respond_to?(:config_for)
185
- config = Rails.application.config_for(:apm_bro) rescue nil
240
+ config = begin
241
+ Rails.application.config_for(:apm_bro)
242
+ rescue
243
+ nil
244
+ end
186
245
  if config && config.is_a?(Hash)
187
246
  return dig_hash(config, *Array(path_keys))
188
247
  end
189
248
  end
190
- rescue StandardError
249
+ rescue
191
250
  end
192
251
 
193
252
  # Try Rails.application.credentials
@@ -198,7 +257,7 @@ module ApmBro
198
257
  value = dig_credentials(creds, *Array(path_keys))
199
258
  return value if present?(value)
200
259
  end
201
- rescue StandardError
260
+ rescue
202
261
  end
203
262
 
204
263
  # Try Rails.application.config.x.apm_bro.api_key
@@ -208,7 +267,7 @@ module ApmBro
208
267
  config_x = x.apm_bro
209
268
  return config_x.public_send(Array(path_keys).last) if config_x.respond_to?(Array(path_keys).last)
210
269
  end
211
- rescue StandardError
270
+ rescue
212
271
  end
213
272
 
214
273
  nil
@@ -248,42 +307,5 @@ module ApmBro
248
307
  path = "/#{path}" unless path.start_with?("/")
249
308
  base + path
250
309
  end
251
-
252
- def extract_user_email_from_request(request_data)
253
- # Try to get user email from various common sources
254
- return nil unless request_data.is_a?(Hash)
255
-
256
- # Check for current_user.email (common in Rails apps)
257
- if request_data[:current_user]&.respond_to?(:email)
258
- return request_data[:current_user].email
259
- end
260
-
261
- # Check for user.email in params
262
- if request_data[:params]&.is_a?(Hash)
263
- user_email = request_data[:params]["user_email"] ||
264
- request_data[:params][:user_email] ||
265
- request_data[:params]["email"] ||
266
- request_data[:params][:email]
267
- return user_email if user_email.present?
268
- end
269
-
270
- # Check for user email in headers
271
- if request_data[:request]&.respond_to?(:headers)
272
- headers = request_data[:request].headers
273
- return headers["X-User-Email"] if headers["X-User-Email"].present?
274
- return headers["HTTP_X_USER_EMAIL"] if headers["HTTP_X_USER_EMAIL"].present?
275
- end
276
-
277
- # Check for user email in session
278
- if request_data[:session]&.is_a?(Hash)
279
- return request_data[:session]["user_email"] || request_data[:session][:user_email]
280
- end
281
-
282
- nil
283
- rescue StandardError
284
- nil
285
- end
286
310
  end
287
311
  end
288
-
289
-