eyeloupe 0.1.0 → 0.3.0
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/CHANGELOG.md +10 -0
- data/README.md +48 -10
- data/app/assets/builds/eyeloupe.css +1 -1
- data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/ai_assistant_controller.js +27 -0
- data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/refresh_controller.js +3 -1
- data/app/assets/stylesheets/application.tailwind.css +8 -0
- data/app/assets/stylesheets/eyeloupe/application.css +0 -1
- data/app/controllers/eyeloupe/ai_assistant_responses_controller.rb +30 -0
- data/app/controllers/eyeloupe/data_controller.rb +2 -0
- data/app/controllers/eyeloupe/exceptions_controller.rb +29 -0
- data/app/models/eyeloupe/exception.rb +6 -0
- data/app/models/eyeloupe/in_request.rb +1 -0
- data/app/models/eyeloupe/out_request.rb +1 -0
- data/app/views/eyeloupe/exceptions/_frame.html.erb +39 -0
- data/app/views/eyeloupe/exceptions/index.html.erb +13 -0
- data/app/views/eyeloupe/exceptions/show.html.erb +126 -0
- data/app/views/eyeloupe/in_requests/_frame.html.erb +1 -1
- data/app/views/eyeloupe/in_requests/index.html.erb +1 -1
- data/app/views/eyeloupe/in_requests/show.html.erb +17 -1
- data/app/views/eyeloupe/out_requests/_frame.html.erb +1 -1
- data/app/views/eyeloupe/out_requests/index.html.erb +1 -1
- data/app/views/eyeloupe/out_requests/show.html.erb +17 -1
- data/app/views/layouts/eyeloupe/application.html.erb +41 -5
- data/config/importmap.rb +2 -1
- data/config/routes.rb +2 -0
- data/db/migrate/20230604190442_create_eyeloupe_exceptions.rb +21 -0
- data/lib/eyeloupe/concerns/rescuable.rb +14 -0
- data/lib/eyeloupe/configuration.rb +7 -0
- data/lib/eyeloupe/engine.rb +46 -0
- data/lib/eyeloupe/processors/exception.rb +71 -0
- data/lib/eyeloupe/processors/in_request.rb +15 -4
- data/lib/eyeloupe/processors/out_request.rb +10 -20
- data/lib/eyeloupe/request_middleware.rb +13 -1
- data/lib/eyeloupe/version.rb +1 -1
- data/lib/eyeloupe.rb +4 -1
- metadata +28 -4
- data/lib/eyeloupe/http.rb +0 -19
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div>
|
|
1
|
+
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
2
|
<div class="px-4 sm:px-0">
|
|
3
3
|
<h3 class="text-xl font-semibold leading-7 text-gray-900">Request details</h3>
|
|
4
4
|
</div>
|
|
@@ -80,3 +80,19 @@
|
|
|
80
80
|
</dl>
|
|
81
81
|
</div>
|
|
82
82
|
</div>
|
|
83
|
+
|
|
84
|
+
<% if @request.exception.present? %>
|
|
85
|
+
<div class="mt-6 bg-white rounded-md shadow-md py-5">
|
|
86
|
+
<h3 class="text-base font-semibold leading-6 text-gray-900 px-4 sm:px-6 lg:px-8 border-b border-gray-100 pb-3">Exceptions</h3>
|
|
87
|
+
|
|
88
|
+
<%= link_to exception_path(@request.exception), class: "text-gray-600 hover:text-gray-900 hover:bg-gray-100 block px-4 sm:px-6 lg:px-8", data: {"turbo_frame": "_top"} do %>
|
|
89
|
+
<div class="divide-y divide-gray-100 px-0 flex justify-between">
|
|
90
|
+
<div class="whitespace-nowrap py-4 pl-4 pr-3 text-base font-medium text-gray-900 sm:pl-0">
|
|
91
|
+
<%= @request.exception.kind %>
|
|
92
|
+
<p class="text-sm font-normal text-gray-500"><%= @request.exception.message %></p>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<% end %>
|
|
96
|
+
|
|
97
|
+
</div>
|
|
98
|
+
<% end %>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<%= render "eyeloupe/shared/verb", verb: request.verb %>
|
|
24
24
|
</td>
|
|
25
25
|
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.hostname %></td>
|
|
26
|
-
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.path.truncate(
|
|
26
|
+
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500"><%= request.path.truncate(100) %></td>
|
|
27
27
|
<td class="whitespace-nowrap px-3 py-4 text-base text-gray-500">
|
|
28
28
|
<%= render "eyeloupe/shared/status_code", code: request.status %>
|
|
29
29
|
</td>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div data-controller="eyeloupe--search">
|
|
1
|
+
<div data-controller="eyeloupe--search" class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
2
|
<div class="sm:flex sm:items-center">
|
|
3
3
|
<div class="sm:flex-auto">
|
|
4
4
|
<h1 class="text-xl font-semibold leading-6 text-gray-900">HTTP Client</h1>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div>
|
|
1
|
+
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
2
2
|
<div class="px-4 sm:px-0">
|
|
3
3
|
<h3 class="text-xl font-semibold leading-7 text-gray-900">HTTP Client Request Details</h3>
|
|
4
4
|
</div>
|
|
@@ -67,3 +67,19 @@
|
|
|
67
67
|
</dl>
|
|
68
68
|
</div>
|
|
69
69
|
</div>
|
|
70
|
+
|
|
71
|
+
<% if @request.exception.present? %>
|
|
72
|
+
<div class="mt-6 bg-white rounded-md shadow-md py-5">
|
|
73
|
+
<h3 class="text-base font-semibold leading-6 text-gray-900 px-4 sm:px-6 lg:px-8 border-b border-gray-100 pb-3">Exceptions</h3>
|
|
74
|
+
|
|
75
|
+
<%= link_to exception_path(@request.exception), class: "text-gray-600 hover:text-gray-900 hover:bg-gray-100 block px-4 sm:px-6 lg:px-8", data: {"turbo_frame": "_top"} do %>
|
|
76
|
+
<div class="divide-y divide-gray-100 px-0 flex justify-between">
|
|
77
|
+
<div class="whitespace-nowrap py-4 pl-4 pr-3 text-base font-medium text-gray-900 sm:pl-0">
|
|
78
|
+
<%= @request.exception.kind %>
|
|
79
|
+
<p class="text-sm font-normal text-gray-500"><%= @request.exception.message %></p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<% end %>
|
|
83
|
+
|
|
84
|
+
</div>
|
|
85
|
+
<% end %>
|
|
@@ -67,6 +67,23 @@
|
|
|
67
67
|
HTTP Client
|
|
68
68
|
<% end %>
|
|
69
69
|
</li>
|
|
70
|
+
<li>
|
|
71
|
+
<%= link_to exceptions_path, class:"#{request.path.include?('/exceptions') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %>
|
|
72
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
73
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
74
|
+
<path d="M9 9v-1a3 3 0 0 1 6 0v1"></path>
|
|
75
|
+
<path d="M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3"></path>
|
|
76
|
+
<path d="M3 13l4 0"></path>
|
|
77
|
+
<path d="M17 13l4 0"></path>
|
|
78
|
+
<path d="M12 20l0 -6"></path>
|
|
79
|
+
<path d="M4 19l3.35 -2"></path>
|
|
80
|
+
<path d="M20 19l-3.35 -2"></path>
|
|
81
|
+
<path d="M4 7l3.75 2.4"></path>
|
|
82
|
+
<path d="M20 7l-3.75 2.4"></path>
|
|
83
|
+
</svg>
|
|
84
|
+
Exceptions
|
|
85
|
+
<% end %>
|
|
86
|
+
</li>
|
|
70
87
|
</ul>
|
|
71
88
|
</li>
|
|
72
89
|
</ul>
|
|
@@ -84,7 +101,7 @@
|
|
|
84
101
|
<%= image_tag "eyeloupe/logo.png", class: "h-12 w-auto" %>
|
|
85
102
|
<h1 class="ml-2 text-2xl font-semibold text-gray-700">Eyeloupe</h1>
|
|
86
103
|
<% end %>
|
|
87
|
-
<nav class="flex flex-1 flex-col">
|
|
104
|
+
<nav class="flex flex-1 flex-col mt-4">
|
|
88
105
|
<ul role="list" class="flex flex-1 flex-col gap-y-7">
|
|
89
106
|
<li>
|
|
90
107
|
<ul role="list" class="-mx-2 space-y-1">
|
|
@@ -114,6 +131,23 @@
|
|
|
114
131
|
HTTP Client
|
|
115
132
|
<% end %>
|
|
116
133
|
</li>
|
|
134
|
+
<li>
|
|
135
|
+
<%= link_to exceptions_path, class:"#{request.path.include?('/exceptions') ? 'bg-gray-200 text-red-500' : ''} hover:bg-gray-200 text-gray-500 group flex gap-x-3 rounded-md p-2 text-base leading-6 font-medium" do %>
|
|
136
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
137
|
+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
|
138
|
+
<path d="M9 9v-1a3 3 0 0 1 6 0v1"></path>
|
|
139
|
+
<path d="M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3"></path>
|
|
140
|
+
<path d="M3 13l4 0"></path>
|
|
141
|
+
<path d="M17 13l4 0"></path>
|
|
142
|
+
<path d="M12 20l0 -6"></path>
|
|
143
|
+
<path d="M4 19l3.35 -2"></path>
|
|
144
|
+
<path d="M20 19l-3.35 -2"></path>
|
|
145
|
+
<path d="M4 7l3.75 2.4"></path>
|
|
146
|
+
<path d="M20 7l-3.75 2.4"></path>
|
|
147
|
+
</svg>
|
|
148
|
+
Exceptions
|
|
149
|
+
<% end %>
|
|
150
|
+
</li>
|
|
117
151
|
</ul>
|
|
118
152
|
</li>
|
|
119
153
|
</ul>
|
|
@@ -191,11 +225,13 @@
|
|
|
191
225
|
</div>
|
|
192
226
|
|
|
193
227
|
<main class="py-10 lg:pl-72 px-5 mx-auto">
|
|
194
|
-
|
|
195
|
-
<div class="px-4 sm:px-6 lg:px-8 bg-white rounded-md shadow-md py-5">
|
|
196
|
-
<%= yield %>
|
|
197
|
-
</div>
|
|
228
|
+
<%= yield %>
|
|
198
229
|
</main>
|
|
230
|
+
|
|
231
|
+
<footer class="text-gray-500 py-1 lg:pl-72 px-5 mx-auto text-sm">
|
|
232
|
+
Eyeloupe <%= Eyeloupe::VERSION %> - <a class="underline" href="https://github.com/alxlion/eyeloupe">Github</a>
|
|
233
|
+
</footer>
|
|
234
|
+
|
|
199
235
|
</div>
|
|
200
236
|
</div>
|
|
201
237
|
|
data/config/importmap.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
pin_all_from File.expand_path("../app/assets/javascripts", __dir__)
|
|
2
2
|
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
|
|
3
3
|
pin "@hotwired/stimulus", to: "https://ga.jspm.io/npm:@hotwired/stimulus@3.0.1/dist/stimulus.js"
|
|
4
|
-
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
|
4
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
|
5
|
+
pin "showdown", to: "https://ga.jspm.io/npm:showdown@2.1.0/dist/showdown.js"
|
data/config/routes.rb
CHANGED
|
@@ -4,6 +4,8 @@ Eyeloupe::Engine.routes.draw do
|
|
|
4
4
|
|
|
5
5
|
resources :in_requests, only: [:index, :show]
|
|
6
6
|
resources :out_requests, only: [:index, :show]
|
|
7
|
+
resources :exceptions, only: [:index, :show]
|
|
8
|
+
resources :ai_assistant_responses, only: [:show]
|
|
7
9
|
|
|
8
10
|
resource :data, only: [:destroy]
|
|
9
11
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CreateEyeloupeExceptions < ActiveRecord::Migration[7.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :eyeloupe_exceptions do |t|
|
|
4
|
+
t.string :hostname
|
|
5
|
+
t.string :kind
|
|
6
|
+
t.string :location
|
|
7
|
+
t.string :file
|
|
8
|
+
t.integer :line
|
|
9
|
+
t.text :stacktrace
|
|
10
|
+
t.string :message
|
|
11
|
+
t.integer :count, default: 1
|
|
12
|
+
t.text :full_message
|
|
13
|
+
t.references :in_request, null: true, foreign_key: { to_table: :eyeloupe_in_requests }
|
|
14
|
+
t.references :out_request, null: true, foreign_key: { to_table: :eyeloupe_out_requests }
|
|
15
|
+
|
|
16
|
+
t.timestamps
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
add_index :eyeloupe_exceptions, [:kind, :file, :line]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -11,9 +11,16 @@ module Eyeloupe
|
|
|
11
11
|
# @return [Boolean]
|
|
12
12
|
attr_accessor :capture
|
|
13
13
|
|
|
14
|
+
# @return [String]
|
|
15
|
+
attr_accessor :openai_access_key
|
|
16
|
+
|
|
17
|
+
# @return [String]
|
|
18
|
+
attr_accessor :openai_model
|
|
19
|
+
|
|
14
20
|
def initialize
|
|
15
21
|
@excluded_paths = %w[]
|
|
16
22
|
@capture = true
|
|
23
|
+
@openai_model = "gpt-3.5-turbo"
|
|
17
24
|
end
|
|
18
25
|
end
|
|
19
26
|
|
data/lib/eyeloupe/engine.rb
CHANGED
|
@@ -12,6 +12,52 @@ module Eyeloupe
|
|
|
12
12
|
app.config.middleware.insert(0, Eyeloupe::RequestMiddleware)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
initializer 'eyeloupe.active_job' do
|
|
16
|
+
ActiveSupport.on_load(:active_job) do
|
|
17
|
+
include Eyeloupe::Concerns::Rescuable
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
initializer "eyeloupe.configure_openai" do
|
|
22
|
+
OpenAI.configure do |config|
|
|
23
|
+
config.access_token = Eyeloupe::configuration.openai_access_key
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
initializer "eyeloupe.configure_sidekiq" do
|
|
28
|
+
if defined?(Sidekiq)
|
|
29
|
+
Sidekiq.configure_server do |config|
|
|
30
|
+
config.error_handlers << proc {|ex,ctx_hash| Eyeloupe::Processors::Exception.instance.process(nil, ex) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
initializer 'eyeloupe.override_net_http_request' do
|
|
36
|
+
require 'net/http'
|
|
37
|
+
Net::HTTP.class_eval do
|
|
38
|
+
alias original_request request
|
|
39
|
+
def request(req, body = nil, &block)
|
|
40
|
+
res, ex = nil
|
|
41
|
+
exception_processor = Eyeloupe::Processors::Exception.instance
|
|
42
|
+
out_request_processor = Eyeloupe::Processors::OutRequest.instance
|
|
43
|
+
|
|
44
|
+
if Eyeloupe.configuration.capture
|
|
45
|
+
begin
|
|
46
|
+
out_request_processor.init(req, body)
|
|
47
|
+
res = original_request(req, body, &block)
|
|
48
|
+
rescue => e
|
|
49
|
+
ex = exception_processor.process(nil, e)
|
|
50
|
+
ensure
|
|
51
|
+
out_request_processor.process(res, ex)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
res = original_request(req, body, &block)
|
|
55
|
+
end
|
|
56
|
+
res
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
15
61
|
initializer "eyeloupe.importmap", :before => "importmap" do |app|
|
|
16
62
|
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
17
63
|
# https://github.com/rails/importmap-rails#sweeping-the-cache-in-development-and-test
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
module Eyeloupe
|
|
3
|
+
module Processors
|
|
4
|
+
class Exception
|
|
5
|
+
include Singleton
|
|
6
|
+
|
|
7
|
+
# @param [Hash, nil] env Rack environment
|
|
8
|
+
# @param [Exception] exception The exception object
|
|
9
|
+
# @return [Eyeloupe::Exception] The exception model
|
|
10
|
+
def process(env, exception)
|
|
11
|
+
if env && env['action_dispatch.backtrace_cleaner'].present?
|
|
12
|
+
backtrace = env['action_dispatch.backtrace_cleaner'].filter(exception.backtrace)
|
|
13
|
+
backtrace = exception.backtrace if backtrace.blank?
|
|
14
|
+
else
|
|
15
|
+
backtrace = exception.backtrace
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
file = backtrace ? backtrace[0].split(":")[0] : ""
|
|
19
|
+
line = backtrace ? backtrace[0].split(":")[1].to_i : 0
|
|
20
|
+
|
|
21
|
+
create_or_update_exception(exception.class.name || "", file, line, backtrace, exception.message, exception.full_message)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
# @param [Array] trace The backtrace
|
|
27
|
+
# @return [Array] The source code lines
|
|
28
|
+
def read_file(trace)
|
|
29
|
+
file = trace.size > 0 ? trace[0].split(":")[0] : ""
|
|
30
|
+
line = trace.size > 0 ? trace[0].split(":")[1].to_i : 0
|
|
31
|
+
|
|
32
|
+
if File.exist?(file)
|
|
33
|
+
lines = File.readlines(file)
|
|
34
|
+
start = line - 5
|
|
35
|
+
start = 0 if start < 0
|
|
36
|
+
lines[start..line+5] || []
|
|
37
|
+
else
|
|
38
|
+
[]
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @param [String] kind The exception class name
|
|
43
|
+
# @param [String] file The file path
|
|
44
|
+
# @param [Integer] line The line number
|
|
45
|
+
# @param [Array] backtrace The backtrace
|
|
46
|
+
# @param [String] message The exception message
|
|
47
|
+
# @param [String] full_message The full exception message
|
|
48
|
+
# @return [Eyeloupe::Exception] The exception model
|
|
49
|
+
def create_or_update_exception(kind, file, line, backtrace, message, full_message)
|
|
50
|
+
obj = Eyeloupe::Exception.find_by(kind: kind, file: file, line: line)
|
|
51
|
+
|
|
52
|
+
if obj
|
|
53
|
+
obj.update(count: obj.count + 1, updated_at: Time.now)
|
|
54
|
+
else
|
|
55
|
+
obj = Eyeloupe::Exception.create(
|
|
56
|
+
hostname: Socket.gethostname,
|
|
57
|
+
kind: kind,
|
|
58
|
+
message: message,
|
|
59
|
+
full_message: full_message,
|
|
60
|
+
location: read_file(backtrace || []).to_json,
|
|
61
|
+
file: file,
|
|
62
|
+
line: line,
|
|
63
|
+
stacktrace: (backtrace || []).to_json,
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
obj
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -31,6 +31,9 @@ module Eyeloupe
|
|
|
31
31
|
# @return [Array]
|
|
32
32
|
attr_accessor :subs
|
|
33
33
|
|
|
34
|
+
# @return [Eyeloupe::Exception, nil]
|
|
35
|
+
attr_accessor :ex
|
|
36
|
+
|
|
34
37
|
def initialize
|
|
35
38
|
@env = {}
|
|
36
39
|
@request = ActionDispatch::Request.new(@env)
|
|
@@ -40,6 +43,7 @@ module Eyeloupe
|
|
|
40
43
|
@timings = {}
|
|
41
44
|
@started_at = nil
|
|
42
45
|
@subs = []
|
|
46
|
+
@ex = nil
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
# @param [ActionDispatch::Request] request The request object
|
|
@@ -47,8 +51,9 @@ module Eyeloupe
|
|
|
47
51
|
# @param [Integer, nil] status HTTP status code
|
|
48
52
|
# @param [Hash, nil] headers HTTP headers
|
|
49
53
|
# @param [String, nil] response HTTP response
|
|
54
|
+
# @param [Eyeloupe::Exception, nil] ex The exception object persisted in db
|
|
50
55
|
# @return [Eyeloupe::Processors::InRequest]
|
|
51
|
-
def init(request, env, status, headers, response)
|
|
56
|
+
def init(request, env, status, headers, response, ex)
|
|
52
57
|
unsubscribe
|
|
53
58
|
|
|
54
59
|
@request = request
|
|
@@ -56,6 +61,7 @@ module Eyeloupe
|
|
|
56
61
|
@status = status
|
|
57
62
|
@headers = headers
|
|
58
63
|
@response = response
|
|
64
|
+
@ex = ex
|
|
59
65
|
|
|
60
66
|
self
|
|
61
67
|
end
|
|
@@ -72,7 +78,8 @@ module Eyeloupe
|
|
|
72
78
|
|
|
73
79
|
# @return [Eyeloupe::InRequest]
|
|
74
80
|
def process
|
|
75
|
-
|
|
81
|
+
|
|
82
|
+
req = Eyeloupe::InRequest.create(
|
|
76
83
|
verb: @request.request_method,
|
|
77
84
|
hostname: @request.host,
|
|
78
85
|
path: @env["REQUEST_URI"],
|
|
@@ -88,6 +95,10 @@ module Eyeloupe
|
|
|
88
95
|
session: (@request.session || {}).to_json,
|
|
89
96
|
response: get_response,
|
|
90
97
|
)
|
|
98
|
+
|
|
99
|
+
@ex.update(in_request_id: req.id) if @ex.present? && @ex.in_request_id.blank?
|
|
100
|
+
|
|
101
|
+
req
|
|
91
102
|
end
|
|
92
103
|
|
|
93
104
|
protected
|
|
@@ -105,11 +116,11 @@ module Eyeloupe
|
|
|
105
116
|
|
|
106
117
|
# @return [String, nil]
|
|
107
118
|
def get_response
|
|
108
|
-
if @request.format.to_s =~ /html/
|
|
119
|
+
if @request.format.to_s =~ /html/ || @headers&.to_json =~ /html/
|
|
109
120
|
"HTML content"
|
|
110
121
|
elsif @response.is_a?(ActionDispatch::Response)
|
|
111
122
|
@response.body
|
|
112
|
-
elsif @response.is_a?(Rack::BodyProxy)
|
|
123
|
+
elsif @response.is_a?(Rack::BodyProxy) && @response.respond_to?(:first)
|
|
113
124
|
@response.first
|
|
114
125
|
else
|
|
115
126
|
@response
|
|
@@ -11,25 +11,13 @@ module Eyeloupe
|
|
|
11
11
|
# @return [String]
|
|
12
12
|
attr_accessor :body
|
|
13
13
|
|
|
14
|
-
# @return [Hash]
|
|
15
|
-
attr_accessor :req_headers
|
|
16
|
-
|
|
17
|
-
# @return [Hash]
|
|
18
|
-
attr_accessor :res_headers
|
|
19
|
-
|
|
20
|
-
# @return [Net::HTTPResponse, nil]
|
|
21
|
-
attr_accessor :response
|
|
22
|
-
|
|
23
14
|
# @return [Time, nil]
|
|
24
15
|
attr_accessor :started_at
|
|
25
16
|
|
|
26
17
|
def initialize
|
|
27
18
|
@request = nil
|
|
28
19
|
@body = ""
|
|
29
|
-
@req_headers = {}
|
|
30
|
-
@res_headers = {}
|
|
31
20
|
@started_at = nil
|
|
32
|
-
@response = nil
|
|
33
21
|
end
|
|
34
22
|
|
|
35
23
|
# @param [Net::HTTPRequest] request The request object
|
|
@@ -41,22 +29,24 @@ module Eyeloupe
|
|
|
41
29
|
end
|
|
42
30
|
|
|
43
31
|
# @param [Net::HTTPResponse] response The response object
|
|
32
|
+
# @param [Eyeloupe::Exception, nil] ex The exception object persisted in db
|
|
44
33
|
# @return [Net::HTTPResponse] The response object
|
|
45
|
-
def process(response)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Eyeloupe::OutRequest.create(
|
|
34
|
+
def process(response, ex)
|
|
35
|
+
req = Eyeloupe::OutRequest.create(
|
|
49
36
|
verb: @request.method,
|
|
50
37
|
hostname: @request['host'],
|
|
51
38
|
path: @request.path,
|
|
52
|
-
status:
|
|
53
|
-
format:
|
|
39
|
+
status: response.code,
|
|
40
|
+
format: response.content_type,
|
|
54
41
|
duration: (Time.now - @started_at) * 1000,
|
|
55
42
|
payload: @request.body,
|
|
56
43
|
req_headers: (get_headers(@request) || {}).to_json,
|
|
57
|
-
res_headers: (get_headers(
|
|
58
|
-
response:
|
|
44
|
+
res_headers: (get_headers(response) || {}).to_json,
|
|
45
|
+
response: response.body,
|
|
59
46
|
)
|
|
47
|
+
|
|
48
|
+
ex.update(out_request_id: req.id) if ex.present? && ex.out_request_id.blank?
|
|
49
|
+
|
|
60
50
|
response
|
|
61
51
|
end
|
|
62
52
|
|
|
@@ -5,30 +5,42 @@ module Eyeloupe
|
|
|
5
5
|
# @return [Eyeloupe::Processors::InRequest]
|
|
6
6
|
attr_accessor :inrequest_processor
|
|
7
7
|
|
|
8
|
+
# @return [Eyeloupe::Processors::Exception]
|
|
9
|
+
attr_accessor :exception_processor
|
|
10
|
+
|
|
8
11
|
def initialize(app)
|
|
9
12
|
@app = app
|
|
10
13
|
@inrequest_processor = Processors::InRequest.instance
|
|
14
|
+
@exception_processor = Processors::Exception.instance
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
# @param [Hash] env Rack environment
|
|
14
18
|
def call(env)
|
|
15
19
|
|
|
16
20
|
request = ActionDispatch::Request.new(env)
|
|
21
|
+
ex = nil
|
|
17
22
|
|
|
18
23
|
if enabled?(request) && !skip_request?(request)
|
|
19
24
|
@inrequest_processor.start_timer
|
|
20
25
|
|
|
21
26
|
begin
|
|
22
27
|
status, headers, response = @app.call(env)
|
|
28
|
+
|
|
29
|
+
framework_exception = env['action_dispatch.exception']
|
|
30
|
+
if framework_exception
|
|
31
|
+
ex = @exception_processor.process(env, framework_exception)
|
|
32
|
+
end
|
|
33
|
+
|
|
23
34
|
[status, headers, response]
|
|
24
35
|
rescue Exception => e
|
|
25
36
|
exception = ActionDispatch::ExceptionWrapper.new(env, e)
|
|
26
37
|
status = exception.status_code
|
|
27
38
|
headers = {}
|
|
28
39
|
response = e.message
|
|
40
|
+
ex = @exception_processor.process(env, e)
|
|
29
41
|
raise
|
|
30
42
|
ensure
|
|
31
|
-
@inrequest_processor.init(request, env, status, headers, response).process
|
|
43
|
+
@inrequest_processor.init(request, env, status, headers, response, ex).process
|
|
32
44
|
end
|
|
33
45
|
else
|
|
34
46
|
@app.call(env)
|
data/lib/eyeloupe/version.rb
CHANGED
data/lib/eyeloupe.rb
CHANGED
|
@@ -3,10 +3,13 @@ require "eyeloupe/engine"
|
|
|
3
3
|
|
|
4
4
|
require 'eyeloupe/request_middleware'
|
|
5
5
|
require 'eyeloupe/configuration'
|
|
6
|
-
require 'eyeloupe/http'
|
|
7
6
|
require 'eyeloupe/processors/in_request'
|
|
8
7
|
require 'eyeloupe/processors/out_request'
|
|
8
|
+
require 'eyeloupe/processors/exception'
|
|
9
|
+
require 'eyeloupe/concerns/rescuable'
|
|
9
10
|
|
|
11
|
+
require 'pagy'
|
|
12
|
+
require "openai"
|
|
10
13
|
module Eyeloupe
|
|
11
14
|
|
|
12
15
|
# @return [Eyeloupe::Configuration]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eyeloupe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexandre Lion
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-06-
|
|
11
|
+
date: 2023-06-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sprockets-rails
|
|
@@ -66,6 +66,20 @@ dependencies:
|
|
|
66
66
|
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '6.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: ruby-openai
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 4.1.0
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 4.1.0
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
84
|
name: sqlite3
|
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,7 +122,8 @@ dependencies:
|
|
|
108
122
|
- - "~>"
|
|
109
123
|
- !ruby/object:Gem::Version
|
|
110
124
|
version: '1.1'
|
|
111
|
-
description:
|
|
125
|
+
description: Eyeloupe is debug assistant for Rails. It provides a simple and elegant
|
|
126
|
+
way to debug your Rails application.
|
|
112
127
|
email:
|
|
113
128
|
- dev@alexandrelion.com
|
|
114
129
|
executables: []
|
|
@@ -124,6 +139,7 @@ files:
|
|
|
124
139
|
- app/assets/images/eyeloupe/logo.png
|
|
125
140
|
- app/assets/javascripts/eyeloupe/application.js
|
|
126
141
|
- app/assets/javascripts/eyeloupe/controllers/application.js
|
|
142
|
+
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/ai_assistant_controller.js
|
|
127
143
|
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/nav_controller.js
|
|
128
144
|
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/pause_controller.js
|
|
129
145
|
- app/assets/javascripts/eyeloupe/controllers/eyeloupe/refresh_controller.js
|
|
@@ -132,17 +148,23 @@ files:
|
|
|
132
148
|
- app/assets/stylesheets/application.tailwind.css
|
|
133
149
|
- app/assets/stylesheets/eyeloupe/application.css
|
|
134
150
|
- app/controllers/concerns/eyeloupe/searchable.rb
|
|
151
|
+
- app/controllers/eyeloupe/ai_assistant_responses_controller.rb
|
|
135
152
|
- app/controllers/eyeloupe/application_controller.rb
|
|
136
153
|
- app/controllers/eyeloupe/configs_controller.rb
|
|
137
154
|
- app/controllers/eyeloupe/data_controller.rb
|
|
155
|
+
- app/controllers/eyeloupe/exceptions_controller.rb
|
|
138
156
|
- app/controllers/eyeloupe/in_requests_controller.rb
|
|
139
157
|
- app/controllers/eyeloupe/out_requests_controller.rb
|
|
140
158
|
- app/helpers/eyeloupe/application_helper.rb
|
|
141
159
|
- app/jobs/eyeloupe/application_job.rb
|
|
142
160
|
- app/mailers/eyeloupe/application_mailer.rb
|
|
143
161
|
- app/models/eyeloupe/application_record.rb
|
|
162
|
+
- app/models/eyeloupe/exception.rb
|
|
144
163
|
- app/models/eyeloupe/in_request.rb
|
|
145
164
|
- app/models/eyeloupe/out_request.rb
|
|
165
|
+
- app/views/eyeloupe/exceptions/_frame.html.erb
|
|
166
|
+
- app/views/eyeloupe/exceptions/index.html.erb
|
|
167
|
+
- app/views/eyeloupe/exceptions/show.html.erb
|
|
146
168
|
- app/views/eyeloupe/in_requests/_frame.html.erb
|
|
147
169
|
- app/views/eyeloupe/in_requests/index.html.erb
|
|
148
170
|
- app/views/eyeloupe/in_requests/show.html.erb
|
|
@@ -157,10 +179,12 @@ files:
|
|
|
157
179
|
- config/tailwind.config.js
|
|
158
180
|
- db/migrate/20230518175305_create_eyeloupe_in_requests.rb
|
|
159
181
|
- db/migrate/20230525125352_create_eyeloupe_out_requests.rb
|
|
182
|
+
- db/migrate/20230604190442_create_eyeloupe_exceptions.rb
|
|
160
183
|
- lib/eyeloupe.rb
|
|
184
|
+
- lib/eyeloupe/concerns/rescuable.rb
|
|
161
185
|
- lib/eyeloupe/configuration.rb
|
|
162
186
|
- lib/eyeloupe/engine.rb
|
|
163
|
-
- lib/eyeloupe/
|
|
187
|
+
- lib/eyeloupe/processors/exception.rb
|
|
164
188
|
- lib/eyeloupe/processors/in_request.rb
|
|
165
189
|
- lib/eyeloupe/processors/out_request.rb
|
|
166
190
|
- lib/eyeloupe/request_middleware.rb
|
data/lib/eyeloupe/http.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
require 'net/http'
|
|
3
|
-
module Net
|
|
4
|
-
class HTTP
|
|
5
|
-
alias original_request request
|
|
6
|
-
|
|
7
|
-
def request(req, body = nil, &block)
|
|
8
|
-
if Eyeloupe.configuration.capture
|
|
9
|
-
Eyeloupe::Processors::OutRequest.instance.init(req, body)
|
|
10
|
-
res = original_request(req, body, &block)
|
|
11
|
-
Eyeloupe::Processors::OutRequest.instance.process(res)
|
|
12
|
-
else
|
|
13
|
-
res = original_request(req, body, &block)
|
|
14
|
-
end
|
|
15
|
-
res
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
end
|