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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +48 -10
  4. data/app/assets/builds/eyeloupe.css +1 -1
  5. data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/ai_assistant_controller.js +27 -0
  6. data/app/assets/javascripts/eyeloupe/controllers/eyeloupe/refresh_controller.js +3 -1
  7. data/app/assets/stylesheets/application.tailwind.css +8 -0
  8. data/app/assets/stylesheets/eyeloupe/application.css +0 -1
  9. data/app/controllers/eyeloupe/ai_assistant_responses_controller.rb +30 -0
  10. data/app/controllers/eyeloupe/data_controller.rb +2 -0
  11. data/app/controllers/eyeloupe/exceptions_controller.rb +29 -0
  12. data/app/models/eyeloupe/exception.rb +6 -0
  13. data/app/models/eyeloupe/in_request.rb +1 -0
  14. data/app/models/eyeloupe/out_request.rb +1 -0
  15. data/app/views/eyeloupe/exceptions/_frame.html.erb +39 -0
  16. data/app/views/eyeloupe/exceptions/index.html.erb +13 -0
  17. data/app/views/eyeloupe/exceptions/show.html.erb +126 -0
  18. data/app/views/eyeloupe/in_requests/_frame.html.erb +1 -1
  19. data/app/views/eyeloupe/in_requests/index.html.erb +1 -1
  20. data/app/views/eyeloupe/in_requests/show.html.erb +17 -1
  21. data/app/views/eyeloupe/out_requests/_frame.html.erb +1 -1
  22. data/app/views/eyeloupe/out_requests/index.html.erb +1 -1
  23. data/app/views/eyeloupe/out_requests/show.html.erb +17 -1
  24. data/app/views/layouts/eyeloupe/application.html.erb +41 -5
  25. data/config/importmap.rb +2 -1
  26. data/config/routes.rb +2 -0
  27. data/db/migrate/20230604190442_create_eyeloupe_exceptions.rb +21 -0
  28. data/lib/eyeloupe/concerns/rescuable.rb +14 -0
  29. data/lib/eyeloupe/configuration.rb +7 -0
  30. data/lib/eyeloupe/engine.rb +46 -0
  31. data/lib/eyeloupe/processors/exception.rb +71 -0
  32. data/lib/eyeloupe/processors/in_request.rb +15 -4
  33. data/lib/eyeloupe/processors/out_request.rb +10 -20
  34. data/lib/eyeloupe/request_middleware.rb +13 -1
  35. data/lib/eyeloupe/version.rb +1 -1
  36. data/lib/eyeloupe.rb +4 -1
  37. metadata +28 -4
  38. 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(50) %></td>
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
@@ -0,0 +1,14 @@
1
+ module Eyeloupe
2
+ module Concerns
3
+ module Rescuable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ rescue_from(StandardError) do |exception|
8
+ Eyeloupe::Processors::Exception.instance.process(nil, exception)
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ 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
 
@@ -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
- Eyeloupe::InRequest.create(
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
- @response = response
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: @response.code,
53
- format: @response.content_type,
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(@response) || {}).to_json,
58
- response: @response.body,
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)
@@ -1,4 +1,4 @@
1
1
  module Eyeloupe
2
2
  # @return [String]
3
- VERSION = "0.1.0"
3
+ VERSION = "0.3.0"
4
4
  end
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.1.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-03 00:00:00.000000000 Z
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: The All in one Rails monitoring tool
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/http.rb
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