rails_trace_viewer 0.1.2 → 0.1.4
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 +5 -1
- data/app/views/rails_trace_viewer/traces/show.html.erb +34 -21
- data/lib/rails_trace_viewer/active_job_extension.rb +45 -0
- data/lib/rails_trace_viewer/collector.rb +0 -17
- data/lib/rails_trace_viewer/engine.rb +55 -32
- data/lib/rails_trace_viewer/subscribers/active_job_subscriber.rb +28 -31
- data/lib/rails_trace_viewer/subscribers/method_subscriber.rb +22 -0
- data/lib/rails_trace_viewer/subscribers/sidekiq_subscriber.rb +2 -11
- data/lib/rails_trace_viewer/subscribers/sql_subscriber.rb +19 -8
- data/lib/rails_trace_viewer/version.rb +1 -1
- data/lib/rails_trace_viewer.rb +1 -1
- metadata +2 -2
- data/lib/rails_trace_viewer/job_link_registry.rb +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abac85cedd4d86a997eea9b60f1b449e88ff3799cd13926fa83c143c3d3d1abb
|
|
4
|
+
data.tar.gz: 5fc903e2e18a93e94acb048a6b3350c06346840e285119f9e9221d9dc3f76b68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 20aa8961b4ce0797d78614075b8cb100f30b28a1a98e9286d839da5ef04c852f7fcc519b97a3228c8ad1bb2642341726048f5a6bae10337f92e0dc23c75d03ed
|
|
7
|
+
data.tar.gz: 9769095c141f0bf4024fea8a6e74da56600e4299eaf2b7d98cfb53139e442856e08ae7dad0a26f30f1fadb244b5a57c5e32cf5b5244b2e0b9cef28a8d0723a01
|
data/README.md
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
An educational and debugging tool for Ruby on Rails to visualize the request lifecycle in real-time.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<div align="center">
|
|
6
|
+
<a href="https://youtu.be/duSncwziSwE">
|
|
7
|
+
<img src="https://markdown-videos-api.jorgenkh.no/url?url=https%3A%2F%2Fyoutu.be%2FduSncwziSwE" alt="Watch the Demo" style="width:100%; max-width:800px; border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.15);">
|
|
8
|
+
</a>
|
|
9
|
+
</div>
|
|
6
10
|
|
|
7
11
|
Rails Trace Viewer provides a beautiful, interactive Call Stack Tree that visualizes how your Rails application processes requests. It traces the flow from the Controller through Models, Views, SQL Queries, and even across process boundaries into Sidekiq Background Jobs.
|
|
8
12
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>Rails Trace Viewer</title>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
|
|
5
8
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
6
9
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
7
10
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
|
|
@@ -144,7 +147,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
144
147
|
const emptyState = document.getElementById("empty-state");
|
|
145
148
|
let graphs = {};
|
|
146
149
|
let nodeBuffer = {};
|
|
147
|
-
let verticalOffset = 60;
|
|
148
150
|
const DAG_SPACING = 200;
|
|
149
151
|
|
|
150
152
|
// Node Dimensions
|
|
@@ -167,24 +169,34 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
167
169
|
let subscription = null;
|
|
168
170
|
|
|
169
171
|
function setupSubscription() {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
172
|
+
try {
|
|
173
|
+
if (cable) cable.disconnect();
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.warn("Error disconnecting previous cable:", err);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
cable = ActionCable.createConsumer();
|
|
180
|
+
subscription = cable.subscriptions.create({ channel: "RailsTraceViewer::TraceChannel" }, {
|
|
181
|
+
connected() { console.log("✅ TraceViewer: Connected"); },
|
|
182
|
+
disconnected() { console.log("❌ TraceViewer: Disconnected"); },
|
|
183
|
+
received(data) {
|
|
184
|
+
try {
|
|
185
|
+
if (!data || !data.node) return;
|
|
186
|
+
if ((data.node.name || "").includes("RailsTraceViewer")) return;
|
|
187
|
+
|
|
188
|
+
emptyState.classList.add("hidden");
|
|
189
|
+
addNodeToGraph(data.trace_id, data.node);
|
|
190
|
+
if (this.redrawTimer) clearTimeout(this.redrawTimer);
|
|
191
|
+
this.redrawTimer = setTimeout(redrawAllGraphs, 100);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error("Error processing trace message:", err);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.error("Failed to create ActionCable consumer. Is ActionCable mounted at /cable?", err);
|
|
199
|
+
}
|
|
188
200
|
}
|
|
189
201
|
|
|
190
202
|
setupSubscription();
|
|
@@ -195,6 +207,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
195
207
|
let changed = false;
|
|
196
208
|
Object.keys(nodeBuffer).forEach(pid => {
|
|
197
209
|
const list = nodeBuffer[pid];
|
|
210
|
+
if (!list) return;
|
|
198
211
|
const kept = [];
|
|
199
212
|
list.forEach(item => {
|
|
200
213
|
if (now - item.receivedAt > 3000) {
|
|
@@ -234,7 +247,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
234
247
|
// --- Rendering ---
|
|
235
248
|
function redrawAllGraphs() {
|
|
236
249
|
g.selectAll("*").remove();
|
|
237
|
-
verticalOffset = 60;
|
|
250
|
+
let verticalOffset = 60;
|
|
238
251
|
|
|
239
252
|
Object.keys(graphs).forEach(tid => {
|
|
240
253
|
renderSingleDAG(graphs[tid], verticalOffset);
|
|
@@ -259,7 +272,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
259
272
|
|
|
260
273
|
Object.values(G.nodes).forEach(n => {
|
|
261
274
|
let lbl = n.name || "Unknown";
|
|
262
|
-
if (lbl.length >
|
|
275
|
+
if (lbl.length > 29) lbl = lbl.substring(0, 27) + "...";
|
|
263
276
|
dag.setNode(n.id, { label: lbl, width: NODE_WIDTH, height: NODE_HEIGHT, type: n.type, fullData: n });
|
|
264
277
|
});
|
|
265
278
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module RailsTraceViewer
|
|
2
|
+
module ActiveJobExtension
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
attr_accessor :rtv_trace_id, :rtv_parent_id
|
|
7
|
+
|
|
8
|
+
around_enqueue do |job, block|
|
|
9
|
+
if defined?(RailsTraceViewer) && RailsTraceViewer.enabled
|
|
10
|
+
if RailsTraceViewer::TraceContext.active?
|
|
11
|
+
job.rtv_trace_id = RailsTraceViewer::TraceContext.trace_id
|
|
12
|
+
job.rtv_parent_id = RailsTraceViewer::TraceContext.parent_id
|
|
13
|
+
else
|
|
14
|
+
job.rtv_trace_id ||= SecureRandom.uuid
|
|
15
|
+
job.rtv_parent_id ||= nil
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
block.call
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialize
|
|
24
|
+
data = super
|
|
25
|
+
|
|
26
|
+
if defined?(RailsTraceViewer) && RailsTraceViewer.enabled
|
|
27
|
+
data["rtv_trace_id"] = rtv_trace_id if respond_to?(:rtv_trace_id) && rtv_trace_id
|
|
28
|
+
data["rtv_parent_id"] = rtv_parent_id if respond_to?(:rtv_parent_id) && rtv_parent_id
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
data
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def deserialize(job_data)
|
|
35
|
+
super(job_data)
|
|
36
|
+
|
|
37
|
+
if defined?(RailsTraceViewer) && RailsTraceViewer.enabled
|
|
38
|
+
self.rtv_trace_id = job_data["rtv_trace_id"] if job_data.key?("rtv_trace_id") && respond_to?(:rtv_trace_id=)
|
|
39
|
+
self.rtv_parent_id = job_data["rtv_parent_id"] if job_data.key?("rtv_parent_id") && respond_to?(:rtv_parent_id=)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -129,23 +129,6 @@ module RailsTraceViewer
|
|
|
129
129
|
|
|
130
130
|
after.each(&:call)
|
|
131
131
|
end
|
|
132
|
-
|
|
133
|
-
def finalize_trace(trace_id)
|
|
134
|
-
trace = nil
|
|
135
|
-
|
|
136
|
-
@mutex.synchronize do
|
|
137
|
-
tree = TRACE_TREES[trace_id]
|
|
138
|
-
return unless tree
|
|
139
|
-
trace = tree[:root]
|
|
140
|
-
TRACE_TREES.delete(trace_id)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
ActionCable.server.broadcast(
|
|
144
|
-
"rails_trace_viewer",
|
|
145
|
-
event: "trace_completed",
|
|
146
|
-
trace: trace
|
|
147
|
-
)
|
|
148
|
-
end
|
|
149
132
|
end
|
|
150
133
|
end
|
|
151
134
|
end
|
|
@@ -2,48 +2,71 @@ module RailsTraceViewer
|
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
isolate_namespace RailsTraceViewer
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
initializer "rails_trace_viewer.active_job_extension" do
|
|
6
|
+
ActiveSupport.on_load(:active_job) do
|
|
7
|
+
include RailsTraceViewer::ActiveJobExtension
|
|
8
8
|
end
|
|
9
|
+
end
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
class << self
|
|
12
|
+
def enable_tracing!
|
|
13
|
+
return if @tracing_initialized
|
|
14
|
+
|
|
15
|
+
if Rails.application.routes.routes.empty?
|
|
16
|
+
Rails.application.reload_routes!
|
|
14
17
|
end
|
|
15
|
-
match_class = (app == RailsTraceViewer::Engine) || (app.to_s.include?("RailsTraceViewer::Engine"))
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
is_mounted = Rails.application.routes.routes.any? do |route|
|
|
20
|
+
app = route.app
|
|
21
|
+
while app.respond_to?(:app) && app != app.app
|
|
22
|
+
app = app.app
|
|
23
|
+
end
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
match_class = (app == RailsTraceViewer::Engine) ||
|
|
26
|
+
app.to_s.include?("RailsTraceViewer::Engine")
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
puts "✅ [RailsTraceViewer] Engine mounted. Tracing is ACTIVE."
|
|
27
|
-
@booted_message_shown = true
|
|
28
|
+
path_match = (route.path.spec.to_s rescue "")
|
|
29
|
+
.include?("/rails_trace_viewer")
|
|
30
|
+
|
|
31
|
+
match_class || path_match
|
|
28
32
|
end
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
if is_mounted
|
|
35
|
+
RailsTraceViewer.enabled = true
|
|
36
|
+
|
|
37
|
+
unless @booted_message_shown
|
|
38
|
+
puts "✅ [RailsTraceViewer] Engine mounted. Tracing is ACTIVE."
|
|
39
|
+
@booted_message_shown = true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
RailsTraceViewer::Subscribers::ControllerSubscriber.attach
|
|
43
|
+
RailsTraceViewer::Subscribers::SqlSubscriber.attach
|
|
44
|
+
RailsTraceViewer::Subscribers::ViewSubscriber.attach
|
|
45
|
+
RailsTraceViewer::Subscribers::ActiveJobSubscriber.attach
|
|
46
|
+
RailsTraceViewer::Subscribers::SidekiqSubscriber.attach if defined?(Sidekiq)
|
|
47
|
+
RailsTraceViewer::Subscribers::MethodSubscriber.attach
|
|
48
|
+
|
|
49
|
+
RailsTraceViewer::Collector.start_sweeper!
|
|
50
|
+
|
|
51
|
+
else
|
|
52
|
+
RailsTraceViewer.enabled = false
|
|
53
|
+
|
|
54
|
+
unless @booted_message_shown
|
|
55
|
+
puts "🚫 [RailsTraceViewer] Engine route not found. Tracing DISABLED (Zero Overhead)."
|
|
56
|
+
@booted_message_shown = true
|
|
57
|
+
end
|
|
45
58
|
end
|
|
59
|
+
|
|
60
|
+
@tracing_initialized = true
|
|
46
61
|
end
|
|
47
62
|
end
|
|
63
|
+
|
|
64
|
+
config.to_prepare do
|
|
65
|
+
RailsTraceViewer::Engine.enable_tracing!
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
console do
|
|
69
|
+
RailsTraceViewer::Engine.enable_tracing!
|
|
70
|
+
end
|
|
48
71
|
end
|
|
49
72
|
end
|
|
@@ -9,19 +9,15 @@ module RailsTraceViewer
|
|
|
9
9
|
|
|
10
10
|
ActiveSupport::Notifications.subscribe("enqueue.active_job") do |*_args, payload|
|
|
11
11
|
job = payload[:job]
|
|
12
|
-
next if job.class.include?(Sidekiq::Worker)
|
|
12
|
+
next if job.class.include?(Sidekiq::Worker) rescue false
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
parent_id = RailsTraceViewer::TraceContext.parent_id
|
|
17
|
-
else
|
|
18
|
-
trace_id = SecureRandom.uuid
|
|
19
|
-
parent_id = nil
|
|
20
|
-
RailsTraceViewer::TraceContext.start!(trace_id)
|
|
21
|
-
RailsTraceViewer::Collector.start_trace(trace_id)
|
|
22
|
-
end
|
|
14
|
+
trace_id = job.respond_to?(:rtv_trace_id) ? job.rtv_trace_id : nil
|
|
15
|
+
parent_id = job.respond_to?(:rtv_parent_id) ? job.rtv_parent_id : nil
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
trace_id ||= SecureRandom.uuid
|
|
18
|
+
parent_id ||= nil
|
|
19
|
+
|
|
20
|
+
enqueue_node_id = "rtv-enqueue-#{job.job_id}"
|
|
25
21
|
|
|
26
22
|
node = {
|
|
27
23
|
id: enqueue_node_id,
|
|
@@ -37,34 +33,35 @@ module RailsTraceViewer
|
|
|
37
33
|
children: []
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
RailsTraceViewer::JobLinkRegistry.register(job, trace_id: trace_id, enqueue_node_id: enqueue_node_id)
|
|
41
36
|
RailsTraceViewer::Collector.add_node(trace_id, node)
|
|
42
37
|
end
|
|
43
38
|
|
|
44
39
|
ActiveSupport::Notifications.subscribe("perform_start.active_job") do |*_args, payload|
|
|
45
40
|
job = payload[:job]
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
trace_id = job.respond_to?(:rtv_trace_id) ? job.rtv_trace_id : nil
|
|
43
|
+
trace_id ||= SecureRandom.uuid
|
|
44
|
+
|
|
45
|
+
enqueue_node_id = "rtv-enqueue-#{job.job_id}"
|
|
46
|
+
|
|
47
|
+
RailsTraceViewer::TraceContext.start_job!(trace_id)
|
|
48
|
+
RailsTraceViewer::Collector.start_trace(trace_id)
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
node_id = SecureRandom.uuid
|
|
51
|
+
node = {
|
|
52
|
+
id: node_id,
|
|
53
|
+
parent_id: enqueue_node_id,
|
|
54
|
+
type: "job_perform",
|
|
55
|
+
name: job.class.name,
|
|
56
|
+
source: "ActiveJob Perform",
|
|
57
|
+
job_id: job.job_id,
|
|
58
|
+
arguments: safe_serialize.call(job.arguments),
|
|
59
|
+
executions: job.executions,
|
|
60
|
+
children: []
|
|
61
|
+
}
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
RailsTraceViewer::JobLinkRegistry.delete(job)
|
|
67
|
-
end
|
|
63
|
+
RailsTraceViewer::Collector.add_node(trace_id, node)
|
|
64
|
+
RailsTraceViewer::TraceContext.push(node_id)
|
|
68
65
|
end
|
|
69
66
|
end
|
|
70
67
|
end
|
|
@@ -40,6 +40,27 @@ module RailsTraceViewer
|
|
|
40
40
|
is_singleton = tp.defined_class.singleton_class? rescue false
|
|
41
41
|
method_sig = "#{class_name}#{is_singleton ? '.' : '#'}#{tp.method_id}"
|
|
42
42
|
|
|
43
|
+
arguments = {}
|
|
44
|
+
if tp.binding
|
|
45
|
+
tp.parameters.each do |type, name|
|
|
46
|
+
begin
|
|
47
|
+
val = tp.binding.local_variable_get(name)
|
|
48
|
+
if val.is_a?(String)
|
|
49
|
+
arguments[name] = val.length > 1000 ? val[0..1000] + "... (truncated)" : val
|
|
50
|
+
elsif val.is_a?(Numeric) || val == true || val == false || val.nil?
|
|
51
|
+
arguments[name] = val
|
|
52
|
+
elsif defined?(ActiveRecord::Base) && val.is_a?(ActiveRecord::Base)
|
|
53
|
+
arguments[name] = val.attributes
|
|
54
|
+
else
|
|
55
|
+
inspected = val.inspect
|
|
56
|
+
arguments[name] = inspected.length > 1000 ? inspected[0..1000] + "... (truncated)" : inspected
|
|
57
|
+
end
|
|
58
|
+
rescue => e
|
|
59
|
+
arguments[name] = "Error"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
43
64
|
node = {
|
|
44
65
|
id: node_id,
|
|
45
66
|
parent_id: parent_id,
|
|
@@ -50,6 +71,7 @@ module RailsTraceViewer
|
|
|
50
71
|
line_number: tp.lineno,
|
|
51
72
|
class_name: class_name,
|
|
52
73
|
method_name: tp.method_id,
|
|
74
|
+
arguments: arguments,
|
|
53
75
|
children: []
|
|
54
76
|
}
|
|
55
77
|
RailsTraceViewer::Collector.add_node(trace_id, node)
|
|
@@ -24,18 +24,13 @@ module RailsTraceViewer
|
|
|
24
24
|
|
|
25
25
|
class ClientMiddleware
|
|
26
26
|
def call(worker_class, job, queue, redis_pool)
|
|
27
|
-
trace_id = nil
|
|
28
|
-
parent_id = nil
|
|
29
|
-
started_new_trace = false
|
|
30
27
|
|
|
31
28
|
if RailsTraceViewer::TraceContext.active?
|
|
32
29
|
trace_id = RailsTraceViewer::TraceContext.trace_id
|
|
33
30
|
parent_id = RailsTraceViewer::TraceContext.parent_id
|
|
34
31
|
else
|
|
35
32
|
trace_id = SecureRandom.uuid
|
|
36
|
-
|
|
37
|
-
RailsTraceViewer::Collector.start_trace(trace_id)
|
|
38
|
-
started_new_trace = true
|
|
33
|
+
parent_id = nil
|
|
39
34
|
end
|
|
40
35
|
|
|
41
36
|
enqueue_node_id = SecureRandom.uuid
|
|
@@ -60,17 +55,13 @@ module RailsTraceViewer
|
|
|
60
55
|
RailsTraceViewer::Collector.add_node(trace_id, node)
|
|
61
56
|
|
|
62
57
|
yield
|
|
63
|
-
ensure
|
|
64
|
-
if started_new_trace
|
|
65
|
-
RailsTraceViewer::TraceContext.stop!
|
|
66
|
-
end
|
|
67
58
|
end
|
|
68
59
|
end
|
|
69
60
|
|
|
70
61
|
class ServerMiddleware
|
|
71
62
|
def call(worker, job, queue)
|
|
72
63
|
trace_id = job["_trace_id"]
|
|
73
|
-
parent_id = job["_enqueue_node_id"]
|
|
64
|
+
parent_id = job["_enqueue_node_id"]
|
|
74
65
|
perform_node_id = nil
|
|
75
66
|
|
|
76
67
|
if trace_id
|
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
module RailsTraceViewer
|
|
2
2
|
module Subscribers
|
|
3
3
|
class SqlSubscriber
|
|
4
|
+
|
|
5
|
+
IGNORED_SQL_PATTERNS = /\A(?:BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE SAVEPOINT)\b/i
|
|
6
|
+
IGNORED_PAYLOAD_NAMES = ["SCHEMA"].to_set
|
|
7
|
+
|
|
4
8
|
def self.attach
|
|
5
9
|
return if @attached
|
|
6
10
|
@attached = true
|
|
7
11
|
|
|
8
12
|
ActiveSupport::Notifications.subscribe("sql.active_record") do |*_args, payload|
|
|
9
13
|
next unless RailsTraceViewer::TraceContext.active?
|
|
10
|
-
next if payload[:name]
|
|
14
|
+
next if IGNORED_PAYLOAD_NAMES.include?(payload[:name])
|
|
15
|
+
|
|
16
|
+
sql = (payload[:sql] || "").to_s
|
|
17
|
+
next if sql.match?(IGNORED_SQL_PATTERNS)
|
|
11
18
|
|
|
12
19
|
source = extract_app_source(caller)
|
|
13
20
|
next unless source
|
|
14
21
|
|
|
15
22
|
raw_binds = payload[:type_casted_binds]
|
|
16
|
-
if raw_binds.respond_to?(:call)
|
|
17
|
-
raw_binds = raw_binds.call
|
|
18
|
-
end
|
|
19
|
-
|
|
23
|
+
raw_binds = raw_binds.call if raw_binds.respond_to?(:call)
|
|
20
24
|
binds = (raw_binds || []).map { |val| val.to_s }
|
|
21
25
|
|
|
22
26
|
node = {
|
|
23
27
|
id: SecureRandom.uuid,
|
|
24
28
|
parent_id: RailsTraceViewer::TraceContext.parent_id,
|
|
25
29
|
type: "sql",
|
|
26
|
-
name:
|
|
30
|
+
name: sql,
|
|
27
31
|
source: source,
|
|
28
|
-
full_sql:
|
|
32
|
+
full_sql: sql,
|
|
29
33
|
bind_values: binds,
|
|
30
34
|
connection_id: payload[:connection_id],
|
|
31
35
|
children: []
|
|
@@ -36,7 +40,14 @@ module RailsTraceViewer
|
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
def self.extract_app_source(bt)
|
|
39
|
-
|
|
43
|
+
app_root = Rails.root.to_s
|
|
44
|
+
bt.find { |line|
|
|
45
|
+
path = line.to_s
|
|
46
|
+
next false unless path.start_with?(app_root)
|
|
47
|
+
next false if path.include?("/vendor/") || path.include?("/node_modules/")
|
|
48
|
+
next false if path.include?("/rails_trace_viewer/")
|
|
49
|
+
true
|
|
50
|
+
}
|
|
40
51
|
end
|
|
41
52
|
end
|
|
42
53
|
end
|
data/lib/rails_trace_viewer.rb
CHANGED
|
@@ -4,10 +4,10 @@ require "active_support"
|
|
|
4
4
|
require "active_support/notifications"
|
|
5
5
|
|
|
6
6
|
require "rails_trace_viewer/engine"
|
|
7
|
+
require "rails_trace_viewer/active_job_extension"
|
|
7
8
|
require "rails_trace_viewer/collector"
|
|
8
9
|
require "rails_trace_viewer/trace_context"
|
|
9
10
|
require "rails_trace_viewer/broadcaster"
|
|
10
|
-
require "rails_trace_viewer/job_link_registry"
|
|
11
11
|
require "rails_trace_viewer/route_resolver"
|
|
12
12
|
|
|
13
13
|
require "rails_trace_viewer/subscribers/controller_subscriber"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_trace_viewer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aditya-JOSH
|
|
@@ -80,10 +80,10 @@ files:
|
|
|
80
80
|
- app/views/rails_trace_viewer/traces/show.html.erb
|
|
81
81
|
- config/routes.rb
|
|
82
82
|
- lib/rails_trace_viewer.rb
|
|
83
|
+
- lib/rails_trace_viewer/active_job_extension.rb
|
|
83
84
|
- lib/rails_trace_viewer/broadcaster.rb
|
|
84
85
|
- lib/rails_trace_viewer/collector.rb
|
|
85
86
|
- lib/rails_trace_viewer/engine.rb
|
|
86
|
-
- lib/rails_trace_viewer/job_link_registry.rb
|
|
87
87
|
- lib/rails_trace_viewer/route_resolver.rb
|
|
88
88
|
- lib/rails_trace_viewer/subscribers/active_job_subscriber.rb
|
|
89
89
|
- lib/rails_trace_viewer/subscribers/controller_subscriber.rb
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
require "concurrent/map"
|
|
2
|
-
|
|
3
|
-
module RailsTraceViewer
|
|
4
|
-
class JobLinkRegistry
|
|
5
|
-
@store = Concurrent::Map.new
|
|
6
|
-
@pending = Concurrent::Map.new
|
|
7
|
-
|
|
8
|
-
class << self
|
|
9
|
-
def register(job, trace_id:, enqueue_node_id:)
|
|
10
|
-
job_id = job.job_id
|
|
11
|
-
|
|
12
|
-
@store[job_id] = {
|
|
13
|
-
trace_id: trace_id,
|
|
14
|
-
enqueue_node_id: enqueue_node_id
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if @pending[job_id]
|
|
18
|
-
@pending[job_id].each do |callback|
|
|
19
|
-
callback.call(trace_id, enqueue_node_id)
|
|
20
|
-
end
|
|
21
|
-
@pending.delete(job_id)
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def on_perform(job, &block)
|
|
26
|
-
job_id = job.job_id
|
|
27
|
-
|
|
28
|
-
if @store[job_id]
|
|
29
|
-
data = @store[job_id]
|
|
30
|
-
block.call(data[:trace_id], data[:enqueue_node_id])
|
|
31
|
-
else
|
|
32
|
-
@pending[job_id] ||= []
|
|
33
|
-
@pending[job_id] << block
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def delete(job)
|
|
38
|
-
@store.delete(job.job_id)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|