rails_exception_log 1.0.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.
@@ -0,0 +1,278 @@
1
+ <div class="min-h-screen bg-slate-50">
2
+ <nav class="bg-white border-b border-slate-200 shadow-sm">
3
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
4
+ <div class="flex justify-between h-16">
5
+ <div class="flex items-center">
6
+ <svg class="w-8 h-8 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
7
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
8
+ </svg>
9
+ <span class="ml-2 text-xl font-semibold text-slate-900">Rails Exception Log</span>
10
+ </div>
11
+ <div class="flex items-center space-x-4">
12
+ <%= link_to "Export CSV", railsexceptionlog_exceptions_path(format: :csv),
13
+ class: "inline-flex items-center px-4 py-2 border border-slate-300 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50" %>
14
+ <div class="relative" data-controller="dropdown">
15
+ <button type="button" class="inline-flex items-center px-4 py-2 border border-red-300 rounded-md text-sm font-medium text-red-700 bg-white hover:bg-red-50" data-action="dropdown#toggle">
16
+ Clear All
17
+ </button>
18
+ <div class="hidden origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" data-dropdown-target="menu">
19
+ <div class="py-1">
20
+ <button type="button" onclick="if(confirm('Delete ALL exceptions?')) { document.getElementById('clear-form').submit(); }" class="block w-full text-left px-4 py-2 text-sm text-red-700 hover:bg-red-50">Delete All</button>
21
+ <button type="button" onclick="if(confirm('Delete only OPEN exceptions?')) { document.getElementById('clear-open-form').submit(); }" class="block w-full text-left px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">Clear Open Only</button>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </nav>
29
+
30
+ <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
31
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
32
+ <div class="bg-white overflow-hidden shadow rounded-lg">
33
+ <div class="p-5">
34
+ <div class="flex items-center">
35
+ <div class="flex-shrink-0">
36
+ <div class="rounded-md bg-indigo-100 p-3">
37
+ <svg class="h-6 w-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
38
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
39
+ </svg>
40
+ </div>
41
+ </div>
42
+ <div class="ml-5 w-0 flex-1">
43
+ <dl>
44
+ <dt class="text-sm font-medium text-slate-500 truncate">Total Exceptions</dt>
45
+ <dd>
46
+ <div class="text-lg font-medium text-slate-900"><%= @stats[:total] %></div>
47
+ </dd>
48
+ </dl>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <div class="bg-white overflow-hidden shadow rounded-lg">
55
+ <div class="p-5">
56
+ <div class="flex items-center">
57
+ <div class="flex-shrink-0">
58
+ <div class="rounded-md bg-red-100 p-3">
59
+ <svg class="h-6 w-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
60
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
61
+ </svg>
62
+ </div>
63
+ </div>
64
+ <div class="ml-5 w-0 flex-1">
65
+ <dl>
66
+ <dt class="text-sm font-medium text-slate-500 truncate">Open</dt>
67
+ <dd>
68
+ <div class="text-lg font-medium text-slate-900"><%= @status_counts[:open] %></div>
69
+ </dd>
70
+ </dl>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+
76
+ <div class="bg-white overflow-hidden shadow rounded-lg">
77
+ <div class="p-5">
78
+ <div class="flex items-center">
79
+ <div class="flex-shrink-0">
80
+ <div class="rounded-md bg-green-100 p-3">
81
+ <svg class="h-6 w-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
83
+ </svg>
84
+ </div>
85
+ </div>
86
+ <div class="ml-5 w-0 flex-1">
87
+ <dl>
88
+ <dt class="text-sm font-medium text-slate-500 truncate">Resolved</dt>
89
+ <dd>
90
+ <div class="text-lg font-medium text-slate-900"><%= @status_counts[:resolved] %></div>
91
+ </dd>
92
+ </dl>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <div class="bg-white overflow-hidden shadow rounded-lg">
99
+ <div class="p-5">
100
+ <div class="flex items-center">
101
+ <div class="flex-shrink-0">
102
+ <div class="rounded-md bg-amber-100 p-3">
103
+ <svg class="h-6 w-6 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
104
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
105
+ </svg>
106
+ </div>
107
+ </div>
108
+ <div class="ml-5 w-0 flex-1">
109
+ <dl>
110
+ <dt class="text-sm font-medium text-slate-500 truncate">Last 24h</dt>
111
+ <dd>
112
+ <div class="text-lg font-medium text-slate-900"><%= @stats[:last_24h] %></div>
113
+ </dd>
114
+ </dl>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <div class="mb-6">
122
+ <h1 class="text-2xl font-bold text-slate-900">Logged Exceptions</h1>
123
+ <p class="mt-1 text-sm text-slate-500">View and manage application exceptions</p>
124
+ </div>
125
+
126
+ <div class="bg-white rounded-lg shadow mb-6 p-4">
127
+ <%= form_with url: railsexceptionlog_exceptions_path, method: :get, class: "flex flex-wrap gap-4 items-end" do |f| %>
128
+ <div class="flex-1 min-w-[200px]">
129
+ <%= f.label :search, "Search", class: "block text-sm font-medium text-slate-700 mb-1" %>
130
+ <%= f.text_field :search, value: params[:search], placeholder: "Search exceptions...",
131
+ class: "w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border" %>
132
+ </div>
133
+ <div class="w-40">
134
+ <%= f.label :date_range, "Date Range", class: "block text-sm font-medium text-slate-700 mb-1" %>
135
+ <%= f.select :date_range,
136
+ options_for_select([
137
+ ["All Time", ""],
138
+ ["Today", "today"],
139
+ ["Last 7 Days", "7days"],
140
+ ["Last 30 Days", "30days"]
141
+ ], params[:date_range]),
142
+ {}, class: "w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border" %>
143
+ </div>
144
+ <div class="w-40">
145
+ <%= f.label :status, "Status", class: "block text-sm font-medium text-slate-700 mb-1" %>
146
+ <%= f.select :status,
147
+ options_for_select([
148
+ ["All Status", ""],
149
+ ["Open", "open"],
150
+ ["Resolved", "resolved"],
151
+ ["Reopened", "reopened"],
152
+ ["Ignored", "ignored"]
153
+ ], params[:status]),
154
+ {}, class: "w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border" %>
155
+ </div>
156
+ <div class="w-48">
157
+ <%= f.label :exception_class, "Exception Type", class: "block text-sm font-medium text-slate-700 mb-1" %>
158
+ <%= f.select :exception_class,
159
+ options_for_select(["All Types"] + @exception_classes, params[:exception_class]),
160
+ {}, class: "w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border" %>
161
+ </div>
162
+ <div class="w-48">
163
+ <%= f.label :controller, "Controller", class: "block text-sm font-medium text-slate-700 mb-1" %>
164
+ <%= f.select :controller,
165
+ options_for_select(["All Controllers"] + @controllers, params[:controller]),
166
+ {}, class: "w-full rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border" %>
167
+ </div>
168
+ <div>
169
+ <%= f.submit "Filter", class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700" %>
170
+ <%= link_to "Clear", railsexceptionlog_exceptions_path, class: "ml-2 inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-md text-slate-700 bg-white hover:bg-slate-50" %>
171
+ </div>
172
+ <% end %>
173
+ </div>
174
+
175
+ <div class="bg-white shadow-sm rounded-lg overflow-hidden">
176
+ <div class="overflow-x-auto">
177
+ <table class="min-w-full divide-y divide-slate-200">
178
+ <thead class="bg-slate-50">
179
+ <tr>
180
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">Status</th>
181
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">Exception</th>
182
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">Occurrences</th>
183
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">Controller / Action</th>
184
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">Request</th>
185
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">Environment</th>
186
+ <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">Last Occurred</th>
187
+ <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-slate-500 uppercase tracking-wider">Actions</th>
188
+ </tr>
189
+ </thead>
190
+ <tbody class="bg-white divide-y divide-slate-200">
191
+ <% if @exceptions.any? %>
192
+ <% @exceptions.each do |exception| %>
193
+ <tr class="hover:bg-slate-50">
194
+ <td class="px-6 py-4 whitespace-nowrap">
195
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= exception_status_badge(exception) %>">
196
+ <%= exception.status_label %>
197
+ </span>
198
+ </td>
199
+ <td class="px-6 py-4 whitespace-nowrap">
200
+ <div class="flex items-center">
201
+ <div class="flex-shrink-0">
202
+ <div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center">
203
+ <svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
204
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
205
+ </svg>
206
+ </div>
207
+ </div>
208
+ <div class="ml-4">
209
+ <div class="text-sm font-medium text-slate-900"><%= exception.exception_class %></div>
210
+ <div class="text-sm text-slate-500"><%= truncate_text(exception.message, length: 60) %></div>
211
+ </div>
212
+ </div>
213
+ </td>
214
+ <td class="px-6 py-4 whitespace-nowrap">
215
+ <span class="text-sm font-medium text-slate-900"><%= exception.occurrence_count %></span>
216
+ </td>
217
+ <td class="px-6 py-4 whitespace-nowrap">
218
+ <div class="text-sm text-slate-900"><%= exception.controller_name %>#<%= exception.action_name %></div>
219
+ </td>
220
+ <td class="px-6 py-4 whitespace-nowrap">
221
+ <div class="flex items-center">
222
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium <%= request_method_class(exception.request_method) %>">
223
+ <%= exception.request_method %>
224
+ </span>
225
+ <span class="ml-2 text-sm text-slate-500 truncate max-w-[150px]"><%= exception.request_path %></span>
226
+ </div>
227
+ </td>
228
+ <td class="px-6 py-4 whitespace-nowrap">
229
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= exception_status_class(exception) %>">
230
+ <%= exception.environment %>
231
+ </span>
232
+ </td>
233
+ <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
234
+ <%= format_datetime_relative(exception.last_occurred_at) || format_datetime(exception.created_at) %>
235
+ </td>
236
+ <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
237
+ <%= link_to "View", railsexceptionlog_exception_path(exception),
238
+ class: "text-indigo-600 hover:text-indigo-900" %>
239
+ </td>
240
+ </tr>
241
+ <% end %>
242
+ <% else %>
243
+ <tr>
244
+ <td colspan="8" class="px-6 py-12 text-center text-slate-500">
245
+ <svg class="mx-auto h-12 w-12 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
246
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
247
+ </svg>
248
+ <p class="mt-2 text-sm">No exceptions logged yet</p>
249
+ </td>
250
+ </tr>
251
+ <% end %>
252
+ </tbody>
253
+ </table>
254
+ </div>
255
+
256
+ <% if @exceptions.respond_to?(:total_pages) && @exceptions.total_pages > 1 %>
257
+ <div class="bg-white px-4 py-3 border-t border-slate-200 sm:px-6">
258
+ <div class="flex items-center justify-between">
259
+ <div class="flex-1 flex justify-between sm:hidden">
260
+ <%= link_to "Previous", url: nil, class: "relative inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-md text-slate-700 bg-white hover:bg-slate-50" if @exceptions.prev_page %>
261
+ <%= link_to "Next", url: nil, class: "ml-3 relative inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-md text-slate-700 bg-white hover:bg-slate-50" if @exceptions.next_page %>
262
+ </div>
263
+ <div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
264
+ <div>
265
+ <p class="text-sm text-slate-700">
266
+ Showing page <span class="font-medium"><%= @exceptions.current_page %></span> of <span class="font-medium"><%= @exceptions.total_pages %></span>
267
+ </p>
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ <% end %>
273
+ </div>
274
+ </main>
275
+
276
+ <%= form_with url: railsexceptionlog_exceptions_path, method: :delete, id: "clear-form" do %><% end %>
277
+ <%= form_with url: railsexceptionlog_exceptions_path(status: "open"), method: :delete, id: "clear-open-form" do %><% end %>
278
+ </div>
@@ -0,0 +1,214 @@
1
+ <div class="min-h-screen bg-slate-50">
2
+ <nav class="bg-white border-b border-slate-200 shadow-sm">
3
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
4
+ <div class="flex justify-between h-16">
5
+ <div class="flex items-center">
6
+ <%= link_to railsexceptionlog_exceptions_path, class: "flex items-center" do %>
7
+ <svg class="w-8 h-8 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
8
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
9
+ </svg>
10
+ <span class="ml-2 text-xl font-semibold text-slate-900">Rails Exception Log</span>
11
+ <% end %>
12
+ </div>
13
+ <div class="flex items-center space-x-2">
14
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium <%= exception_status_badge(@exception) %>">
15
+ <%= @exception.status_label %>
16
+ </span>
17
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= exception_status_class(@exception) %>">
18
+ <%= @exception.environment %>
19
+ </span>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </nav>
24
+
25
+ <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
26
+ <div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
27
+ <div>
28
+ <h1 class="text-2xl font-bold text-slate-900">Exception Details</h1>
29
+ <p class="mt-1 text-sm text-slate-500">Full exception information and stack trace</p>
30
+ </div>
31
+ <div class="flex items-center space-x-2">
32
+ <% case @exception.status.to_s %>
33
+ <% when 'open' %>
34
+ <%= button_to "Mark Resolved", resolve_railsexceptionlog_exception_path(@exception),
35
+ method: :post,
36
+ class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700" %>
37
+ <%= button_to "Ignore", ignore_railsexceptionlog_exception_path(@exception),
38
+ method: :post,
39
+ class: "inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-md text-slate-700 bg-white hover:bg-slate-50" %>
40
+ <% when 'resolved' %>
41
+ <%= button_to "Reopen", reopen_railsexceptionlog_exception_path(@exception),
42
+ method: :post,
43
+ class: "inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-md text-slate-700 bg-white hover:bg-slate-50" %>
44
+ <% when 'reopened' %>
45
+ <%= button_to "Mark Resolved", resolve_railsexceptionlog_exception_path(@exception),
46
+ method: :post,
47
+ class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700" %>
48
+ <% when 'ignored' %>
49
+ <%= button_to "Unignore", reopen_railsexceptionlog_exception_path(@exception),
50
+ method: :post,
51
+ class: "inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-md text-slate-700 bg-white hover:bg-slate-50" %>
52
+ <% end %>
53
+ <%= button_to "Delete", railsexceptionlog_exception_path(@exception),
54
+ method: :delete,
55
+ data: { confirm: "Are you sure?" },
56
+ class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700" %>
57
+ </div>
58
+ </div>
59
+
60
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
61
+ <div class="lg:col-span-2 space-y-6">
62
+ <div class="bg-white shadow rounded-lg">
63
+ <div class="px-6 py-4 border-b border-slate-200 flex items-center justify-between">
64
+ <h2 class="text-lg font-medium text-slate-900">Exception Information</h2>
65
+ <div class="flex items-center space-x-2">
66
+ <span class="text-sm text-slate-500">Occurrences: </span>
67
+ <span class="text-sm font-bold text-slate-900"><%= @exception.occurrence_count %></span>
68
+ </div>
69
+ </div>
70
+ <div class="p-6">
71
+ <dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
72
+ <div class="sm:col-span-1">
73
+ <dt class="text-sm font-medium text-slate-500">Exception Class</dt>
74
+ <dd class="mt-1 text-sm text-slate-900 font-mono bg-red-50 px-3 py-2 rounded"><%= @exception.exception_class %></dd>
75
+ </div>
76
+ <div class="sm:col-span-1">
77
+ <dt class="text-sm font-medium text-slate-500">Status</dt>
78
+ <dd class="mt-1 text-sm">
79
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium <%= exception_status_badge(@exception) %>">
80
+ <%= @exception.status_label %>
81
+ </span>
82
+ </dd>
83
+ </div>
84
+ <div class="sm:col-span-1">
85
+ <dt class="text-sm font-medium text-slate-500">First Occurred</dt>
86
+ <dd class="mt-1 text-sm text-slate-900"><%= format_datetime(@exception.created_at) %></dd>
87
+ </div>
88
+ <div class="sm:col-span-1">
89
+ <dt class="text-sm font-medium text-slate-500">Last Occurred</dt>
90
+ <dd class="mt-1 text-sm text-slate-900"><%= format_datetime(@exception.last_occurred_at) %></dd>
91
+ </div>
92
+ <% if @exception.resolved_at %>
93
+ <div class="sm:col-span-1">
94
+ <dt class="text-sm font-medium text-slate-500">Resolved At</dt>
95
+ <dd class="mt-1 text-sm text-slate-900"><%= format_datetime(@exception.resolved_at) %></dd>
96
+ </div>
97
+ <% end %>
98
+ <% if @exception.user_email %>
99
+ <div class="sm:col-span-1">
100
+ <dt class="text-sm font-medium text-slate-500">Affected User</dt>
101
+ <dd class="mt-1 text-sm text-slate-900"><%= @exception.user_email %></dd>
102
+ </div>
103
+ <% end %>
104
+ <div class="sm:col-span-2">
105
+ <dt class="text-sm font-medium text-slate-500">Message</dt>
106
+ <dd class="mt-1 text-sm text-slate-900 bg-slate-50 px-3 py-2 rounded whitespace-pre-wrap font-mono"><%= @exception.message %></dd>
107
+ </div>
108
+ </dl>
109
+ </div>
110
+ </div>
111
+
112
+ <div class="bg-white shadow rounded-lg">
113
+ <div class="px-6 py-4 border-b border-slate-200">
114
+ <h2 class="text-lg font-medium text-slate-900">Stack Trace</h2>
115
+ </div>
116
+ <div class="p-6">
117
+ <pre class="text-sm text-slate-800 overflow-x-auto font-mono whitespace-pre-wrap bg-slate-900 text-slate-100 p-4 rounded"><%= @exception.backtrace %></pre>
118
+ </div>
119
+ </div>
120
+
121
+ <div class="bg-white shadow rounded-lg">
122
+ <div class="px-6 py-4 border-b border-slate-200 flex items-center justify-between">
123
+ <h2 class="text-lg font-medium text-slate-900">Comments</h2>
124
+ </div>
125
+ <div class="p-6">
126
+ <% if @comments.any? %>
127
+ <div class="space-y-4 mb-6">
128
+ <% @comments.each do |comment| %>
129
+ <div class="bg-slate-50 rounded-lg p-4">
130
+ <div class="flex items-center justify-between mb-2">
131
+ <span class="text-sm font-medium text-slate-900"><%= comment['author'] %></span>
132
+ <span class="text-xs text-slate-500"><%= comment['created_at'] %></span>
133
+ </div>
134
+ <p class="text-sm text-slate-700"><%= comment['body'] %></p>
135
+ </div>
136
+ <% end %>
137
+ </div>
138
+ <% end %>
139
+ <%= form_with url: add_comment_railsexceptionlog_exception_path(@exception), method: :post, local: true do |f| %>
140
+ <div class="flex gap-2">
141
+ <%= f.text_field :comment, placeholder: "Add a comment...",
142
+ class: "flex-1 rounded-md border-slate-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm px-3 py-2 border" %>
143
+ <%= f.submit "Add Comment", class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700" %>
144
+ </div>
145
+ <% end %>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="space-y-6">
151
+ <div class="bg-white shadow rounded-lg">
152
+ <div class="px-6 py-4 border-b border-slate-200">
153
+ <h2 class="text-lg font-medium text-slate-900">Request</h2>
154
+ </div>
155
+ <div class="p-6 space-y-4">
156
+ <div>
157
+ <dt class="text-sm font-medium text-slate-500">Method</dt>
158
+ <dd class="mt-1">
159
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded text-xs font-medium <%= request_method_class(@exception.request_method) %>">
160
+ <%= @exception.request_method %>
161
+ </span>
162
+ </dd>
163
+ </div>
164
+ <div>
165
+ <dt class="text-sm font-medium text-slate-500">Path</dt>
166
+ <dd class="mt-1 text-sm text-slate-900 font-mono break-all"><%= @exception.request_path %></dd>
167
+ </div>
168
+ <div>
169
+ <dt class="text-sm font-medium text-slate-500">Controller / Action</dt>
170
+ <dd class="mt-1 text-sm text-slate-900"><%= @exception.controller_name %>#<%= @exception.action_name %></dd>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ <div class="bg-white shadow rounded-lg">
176
+ <div class="px-6 py-4 border-b border-slate-200">
177
+ <h2 class="text-lg font-medium text-slate-900">Request Parameters</h2>
178
+ </div>
179
+ <div class="p-6">
180
+ <% if @exception_data.present? %>
181
+ <pre class="text-xs text-slate-800 overflow-x-auto font-mono whitespace-pre-wrap"><%= JSON.pretty_generate(@exception_data) %></pre>
182
+ <% else %>
183
+ <p class="text-sm text-slate-500">No parameters recorded</p>
184
+ <% end %>
185
+ </div>
186
+ </div>
187
+
188
+ <div class="bg-white shadow rounded-lg">
189
+ <div class="px-6 py-4 border-b border-slate-200">
190
+ <h2 class="text-lg font-medium text-slate-900">Session Data</h2>
191
+ </div>
192
+ <div class="p-6">
193
+ <% if @session_data.present? %>
194
+ <pre class="text-xs text-slate-800 overflow-x-auto font-mono whitespace-pre-wrap"><%= JSON.pretty_generate(@session_data) %></pre>
195
+ <% else %>
196
+ <p class="text-sm text-slate-500">No session data recorded</p>
197
+ <% end %>
198
+ </div>
199
+ </div>
200
+
201
+ <% if @headers.present? %>
202
+ <div class="bg-white shadow rounded-lg">
203
+ <div class="px-6 py-4 border-b border-slate-200">
204
+ <h2 class="text-lg font-medium text-slate-900">Headers</h2>
205
+ </div>
206
+ <div class="p-6">
207
+ <pre class="text-xs text-slate-800 overflow-x-auto font-mono whitespace-pre-wrap"><%= JSON.pretty_generate(@headers) %></pre>
208
+ </div>
209
+ </div>
210
+ <% end %>
211
+ </div>
212
+ </div>
213
+ </main>
214
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,17 @@
1
+ module RailsExceptionLog
2
+ module Routes
3
+ def draw
4
+ get '/exceptions', to: 'logged_exceptions#index', as: :railsexceptionlog_exceptions
5
+ get '/exceptions/:id', to: 'logged_exceptions#show', as: :railsexceptionlog_exception
6
+ delete '/exceptions/:id', to: 'logged_exceptions#destroy'
7
+ delete '/exceptions', to: 'logged_exceptions#destroy_all'
8
+ get '/exceptions/export', to: 'logged_exceptions#export'
9
+
10
+ post '/exceptions/:id/resolve', to: 'logged_exceptions#resolve', as: :resolve_railsexceptionlog_exception
11
+ post '/exceptions/:id/reopen', to: 'logged_exceptions#reopen', as: :reopen_railsexceptionlog_exception
12
+ post '/exceptions/:id/ignore', to: 'logged_exceptions#ignore', as: :ignore_railsexceptionlog_exception
13
+ post '/exceptions/:id/add_comment', to: 'logged_exceptions#add_comment',
14
+ as: :add_comment_railsexceptionlog_exception
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ class CreateRailsExceptionLogLoggedExceptions < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :rails_exception_log_logged_exceptions do |t|
4
+ t.string :exception_class, null: false
5
+ t.text :message
6
+ t.text :backtrace
7
+ t.string :controller_name
8
+ t.string :action_name
9
+ t.string :request_method
10
+ t.string :request_path
11
+ t.json :request_params
12
+ t.json :request_headers
13
+ t.json :session_data
14
+ t.text :exception_data
15
+ t.string :environment
16
+ t.string :fingerprint
17
+ t.integer :status, default: 0, null: false
18
+ t.integer :occurrence_count, default: 1, null: false
19
+ t.string :user_email
20
+ t.string :user_id
21
+ t.text :comments
22
+ t.datetime :resolved_at
23
+ t.datetime :last_occurred_at
24
+ t.integer :severity, default: 0
25
+
26
+ t.timestamps
27
+ end
28
+
29
+ add_index :rails_exception_log_logged_exceptions, :created_at
30
+ add_index :rails_exception_log_logged_exceptions, :exception_class
31
+ add_index :rails_exception_log_logged_exceptions, %i[controller_name action_name]
32
+ add_index :rails_exception_log_logged_exceptions, :fingerprint
33
+ add_index :rails_exception_log_logged_exceptions, :status
34
+ add_index :rails_exception_log_logged_exceptions, :last_occurred_at
35
+ end
36
+ end
@@ -0,0 +1,54 @@
1
+ require 'rails/generators/base'
2
+
3
+ module RailsExceptionLog
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('install/templates', __dir__)
7
+
8
+ class_option :skip_migrations, type: :boolean, default: false,
9
+ desc: 'Skip migrations installation'
10
+
11
+ def copy_migrations
12
+ return if options[:skip_migrations]
13
+
14
+ puts 'Copying migrations...'
15
+ migrations_dir = File.expand_path('db/migrate', destination_root)
16
+ FileUtils.mkdir_p(migrations_dir)
17
+
18
+ source_migrations = File.join(__dir__, '../../db/migrate/*.rb')
19
+ Dir.glob(source_migrations).each do |file|
20
+ filename = File.basename(file)
21
+ dest = File.join(migrations_dir, filename)
22
+ if File.exist?(dest)
23
+ puts " skip #{filename} (already exists)"
24
+ else
25
+ FileUtils.cp(file, dest)
26
+ puts " create #{filename}"
27
+ end
28
+ end
29
+ end
30
+
31
+ def add_routes
32
+ route_content = "
33
+ # Exception Log Routes
34
+ mount RailsExceptionLog::Engine => '/exceptions'
35
+ "
36
+
37
+ route_file = File.expand_path('config/routes.rb', destination_root)
38
+ return unless File.exist?(route_file)
39
+
40
+ existing_content = File.read(route_file)
41
+ return if existing_content.include?('RailsExceptionLog::Engine')
42
+
43
+ File.open(route_file, 'a') do |f|
44
+ f.puts route_content
45
+ end
46
+ puts ' route mount RailsExceptionLog::Engine'
47
+ end
48
+
49
+ def show_readme
50
+ readme 'lib/generators/install/templates/README.md' if behavior == :invoke
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,26 @@
1
+ require 'rails'
2
+
3
+ module RailsExceptionLog
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace RailsExceptionLog
6
+
7
+ initializer 'rails_exception_log.assets' do |app|
8
+ app.config.assets.paths << root.join('app/assets/stylesheets') if defined?(Sprockets) && app.config.assets
9
+
10
+ if defined?(Propshaft)
11
+ app.config.assets.loader = 'bun'
12
+ app.config.assets.build_paths << root.join('app/assets/builds')
13
+ end
14
+ end
15
+
16
+ initializer 'rails_exception_log.helpers' do
17
+ ActionView::Base.include RailsExceptionLog::ApplicationHelper
18
+ end
19
+
20
+ config.after_initialize do
21
+ RailsExceptionLog.before_log_exception = lambda { |_controller|
22
+ true
23
+ }
24
+ end
25
+ end
26
+ end