apm_bro 0.1.14 → 0.1.15
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 +4 -4
- data/README.md +0 -35
- data/lib/apm_bro/cache_subscriber.rb +15 -25
- data/lib/apm_bro/circuit_breaker.rb +9 -17
- data/lib/apm_bro/client.rb +28 -36
- data/lib/apm_bro/configuration.rb +44 -62
- data/lib/apm_bro/error_middleware.rb +16 -30
- data/lib/apm_bro/http_instrumentation.rb +22 -16
- data/lib/apm_bro/job_sql_tracking_middleware.rb +1 -1
- data/lib/apm_bro/job_subscriber.rb +41 -25
- data/lib/apm_bro/lightweight_memory_tracker.rb +8 -8
- data/lib/apm_bro/logger.rb +8 -9
- data/lib/apm_bro/memory_helpers.rb +13 -13
- data/lib/apm_bro/memory_leak_detector.rb +37 -37
- data/lib/apm_bro/memory_tracking_subscriber.rb +62 -54
- data/lib/apm_bro/railtie.rb +28 -30
- data/lib/apm_bro/redis_subscriber.rb +34 -37
- data/lib/apm_bro/sql_subscriber.rb +60 -59
- data/lib/apm_bro/sql_tracking_middleware.rb +11 -11
- data/lib/apm_bro/subscriber.rb +53 -39
- data/lib/apm_bro/version.rb +1 -1
- data/lib/apm_bro/view_rendering_subscriber.rb +23 -23
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 26626be56405c7aa4561db094b0e1bf27b1469cef67275786ec1ece6e28947e3
|
|
4
|
+
data.tar.gz: c07ec1b39ff873b5cccfa909fb726aa5971bc87b6abdae8a59de4bd4550b898b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c8dc70fec2841c841e71b945f9e6db3aaf40f35bd7e750f3386130829a0733e44e91c16da05b0eeb5e52175bad335af9990942ed66385ff5926460fc98b64f9
|
|
7
|
+
data.tar.gz: 356a4e343d70b3bac7aee65d285fabe765dbb91d0ba74c4a2b594ca6459e13b7cfc68e8a96fbe43c525f5252e4a3f0692028dbcd38c01a0d7ad0d52b6d4cbda5
|
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.
|
|
@@ -19,18 +19,16 @@ module ApmBro
|
|
|
19
19
|
|
|
20
20
|
def self.subscribe!
|
|
21
21
|
EVENTS.each do |event_name|
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
47
|
-
@state
|
|
48
|
-
end
|
|
46
|
+
attr_reader :state
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
@failure_count
|
|
52
|
-
end
|
|
48
|
+
attr_reader :failure_count
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
@last_failure_time
|
|
56
|
-
end
|
|
50
|
+
attr_reader :last_failure_time
|
|
57
51
|
|
|
58
|
-
|
|
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
|
|
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
|
data/lib/apm_bro/client.rb
CHANGED
|
@@ -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
|
-
|
|
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 = {
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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"
|
|
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, :
|
|
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
|
|
8
8
|
|
|
9
9
|
def initialize
|
|
10
10
|
@api_key = nil
|
|
@@ -19,8 +19,6 @@ 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 = []
|
|
@@ -81,6 +79,9 @@ module ApmBro
|
|
|
81
79
|
end
|
|
82
80
|
|
|
83
81
|
# Prefer explicit env var, then common platform-specific var
|
|
82
|
+
apm_bro_deploy_id = ENV["APM_BRO_DEPLOY_ID"]
|
|
83
|
+
return apm_bro_deploy_id if present?(apm_bro_deploy_id)
|
|
84
|
+
|
|
84
85
|
env_val = ENV["GIT_REV"]
|
|
85
86
|
return env_val if present?(env_val)
|
|
86
87
|
|
|
@@ -91,6 +92,12 @@ module ApmBro
|
|
|
91
92
|
ApmBro.process_deploy_id
|
|
92
93
|
end
|
|
93
94
|
|
|
95
|
+
def excluded_controller?(controller_name)
|
|
96
|
+
list = resolve_excluded_controllers
|
|
97
|
+
return false if list.nil? || list.empty?
|
|
98
|
+
list.any? { |pat| match_name_or_pattern?(controller_name, pat) }
|
|
99
|
+
end
|
|
100
|
+
|
|
94
101
|
def excluded_job?(job_class_name)
|
|
95
102
|
list = resolve_excluded_jobs
|
|
96
103
|
return false if list.nil? || list.empty?
|
|
@@ -118,6 +125,20 @@ module ApmBro
|
|
|
118
125
|
[]
|
|
119
126
|
end
|
|
120
127
|
|
|
128
|
+
def resolve_excluded_controllers
|
|
129
|
+
return @excluded_controllers if @excluded_controllers && !@excluded_controllers.empty?
|
|
130
|
+
|
|
131
|
+
if defined?(Rails)
|
|
132
|
+
list = fetch_from_rails_settings(%w[apm_bro excluded_controllers])
|
|
133
|
+
return Array(list) if list
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
env = ENV["APM_BRO_EXCLUDED_CONTROLLERS"]
|
|
137
|
+
return env.split(",").map(&:strip) if env && !env.strip.empty?
|
|
138
|
+
|
|
139
|
+
[]
|
|
140
|
+
end
|
|
141
|
+
|
|
121
142
|
def resolve_excluded_jobs
|
|
122
143
|
return @excluded_jobs if @excluded_jobs && !@excluded_jobs.empty?
|
|
123
144
|
|
|
@@ -136,29 +157,23 @@ module ApmBro
|
|
|
136
157
|
sample_rate = resolve_sample_rate
|
|
137
158
|
return true if sample_rate >= 100
|
|
138
159
|
return false if sample_rate <= 0
|
|
139
|
-
|
|
160
|
+
|
|
140
161
|
# Generate random number 1-100 and check if it's within sample rate
|
|
141
162
|
rand(1..100) <= sample_rate
|
|
142
163
|
end
|
|
143
164
|
|
|
144
165
|
def sample_rate=(value)
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
# Allow nil to use default/resolved value
|
|
167
|
+
if value.nil?
|
|
168
|
+
@sample_rate = nil
|
|
169
|
+
return
|
|
147
170
|
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
171
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
# Allow 0 to disable sampling, or 1-100 for percentage
|
|
173
|
+
unless value.is_a?(Integer) && value >= 0 && value <= 100
|
|
174
|
+
raise ArgumentError, "Sample rate must be an integer between 0 and 100, got: #{value.inspect}"
|
|
158
175
|
end
|
|
159
|
-
|
|
160
|
-
# Default extraction logic
|
|
161
|
-
extract_user_email_from_request(request_data)
|
|
176
|
+
@sample_rate = value
|
|
162
177
|
end
|
|
163
178
|
|
|
164
179
|
private
|
|
@@ -172,9 +187,9 @@ module ApmBro
|
|
|
172
187
|
pat = pattern.to_s
|
|
173
188
|
return !!(name.to_s == pat) unless pat.include?("*")
|
|
174
189
|
# Convert simple wildcard pattern (e.g., "Admin::*") to regex
|
|
175
|
-
regex = Regexp.new("^" + Regexp.escape(pat).gsub(
|
|
190
|
+
regex = Regexp.new("^" + Regexp.escape(pat).gsub("\\*", "[^:]*") + "$")
|
|
176
191
|
!!(name.to_s =~ regex)
|
|
177
|
-
rescue
|
|
192
|
+
rescue
|
|
178
193
|
false
|
|
179
194
|
end
|
|
180
195
|
|
|
@@ -182,12 +197,16 @@ module ApmBro
|
|
|
182
197
|
# Try Rails.application.config_for(:apm_bro)
|
|
183
198
|
begin
|
|
184
199
|
if Rails.respond_to?(:application) && Rails.application.respond_to?(:config_for)
|
|
185
|
-
config =
|
|
200
|
+
config = begin
|
|
201
|
+
Rails.application.config_for(:apm_bro)
|
|
202
|
+
rescue
|
|
203
|
+
nil
|
|
204
|
+
end
|
|
186
205
|
if config && config.is_a?(Hash)
|
|
187
206
|
return dig_hash(config, *Array(path_keys))
|
|
188
207
|
end
|
|
189
208
|
end
|
|
190
|
-
rescue
|
|
209
|
+
rescue
|
|
191
210
|
end
|
|
192
211
|
|
|
193
212
|
# Try Rails.application.credentials
|
|
@@ -198,7 +217,7 @@ module ApmBro
|
|
|
198
217
|
value = dig_credentials(creds, *Array(path_keys))
|
|
199
218
|
return value if present?(value)
|
|
200
219
|
end
|
|
201
|
-
rescue
|
|
220
|
+
rescue
|
|
202
221
|
end
|
|
203
222
|
|
|
204
223
|
# Try Rails.application.config.x.apm_bro.api_key
|
|
@@ -208,7 +227,7 @@ module ApmBro
|
|
|
208
227
|
config_x = x.apm_bro
|
|
209
228
|
return config_x.public_send(Array(path_keys).last) if config_x.respond_to?(Array(path_keys).last)
|
|
210
229
|
end
|
|
211
|
-
rescue
|
|
230
|
+
rescue
|
|
212
231
|
end
|
|
213
232
|
|
|
214
233
|
nil
|
|
@@ -248,42 +267,5 @@ module ApmBro
|
|
|
248
267
|
path = "/#{path}" unless path.start_with?("/")
|
|
249
268
|
base + path
|
|
250
269
|
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
270
|
end
|
|
287
271
|
end
|
|
288
|
-
|
|
289
|
-
|
|
@@ -4,7 +4,7 @@ require "rack"
|
|
|
4
4
|
|
|
5
5
|
module ApmBro
|
|
6
6
|
class ErrorMiddleware
|
|
7
|
-
EVENT_NAME = "exception.uncaught"
|
|
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
|
|
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:
|
|
43
|
-
path:
|
|
44
|
-
fullpath:
|
|
45
|
-
ip:
|
|
46
|
-
user_agent: truncate(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|