sinaliza 0.2.1 → 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/README.md +55 -0
- data/app/assets/stylesheets/sinaliza/application.css +578 -56
- data/app/controllers/sinaliza/interceptors_controller.rb +62 -0
- data/app/models/sinaliza/interceptor.rb +61 -0
- data/app/views/layouts/sinaliza/application.html.erb +13 -2
- data/app/views/sinaliza/events/index.html.erb +26 -5
- data/app/views/sinaliza/events/show.html.erb +46 -10
- data/app/views/sinaliza/interceptors/_form.html.erb +42 -0
- data/app/views/sinaliza/interceptors/edit.html.erb +7 -0
- data/app/views/sinaliza/interceptors/index.html.erb +57 -0
- data/app/views/sinaliza/interceptors/new.html.erb +7 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20260314000000_create_sinaliza_interceptors.rb +20 -0
- data/lib/sinaliza/engine.rb +23 -0
- data/lib/sinaliza/interceptor_registry.rb +109 -0
- data/lib/sinaliza/version.rb +1 -1
- data/lib/sinaliza.rb +12 -0
- metadata +9 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Sinaliza
|
|
2
|
+
class InterceptorsController < ApplicationController
|
|
3
|
+
before_action :set_interceptor, only: [ :edit, :update, :destroy, :toggle ]
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
@interceptors = Interceptor.order(created_at: :desc)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def new
|
|
10
|
+
@interceptor = Interceptor.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
@interceptor = Interceptor.new(interceptor_params)
|
|
15
|
+
|
|
16
|
+
if @interceptor.save
|
|
17
|
+
redirect_to interceptors_path, notice: "Interceptor created."
|
|
18
|
+
else
|
|
19
|
+
render :new, status: :unprocessable_entity
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def edit
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update
|
|
27
|
+
if @interceptor.update(interceptor_params)
|
|
28
|
+
redirect_to interceptors_path, notice: "Interceptor updated."
|
|
29
|
+
else
|
|
30
|
+
render :edit, status: :unprocessable_entity
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def destroy
|
|
35
|
+
@interceptor.destroy
|
|
36
|
+
redirect_to interceptors_path, notice: "Interceptor removed."
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def toggle
|
|
40
|
+
if @interceptor.active?
|
|
41
|
+
@interceptor.deactivate!
|
|
42
|
+
else
|
|
43
|
+
@interceptor.activate!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
redirect_to interceptors_path
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def set_interceptor
|
|
52
|
+
@interceptor = Interceptor.find(params[:id])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def interceptor_params
|
|
56
|
+
params.require(:interceptor).permit(
|
|
57
|
+
:target_class, :method_name, :method_type, :event_name,
|
|
58
|
+
:capture_args, :capture_return, :capture_execution_time
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Sinaliza
|
|
2
|
+
class Interceptor < ApplicationRecord
|
|
3
|
+
METHOD_TYPES = %w[instance class].freeze
|
|
4
|
+
|
|
5
|
+
validates :target_class, presence: true
|
|
6
|
+
validates :method_name, presence: true
|
|
7
|
+
validates :event_name, presence: true
|
|
8
|
+
validates :method_type, presence: true, inclusion: { in: METHOD_TYPES }
|
|
9
|
+
validates :target_class, uniqueness: { scope: [ :method_name, :method_type ] }
|
|
10
|
+
validate :target_class_exists
|
|
11
|
+
validate :method_exists_on_target
|
|
12
|
+
|
|
13
|
+
scope :active, -> { where(active: true) }
|
|
14
|
+
scope :inactive, -> { where(active: false) }
|
|
15
|
+
scope :by_target_class, ->(name) { where(target_class: name) }
|
|
16
|
+
|
|
17
|
+
after_create :apply_interceptor
|
|
18
|
+
after_destroy :deactivate!
|
|
19
|
+
|
|
20
|
+
def activate!
|
|
21
|
+
update!(active: true)
|
|
22
|
+
apply_interceptor unless InterceptorRegistry.applied?(self)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def deactivate!
|
|
26
|
+
update!(active: false) unless destroyed?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def key
|
|
30
|
+
separator = method_type == "instance" ? "#" : "."
|
|
31
|
+
"#{target_class}#{separator}#{method_name}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def apply_interceptor
|
|
37
|
+
InterceptorRegistry.apply!(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def target_class_exists
|
|
41
|
+
return if target_class.blank?
|
|
42
|
+
|
|
43
|
+
target_class.constantize
|
|
44
|
+
rescue NameError
|
|
45
|
+
errors.add(:target_class, "\"#{target_class}\" does not exist")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def method_exists_on_target
|
|
49
|
+
return if target_class.blank? || method_name.blank? || method_type.blank?
|
|
50
|
+
|
|
51
|
+
klass = target_class.constantize
|
|
52
|
+
available = method_type == "class" ? klass.methods : klass.instance_methods + klass.private_instance_methods
|
|
53
|
+
|
|
54
|
+
return if available.include?(method_name.to_sym)
|
|
55
|
+
|
|
56
|
+
errors.add(:method_name, "\"#{method_name}\" does not exist on #{target_class}")
|
|
57
|
+
rescue NameError
|
|
58
|
+
# target_class_exists already handles this
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<title>Sinaliza</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5
6
|
<%= csrf_meta_tags %>
|
|
6
7
|
<%= csp_meta_tag %>
|
|
7
8
|
|
|
8
9
|
<%= yield :head %>
|
|
9
10
|
|
|
10
|
-
<%= stylesheet_link_tag
|
|
11
|
+
<%= stylesheet_link_tag "sinaliza/application", media: "all" %>
|
|
11
12
|
</head>
|
|
12
|
-
<body>
|
|
13
|
+
<body class="sinaliza-root">
|
|
14
|
+
|
|
15
|
+
<nav class="sinaliza-nav">
|
|
16
|
+
<div class="sinaliza-nav__container">
|
|
17
|
+
<span class="sinaliza-nav__brand">sinaliza</span>
|
|
18
|
+
<div class="sinaliza-nav__links">
|
|
19
|
+
<%= link_to "Events", sinaliza.events_path, class: "sinaliza-nav__link" %>
|
|
20
|
+
<%= link_to "Interceptors", sinaliza.interceptors_path, class: "sinaliza-nav__link" %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</nav>
|
|
13
24
|
|
|
14
25
|
<%= yield %>
|
|
15
26
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="sinaliza-dashboard">
|
|
2
|
-
<h1>Events
|
|
2
|
+
<h1>Events</h1>
|
|
3
3
|
|
|
4
4
|
<%= render "filters" %>
|
|
5
5
|
|
|
@@ -22,10 +22,31 @@
|
|
|
22
22
|
<td class="sinaliza-time"><%= event.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
|
|
23
23
|
<td><span class="sinaliza-badge"><%= event.name %></span></td>
|
|
24
24
|
<td><span class="sinaliza-badge sinaliza-badge--source"><%= event.source %></span></td>
|
|
25
|
-
<td
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
<td>
|
|
26
|
+
<% if event.actor %>
|
|
27
|
+
<span class="sinaliza-ref"><%= event.actor_type %>#<%= event.actor_id %></span>
|
|
28
|
+
<% else %>
|
|
29
|
+
<span class="sinaliza-null">—</span>
|
|
30
|
+
<% end %>
|
|
31
|
+
</td>
|
|
32
|
+
<td>
|
|
33
|
+
<% if event.target %>
|
|
34
|
+
<span class="sinaliza-ref"><%= event.target_type %>#<%= event.target_id %></span>
|
|
35
|
+
<% else %>
|
|
36
|
+
<span class="sinaliza-null">—</span>
|
|
37
|
+
<% end %>
|
|
38
|
+
</td>
|
|
39
|
+
<td>
|
|
40
|
+
<% if event.context %>
|
|
41
|
+
<span class="sinaliza-ref"><%= event.context_type %>#<%= event.context_id %></span>
|
|
42
|
+
<% else %>
|
|
43
|
+
<span class="sinaliza-null">—</span>
|
|
44
|
+
<% end %>
|
|
45
|
+
</td>
|
|
46
|
+
<td>
|
|
47
|
+
<% count = event.children.size %>
|
|
48
|
+
<span class="sinaliza-children-count <%= 'sinaliza-children-count--has' if count > 0 %>"><%= count %></span>
|
|
49
|
+
</td>
|
|
29
50
|
<td><%= link_to "Detail", event_path(event), class: "sinaliza-link" %></td>
|
|
30
51
|
</tr>
|
|
31
52
|
<% end %>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="sinaliza-dashboard">
|
|
2
|
-
<p><%= link_to "Back to events", events_path, class: "sinaliza-link" %></p>
|
|
2
|
+
<p><%= link_to "Back to events", events_path, class: "sinaliza-link sinaliza-link--back" %></p>
|
|
3
3
|
|
|
4
4
|
<h1>Event #<%= @event.id %></h1>
|
|
5
5
|
|
|
@@ -20,15 +20,33 @@
|
|
|
20
20
|
</tr>
|
|
21
21
|
<tr>
|
|
22
22
|
<th>Actor</th>
|
|
23
|
-
<td
|
|
23
|
+
<td>
|
|
24
|
+
<% if @event.actor %>
|
|
25
|
+
<span class="sinaliza-ref"><%= @event.actor_type %>#<%= @event.actor_id %></span>
|
|
26
|
+
<% else %>
|
|
27
|
+
<span class="sinaliza-null">—</span>
|
|
28
|
+
<% end %>
|
|
29
|
+
</td>
|
|
24
30
|
</tr>
|
|
25
31
|
<tr>
|
|
26
32
|
<th>Target</th>
|
|
27
|
-
<td
|
|
33
|
+
<td>
|
|
34
|
+
<% if @event.target %>
|
|
35
|
+
<span class="sinaliza-ref"><%= @event.target_type %>#<%= @event.target_id %></span>
|
|
36
|
+
<% else %>
|
|
37
|
+
<span class="sinaliza-null">—</span>
|
|
38
|
+
<% end %>
|
|
39
|
+
</td>
|
|
28
40
|
</tr>
|
|
29
41
|
<tr>
|
|
30
42
|
<th>Context</th>
|
|
31
|
-
<td
|
|
43
|
+
<td>
|
|
44
|
+
<% if @event.context %>
|
|
45
|
+
<span class="sinaliza-ref"><%= @event.context_type %>#<%= @event.context_id %></span>
|
|
46
|
+
<% else %>
|
|
47
|
+
<span class="sinaliza-null">—</span>
|
|
48
|
+
<% end %>
|
|
49
|
+
</td>
|
|
32
50
|
</tr>
|
|
33
51
|
<tr>
|
|
34
52
|
<th>Metadata</th>
|
|
@@ -36,19 +54,25 @@
|
|
|
36
54
|
</tr>
|
|
37
55
|
<tr>
|
|
38
56
|
<th>IP address</th>
|
|
39
|
-
<td><%= @event.ip_address || "-" %></td>
|
|
57
|
+
<td><%= @event.ip_address || content_tag(:span, "—".html_safe, class: "sinaliza-null") %></td>
|
|
40
58
|
</tr>
|
|
41
59
|
<tr>
|
|
42
60
|
<th>User agent</th>
|
|
43
|
-
<td><%= @event.user_agent || "-" %></td>
|
|
61
|
+
<td><%= @event.user_agent || content_tag(:span, "—".html_safe, class: "sinaliza-null") %></td>
|
|
44
62
|
</tr>
|
|
45
63
|
<tr>
|
|
46
64
|
<th>Request ID</th>
|
|
47
|
-
<td
|
|
65
|
+
<td>
|
|
66
|
+
<% if @event.request_id %>
|
|
67
|
+
<span class="sinaliza-ref"><%= @event.request_id %></span>
|
|
68
|
+
<% else %>
|
|
69
|
+
<span class="sinaliza-null">—</span>
|
|
70
|
+
<% end %>
|
|
71
|
+
</td>
|
|
48
72
|
</tr>
|
|
49
73
|
<tr>
|
|
50
74
|
<th>Created at</th>
|
|
51
|
-
<td><%= @event.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
|
|
75
|
+
<td class="sinaliza-time"><%= @event.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
|
|
52
76
|
</tr>
|
|
53
77
|
</table>
|
|
54
78
|
|
|
@@ -72,8 +96,20 @@
|
|
|
72
96
|
<td class="sinaliza-time"><%= child.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
|
|
73
97
|
<td><span class="sinaliza-badge"><%= child.name %></span></td>
|
|
74
98
|
<td><span class="sinaliza-badge sinaliza-badge--source"><%= child.source %></span></td>
|
|
75
|
-
<td
|
|
76
|
-
|
|
99
|
+
<td>
|
|
100
|
+
<% if child.actor %>
|
|
101
|
+
<span class="sinaliza-ref"><%= child.actor_type %>#<%= child.actor_id %></span>
|
|
102
|
+
<% else %>
|
|
103
|
+
<span class="sinaliza-null">—</span>
|
|
104
|
+
<% end %>
|
|
105
|
+
</td>
|
|
106
|
+
<td>
|
|
107
|
+
<% if child.target %>
|
|
108
|
+
<span class="sinaliza-ref"><%= child.target_type %>#<%= child.target_id %></span>
|
|
109
|
+
<% else %>
|
|
110
|
+
<span class="sinaliza-null">—</span>
|
|
111
|
+
<% end %>
|
|
112
|
+
</td>
|
|
77
113
|
<td><%= link_to "Detail", event_path(child), class: "sinaliza-link" %></td>
|
|
78
114
|
</tr>
|
|
79
115
|
<% end %>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<%= form_with model: @interceptor, url: url, method: method, class: "sinaliza-form" do |f| %>
|
|
2
|
+
<% if @interceptor.errors.any? %>
|
|
3
|
+
<div class="sinaliza-errors">
|
|
4
|
+
<ul>
|
|
5
|
+
<% @interceptor.errors.full_messages.each do |message| %>
|
|
6
|
+
<li><%= message %></li>
|
|
7
|
+
<% end %>
|
|
8
|
+
</ul>
|
|
9
|
+
</div>
|
|
10
|
+
<% end %>
|
|
11
|
+
|
|
12
|
+
<div class="sinaliza-form__field">
|
|
13
|
+
<%= f.label :target_class, "Target class" %>
|
|
14
|
+
<%= f.text_field :target_class, placeholder: "User" %>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="sinaliza-form__field">
|
|
18
|
+
<%= f.label :method_name, "Method name" %>
|
|
19
|
+
<%= f.text_field :method_name, placeholder: "update_profile" %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="sinaliza-form__field">
|
|
23
|
+
<%= f.label :method_type, "Method type" %>
|
|
24
|
+
<%= f.select :method_type, Sinaliza::Interceptor::METHOD_TYPES %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="sinaliza-form__field">
|
|
28
|
+
<%= f.label :event_name, "Event name" %>
|
|
29
|
+
<%= f.text_field :event_name, placeholder: "user.profile_updated" %>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="sinaliza-form__checkboxes">
|
|
33
|
+
<label><%= f.check_box :capture_args %> Capture arguments</label>
|
|
34
|
+
<label><%= f.check_box :capture_return %> Capture return value</label>
|
|
35
|
+
<label><%= f.check_box :capture_execution_time %> Capture execution time</label>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="sinaliza-form__actions">
|
|
39
|
+
<%= f.submit class: "sinaliza-btn" %>
|
|
40
|
+
<%= link_to "Cancel", interceptors_path, class: "sinaliza-link" %>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<div class="sinaliza-dashboard">
|
|
2
|
+
<div class="sinaliza-header">
|
|
3
|
+
<h1>Interceptors</h1>
|
|
4
|
+
<%= link_to "New interceptor", new_interceptor_path, class: "sinaliza-btn" %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<table class="sinaliza-table">
|
|
8
|
+
<thead>
|
|
9
|
+
<tr>
|
|
10
|
+
<th>Target</th>
|
|
11
|
+
<th>Event name</th>
|
|
12
|
+
<th>Capture</th>
|
|
13
|
+
<th>Status</th>
|
|
14
|
+
<th></th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody>
|
|
18
|
+
<% @interceptors.each do |interceptor| %>
|
|
19
|
+
<tr>
|
|
20
|
+
<td><span class="sinaliza-badge"><%= interceptor.key %></span></td>
|
|
21
|
+
<td><span class="sinaliza-badge sinaliza-badge--source"><%= interceptor.event_name %></span></td>
|
|
22
|
+
<td>
|
|
23
|
+
<% captures = [] %>
|
|
24
|
+
<% captures << "args" if interceptor.capture_args %>
|
|
25
|
+
<% captures << "return" if interceptor.capture_return %>
|
|
26
|
+
<% captures << "time" if interceptor.capture_execution_time %>
|
|
27
|
+
<% if captures.any? %>
|
|
28
|
+
<% captures.each do |c| %>
|
|
29
|
+
<span class="sinaliza-capture-tag"><%= c %></span>
|
|
30
|
+
<% end %>
|
|
31
|
+
<% else %>
|
|
32
|
+
<span class="sinaliza-null">—</span>
|
|
33
|
+
<% end %>
|
|
34
|
+
</td>
|
|
35
|
+
<td>
|
|
36
|
+
<%= button_to toggle_interceptor_path(interceptor), method: :patch, class: "sinaliza-switch-form" do %>
|
|
37
|
+
<span class="sinaliza-switch <%= 'sinaliza-switch--on' if interceptor.active? %>">
|
|
38
|
+
<span class="sinaliza-switch__track">
|
|
39
|
+
<span class="sinaliza-switch__knob"></span>
|
|
40
|
+
</span>
|
|
41
|
+
</span>
|
|
42
|
+
<% end %>
|
|
43
|
+
</td>
|
|
44
|
+
<td class="sinaliza-actions">
|
|
45
|
+
<%= link_to "Edit", edit_interceptor_path(interceptor), class: "sinaliza-link" %>
|
|
46
|
+
<%= button_to "Delete", interceptor_path(interceptor), method: :delete,
|
|
47
|
+
data: { turbo_confirm: "Are you sure?" }, class: "sinaliza-link sinaliza-link--danger" %>
|
|
48
|
+
</td>
|
|
49
|
+
</tr>
|
|
50
|
+
<% end %>
|
|
51
|
+
</tbody>
|
|
52
|
+
</table>
|
|
53
|
+
|
|
54
|
+
<% if @interceptors.empty? %>
|
|
55
|
+
<p class="sinaliza-empty">No interceptors configured.</p>
|
|
56
|
+
<% end %>
|
|
57
|
+
</div>
|
data/config/routes.rb
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class CreateSinalizaInterceptors < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :sinaliza_interceptors do |t|
|
|
4
|
+
t.string :target_class, null: false
|
|
5
|
+
t.string :method_name, null: false
|
|
6
|
+
t.string :method_type, null: false, default: "instance"
|
|
7
|
+
t.string :event_name, null: false
|
|
8
|
+
t.boolean :capture_args, default: false
|
|
9
|
+
t.boolean :capture_return, default: false
|
|
10
|
+
t.boolean :capture_execution_time, default: false
|
|
11
|
+
t.boolean :active, default: true
|
|
12
|
+
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :sinaliza_interceptors, [ :target_class, :method_name, :method_type ],
|
|
17
|
+
unique: true, name: "idx_sinaliza_interceptors_uniqueness"
|
|
18
|
+
add_index :sinaliza_interceptors, :active
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/sinaliza/engine.rb
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
module Sinaliza
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
isolate_namespace Sinaliza
|
|
4
|
+
|
|
5
|
+
initializer "sinaliza.interceptors", after: :load_config_initializers do
|
|
6
|
+
ActiveSupport.on_load(:active_record) do
|
|
7
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
|
8
|
+
if ActiveRecord::Base.connection.table_exists?(:sinaliza_interceptors)
|
|
9
|
+
Sinaliza::InterceptorRegistry.apply_all!
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
rescue ActiveRecord::NoDatabaseError
|
|
13
|
+
# Database not created yet — skip
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.to_prepare do
|
|
18
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
|
19
|
+
if ActiveRecord::Base.connection.table_exists?(:sinaliza_interceptors)
|
|
20
|
+
Sinaliza::InterceptorRegistry.reset!
|
|
21
|
+
Sinaliza::InterceptorRegistry.apply_all!
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
rescue ActiveRecord::NoDatabaseError
|
|
25
|
+
# Database not created yet — skip
|
|
26
|
+
end
|
|
4
27
|
end
|
|
5
28
|
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module Sinaliza
|
|
2
|
+
module InterceptorRegistry
|
|
3
|
+
class << self
|
|
4
|
+
def apply_all!
|
|
5
|
+
Sinaliza::Interceptor.find_each do |interceptor|
|
|
6
|
+
apply!(interceptor)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def apply!(interceptor)
|
|
11
|
+
key = interceptor.key
|
|
12
|
+
|
|
13
|
+
# Store the authoritative interceptor ID for this key
|
|
14
|
+
authoritative_ids[key] = interceptor.id
|
|
15
|
+
|
|
16
|
+
klass = interceptor.target_class.constantize
|
|
17
|
+
target = interceptor.method_type == "class" ? klass.singleton_class : klass
|
|
18
|
+
class_id = target.object_id
|
|
19
|
+
|
|
20
|
+
# Only prepend if the class object changed (reloaded) or never prepended
|
|
21
|
+
unless prepended_classes[key] == class_id
|
|
22
|
+
mod = build_module(key)
|
|
23
|
+
target.prepend(mod)
|
|
24
|
+
prepended_classes[key] = class_id
|
|
25
|
+
end
|
|
26
|
+
rescue NameError
|
|
27
|
+
# Target class does not exist — skip
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def applied?(interceptor)
|
|
31
|
+
prepended_classes.key?(interceptor.key)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def authoritative_id_for(key)
|
|
35
|
+
authoritative_ids[key]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def reset!
|
|
39
|
+
prepended_classes.clear
|
|
40
|
+
authoritative_ids.clear
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def authoritative_ids
|
|
46
|
+
@authoritative_ids ||= {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def prepended_classes
|
|
50
|
+
@prepended_classes ||= {}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_module(key)
|
|
54
|
+
is_instance = key.include?("#")
|
|
55
|
+
|
|
56
|
+
Module.new do
|
|
57
|
+
method_name = is_instance ? key.split("#").last : key.split(".").last
|
|
58
|
+
|
|
59
|
+
define_method(method_name.to_sym) do |*args, **kwargs, &block|
|
|
60
|
+
# Prevent reentrant recording (e.g. ActiveRecord calling save internally)
|
|
61
|
+
thread_key = :"_sinaliza_interceptor_#{key}"
|
|
62
|
+
if Thread.current[thread_key]
|
|
63
|
+
return super(*args, **kwargs, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
auth_id = Sinaliza::InterceptorRegistry.authoritative_id_for(key)
|
|
67
|
+
record = auth_id ? Sinaliza::Interceptor.find_by(id: auth_id) : nil
|
|
68
|
+
|
|
69
|
+
unless record&.active?
|
|
70
|
+
return super(*args, **kwargs, &block)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
metadata = {}
|
|
74
|
+
|
|
75
|
+
if record.capture_args
|
|
76
|
+
metadata[:args] = args.map(&:inspect)
|
|
77
|
+
metadata[:kwargs] = kwargs.transform_values(&:inspect) if kwargs.any?
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
begin
|
|
81
|
+
Thread.current[thread_key] = true
|
|
82
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) if record.capture_execution_time
|
|
83
|
+
result = super(*args, **kwargs, &block)
|
|
84
|
+
ensure
|
|
85
|
+
Thread.current[thread_key] = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if record.capture_execution_time && start_time
|
|
89
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
90
|
+
metadata[:execution_time_ms] = (elapsed * 1000).round(2)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
metadata[:return] = result.inspect if record.capture_return
|
|
94
|
+
|
|
95
|
+
event_attrs = { name: record.event_name, metadata: metadata, source: "interceptor" }
|
|
96
|
+
|
|
97
|
+
# For instance methods, self is the target object
|
|
98
|
+
if is_instance && self.class < ActiveRecord::Base
|
|
99
|
+
event_attrs[:target] = self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
Sinaliza.record(**event_attrs)
|
|
103
|
+
result
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/sinaliza/version.rb
CHANGED
data/lib/sinaliza.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "sinaliza/version"
|
|
2
2
|
require "sinaliza/engine"
|
|
3
3
|
require "sinaliza/configuration"
|
|
4
|
+
require "sinaliza/interceptor_registry"
|
|
4
5
|
|
|
5
6
|
module Sinaliza
|
|
6
7
|
class << self
|
|
@@ -29,6 +30,17 @@ module Sinaliza
|
|
|
29
30
|
)
|
|
30
31
|
end
|
|
31
32
|
|
|
33
|
+
def intercept(target_class, method_name, event_name:, method_type: "instance", **options)
|
|
34
|
+
interceptor = Sinaliza::Interceptor.create!(
|
|
35
|
+
target_class: target_class.to_s,
|
|
36
|
+
method_name: method_name.to_s,
|
|
37
|
+
method_type: method_type,
|
|
38
|
+
event_name: event_name,
|
|
39
|
+
**options
|
|
40
|
+
)
|
|
41
|
+
interceptor
|
|
42
|
+
end
|
|
43
|
+
|
|
32
44
|
def record_later(name:, actor: nil, target: nil, context: nil, parent: nil, metadata: {}, source: nil, ip_address: nil, user_agent: nil, request_id: nil)
|
|
33
45
|
attributes = {
|
|
34
46
|
name: name,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sinaliza
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Marcelo Moraes
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- app/controllers/concerns/sinaliza/traceable.rb
|
|
46
46
|
- app/controllers/sinaliza/application_controller.rb
|
|
47
47
|
- app/controllers/sinaliza/events_controller.rb
|
|
48
|
+
- app/controllers/sinaliza/interceptors_controller.rb
|
|
48
49
|
- app/helpers/sinaliza/application_helper.rb
|
|
49
50
|
- app/jobs/sinaliza/application_job.rb
|
|
50
51
|
- app/jobs/sinaliza/record_event_job.rb
|
|
@@ -52,17 +53,24 @@ files:
|
|
|
52
53
|
- app/models/concerns/sinaliza/trackable.rb
|
|
53
54
|
- app/models/sinaliza/application_record.rb
|
|
54
55
|
- app/models/sinaliza/event.rb
|
|
56
|
+
- app/models/sinaliza/interceptor.rb
|
|
55
57
|
- app/views/layouts/sinaliza/application.html.erb
|
|
56
58
|
- app/views/sinaliza/events/_filters.html.erb
|
|
57
59
|
- app/views/sinaliza/events/index.html.erb
|
|
58
60
|
- app/views/sinaliza/events/show.html.erb
|
|
61
|
+
- app/views/sinaliza/interceptors/_form.html.erb
|
|
62
|
+
- app/views/sinaliza/interceptors/edit.html.erb
|
|
63
|
+
- app/views/sinaliza/interceptors/index.html.erb
|
|
64
|
+
- app/views/sinaliza/interceptors/new.html.erb
|
|
59
65
|
- config/routes.rb
|
|
60
66
|
- db/migrate/20260219000000_create_sinaliza_events.rb
|
|
61
67
|
- db/migrate/20260219100000_add_parent_id_to_sinaliza_events.rb
|
|
62
68
|
- db/migrate/20260220000000_add_context_to_sinaliza_events.rb
|
|
69
|
+
- db/migrate/20260314000000_create_sinaliza_interceptors.rb
|
|
63
70
|
- lib/sinaliza.rb
|
|
64
71
|
- lib/sinaliza/configuration.rb
|
|
65
72
|
- lib/sinaliza/engine.rb
|
|
73
|
+
- lib/sinaliza/interceptor_registry.rb
|
|
66
74
|
- lib/sinaliza/version.rb
|
|
67
75
|
- lib/tasks/sinaliza_tasks.rake
|
|
68
76
|
homepage: https://github.com/marcelonmoraes/sinaliza
|