dead_bro 0.2.8 → 0.2.9
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 +42 -43
- data/lib/dead_bro/circuit_breaker.rb +58 -38
- data/lib/dead_bro/client.rb +112 -143
- data/lib/dead_bro/configuration.rb +76 -40
- data/lib/dead_bro/dispatcher.rb +130 -0
- data/lib/dead_bro/error_middleware.rb +1 -1
- data/lib/dead_bro/job_subscriber.rb +35 -12
- data/lib/dead_bro/lightweight_memory_tracker.rb +5 -7
- data/lib/dead_bro/logger.rb +30 -11
- data/lib/dead_bro/memory_details.rb +71 -0
- data/lib/dead_bro/memory_helpers.rb +62 -0
- data/lib/dead_bro/memory_leak_detector.rb +178 -158
- data/lib/dead_bro/memory_tracking_subscriber.rb +7 -31
- data/lib/dead_bro/monitor.rb +18 -5
- data/lib/dead_bro/railtie.rb +6 -6
- data/lib/dead_bro/sql_subscriber.rb +103 -70
- data/lib/dead_bro/subscriber.rb +36 -14
- data/lib/dead_bro/version.rb +1 -1
- data/lib/dead_bro.rb +85 -88
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8badc23e17a92bca8e8057c8c49c6ba7a1535975a7da9f61a43fd0e2b80f56b5
|
|
4
|
+
data.tar.gz: 32ed00be83e65d97b89e3467129f95986d0417ec27839066a83b7706182a28ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: becf47113fbfb03c8f851f54e3e518588e8ebc963f762d68b699b3a01dad7bab381c6d07c323abeb1d1414f20a24d949bf4cc245821ea96c4ee38e8e155f82ad
|
|
7
|
+
data.tar.gz: 1071fe9473be03be215822ab0bc9349ba1569a03f6d01fca372911f390c584227ebcd4c42272da8dd66198b560c5f09de83c42b6238dd8710241889c9e2cdb55
|
data/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# DeadBro (Beta Version)
|
|
2
2
|
|
|
3
|
-
Minimal APM for Rails apps. Automatically measures each controller action's total time, tracks SQL queries, monitors view rendering performance, tracks memory usage and detects leaks, monitors background jobs, and posts metrics to
|
|
3
|
+
Minimal APM for Rails apps. Automatically measures each controller action's total time, tracks SQL queries, monitors view rendering performance, tracks memory usage and detects leaks, monitors background jobs, and posts metrics to DeadBro.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Current versions:** almost everything (sampling, exclusions, SQL EXPLAIN, memory toggles, control-plane collectors, enable/disable, and more) is configured in the [DeadBro](https://www.deadbro.com) web app. The API returns those settings on successful requests and the gem applies them in memory—no need to mirror them in Ruby unless you want optional code-based overrides.
|
|
6
|
+
|
|
7
|
+
To use the gem you need a free account with [DeadBro - Rails APM](https://www.deadbro.com).
|
|
6
8
|
|
|
7
9
|
## Installation
|
|
8
10
|
|
|
@@ -16,21 +18,33 @@ gem "dead_bro", git: "https://github.com/rubydevro/dead_bro.git"
|
|
|
16
18
|
|
|
17
19
|
By default, if Rails is present, DeadBro auto-subscribes to `process_action.action_controller` and posts metrics asynchronously.
|
|
18
20
|
|
|
19
|
-
###
|
|
20
|
-
|
|
21
|
-
You can set via an initializer:
|
|
21
|
+
### Required: API key in your app
|
|
22
22
|
|
|
23
|
+
Add an initializer (for example `config/initializers/dead_bro.rb`) and set your API key from the environment, [Rails credentials](https://guides.rubyonrails.org/security.html#custom-credentials), or another secret store:
|
|
23
24
|
|
|
24
25
|
```ruby
|
|
25
26
|
DeadBro.configure do |config|
|
|
26
27
|
config.api_key = ENV["DEAD_BRO_API_KEY"]
|
|
27
|
-
config.enabled = true
|
|
28
28
|
end
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
That is enough to start shipping metrics. Create or copy the key from your DeadBro account, then wire it into `DEAD_BRO_API_KEY` (or assign `config.api_key` directly).
|
|
32
|
+
|
|
33
|
+
### Dashboard configuration
|
|
34
|
+
|
|
35
|
+
Use the DeadBro UI to turn features on or off, set sample rates, define controller/job inclusions and exclusions, tune slow-query EXPLAIN, enable queue and system metrics, and adjust related limits. After you deploy the initializer above, those choices take effect when the gem receives them from the API (typically on the next successful metric or heartbeat response).
|
|
36
|
+
|
|
37
|
+
### Optional local flags
|
|
38
|
+
|
|
39
|
+
You can still set `config.enabled` in Ruby if you need to force the integration off in a given environment before any remote settings arrive; otherwise the dashboard can control `enabled` like other remote settings.
|
|
40
|
+
|
|
41
|
+
## Optional: configuration in Ruby
|
|
42
|
+
|
|
43
|
+
The sections below describe the same knobs you can manage in the DeadBro app. Use them only when you want values in source control, per-environment initializer logic, or other overrides outside the UI.
|
|
44
|
+
|
|
31
45
|
## Request Sampling
|
|
32
46
|
|
|
33
|
-
DeadBro supports configurable request sampling to reduce the volume of metrics sent to your APM endpoint, which is useful for high-traffic applications.
|
|
47
|
+
DeadBro supports configurable request sampling to reduce the volume of metrics sent to your APM endpoint, which is useful for high-traffic applications. Prefer setting this in the DeadBro app; use Ruby if you need a local override.
|
|
34
48
|
|
|
35
49
|
### Configuration
|
|
36
50
|
|
|
@@ -70,22 +84,20 @@ end
|
|
|
70
84
|
|
|
71
85
|
## Excluding Controllers and Jobs
|
|
72
86
|
|
|
73
|
-
You can exclude specific controllers and jobs from APM tracking.
|
|
87
|
+
You can exclude specific controllers and jobs from APM tracking (dashboard first; Ruby optional).
|
|
74
88
|
|
|
75
89
|
### Configuration
|
|
76
90
|
|
|
77
91
|
|
|
78
92
|
```ruby
|
|
79
93
|
DeadBro.configure do |config|
|
|
94
|
+
# Controller-only or controller#action patterns in one list (wildcards supported)
|
|
80
95
|
config.excluded_controllers = [
|
|
81
96
|
"HealthChecksController",
|
|
82
|
-
"Admin::*"
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
config.excluded_controller_actions = [
|
|
97
|
+
"Admin::*",
|
|
86
98
|
"UsersController#show",
|
|
87
99
|
"Admin::ReportsController#index",
|
|
88
|
-
"Admin::*#*"
|
|
100
|
+
"Admin::*#*"
|
|
89
101
|
]
|
|
90
102
|
|
|
91
103
|
config.excluded_jobs = [
|
|
@@ -96,39 +108,38 @@ end
|
|
|
96
108
|
```
|
|
97
109
|
|
|
98
110
|
Notes:
|
|
99
|
-
- Wildcards `*` are supported
|
|
100
|
-
- Matching
|
|
111
|
+
- Wildcards `*` are supported (e.g., `Admin::*`, `Admin::*#*`).
|
|
112
|
+
- Matching uses full names like `UsersController`, `Admin::ReportsController#index`, `MyJob`.
|
|
101
113
|
|
|
102
114
|
## Exclusive Tracking (Whitelist Mode)
|
|
103
115
|
|
|
104
|
-
You can configure DeadBro to **only** track specific controllers, actions, or jobs.
|
|
116
|
+
You can configure DeadBro to **only** track specific controllers, actions, or jobs. Prefer the dashboard; use Ruby for overrides.
|
|
105
117
|
|
|
106
118
|
### Configuration
|
|
107
119
|
|
|
108
120
|
```ruby
|
|
109
121
|
DeadBro.configure do |config|
|
|
110
|
-
# Only track these
|
|
111
|
-
config.
|
|
122
|
+
# Only track these controllers/actions (patterns can include #action or wildcards)
|
|
123
|
+
config.exclusive_controllers = [
|
|
112
124
|
"UsersController#show",
|
|
113
125
|
"UsersController#index",
|
|
114
|
-
"Admin::ReportsController#*",
|
|
115
|
-
"Api::*#*"
|
|
126
|
+
"Admin::ReportsController#*",
|
|
127
|
+
"Api::*#*"
|
|
116
128
|
]
|
|
117
129
|
|
|
118
|
-
# Only track these specific jobs
|
|
119
130
|
config.exclusive_jobs = [
|
|
120
131
|
"PaymentProcessingJob",
|
|
121
132
|
"EmailDeliveryJob",
|
|
122
|
-
"Admin::*"
|
|
133
|
+
"Admin::*"
|
|
123
134
|
]
|
|
124
135
|
end
|
|
125
136
|
```
|
|
126
137
|
|
|
127
138
|
### How It Works
|
|
128
139
|
|
|
129
|
-
- **If `
|
|
130
|
-
- **If `
|
|
131
|
-
- **Exclusion takes precedence**: If something
|
|
140
|
+
- **If `exclusive_controllers` or `exclusive_jobs` is empty/not defined**: All controllers/actions/jobs are tracked (default behavior)
|
|
141
|
+
- **If `exclusive_controllers` or `exclusive_jobs` is defined with values**: Only matching controllers/actions/jobs are tracked
|
|
142
|
+
- **Exclusion takes precedence**: If something matches both `excluded_*` and `exclusive_*`, it is excluded (exclusion is checked first)
|
|
132
143
|
|
|
133
144
|
### Use Cases
|
|
134
145
|
|
|
@@ -137,18 +148,6 @@ end
|
|
|
137
148
|
- **Debugging**: Temporarily focus on specific controllers/jobs during investigation
|
|
138
149
|
- **Compliance**: Track only operations that require monitoring for compliance reasons
|
|
139
150
|
|
|
140
|
-
### Environment Variables
|
|
141
|
-
|
|
142
|
-
You can also configure exclusive tracking via environment variables:
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
# Comma-separated list of controller#action patterns
|
|
146
|
-
dead_bro_EXCLUSIVE_CONTROLLER_ACTIONS="UsersController#show,Admin::*#*"
|
|
147
|
-
|
|
148
|
-
# Comma-separated list of job patterns
|
|
149
|
-
dead_bro_EXCLUSIVE_JOBS="PaymentProcessingJob,EmailDeliveryJob"
|
|
150
|
-
```
|
|
151
|
-
|
|
152
151
|
## SQL Query Tracking
|
|
153
152
|
|
|
154
153
|
DeadBro automatically tracks SQL queries executed during each request and job. Each request will include a `sql_queries` array containing:
|
|
@@ -173,6 +172,8 @@ DeadBro can automatically run `EXPLAIN ANALYZE` on slow SQL queries to help you
|
|
|
173
172
|
|
|
174
173
|
### Configuration
|
|
175
174
|
|
|
175
|
+
These options are usually set in the DeadBro UI. In Ruby:
|
|
176
|
+
|
|
176
177
|
- **`explain_analyze_enabled`** (default: `false`) - Set to `true` to enable automatic EXPLAIN ANALYZE
|
|
177
178
|
- **`slow_query_threshold_ms`** (default: `500`) - Queries taking longer than this threshold will have their execution plan captured
|
|
178
179
|
|
|
@@ -180,13 +181,10 @@ DeadBro can automatically run `EXPLAIN ANALYZE` on slow SQL queries to help you
|
|
|
180
181
|
|
|
181
182
|
```ruby
|
|
182
183
|
DeadBro.configure do |config|
|
|
183
|
-
config.api_key = ENV['DEAD_BRO_API_KEY']
|
|
184
|
-
config.enabled = true
|
|
185
|
-
|
|
186
184
|
# Enable EXPLAIN ANALYZE for queries slower than 500ms
|
|
187
185
|
config.explain_analyze_enabled = true
|
|
188
186
|
config.slow_query_threshold_ms = 500
|
|
189
|
-
|
|
187
|
+
|
|
190
188
|
# Or use a higher threshold for production
|
|
191
189
|
# config.slow_query_threshold_ms = 1000 # Only explain queries > 1 second
|
|
192
190
|
end
|
|
@@ -232,8 +230,9 @@ By default, DeadBro uses **lightweight memory tracking** that has minimal perfor
|
|
|
232
230
|
|
|
233
231
|
### Configuration Options
|
|
234
232
|
|
|
233
|
+
Usually managed in the dashboard; Ruby example:
|
|
234
|
+
|
|
235
235
|
```ruby
|
|
236
|
-
# In your Rails configuration
|
|
237
236
|
DeadBro.configure do |config|
|
|
238
237
|
config.memory_tracking_enabled = true # Enable lightweight memory tracking (default: true)
|
|
239
238
|
config.allocation_tracking_enabled = false # Enable detailed allocation tracking (default: false)
|
|
@@ -279,7 +278,7 @@ Everything is **best effort** and designed to be **safe and low overhead**:
|
|
|
279
278
|
|
|
280
279
|
### Configuration
|
|
281
280
|
|
|
282
|
-
|
|
281
|
+
Enable or disable collectors in the DeadBro app, or use `DeadBro.configure` for code-based overrides:
|
|
283
282
|
|
|
284
283
|
```ruby
|
|
285
284
|
DeadBro.configure do |config|
|
|
@@ -25,15 +25,17 @@ module DeadBro
|
|
|
25
25
|
@failure_count = 0
|
|
26
26
|
@last_failure_time = nil
|
|
27
27
|
@last_success_time = nil
|
|
28
|
+
@mutex = Mutex.new
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def call(&block)
|
|
31
|
-
|
|
32
|
+
state = @mutex.synchronize { @state }
|
|
33
|
+
case state
|
|
32
34
|
when CLOSED
|
|
33
35
|
execute_with_monitoring(&block)
|
|
34
36
|
when OPEN
|
|
35
37
|
if should_attempt_reset?
|
|
36
|
-
@state = HALF_OPEN
|
|
38
|
+
@mutex.synchronize { @state = HALF_OPEN }
|
|
37
39
|
execute_with_monitoring(&block)
|
|
38
40
|
else
|
|
39
41
|
:circuit_open
|
|
@@ -43,75 +45,93 @@ module DeadBro
|
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
def state
|
|
49
|
+
@mutex.synchronize { @state }
|
|
50
|
+
end
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
def failure_count
|
|
53
|
+
@mutex.synchronize { @failure_count }
|
|
54
|
+
end
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
def last_failure_time
|
|
57
|
+
@mutex.synchronize { @last_failure_time }
|
|
58
|
+
end
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
def last_success_time
|
|
61
|
+
@mutex.synchronize { @last_success_time }
|
|
62
|
+
end
|
|
53
63
|
|
|
54
64
|
def reset!
|
|
55
|
-
@
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
@mutex.synchronize do
|
|
66
|
+
@state = CLOSED
|
|
67
|
+
@failure_count = 0
|
|
68
|
+
@last_failure_time = nil
|
|
69
|
+
end
|
|
58
70
|
end
|
|
59
71
|
|
|
60
72
|
def open!
|
|
61
|
-
@
|
|
62
|
-
|
|
73
|
+
@mutex.synchronize do
|
|
74
|
+
@state = OPEN
|
|
75
|
+
@last_failure_time = Time.now
|
|
76
|
+
end
|
|
63
77
|
end
|
|
64
78
|
|
|
65
79
|
def transition_to_half_open!
|
|
66
|
-
@state = HALF_OPEN
|
|
80
|
+
@mutex.synchronize { @state = HALF_OPEN }
|
|
67
81
|
end
|
|
68
82
|
|
|
69
83
|
def should_attempt_reset?
|
|
70
|
-
|
|
84
|
+
@mutex.synchronize do
|
|
85
|
+
return false unless @last_failure_time
|
|
86
|
+
(Time.now - @last_failure_time) >= @recovery_timeout
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Public entry points for callers that already know the outcome (e.g. the
|
|
91
|
+
# HTTP dispatcher thread). Preferred over `call(&block)` when the caller
|
|
92
|
+
# is doing its own error handling.
|
|
93
|
+
def record_success
|
|
94
|
+
@mutex.synchronize do
|
|
95
|
+
@failure_count = 0
|
|
96
|
+
@last_success_time = Time.now
|
|
97
|
+
@state = CLOSED
|
|
98
|
+
end
|
|
99
|
+
end
|
|
71
100
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
101
|
+
def record_failure
|
|
102
|
+
@mutex.synchronize do
|
|
103
|
+
@failure_count += 1
|
|
104
|
+
@last_failure_time = Time.now
|
|
105
|
+
if @state == HALF_OPEN || @failure_count >= @failure_threshold
|
|
106
|
+
@state = OPEN
|
|
107
|
+
end
|
|
108
|
+
end
|
|
75
109
|
end
|
|
76
110
|
|
|
77
111
|
private
|
|
78
112
|
|
|
113
|
+
# Historical names kept as private aliases so existing specs and any
|
|
114
|
+
# internal callers that reach in via `send(:on_success)` still work.
|
|
115
|
+
alias_method :on_success, :record_success
|
|
116
|
+
alias_method :on_failure, :record_failure
|
|
117
|
+
|
|
79
118
|
def execute_with_monitoring(&block)
|
|
80
119
|
result = block.call
|
|
81
120
|
|
|
82
121
|
if success?(result)
|
|
83
|
-
|
|
122
|
+
record_success
|
|
84
123
|
result
|
|
85
124
|
else
|
|
86
|
-
|
|
125
|
+
record_failure
|
|
87
126
|
result
|
|
88
127
|
end
|
|
89
128
|
rescue => e
|
|
90
|
-
|
|
129
|
+
record_failure
|
|
91
130
|
raise e
|
|
92
131
|
end
|
|
93
132
|
|
|
94
133
|
def success?(result)
|
|
95
|
-
# Consider 2xx status codes as success
|
|
96
134
|
result.is_a?(Net::HTTPSuccess)
|
|
97
135
|
end
|
|
98
|
-
|
|
99
|
-
def on_success
|
|
100
|
-
@failure_count = 0
|
|
101
|
-
@last_success_time = Time.now
|
|
102
|
-
@state = CLOSED
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def on_failure
|
|
106
|
-
@failure_count += 1
|
|
107
|
-
@last_failure_time = Time.now
|
|
108
|
-
|
|
109
|
-
# If we're in half-open state and get a failure, go back to open
|
|
110
|
-
if @state == HALF_OPEN
|
|
111
|
-
@state = OPEN
|
|
112
|
-
elsif @failure_count >= @failure_threshold
|
|
113
|
-
@state = OPEN
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
136
|
end
|
|
117
137
|
end
|
data/lib/dead_bro/client.rb
CHANGED
|
@@ -12,30 +12,21 @@ module DeadBro
|
|
|
12
12
|
@circuit_breaker = create_circuit_breaker
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def post_metric(event_name:, payload:)
|
|
15
|
+
def post_metric(event_name:, payload:, force: false)
|
|
16
16
|
return if @configuration.api_key.nil?
|
|
17
17
|
return unless @configuration.enabled
|
|
18
|
+
return if !force && !@configuration.should_sample?
|
|
19
|
+
return if circuit_open?
|
|
18
20
|
|
|
19
|
-
# Check sampling rate - skip if not selected for sampling
|
|
20
|
-
return unless @configuration.should_sample?
|
|
21
|
-
|
|
22
|
-
# Check circuit breaker before making request
|
|
23
|
-
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
24
|
-
if @circuit_breaker.state == :open
|
|
25
|
-
# Check if we should attempt a reset to half-open state
|
|
26
|
-
if @circuit_breaker.should_attempt_reset?
|
|
27
|
-
@circuit_breaker.transition_to_half_open!
|
|
28
|
-
else
|
|
29
|
-
return
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Truncate large arrays to avoid 413 Request Entity Too Large
|
|
35
21
|
payload = truncate_payload_for_request(payload)
|
|
22
|
+
body = {event: event_name, payload: payload, sent_at: Time.now.utc.iso8601, revision: @configuration.resolve_deploy_id}
|
|
36
23
|
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
dispatch_request(
|
|
25
|
+
url: metrics_endpoint_url,
|
|
26
|
+
body: body,
|
|
27
|
+
event_name: event_name,
|
|
28
|
+
apply_settings: true
|
|
29
|
+
)
|
|
39
30
|
|
|
40
31
|
nil
|
|
41
32
|
end
|
|
@@ -44,7 +35,15 @@ module DeadBro
|
|
|
44
35
|
return if @configuration.api_key.nil?
|
|
45
36
|
|
|
46
37
|
@configuration.last_heartbeat_attempt_at = Time.now.utc
|
|
47
|
-
|
|
38
|
+
body = {event: "heartbeat", payload: {}, sent_at: Time.now.utc.iso8601, revision: @configuration.resolve_deploy_id}
|
|
39
|
+
|
|
40
|
+
dispatch_request(
|
|
41
|
+
url: metrics_endpoint_url,
|
|
42
|
+
body: body,
|
|
43
|
+
event_name: "heartbeat",
|
|
44
|
+
apply_settings: true
|
|
45
|
+
)
|
|
46
|
+
|
|
48
47
|
nil
|
|
49
48
|
end
|
|
50
49
|
|
|
@@ -52,27 +51,108 @@ module DeadBro
|
|
|
52
51
|
return if @configuration.api_key.nil?
|
|
53
52
|
return unless @configuration.enabled
|
|
54
53
|
return unless @configuration.job_queue_monitoring_enabled
|
|
54
|
+
return if circuit_open?
|
|
55
|
+
|
|
56
|
+
body = {payload: payload, sent_at: Time.now.utc.iso8601, revision: @configuration.resolve_deploy_id}
|
|
57
|
+
|
|
58
|
+
dispatch_request(
|
|
59
|
+
url: monitor_endpoint_url,
|
|
60
|
+
body: body,
|
|
61
|
+
event_name: nil,
|
|
62
|
+
apply_settings: false
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Returns true (and short-circuits) when the circuit is open and not ready
|
|
71
|
+
# to probe. Transitions to HALF_OPEN when the recovery timeout has elapsed.
|
|
72
|
+
def circuit_open?
|
|
73
|
+
return false unless @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
74
|
+
return false unless @circuit_breaker.state == :open
|
|
75
|
+
|
|
76
|
+
if @circuit_breaker.should_attempt_reset?
|
|
77
|
+
@circuit_breaker.transition_to_half_open!
|
|
78
|
+
false
|
|
79
|
+
else
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def metrics_endpoint_url
|
|
85
|
+
if @configuration.ruby_dev
|
|
86
|
+
"http://localhost:3100/apm/v1/metrics"
|
|
87
|
+
elsif ENV["USE_STAGING_ENDPOINT"] && !ENV["USE_STAGING_ENDPOINT"].empty?
|
|
88
|
+
"https://deadbro.aberatii.com/apm/v1/metrics"
|
|
89
|
+
else
|
|
90
|
+
"https://www.deadbro.com/apm/v1/metrics"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
55
93
|
|
|
56
|
-
|
|
57
|
-
if @
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
94
|
+
def monitor_endpoint_url
|
|
95
|
+
if @configuration.ruby_dev
|
|
96
|
+
"http://localhost:3100/apm/v1/monitor"
|
|
97
|
+
elsif ENV["USE_STAGING_ENDPOINT"] && !ENV["USE_STAGING_ENDPOINT"].empty?
|
|
98
|
+
"https://deadbro.aberatii.com/apm/v1/monitor"
|
|
99
|
+
else
|
|
100
|
+
"https://www.deadbro.com/apm/v1/monitor"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def dispatch_request(url:, body:, event_name:, apply_settings: false)
|
|
105
|
+
uri = URI.parse(url)
|
|
106
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
107
|
+
http.use_ssl = (uri.scheme == "https")
|
|
108
|
+
http.open_timeout = @configuration.open_timeout
|
|
109
|
+
http.read_timeout = @configuration.read_timeout
|
|
110
|
+
|
|
111
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
112
|
+
request["Content-Type"] = "application/json"
|
|
113
|
+
request["Authorization"] = "Bearer #{@configuration.api_key}"
|
|
114
|
+
if @configuration.settings_received_at
|
|
115
|
+
request["X-Settings-Received-At"] = @configuration.settings_received_at.utc.iso8601
|
|
116
|
+
end
|
|
117
|
+
request.body = JSON.dump(body)
|
|
118
|
+
|
|
119
|
+
DeadBro::Dispatcher.instance.dispatch do
|
|
120
|
+
perform_request(http, request, event_name: event_name, apply_settings: apply_settings)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def perform_request(http, request, event_name:, apply_settings: false)
|
|
125
|
+
response = http.request(request)
|
|
126
|
+
|
|
127
|
+
if response
|
|
128
|
+
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
129
|
+
if response.is_a?(Net::HTTPSuccess)
|
|
130
|
+
@circuit_breaker.record_success
|
|
62
131
|
else
|
|
63
|
-
|
|
132
|
+
@circuit_breaker.record_failure
|
|
64
133
|
end
|
|
65
134
|
end
|
|
66
|
-
end
|
|
67
135
|
|
|
68
|
-
|
|
69
|
-
|
|
136
|
+
if apply_settings
|
|
137
|
+
apply_settings_from_response(response)
|
|
138
|
+
|
|
139
|
+
if response.is_a?(Net::HTTPSuccess) && event_name == "heartbeat"
|
|
140
|
+
@configuration.last_heartbeat_at = Time.now.utc
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
elsif @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
144
|
+
@circuit_breaker.record_failure
|
|
145
|
+
end
|
|
70
146
|
|
|
147
|
+
response
|
|
148
|
+
rescue Timeout::Error
|
|
149
|
+
@circuit_breaker&.record_failure if @configuration.circuit_breaker_enabled
|
|
150
|
+
nil
|
|
151
|
+
rescue
|
|
152
|
+
@circuit_breaker&.record_failure if @configuration.circuit_breaker_enabled
|
|
71
153
|
nil
|
|
72
154
|
end
|
|
73
155
|
|
|
74
|
-
private
|
|
75
|
-
|
|
76
156
|
def apply_settings_from_response(response)
|
|
77
157
|
return unless response.is_a?(Net::HTTPSuccess)
|
|
78
158
|
|
|
@@ -87,7 +167,6 @@ module DeadBro
|
|
|
87
167
|
# Malformed response — ignore, settings stay as-is
|
|
88
168
|
end
|
|
89
169
|
|
|
90
|
-
# Limit payload size to avoid 413 from nginx/reverse proxies. Returns a new hash.
|
|
91
170
|
def truncate_payload_for_request(payload)
|
|
92
171
|
return payload unless payload.is_a?(Hash)
|
|
93
172
|
|
|
@@ -119,116 +198,6 @@ module DeadBro
|
|
|
119
198
|
)
|
|
120
199
|
end
|
|
121
200
|
|
|
122
|
-
def make_http_request(event_name, payload, api_key)
|
|
123
|
-
use_staging = ENV["USE_STAGING_ENDPOINT"] && !ENV["USE_STAGING_ENDPOINT"].empty?
|
|
124
|
-
production_url = use_staging ? "https://deadbro.aberatii.com/apm/v1/metrics" : "https://www.deadbro.com/apm/v1/metrics"
|
|
125
|
-
endpoint_url = @configuration.ruby_dev ? "http://localhost:3100/apm/v1/metrics" : production_url
|
|
126
|
-
uri = URI.parse(endpoint_url)
|
|
127
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
128
|
-
http.use_ssl = (uri.scheme == "https")
|
|
129
|
-
http.open_timeout = @configuration.open_timeout
|
|
130
|
-
http.read_timeout = @configuration.read_timeout
|
|
131
|
-
|
|
132
|
-
request = Net::HTTP::Post.new(uri.request_uri)
|
|
133
|
-
request["Content-Type"] = "application/json"
|
|
134
|
-
request["Authorization"] = "Bearer #{api_key}"
|
|
135
|
-
if @configuration.settings_received_at
|
|
136
|
-
request["X-Settings-Received-At"] = @configuration.settings_received_at.utc.iso8601
|
|
137
|
-
end
|
|
138
|
-
body = {event: event_name, payload: payload, sent_at: Time.now.utc.iso8601, revision: @configuration.resolve_deploy_id}
|
|
139
|
-
request.body = JSON.dump(body)
|
|
140
|
-
|
|
141
|
-
# Fire-and-forget using a short-lived thread to avoid blocking the request cycle.
|
|
142
|
-
Thread.new do
|
|
143
|
-
response = http.request(request)
|
|
144
|
-
|
|
145
|
-
if response
|
|
146
|
-
# Update circuit breaker based on response
|
|
147
|
-
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
148
|
-
if response.is_a?(Net::HTTPSuccess)
|
|
149
|
-
@circuit_breaker.send(:on_success)
|
|
150
|
-
else
|
|
151
|
-
@circuit_breaker.send(:on_failure)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Apply remote settings if the backend included them in the response
|
|
156
|
-
apply_settings_from_response(response)
|
|
157
|
-
|
|
158
|
-
if response.is_a?(Net::HTTPSuccess) && event_name == "heartbeat"
|
|
159
|
-
@configuration.last_heartbeat_at = Time.now.utc
|
|
160
|
-
end
|
|
161
|
-
elsif @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
162
|
-
# Treat nil response as failure for circuit breaker
|
|
163
|
-
@circuit_breaker.send(:on_failure)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
response
|
|
167
|
-
rescue Timeout::Error
|
|
168
|
-
# Update circuit breaker on timeout
|
|
169
|
-
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
170
|
-
@circuit_breaker.send(:on_failure)
|
|
171
|
-
end
|
|
172
|
-
rescue
|
|
173
|
-
# Update circuit breaker on exception
|
|
174
|
-
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
175
|
-
@circuit_breaker.send(:on_failure)
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
nil
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def make_monitor_request(payload, api_key)
|
|
183
|
-
use_staging = ENV["USE_STAGING_ENDPOINT"] && !ENV["USE_STAGING_ENDPOINT"].empty?
|
|
184
|
-
production_url = use_staging ? "https://deadbro.aberatii.com/apm/v1/monitor" : "https://www.deadbro.com/apm/v1/monitor"
|
|
185
|
-
endpoint_url = @configuration.ruby_dev ? "http://localhost:3100/apm/v1/monitor" : production_url
|
|
186
|
-
uri = URI.parse(endpoint_url)
|
|
187
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
188
|
-
http.use_ssl = (uri.scheme == "https")
|
|
189
|
-
http.open_timeout = @configuration.open_timeout
|
|
190
|
-
http.read_timeout = @configuration.read_timeout
|
|
191
|
-
|
|
192
|
-
request = Net::HTTP::Post.new(uri.request_uri)
|
|
193
|
-
request["Content-Type"] = "application/json"
|
|
194
|
-
request["Authorization"] = "Bearer #{api_key}"
|
|
195
|
-
body = {payload: payload, sent_at: Time.now.utc.iso8601, revision: @configuration.resolve_deploy_id}
|
|
196
|
-
request.body = JSON.dump(body)
|
|
197
|
-
|
|
198
|
-
# Fire-and-forget using a short-lived thread to avoid blocking
|
|
199
|
-
Thread.new do
|
|
200
|
-
response = http.request(request)
|
|
201
|
-
|
|
202
|
-
if response
|
|
203
|
-
# Update circuit breaker based on response
|
|
204
|
-
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
205
|
-
if response.is_a?(Net::HTTPSuccess)
|
|
206
|
-
@circuit_breaker.send(:on_success)
|
|
207
|
-
else
|
|
208
|
-
@circuit_breaker.send(:on_failure)
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
elsif @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
212
|
-
# Treat nil response as failure for circuit breaker
|
|
213
|
-
@circuit_breaker.send(:on_failure)
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
response
|
|
217
|
-
rescue Timeout::Error
|
|
218
|
-
# Update circuit breaker on timeout
|
|
219
|
-
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
220
|
-
@circuit_breaker.send(:on_failure)
|
|
221
|
-
end
|
|
222
|
-
rescue
|
|
223
|
-
# Update circuit breaker on exception
|
|
224
|
-
if @circuit_breaker && @configuration.circuit_breaker_enabled
|
|
225
|
-
@circuit_breaker.send(:on_failure)
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
nil
|
|
230
|
-
end
|
|
231
|
-
|
|
232
201
|
def log_debug(message)
|
|
233
202
|
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
234
203
|
Rails.logger.debug(message)
|