brainzlab 0.1.2 → 0.1.3
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/LICENSE +6 -21
- data/README.md +16 -2
- data/lib/brainzlab/beacon/client.rb +38 -40
- data/lib/brainzlab/beacon/provisioner.rb +1 -1
- data/lib/brainzlab/beacon.rb +15 -15
- data/lib/brainzlab/configuration.rb +92 -90
- data/lib/brainzlab/context.rb +2 -3
- data/lib/brainzlab/cortex/client.rb +29 -31
- data/lib/brainzlab/cortex/provisioner.rb +1 -1
- data/lib/brainzlab/cortex.rb +7 -11
- data/lib/brainzlab/dendrite/client.rb +42 -44
- data/lib/brainzlab/dendrite/provisioner.rb +1 -1
- data/lib/brainzlab/dendrite.rb +4 -4
- data/lib/brainzlab/devtools/data/collector.rb +22 -22
- data/lib/brainzlab/devtools/middleware/asset_server.rb +14 -14
- data/lib/brainzlab/devtools/middleware/database_handler.rb +52 -55
- data/lib/brainzlab/devtools/middleware/debug_panel.rb +19 -19
- data/lib/brainzlab/devtools/middleware/error_page.rb +45 -44
- data/lib/brainzlab/devtools/renderers/debug_panel_renderer.rb +39 -35
- data/lib/brainzlab/devtools/renderers/error_page_renderer.rb +13 -9
- data/lib/brainzlab/devtools.rb +11 -11
- data/lib/brainzlab/flux/buffer.rb +3 -3
- data/lib/brainzlab/flux/client.rb +14 -16
- data/lib/brainzlab/flux/provisioner.rb +13 -13
- data/lib/brainzlab/flux.rb +8 -8
- data/lib/brainzlab/instrumentation/action_mailer.rb +14 -13
- data/lib/brainzlab/instrumentation/active_record.rb +13 -15
- data/lib/brainzlab/instrumentation/aws.rb +43 -39
- data/lib/brainzlab/instrumentation/dalli.rb +20 -20
- data/lib/brainzlab/instrumentation/delayed_job.rb +27 -29
- data/lib/brainzlab/instrumentation/elasticsearch.rb +23 -24
- data/lib/brainzlab/instrumentation/excon.rb +27 -27
- data/lib/brainzlab/instrumentation/faraday.rb +3 -4
- data/lib/brainzlab/instrumentation/good_job.rb +28 -28
- data/lib/brainzlab/instrumentation/grape.rb +24 -24
- data/lib/brainzlab/instrumentation/graphql.rb +24 -23
- data/lib/brainzlab/instrumentation/httparty.rb +13 -14
- data/lib/brainzlab/instrumentation/mongodb.rb +7 -7
- data/lib/brainzlab/instrumentation/net_http.rb +6 -6
- data/lib/brainzlab/instrumentation/redis.rb +14 -21
- data/lib/brainzlab/instrumentation/resque.rb +23 -24
- data/lib/brainzlab/instrumentation/sidekiq.rb +29 -28
- data/lib/brainzlab/instrumentation/solid_queue.rb +37 -41
- data/lib/brainzlab/instrumentation/stripe.rb +36 -37
- data/lib/brainzlab/instrumentation/typhoeus.rb +19 -17
- data/lib/brainzlab/instrumentation.rb +20 -20
- data/lib/brainzlab/nerve/client.rb +38 -40
- data/lib/brainzlab/nerve/provisioner.rb +1 -1
- data/lib/brainzlab/nerve.rb +6 -6
- data/lib/brainzlab/pulse/client.rb +15 -11
- data/lib/brainzlab/pulse/instrumentation.rb +61 -57
- data/lib/brainzlab/pulse/propagation.rb +28 -28
- data/lib/brainzlab/pulse/provisioner.rb +12 -12
- data/lib/brainzlab/pulse/tracer.rb +3 -3
- data/lib/brainzlab/pulse.rb +13 -13
- data/lib/brainzlab/rails/log_formatter.rb +127 -121
- data/lib/brainzlab/rails/log_subscriber.rb +70 -76
- data/lib/brainzlab/rails/railtie.rb +66 -89
- data/lib/brainzlab/recall/buffer.rb +1 -1
- data/lib/brainzlab/recall/client.rb +14 -10
- data/lib/brainzlab/recall/logger.rb +16 -18
- data/lib/brainzlab/recall/provisioner.rb +16 -16
- data/lib/brainzlab/recall.rb +11 -13
- data/lib/brainzlab/reflex/breadcrumbs.rb +2 -2
- data/lib/brainzlab/reflex/client.rb +14 -10
- data/lib/brainzlab/reflex/provisioner.rb +12 -12
- data/lib/brainzlab/reflex.rb +29 -29
- data/lib/brainzlab/sentinel/client.rb +40 -42
- data/lib/brainzlab/sentinel/provisioner.rb +1 -1
- data/lib/brainzlab/sentinel.rb +5 -5
- data/lib/brainzlab/signal/client.rb +12 -14
- data/lib/brainzlab/signal/provisioner.rb +12 -12
- data/lib/brainzlab/signal.rb +7 -7
- data/lib/brainzlab/synapse/client.rb +42 -44
- data/lib/brainzlab/synapse/provisioner.rb +1 -1
- data/lib/brainzlab/synapse.rb +6 -6
- data/lib/brainzlab/utilities/circuit_breaker.rb +37 -41
- data/lib/brainzlab/utilities/health_check.rb +53 -55
- data/lib/brainzlab/utilities/log_formatter.rb +38 -40
- data/lib/brainzlab/utilities/rate_limiter.rb +5 -5
- data/lib/brainzlab/utilities.rb +4 -4
- data/lib/brainzlab/vault/cache.rb +1 -1
- data/lib/brainzlab/vault/client.rb +39 -41
- data/lib/brainzlab/vault/provisioner.rb +1 -1
- data/lib/brainzlab/vault.rb +19 -25
- data/lib/brainzlab/version.rb +1 -1
- data/lib/brainzlab/vision/client.rb +20 -20
- data/lib/brainzlab/vision/provisioner.rb +21 -21
- data/lib/brainzlab/vision.rb +17 -19
- data/lib/brainzlab-sdk.rb +1 -1
- data/lib/brainzlab.rb +22 -24
- data/lib/generators/brainzlab/install/install_generator.rb +29 -27
- metadata +1 -1
|
@@ -4,7 +4,7 @@ module BrainzLab
|
|
|
4
4
|
module DevTools
|
|
5
5
|
module Middleware
|
|
6
6
|
class DebugPanel
|
|
7
|
-
HTML_CONTENT_TYPE =
|
|
7
|
+
HTML_CONTENT_TYPE = 'text/html'
|
|
8
8
|
|
|
9
9
|
def initialize(app)
|
|
10
10
|
@app = app
|
|
@@ -38,24 +38,24 @@ module BrainzLab
|
|
|
38
38
|
return false unless DevTools.debug_panel_enabled?
|
|
39
39
|
return false unless DevTools.allowed_environment?
|
|
40
40
|
return false unless DevTools.allowed_ip?(extract_ip(env))
|
|
41
|
-
return false if asset_request?(env[
|
|
42
|
-
return false if devtools_asset_request?(env[
|
|
41
|
+
return false if asset_request?(env['PATH_INFO'])
|
|
42
|
+
return false if devtools_asset_request?(env['PATH_INFO'])
|
|
43
43
|
return false if turbo_stream_request?(env)
|
|
44
44
|
|
|
45
45
|
true
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def extract_ip(env)
|
|
49
|
-
forwarded = env[
|
|
50
|
-
return forwarded.split(
|
|
49
|
+
forwarded = env['HTTP_X_FORWARDED_FOR']
|
|
50
|
+
return forwarded.split(',').first.strip if forwarded
|
|
51
51
|
|
|
52
|
-
env[
|
|
52
|
+
env['REMOTE_ADDR']
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def injectable_response?(status, headers)
|
|
56
56
|
return false unless status == 200
|
|
57
57
|
|
|
58
|
-
content_type = headers[
|
|
58
|
+
content_type = headers['Content-Type']
|
|
59
59
|
return false unless content_type
|
|
60
60
|
|
|
61
61
|
content_type.include?(HTML_CONTENT_TYPE)
|
|
@@ -78,11 +78,11 @@ module BrainzLab
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def turbo_stream_request?(env)
|
|
81
|
-
accept = env[
|
|
82
|
-
accept.include?(
|
|
81
|
+
accept = env['HTTP_ACCEPT'] || ''
|
|
82
|
+
accept.include?('text/vnd.turbo-stream.html')
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
def inject_panel(body,
|
|
85
|
+
def inject_panel(body, _env, status, headers)
|
|
86
86
|
# Collect all response body parts
|
|
87
87
|
full_body = collect_body(body)
|
|
88
88
|
|
|
@@ -91,25 +91,25 @@ module BrainzLab
|
|
|
91
91
|
data[:response] = {
|
|
92
92
|
status: status,
|
|
93
93
|
headers: headers.to_h,
|
|
94
|
-
content_type: headers[
|
|
94
|
+
content_type: headers['Content-Type']
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
# Render panel HTML
|
|
98
98
|
panel_html = @renderer.render(data)
|
|
99
99
|
|
|
100
100
|
# Inject before </body>
|
|
101
|
-
if full_body.include?(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
full_body = if full_body.include?('</body>')
|
|
102
|
+
full_body.sub('</body>', "#{panel_html}</body>")
|
|
103
|
+
else
|
|
104
|
+
# If no </body> tag, append at the end
|
|
105
|
+
"#{full_body}#{panel_html}"
|
|
106
|
+
end
|
|
107
107
|
|
|
108
108
|
[full_body]
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def collect_body(body)
|
|
112
|
-
full_body = +
|
|
112
|
+
full_body = +''
|
|
113
113
|
body.each { |part| full_body << part }
|
|
114
114
|
body.close if body.respond_to?(:close)
|
|
115
115
|
full_body
|
|
@@ -117,7 +117,7 @@ module BrainzLab
|
|
|
117
117
|
|
|
118
118
|
def update_content_length(headers, body)
|
|
119
119
|
headers = headers.to_h.dup
|
|
120
|
-
headers[
|
|
120
|
+
headers['Content-Length'] = body.sum(&:bytesize).to_s
|
|
121
121
|
headers
|
|
122
122
|
end
|
|
123
123
|
end
|
|
@@ -19,7 +19,7 @@ module BrainzLab
|
|
|
19
19
|
if status >= 400 && html_response?(headers) && !json_request?(env)
|
|
20
20
|
# Check if this looks like Rails' default error page
|
|
21
21
|
body_content = collect_body(body)
|
|
22
|
-
if body_content.include?(
|
|
22
|
+
if body_content.include?('Action Controller: Exception caught') || body_content.include?('background: #C00')
|
|
23
23
|
# Extract exception info from the page
|
|
24
24
|
exception_info = extract_exception_from_html(body_content)
|
|
25
25
|
if exception_info
|
|
@@ -30,41 +30,41 @@ module BrainzLab
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
[status, headers, body]
|
|
33
|
-
rescue Exception =>
|
|
33
|
+
rescue Exception => e
|
|
34
34
|
# Don't intercept if request wants JSON
|
|
35
|
-
return raise_exception(
|
|
35
|
+
return raise_exception(e) if json_request?(env)
|
|
36
36
|
|
|
37
37
|
# Still capture to Reflex if available
|
|
38
|
-
capture_to_reflex(
|
|
38
|
+
capture_to_reflex(e)
|
|
39
39
|
|
|
40
40
|
# Collect debug data and render branded error page
|
|
41
|
-
data = collect_debug_data(env,
|
|
42
|
-
render_error_page(
|
|
41
|
+
data = collect_debug_data(env, e)
|
|
42
|
+
render_error_page(e, data)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def html_response?(headers)
|
|
47
47
|
# Handle both uppercase and lowercase header names
|
|
48
|
-
content_type = headers[
|
|
49
|
-
content_type.to_s.downcase.include?(
|
|
48
|
+
content_type = headers['Content-Type'] || headers['content-type'] || ''
|
|
49
|
+
content_type.to_s.downcase.include?('text/html')
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def extract_exception_from_html(body)
|
|
53
53
|
# Try to extract exception class and message from Rails error page
|
|
54
|
-
if match = body.match(
|
|
54
|
+
if (match = body.match(%r{<h1>([^<]+)</h1>}))
|
|
55
55
|
error_title = match[1]
|
|
56
56
|
# Extract the exception message from the page
|
|
57
|
-
if msg_match = body.match(
|
|
57
|
+
if (msg_match = body.match(%r{<pre[^>]*>([^<]+)</pre>}))
|
|
58
58
|
error_message = msg_match[1]
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Try to extract backtrace from Rails 8 format
|
|
62
62
|
# Format: <a class="trace-frames ...">path/to/file.rb:123:in 'method'</a>
|
|
63
63
|
backtrace = []
|
|
64
|
-
body.scan(
|
|
64
|
+
body.scan(%r{<a[^>]*class="trace-frames[^"]*"[^>]*>\s*([^<]+)\s*</a>}m) do |trace_match|
|
|
65
65
|
line = trace_match[0].strip
|
|
66
66
|
# Decode HTML entities
|
|
67
|
-
line = line.gsub(
|
|
67
|
+
line = line.gsub(''', "'").gsub('"', '"').gsub('&', '&').gsub('<', '<').gsub('>', '>')
|
|
68
68
|
backtrace << line unless line.empty?
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -78,12 +78,13 @@ module BrainzLab
|
|
|
78
78
|
|
|
79
79
|
def decode_html_entities(str)
|
|
80
80
|
return str unless str
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.gsub(
|
|
84
|
-
.gsub(
|
|
85
|
-
.gsub(
|
|
86
|
-
.gsub(
|
|
81
|
+
|
|
82
|
+
str.gsub(''', "'")
|
|
83
|
+
.gsub('"', '"')
|
|
84
|
+
.gsub('&', '&')
|
|
85
|
+
.gsub('<', '<')
|
|
86
|
+
.gsub('>', '>')
|
|
87
|
+
.gsub(' ', ' ')
|
|
87
88
|
end
|
|
88
89
|
|
|
89
90
|
def collect_debug_data_from_info(env, info)
|
|
@@ -127,9 +128,9 @@ module BrainzLab
|
|
|
127
128
|
[
|
|
128
129
|
500,
|
|
129
130
|
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
'Content-Type' => 'text/html; charset=utf-8',
|
|
132
|
+
'Content-Length' => html.bytesize.to_s,
|
|
133
|
+
'X-Content-Type-Options' => 'nosniff'
|
|
133
134
|
},
|
|
134
135
|
[html]
|
|
135
136
|
]
|
|
@@ -138,7 +139,7 @@ module BrainzLab
|
|
|
138
139
|
private
|
|
139
140
|
|
|
140
141
|
def collect_body(body)
|
|
141
|
-
full_body = +
|
|
142
|
+
full_body = +''
|
|
142
143
|
body.each { |part| full_body << part }
|
|
143
144
|
body.close if body.respond_to?(:close)
|
|
144
145
|
full_body
|
|
@@ -153,19 +154,19 @@ module BrainzLab
|
|
|
153
154
|
end
|
|
154
155
|
|
|
155
156
|
def extract_ip(env)
|
|
156
|
-
forwarded = env[
|
|
157
|
-
return forwarded.split(
|
|
157
|
+
forwarded = env['HTTP_X_FORWARDED_FOR']
|
|
158
|
+
return forwarded.split(',').first.strip if forwarded
|
|
158
159
|
|
|
159
|
-
env[
|
|
160
|
+
env['REMOTE_ADDR']
|
|
160
161
|
end
|
|
161
162
|
|
|
162
163
|
def json_request?(env)
|
|
163
|
-
accept = env[
|
|
164
|
-
content_type = env[
|
|
164
|
+
accept = env['HTTP_ACCEPT'] || ''
|
|
165
|
+
content_type = env['CONTENT_TYPE'] || ''
|
|
165
166
|
|
|
166
|
-
accept.include?(
|
|
167
|
-
content_type.include?(
|
|
168
|
-
env[
|
|
167
|
+
accept.include?('application/json') ||
|
|
168
|
+
content_type.include?('application/json') ||
|
|
169
|
+
env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
|
|
169
170
|
end
|
|
170
171
|
|
|
171
172
|
def capture_to_reflex(exception)
|
|
@@ -199,9 +200,9 @@ module BrainzLab
|
|
|
199
200
|
request = defined?(ActionDispatch::Request) ? ActionDispatch::Request.new(env) : nil
|
|
200
201
|
|
|
201
202
|
{
|
|
202
|
-
method: request&.request_method || env[
|
|
203
|
-
path: request&.path || env[
|
|
204
|
-
url: request&.url || env[
|
|
203
|
+
method: request&.request_method || env['REQUEST_METHOD'],
|
|
204
|
+
path: request&.path || env['PATH_INFO'],
|
|
205
|
+
url: request&.url || env['REQUEST_URI'],
|
|
205
206
|
params: scrub_params(context&.request_params || extract_params(env)),
|
|
206
207
|
headers: extract_headers(env),
|
|
207
208
|
session: {}
|
|
@@ -228,8 +229,8 @@ module BrainzLab
|
|
|
228
229
|
def extract_headers(env)
|
|
229
230
|
headers = {}
|
|
230
231
|
env.each do |key, value|
|
|
231
|
-
if key.start_with?(
|
|
232
|
-
header_name = key.sub(
|
|
232
|
+
if key.start_with?('HTTP_')
|
|
233
|
+
header_name = key.sub('HTTP_', '').split('_').map(&:capitalize).join('-')
|
|
233
234
|
headers[header_name] = value
|
|
234
235
|
end
|
|
235
236
|
end
|
|
@@ -243,7 +244,7 @@ module BrainzLab
|
|
|
243
244
|
|
|
244
245
|
params.transform_values.with_index do |(key, value), _|
|
|
245
246
|
if scrub_fields.include?(key.to_s.downcase)
|
|
246
|
-
|
|
247
|
+
'[FILTERED]'
|
|
247
248
|
elsif value.is_a?(Hash)
|
|
248
249
|
scrub_params(value)
|
|
249
250
|
else
|
|
@@ -277,14 +278,14 @@ module BrainzLab
|
|
|
277
278
|
def in_app_frame?(file)
|
|
278
279
|
return false unless file
|
|
279
280
|
|
|
280
|
-
file.include?(
|
|
281
|
+
file.include?('/app/') && !file.include?('/vendor/') && !file.include?('/gems/')
|
|
281
282
|
end
|
|
282
283
|
|
|
283
284
|
def extract_source_from_backtrace(backtrace_lines)
|
|
284
285
|
return nil if backtrace_lines.empty?
|
|
285
286
|
|
|
286
287
|
# Find the first in-app frame
|
|
287
|
-
target_line = backtrace_lines.find { |line| in_app_frame?(line.split(
|
|
288
|
+
target_line = backtrace_lines.find { |line| in_app_frame?(line.split(':').first) }
|
|
288
289
|
target_line ||= backtrace_lines.first
|
|
289
290
|
|
|
290
291
|
match = target_line.match(/\A(.+):(\d+)/)
|
|
@@ -317,7 +318,7 @@ module BrainzLab
|
|
|
317
318
|
return nil unless exception.backtrace&.any?
|
|
318
319
|
|
|
319
320
|
# Find the first in-app frame (application code, not gems/framework)
|
|
320
|
-
target_line = exception.backtrace.find { |line| in_app_frame?(line.split(
|
|
321
|
+
target_line = exception.backtrace.find { |line| in_app_frame?(line.split(':').first) }
|
|
321
322
|
# Fall back to first frame if no in-app frame found
|
|
322
323
|
target_line ||= exception.backtrace.first
|
|
323
324
|
|
|
@@ -349,10 +350,10 @@ module BrainzLab
|
|
|
349
350
|
|
|
350
351
|
def collect_environment_info
|
|
351
352
|
{
|
|
352
|
-
rails_version: defined?(Rails::VERSION::STRING) ? Rails::VERSION::STRING :
|
|
353
|
+
rails_version: defined?(::Rails::VERSION::STRING) ? ::Rails::VERSION::STRING : 'N/A',
|
|
353
354
|
ruby_version: RUBY_VERSION,
|
|
354
355
|
env: BrainzLab.configuration.environment,
|
|
355
|
-
server: ENV[
|
|
356
|
+
server: ENV['SERVER_SOFTWARE'] || 'Unknown',
|
|
356
357
|
pid: Process.pid
|
|
357
358
|
}
|
|
358
359
|
end
|
|
@@ -363,9 +364,9 @@ module BrainzLab
|
|
|
363
364
|
[
|
|
364
365
|
500,
|
|
365
366
|
{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
367
|
+
'Content-Type' => 'text/html; charset=utf-8',
|
|
368
|
+
'Content-Length' => html.bytesize.to_s,
|
|
369
|
+
'X-Content-Type-Options' => 'nosniff'
|
|
369
370
|
},
|
|
370
371
|
[html]
|
|
371
372
|
]
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'cgi'
|
|
5
|
+
require 'json'
|
|
6
6
|
|
|
7
7
|
module BrainzLab
|
|
8
8
|
module DevTools
|
|
9
9
|
module Renderers
|
|
10
10
|
class DebugPanelRenderer
|
|
11
11
|
def initialize
|
|
12
|
-
@template_path = File.join(DevTools::ASSETS_PATH,
|
|
12
|
+
@template_path = File.join(DevTools::ASSETS_PATH, 'templates', 'debug_panel.html.erb')
|
|
13
13
|
# Cache compiled ERB template to avoid file I/O on every request
|
|
14
14
|
@cached_erb = nil
|
|
15
15
|
@template_mtime = nil
|
|
@@ -47,7 +47,7 @@ module BrainzLab
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def json_pretty(obj)
|
|
50
|
-
return
|
|
50
|
+
return '' if obj.nil? || (obj.respond_to?(:empty?) && obj.empty?)
|
|
51
51
|
|
|
52
52
|
JSON.pretty_generate(obj)
|
|
53
53
|
rescue StandardError
|
|
@@ -55,14 +55,14 @@ module BrainzLab
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def truncate(text, length = 80)
|
|
58
|
-
return
|
|
58
|
+
return '' unless text
|
|
59
59
|
|
|
60
60
|
text = text.to_s
|
|
61
61
|
text.length > length ? "#{text[0...length]}..." : text
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def format_duration(ms)
|
|
65
|
-
return
|
|
65
|
+
return '0ms' unless ms
|
|
66
66
|
|
|
67
67
|
if ms >= 1000
|
|
68
68
|
"#{(ms / 1000.0).round(2)}s"
|
|
@@ -72,78 +72,82 @@ module BrainzLab
|
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def duration_class(ms)
|
|
75
|
-
return
|
|
75
|
+
return '' unless ms
|
|
76
76
|
|
|
77
77
|
if ms > 1000
|
|
78
|
-
|
|
78
|
+
'very-slow'
|
|
79
79
|
elsif ms > 500
|
|
80
|
-
|
|
80
|
+
'slow'
|
|
81
81
|
elsif ms > 200
|
|
82
|
-
|
|
82
|
+
'moderate'
|
|
83
83
|
else
|
|
84
|
-
|
|
84
|
+
''
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def query_duration_class(ms)
|
|
89
|
-
return
|
|
89
|
+
return '' unless ms
|
|
90
90
|
|
|
91
91
|
if ms > 100
|
|
92
|
-
|
|
92
|
+
'very-slow'
|
|
93
93
|
elsif ms > 50
|
|
94
|
-
|
|
94
|
+
'slow'
|
|
95
95
|
elsif ms > 10
|
|
96
|
-
|
|
96
|
+
'moderate'
|
|
97
97
|
else
|
|
98
|
-
|
|
98
|
+
''
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def status_class(status)
|
|
103
103
|
case status
|
|
104
|
-
when 200..299 then
|
|
105
|
-
when 300..399 then
|
|
106
|
-
when 400..499 then
|
|
107
|
-
when 500..599 then
|
|
108
|
-
else
|
|
104
|
+
when 200..299 then 'success'
|
|
105
|
+
when 300..399 then 'redirect'
|
|
106
|
+
when 400..499 then 'client-error'
|
|
107
|
+
when 500..599 then 'server-error'
|
|
108
|
+
else ''
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def log_level_class(level)
|
|
113
113
|
case level.to_s.downcase
|
|
114
|
-
when
|
|
115
|
-
when
|
|
116
|
-
when
|
|
117
|
-
when
|
|
118
|
-
else
|
|
114
|
+
when 'error', 'fatal' then 'error'
|
|
115
|
+
when 'warn', 'warning' then 'warning'
|
|
116
|
+
when 'info' then 'info'
|
|
117
|
+
when 'debug' then 'debug'
|
|
118
|
+
else ''
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def format_timestamp(time)
|
|
123
|
-
return
|
|
123
|
+
return '' unless time
|
|
124
124
|
|
|
125
|
-
time.strftime(
|
|
125
|
+
time.strftime('%H:%M:%S.%L')
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def memory_class(delta_mb)
|
|
129
|
-
return
|
|
129
|
+
return '' unless delta_mb
|
|
130
130
|
|
|
131
131
|
if delta_mb > 50
|
|
132
|
-
|
|
132
|
+
'high'
|
|
133
133
|
elsif delta_mb > 20
|
|
134
|
-
|
|
134
|
+
'moderate'
|
|
135
135
|
else
|
|
136
|
-
|
|
136
|
+
''
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
# Cache compiled ERB template, reloading only if file changed (dev mode)
|
|
141
141
|
def cached_erb
|
|
142
|
-
current_mtime =
|
|
142
|
+
current_mtime = begin
|
|
143
|
+
File.mtime(@template_path)
|
|
144
|
+
rescue StandardError
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
143
147
|
|
|
144
148
|
if @cached_erb.nil? || (current_mtime && current_mtime != @template_mtime)
|
|
145
149
|
template = File.read(@template_path)
|
|
146
|
-
@cached_erb = ERB.new(template, trim_mode:
|
|
150
|
+
@cached_erb = ERB.new(template, trim_mode: '-')
|
|
147
151
|
@template_mtime = current_mtime
|
|
148
152
|
end
|
|
149
153
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'cgi'
|
|
5
5
|
|
|
6
6
|
module BrainzLab
|
|
7
7
|
module DevTools
|
|
8
8
|
module Renderers
|
|
9
9
|
class ErrorPageRenderer
|
|
10
10
|
def initialize
|
|
11
|
-
@template_path = File.join(DevTools::ASSETS_PATH,
|
|
11
|
+
@template_path = File.join(DevTools::ASSETS_PATH, 'templates', 'error_page.html.erb')
|
|
12
12
|
@cached_erb = nil
|
|
13
13
|
@template_mtime = nil
|
|
14
14
|
end
|
|
@@ -40,10 +40,10 @@ module BrainzLab
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def format_params(params, indent = 0)
|
|
43
|
-
return
|
|
43
|
+
return '' if params.nil? || params.empty?
|
|
44
44
|
|
|
45
45
|
lines = []
|
|
46
|
-
prefix =
|
|
46
|
+
prefix = ' ' * indent
|
|
47
47
|
|
|
48
48
|
params.each do |key, value|
|
|
49
49
|
if value.is_a?(Hash)
|
|
@@ -60,13 +60,13 @@ module BrainzLab
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def truncate(text, length = 100)
|
|
63
|
-
return
|
|
63
|
+
return '' unless text
|
|
64
64
|
|
|
65
65
|
text.length > length ? "#{text[0...length]}..." : text
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def time_ago(time)
|
|
69
|
-
return
|
|
69
|
+
return 'unknown' unless time
|
|
70
70
|
|
|
71
71
|
seconds = Time.now.utc - time
|
|
72
72
|
case seconds
|
|
@@ -78,11 +78,15 @@ module BrainzLab
|
|
|
78
78
|
|
|
79
79
|
# Cache compiled ERB template, reloading only if file changed
|
|
80
80
|
def cached_erb
|
|
81
|
-
current_mtime =
|
|
81
|
+
current_mtime = begin
|
|
82
|
+
File.mtime(@template_path)
|
|
83
|
+
rescue StandardError
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
82
86
|
|
|
83
87
|
if @cached_erb.nil? || (current_mtime && current_mtime != @template_mtime)
|
|
84
88
|
template = File.read(@template_path)
|
|
85
|
-
@cached_erb = ERB.new(template, trim_mode:
|
|
89
|
+
@cached_erb = ERB.new(template, trim_mode: '-')
|
|
86
90
|
@template_mtime = current_mtime
|
|
87
91
|
end
|
|
88
92
|
|
data/lib/brainzlab/devtools.rb
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
3
|
+
require_relative 'devtools/data/collector'
|
|
4
|
+
require_relative 'devtools/middleware/asset_server'
|
|
5
|
+
require_relative 'devtools/middleware/database_handler'
|
|
6
|
+
require_relative 'devtools/middleware/error_page'
|
|
7
|
+
require_relative 'devtools/middleware/debug_panel'
|
|
8
|
+
require_relative 'devtools/renderers/error_page_renderer'
|
|
9
|
+
require_relative 'devtools/renderers/debug_panel_renderer'
|
|
10
10
|
|
|
11
11
|
module BrainzLab
|
|
12
12
|
module DevTools
|
|
13
|
-
ASSETS_PATH = File.expand_path(
|
|
13
|
+
ASSETS_PATH = File.expand_path('devtools/assets', __dir__)
|
|
14
14
|
|
|
15
15
|
class << self
|
|
16
16
|
def enabled?
|
|
@@ -33,7 +33,7 @@ module BrainzLab
|
|
|
33
33
|
|
|
34
34
|
def allowed_ip?(request_ip)
|
|
35
35
|
# Skip IP checking in development - environment check is enough
|
|
36
|
-
return true if BrainzLab.configuration.environment ==
|
|
36
|
+
return true if BrainzLab.configuration.environment == 'development'
|
|
37
37
|
|
|
38
38
|
return true if BrainzLab.configuration.devtools_allowed_ips.empty?
|
|
39
39
|
|
|
@@ -42,7 +42,7 @@ module BrainzLab
|
|
|
42
42
|
|
|
43
43
|
# Check CIDR ranges
|
|
44
44
|
allowed_ips.any? do |ip|
|
|
45
|
-
if ip.include?(
|
|
45
|
+
if ip.include?('/')
|
|
46
46
|
ip_in_cidr?(request_ip, ip)
|
|
47
47
|
else
|
|
48
48
|
ip == request_ip
|
|
@@ -65,7 +65,7 @@ module BrainzLab
|
|
|
65
65
|
private
|
|
66
66
|
|
|
67
67
|
def ip_in_cidr?(ip, cidr)
|
|
68
|
-
require
|
|
68
|
+
require 'ipaddr'
|
|
69
69
|
IPAddr.new(cidr).include?(IPAddr.new(ip))
|
|
70
70
|
rescue IPAddr::InvalidAddressError
|
|
71
71
|
false
|
|
@@ -75,7 +75,7 @@ module BrainzLab
|
|
|
75
75
|
return if events.empty? && metrics.empty?
|
|
76
76
|
|
|
77
77
|
@client.send_batch(events: events, metrics: metrics)
|
|
78
|
-
rescue => e
|
|
78
|
+
rescue StandardError => e
|
|
79
79
|
BrainzLab.debug("[Flux] Batch send failed: #{e.message}")
|
|
80
80
|
end
|
|
81
81
|
|
|
@@ -84,8 +84,8 @@ module BrainzLab
|
|
|
84
84
|
loop do
|
|
85
85
|
sleep FLUSH_INTERVAL
|
|
86
86
|
begin
|
|
87
|
-
flush! if size
|
|
88
|
-
rescue => e
|
|
87
|
+
flush! if size.positive?
|
|
88
|
+
rescue StandardError => e
|
|
89
89
|
BrainzLab.debug("[Flux] Flush thread error: #{e.message}")
|
|
90
90
|
end
|
|
91
91
|
end
|