rails-buddy 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +45 -0
- data/Rakefile +10 -0
- data/app/assets/builds/rails/buddy/application.css +7 -0
- data/app/assets/builds/rails/buddy/application.js +28660 -0
- data/app/assets/config/rails_buddy_manifest.js +1 -0
- data/app/controllers/rails/buddy/application_controller.rb +8 -0
- data/app/controllers/rails/buddy/requests_controller.rb +23 -0
- data/app/helpers/rails/buddy/application_helper.rb +8 -0
- data/app/jobs/rails/buddy/application_job.rb +8 -0
- data/app/mailers/rails/buddy/application_mailer.rb +10 -0
- data/app/models/rails/buddy/application_record.rb +9 -0
- data/app/views/layouts/rails/buddy/application.html.erb +14 -0
- data/app/views/rails/buddy/icons/_expand.html.erb +50 -0
- data/app/views/rails/buddy/requests/_request.html.erb +7 -0
- data/app/views/rails/buddy/requests/close.html.erb +2 -0
- data/app/views/rails/buddy/requests/deleted.html.erb +14 -0
- data/app/views/rails/buddy/requests/index.html.erb +32 -0
- data/app/views/rails/buddy/requests/show/_controller.html.erb +60 -0
- data/app/views/rails/buddy/requests/show/_models.html.erb +10 -0
- data/app/views/rails/buddy/requests/show/_queries.html.erb +29 -0
- data/app/views/rails/buddy/requests/show.html.erb +37 -0
- data/config/routes.rb +10 -0
- data/lib/generators/rails/buddy/config_generator.rb +22 -0
- data/lib/generators/rails/buddy/templates/initializer.rb +6 -0
- data/lib/rails/buddy/config.rb +33 -0
- data/lib/rails/buddy/current.rb +20 -0
- data/lib/rails/buddy/engine.rb +39 -0
- data/lib/rails/buddy/middlewares/track_current_request.rb +37 -0
- data/lib/rails/buddy/request.rb +66 -0
- data/lib/rails/buddy/requests_buffer.rb +31 -0
- data/lib/rails/buddy/subscribers/action_controller.rb +40 -0
- data/lib/rails/buddy/subscribers/active_record.rb +61 -0
- data/lib/rails/buddy/subscribers/base.rb +19 -0
- data/lib/rails/buddy/tracker.rb +25 -0
- data/lib/rails/buddy/version.rb +7 -0
- data/lib/rails/buddy.rb +26 -0
- data/lib/tasks/rails/buddy_tasks.rake +6 -0
- metadata +99 -0
@@ -0,0 +1 @@
|
|
1
|
+
//= link_directory ../builds/rails/buddy
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Buddy
|
5
|
+
class RequestsController < ApplicationController
|
6
|
+
def index
|
7
|
+
@requests = RequestsBuffer.all
|
8
|
+
end
|
9
|
+
|
10
|
+
def show
|
11
|
+
@request = RequestsBuffer.find(params[:id])
|
12
|
+
render :deleted if @request.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def clear
|
16
|
+
RequestsBuffer.clear!
|
17
|
+
redirect_to root_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def close; end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Rails Buddy</title>
|
5
|
+
<%= csrf_meta_tags %>
|
6
|
+
<%= csp_meta_tag %>
|
7
|
+
|
8
|
+
<%= stylesheet_link_tag "rails/buddy/application", media: "all" %>
|
9
|
+
<%= javascript_include_tag "rails/buddy/application", media: "all" %>
|
10
|
+
</head>
|
11
|
+
<body data-controller="home" data-action="keydown.esc->home#closeRequest">
|
12
|
+
<%= yield %>
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<svg id="svg_expand" class="h-4" viewBox="0 0 30 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
2
|
+
<style>
|
3
|
+
#svg_expand {
|
4
|
+
line {
|
5
|
+
stroke: var(--color1);
|
6
|
+
transform-box: fill-box;
|
7
|
+
transform-origin: center;
|
8
|
+
transition: all 0.3s ease-in-out;
|
9
|
+
}
|
10
|
+
|
11
|
+
path {
|
12
|
+
fill: var(--color2);
|
13
|
+
transform-box: fill-box;
|
14
|
+
transform-origin: center;
|
15
|
+
transition: all 0.3s ease-in-out;
|
16
|
+
transform: scaleY(-1);
|
17
|
+
}
|
18
|
+
|
19
|
+
line {
|
20
|
+
transform: translateY(var(--translate));
|
21
|
+
}
|
22
|
+
|
23
|
+
#linetop {
|
24
|
+
--translate: 10px;
|
25
|
+
opacity: 0;
|
26
|
+
}
|
27
|
+
|
28
|
+
#linebottom {
|
29
|
+
--translate: -9px;
|
30
|
+
opacity: 0;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
.<%= css_class_expanded %> #svg_expand {
|
35
|
+
line {
|
36
|
+
transform: translateY(0px);
|
37
|
+
opacity: 1 !important;
|
38
|
+
}
|
39
|
+
|
40
|
+
path {
|
41
|
+
transform: scaleY(1);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
</style>
|
45
|
+
<line id="linetop" x1="10" y1="1" x2="30" y2="1" stroke-width="2"/>
|
46
|
+
<line id="linemiddle" x1="10" y1="11" x2="30" y2="11" stroke-width="2"/>
|
47
|
+
<line id="linebottom" x1="10" y1="20" x2="30" y2="20" stroke-width="2"/>
|
48
|
+
<path id="arrowtop" d="M4.35355 0.646447C4.15829 0.451184 3.84171 0.451184 3.64645 0.646447L0.464466 3.82843C0.269204 4.02369 0.269204 4.34027 0.464466 4.53553C0.659728 4.7308 0.976311 4.7308 1.17157 4.53553L4 1.70711L6.82843 4.53553C7.02369 4.7308 7.34027 4.7308 7.53553 4.53553C7.7308 4.34027 7.7308 4.02369 7.53553 3.82843L4.35355 0.646447ZM4.5 9L4.5 1H3.5L3.5 9H4.5Z"/>
|
49
|
+
<path id="arrowbottom" d="M3.64645 20.3536C3.84171 20.5488 4.15829 20.5488 4.35355 20.3536L7.53553 17.1716C7.7308 16.9763 7.7308 16.6597 7.53553 16.4645C7.34027 16.2692 7.02369 16.2692 6.82843 16.4645L4 19.2929L1.17157 16.4645C0.97631 16.2692 0.659728 16.2692 0.464466 16.4645C0.269204 16.6597 0.269204 16.9763 0.464466 17.1716L3.64645 20.3536ZM3.5 12L3.5 20H4.5L4.5 12H3.5Z"/>
|
50
|
+
</svg>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<%= link_to "#{Rails::Buddy.config.prefix}/requests/#{request.id}", class: 'request-line', data: {turbo_frame: :request} do %>
|
2
|
+
<span class="method method--<%= request.method %>"><%= request.method %></span>
|
3
|
+
<p class="px-4 truncate"><%= request.path %></p>
|
4
|
+
<span class="status status--<%= request.familly_status.dasherize %>"><%= request.status %></span>
|
5
|
+
<span class="truncate"><%= request.meta&.dig(:controller) %></span>
|
6
|
+
<span class="truncate"><%= request.meta&.dig(:action) %></span>
|
7
|
+
<% end %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%= turbo_frame_tag :request do %>
|
2
|
+
<div class="p-8">
|
3
|
+
<p class="font-bold">This request has been <span class="text-red-500">deleted</span>.</p>
|
4
|
+
|
5
|
+
<p class="mb-4">You exceeded the maximum number of requests that can be stored in buffer. You can increase the limit by changing the value of buffer size in the configuration file.</p>
|
6
|
+
|
7
|
+
<pre class="border border-gray-300 rounded bg-gray-50">
|
8
|
+
<code>
|
9
|
+
Rails::Buddy.configure do |config|
|
10
|
+
config.buffer_size = <%= Rails::Buddy::Config::DEFAULT_BUGGER_SIZE * 2 %> # default is <%= Rails::Buddy::Config::DEFAULT_BUGGER_SIZE %>
|
11
|
+
end</code>
|
12
|
+
</pre>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<%= turbo_stream_from :requests %>
|
2
|
+
|
3
|
+
|
4
|
+
<div class="flex items-center justify-between px-4 bg-indigo-800 h-navbar">
|
5
|
+
<h1 class="text-2xl font-bold text-white">Buddy <span class="text-indigo-500">Monitoring</<span></h1>
|
6
|
+
<%= button_to clear_requests_path, class: 'transition-all flex gap-2 items-center px-4 text-sm py-2 font-semibold rounded-full border border-indigo-500 text-indigo-500 hover:text-indigo-300 hover:border-indigo-300' do %>
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4">
|
8
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
9
|
+
</svg>
|
10
|
+
<span>
|
11
|
+
Clean up
|
12
|
+
</span>
|
13
|
+
<% end %>
|
14
|
+
</div>
|
15
|
+
<div class="flex flex-col h-full-nonav">
|
16
|
+
<div class="requests-grid requests-header">
|
17
|
+
<span>Method</span>
|
18
|
+
<span class="px-4">Path</span>
|
19
|
+
<span>Status</span>
|
20
|
+
<span>Controller</span>
|
21
|
+
<span>Action</span>
|
22
|
+
</div>
|
23
|
+
<div class="flex-grow overflow-y-auto">
|
24
|
+
<%= content_tag :div, id: :requests, class: 'flex flex-col-reverse' do %>
|
25
|
+
<%= render partial: 'request', collection: @requests %>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<div class="fixed bottom-0 right-0 w-3/4 overflow-y-auto transition-all translate-x-full bg-white border shadow-lg h-full-nonav request-opened:translate-x-0 rounded-l-md">
|
31
|
+
<%= turbo_frame_tag :request, data: {action: 'turbo:frame-load->home#openRequest'} %>
|
32
|
+
</div>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
<h3 class="flex items-center gap-4 p-2 mb-8 border border-gray-300 rounded bg-gray-50">
|
2
|
+
<span class="font-semibold text-indigo-500 uppercase"><%= @request.method %></span>
|
3
|
+
<span class="flex-grow truncate"><%= @request.path %></span>
|
4
|
+
<% if @request.meta %>
|
5
|
+
<span class="font-semibold text-gray-700 uppercase"><%= @request.meta[:format] %></span>
|
6
|
+
<% end %>
|
7
|
+
</h3>
|
8
|
+
|
9
|
+
|
10
|
+
<% if @request.meta %>
|
11
|
+
<div class="p-2 mb-8 border border-gray-300 rounded bg-gray-50">
|
12
|
+
<%= @request.meta[:params] %>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div class="flex items-center gap-4 mb-8">
|
16
|
+
<p class="font-semibold"><%= @request.meta[:controller] %><span class="text-indigo-500">#</span><%= @request.meta[:action] %></p>
|
17
|
+
<%= link_to "Open in Vscode", "vscode://file#{@request.action_definition}", class: 'text-xs' %>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<table class="table-auto">
|
21
|
+
<tbody class="divide-y">
|
22
|
+
<% if @request.meta[:view_runtime].presence %>
|
23
|
+
<tr>
|
24
|
+
<td class="px-2 py-1 font-semibold">view_runtime</td>
|
25
|
+
<td><span class="badge"><%= @request.meta[:view_runtime].round(2) %> ms</span></td>
|
26
|
+
</tr>
|
27
|
+
<% end %>
|
28
|
+
<% if @request.meta[:db_runtime].presence %>
|
29
|
+
<tr>
|
30
|
+
<td class="px-2 py-1 font-semibold">db_runtime</td>
|
31
|
+
<td><span class="badge"><%= @request.meta[:db_runtime].round(2) %> ms</span></td>
|
32
|
+
</tr>
|
33
|
+
<% end %>
|
34
|
+
<% if @request.meta[:duration].presence %>
|
35
|
+
<tr>
|
36
|
+
<td class="px-2 py-1 font-semibold">duration</td>
|
37
|
+
<td><span class="badge"><%= @request.meta[:duration].round(2) %> ms</span></td>
|
38
|
+
</tr>
|
39
|
+
<% end %>
|
40
|
+
<% if @request.meta[:cpu_time].presence %>
|
41
|
+
<tr>
|
42
|
+
<td class="px-2 py-1 font-semibold">cpu_time</td>
|
43
|
+
<td><span class="badge"><%= @request.meta[:cpu_time].round(2) %> ms</span></td>
|
44
|
+
</tr>
|
45
|
+
<% end %>
|
46
|
+
<% if @request.meta[:idle_time].presence %>
|
47
|
+
<tr>
|
48
|
+
<td class="px-2 py-1 font-semibold">idle_time</td>
|
49
|
+
<td><span class="badge"><%= @request.meta[:idle_time].round(2) %> ms</span></td>
|
50
|
+
</tr>
|
51
|
+
<% end %>
|
52
|
+
<% if @request.meta[:allocations].presence %>
|
53
|
+
<tr>
|
54
|
+
<td class="px-2 py-1 font-semibold">allocations</td>
|
55
|
+
<td><span class="badge"><%= @request.meta[:allocations] %></span></td>
|
56
|
+
</tr>
|
57
|
+
<% end %>
|
58
|
+
</tbody>
|
59
|
+
</table>
|
60
|
+
<% end %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<table class="table-auto">
|
2
|
+
<tbody class="divide-y">
|
3
|
+
<% models.sort_by {|_k, v| -v }.to_h.each do |model_name, count| %>
|
4
|
+
<tr class="">
|
5
|
+
<td class="px-2 py-1 font-semibold"><%= model_name %></td>
|
6
|
+
<td><span class="badge"><%= count %></span></td>
|
7
|
+
</tr>
|
8
|
+
<% end %>
|
9
|
+
</tbody>
|
10
|
+
</table>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<div class="divide-y">
|
2
|
+
<% queries.each do |query| %>
|
3
|
+
<div class="p-4 text-sm odd:bg-gray-50 group" data-controller="code-syntax" data-code-syntax-query-value="<%= {sql: query[:sql]}.to_json %>">
|
4
|
+
<div class="flex items-center h-6 mb-2">
|
5
|
+
<h3 class="mr-4 font-bold"><%= query[:title] %></h3>
|
6
|
+
|
7
|
+
<span class="[--color1:--color-indigo-500] [--color2:--color-gray-800] mr-auto cursor-pointer" data-action="click->code-syntax#toggleFormat">
|
8
|
+
<%= render 'rails/buddy/icons/expand', css_class_expanded: 'formatted' %>
|
9
|
+
</span>
|
10
|
+
|
11
|
+
<div class="items-center justify-center hidden gap-1 py-1 text-xs font-semibold text-gray-500 border rounded cursor-pointer group-hover:flex w-36 hover:text-indigo-500 transition-color hover:border-indigo-500" data-action="click->code-syntax#copyClipboard">
|
12
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-4 copied:hidden">
|
13
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184" />
|
14
|
+
</svg>
|
15
|
+
<span class="copied:hidden">
|
16
|
+
Copy to clipboard
|
17
|
+
</span>
|
18
|
+
<span class="hidden copied:block">
|
19
|
+
Copied!
|
20
|
+
</span>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="w-full py-4 overflow-x-auto">
|
25
|
+
<pre data-code-syntax-target="container"></pre>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<%= turbo_frame_tag :request do %>
|
2
|
+
<div data-controller="tabs">
|
3
|
+
<div class="flex items-center justify-between border-b">
|
4
|
+
<ul class="flex">
|
5
|
+
<li data-action="click->tabs#nav" data-tabs-target="tab" target="controller" class="current">Controller</li>
|
6
|
+
<li data-action="click->tabs#nav" data-tabs-target="tab" target="models">
|
7
|
+
Models
|
8
|
+
<% if @request.models.present? %>
|
9
|
+
<span><%= @request.models.values.sum %></span>
|
10
|
+
<% end %>
|
11
|
+
</li>
|
12
|
+
<li data-action="click->tabs#nav" data-tabs-target="tab" target="queries">
|
13
|
+
Queries
|
14
|
+
<% if @request.queries.present? %>
|
15
|
+
<span><%= @request.queries.size %></span>
|
16
|
+
<% end %>
|
17
|
+
</li>
|
18
|
+
</ul>
|
19
|
+
|
20
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="h-5 mr-2 rotate-45 cursor-pointer hover:scale-110 hover:text-indigo-500" data-action="click->home#closeRequest">
|
21
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
22
|
+
</svg>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div data-tabs-target="panel" id="controller">
|
26
|
+
<%= render 'rails/buddy/requests/show/controller' %>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div data-tabs-target="panel" id="models" class="hidden">
|
30
|
+
<%= render 'rails/buddy/requests/show/models', models: @request.models %>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div data-tabs-target="panel" id="queries" class="hidden">
|
34
|
+
<%= render 'rails/buddy/requests/show/queries', queries: @request.queries %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
module Rails
|
6
|
+
module Buddy
|
7
|
+
class ConfigGenerator < Rails::Generators::Base
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
desc 'This generator creates an initializer file at config/initializers, ' \
|
11
|
+
'with the default configuration options for Rails::Buddy.'
|
12
|
+
def add_initializer
|
13
|
+
path = File.expand_path('templates/initializer.rb', __dir__)
|
14
|
+
content = File.binread(path)
|
15
|
+
.gsub!('[PREFIX]', Config::DEFAULT_PATH_PREFIX)
|
16
|
+
.gsub!('[BUFFER_SIZE]', Config::DEFAULT_BUGGER_SIZE.to_s)
|
17
|
+
File.binwrite('config/initializers/buddy.rb', content)
|
18
|
+
say_status :create, relative_to_original_destination_root('config/initializers/buddy.rb')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Buddy
|
5
|
+
class Config
|
6
|
+
DEFAULT_BUGGER_SIZE = 200
|
7
|
+
DEFAULT_PATH_PREFIX = '/buddy'
|
8
|
+
|
9
|
+
attr_accessor :prefix, :buffer_size
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
opt = defaults.merge options
|
13
|
+
|
14
|
+
@prefix = opt[:prefix]
|
15
|
+
@buffer_size = opt[:buffer_size]
|
16
|
+
end
|
17
|
+
|
18
|
+
def ignore_request?(env)
|
19
|
+
[prefix, '/assets'].any? { |s| env['PATH_INFO'].start_with? s } || env['HTTP_UPGRADE'] == 'websocket'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def defaults
|
25
|
+
{
|
26
|
+
enabled: Rails.env.development?,
|
27
|
+
prefix: DEFAULT_PATH_PREFIX,
|
28
|
+
buffer_size: DEFAULT_BUGGER_SIZE
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Buddy
|
5
|
+
class Current < ActiveSupport::CurrentAttributes
|
6
|
+
attribute :request
|
7
|
+
attribute :ignore
|
8
|
+
|
9
|
+
alias ignore? ignore
|
10
|
+
|
11
|
+
def new_request!(rack_env)
|
12
|
+
self.request = Request.from_rack_env(rack_env)
|
13
|
+
end
|
14
|
+
|
15
|
+
def pop_request!
|
16
|
+
request.tap { self.request = nil }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'middlewares/track_current_request'
|
4
|
+
|
5
|
+
module Rails
|
6
|
+
module Buddy
|
7
|
+
class Engine < ::Rails::Engine
|
8
|
+
isolate_namespace Rails::Buddy
|
9
|
+
|
10
|
+
initializer 'rails_buddy.init' do |_app|
|
11
|
+
RequestsBuffer.init
|
12
|
+
end
|
13
|
+
|
14
|
+
initializer 'rails_buddy.middlewares' do |app|
|
15
|
+
app.middleware.insert_after ActionDispatch::Executor, TrackCurrentRequest
|
16
|
+
end
|
17
|
+
|
18
|
+
initializer 'rails_buddy.routing' do |app|
|
19
|
+
app.routes.append do
|
20
|
+
mount Engine => Buddy.config.prefix
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
initializer 'rails_buddy.precompile' do |app|
|
25
|
+
app.config.assets.precompile += %w[
|
26
|
+
rails/buddy/application.css
|
27
|
+
rails/buddy/application.js
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
initializer 'rails_buddy.subscribe' do |_app|
|
32
|
+
require_relative 'subscribers/action_controller'
|
33
|
+
Subscribers::ActionController.subscribe
|
34
|
+
require_relative 'subscribers/active_record'
|
35
|
+
Subscribers::ActiveRecord.subscribe
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Buddy
|
5
|
+
class TrackCurrentRequest
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
Buddy::Current.ignore = Buddy.config.ignore_request?(env)
|
12
|
+
return @app.call(env) if Buddy::Current.ignore?
|
13
|
+
|
14
|
+
Buddy::Current.new_request!(env)
|
15
|
+
|
16
|
+
@app.call(env).tap do |response|
|
17
|
+
save_request(response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def save_request(response)
|
24
|
+
request = Buddy::Current.pop_request!
|
25
|
+
return unless request
|
26
|
+
|
27
|
+
request.status = response[0]
|
28
|
+
Buddy::RequestsBuffer.push(request)
|
29
|
+
|
30
|
+
Turbo::StreamsChannel.broadcast_append_to :requests,
|
31
|
+
target: :requests,
|
32
|
+
partial: 'rails/buddy/requests/request',
|
33
|
+
locals: { request: }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Buddy
|
5
|
+
class Request
|
6
|
+
class << self
|
7
|
+
attr_reader :request
|
8
|
+
|
9
|
+
def from_rack_env(rack_env)
|
10
|
+
@request = ::Rack::Request.new(rack_env)
|
11
|
+
new(path:, method:)
|
12
|
+
end
|
13
|
+
|
14
|
+
def path = request.fullpath
|
15
|
+
|
16
|
+
def method
|
17
|
+
request_method = request.env['REQUEST_METHOD'].downcase.to_sym
|
18
|
+
if request_method == :post
|
19
|
+
request.params['_method'].presence&.to_sym || request_method
|
20
|
+
else
|
21
|
+
request_method
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :request_id, :path, :method, :time, :models, :queries
|
27
|
+
attr_accessor :request, :response, :meta, :status
|
28
|
+
|
29
|
+
alias id request_id
|
30
|
+
|
31
|
+
def initialize(path:, method:)
|
32
|
+
@request_id = SecureRandom.uuid
|
33
|
+
@time = Time.current
|
34
|
+
@path = path
|
35
|
+
@method = method
|
36
|
+
@models = Hash.new(0)
|
37
|
+
@queries = []
|
38
|
+
end
|
39
|
+
|
40
|
+
def familly_status
|
41
|
+
case status.to_s[0]
|
42
|
+
when '2' then 'success'
|
43
|
+
when '3' then 'redirect'
|
44
|
+
when '4' then 'error'
|
45
|
+
when '5' then 'fatal_error'
|
46
|
+
else
|
47
|
+
'UNKNOWN'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_model(model)
|
52
|
+
@models[model.class.to_s] += 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_query(query)
|
56
|
+
@queries << query
|
57
|
+
end
|
58
|
+
|
59
|
+
def action_definition
|
60
|
+
return unless meta[:controller] && meta[:action]
|
61
|
+
|
62
|
+
meta[:controller].constantize.instance_method(meta[:action]).source_location.join(':')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Buddy
|
5
|
+
class RequestsBuffer
|
6
|
+
class << self
|
7
|
+
def init
|
8
|
+
@collection = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(id)
|
12
|
+
@collection.find { |r| r.id == id }
|
13
|
+
end
|
14
|
+
|
15
|
+
def push(request)
|
16
|
+
@collection << request
|
17
|
+
@collection.shift if @collection.size > Buddy.config.buffer_size
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def all
|
22
|
+
@collection
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear!
|
26
|
+
@collection = []
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Rails
|
6
|
+
module Buddy
|
7
|
+
module Subscribers
|
8
|
+
class ActionController < Base
|
9
|
+
EVENTS = { 'process_action.action_controller' => :process_action }.freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def process_action(event)
|
13
|
+
return if prevent_processing?
|
14
|
+
|
15
|
+
meta = event.payload
|
16
|
+
meta.delete :headers
|
17
|
+
meta.merge!({
|
18
|
+
duration: event.duration,
|
19
|
+
cpu_time: event.cpu_time,
|
20
|
+
idle_time: event.idle_time,
|
21
|
+
allocations: event.allocations
|
22
|
+
})
|
23
|
+
|
24
|
+
save_meta_in_current_request(meta)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def prevent_processing? = Current.ignore? || Current.request.nil?
|
30
|
+
|
31
|
+
def save_meta_in_current_request(meta)
|
32
|
+
Current.request.request = meta.delete(:request)
|
33
|
+
Current.request.response = meta.delete(:response)
|
34
|
+
Current.request.meta = meta
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|