railswatch_gem 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +130 -0
- data/Rakefile +8 -0
- data/lib/railswatch_gem/client.rb +145 -0
- data/lib/railswatch_gem/configuration.rb +53 -0
- data/lib/railswatch_gem/helpers.rb +71 -0
- data/lib/railswatch_gem/instrumentation/cache_instrumenter.rb +64 -0
- data/lib/railswatch_gem/instrumentation/commands_instrumenter.rb +93 -0
- data/lib/railswatch_gem/instrumentation/errors_instrumenter.rb +66 -0
- data/lib/railswatch_gem/instrumentation/jobs_instrumenter.rb +74 -0
- data/lib/railswatch_gem/instrumentation/logs_instrumenter.rb +104 -0
- data/lib/railswatch_gem/instrumentation/mail_instrumenter.rb +67 -0
- data/lib/railswatch_gem/instrumentation/models_instrumenter.rb +91 -0
- data/lib/railswatch_gem/instrumentation/notifications_instrumenter.rb +71 -0
- data/lib/railswatch_gem/instrumentation/outgoing_requests_instrumenter.rb +96 -0
- data/lib/railswatch_gem/instrumentation/queries_instrumenter.rb +58 -0
- data/lib/railswatch_gem/instrumentation/registry.rb +49 -0
- data/lib/railswatch_gem/instrumentation/requests_instrumenter.rb +145 -0
- data/lib/railswatch_gem/instrumentation/scheduled_tasks_instrumenter.rb +82 -0
- data/lib/railswatch_gem/middleware/request_context.rb +28 -0
- data/lib/railswatch_gem/railtie.rb +22 -0
- data/lib/railswatch_gem/version.rb +5 -0
- data/lib/railswatch_gem.rb +75 -0
- data/manual_test.rb +103 -0
- data/sig/railswatch_gem.rbs +4 -0
- metadata +72 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/notifications"
|
|
4
|
+
|
|
5
|
+
module RailswatchGem
|
|
6
|
+
module Instrumentation
|
|
7
|
+
class RequestsInstrumenter
|
|
8
|
+
def initialize(client, config)
|
|
9
|
+
@client = client
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start
|
|
14
|
+
# Subscribe to the standard Rails controller event.
|
|
15
|
+
# This event includes details like status, path, duration, and DB runtime.
|
|
16
|
+
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
|
|
17
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
18
|
+
process_event(event)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def process_event(event)
|
|
25
|
+
payload = event.payload
|
|
26
|
+
|
|
27
|
+
# Retrieve request ID for correlation
|
|
28
|
+
request_id = payload[:request_id] || Thread.current[:railswatch_request_id]
|
|
29
|
+
|
|
30
|
+
# Filter parameters to prevent PII leakage
|
|
31
|
+
raw_params = payload[:params] || {}
|
|
32
|
+
safe_params = filter_parameters(raw_params)
|
|
33
|
+
|
|
34
|
+
# Extract extra context (Headers, Session, User)
|
|
35
|
+
headers_obj = payload[:headers]
|
|
36
|
+
|
|
37
|
+
safe_headers = extract_headers(headers_obj)
|
|
38
|
+
safe_session = extract_session(headers_obj)
|
|
39
|
+
user_info = extract_user(headers_obj)
|
|
40
|
+
|
|
41
|
+
data = {
|
|
42
|
+
event_type: "request",
|
|
43
|
+
# Fix: Ensure timestamp is a Time object before calling utc
|
|
44
|
+
timestamp: Time.at(event.end).utc.iso8601,
|
|
45
|
+
request_id: request_id,
|
|
46
|
+
|
|
47
|
+
# HTTP Context
|
|
48
|
+
method: payload[:method],
|
|
49
|
+
path: payload[:path],
|
|
50
|
+
status: payload[:status] || 500,
|
|
51
|
+
format: payload[:format].to_s,
|
|
52
|
+
|
|
53
|
+
# Inputs & State
|
|
54
|
+
params: safe_params,
|
|
55
|
+
headers: safe_headers,
|
|
56
|
+
session: safe_session,
|
|
57
|
+
user: user_info,
|
|
58
|
+
|
|
59
|
+
# Rails Context
|
|
60
|
+
controller: payload[:controller],
|
|
61
|
+
action: payload[:action],
|
|
62
|
+
|
|
63
|
+
# Performance Metrics
|
|
64
|
+
duration_ms: event.duration.round(2),
|
|
65
|
+
view_runtime_ms: payload[:view_runtime]&.round(2),
|
|
66
|
+
db_runtime_ms: payload[:db_runtime]&.round(2),
|
|
67
|
+
allocations: event.allocations
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Capture Exception Details if the request failed
|
|
71
|
+
if payload[:exception]
|
|
72
|
+
data[:error_class] = payload[:exception].first.to_s
|
|
73
|
+
data[:error_message] = payload[:exception].last.to_s
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
@client.record(data)
|
|
77
|
+
rescue => e
|
|
78
|
+
warn "RailswatchGem: Failed to process request event: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# --- Extraction Helpers ---
|
|
82
|
+
|
|
83
|
+
def extract_headers(headers)
|
|
84
|
+
return {} unless headers.respond_to?(:env)
|
|
85
|
+
|
|
86
|
+
interesting_headers = %w[
|
|
87
|
+
CONTENT_TYPE CONTENT_LENGTH
|
|
88
|
+
HTTP_AUTHORIZATION HTTP_USER_AGENT HTTP_REFERER HTTP_ACCEPT
|
|
89
|
+
HTTP_X_REQUEST_ID HTTP_X_FORWARDED_FOR
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
headers.env.select { |k, _| interesting_headers.include?(k) }.transform_keys do |k|
|
|
93
|
+
k.sub(/^HTTP_/, "").split("_").map(&:capitalize).join("-")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def extract_session(headers)
|
|
98
|
+
return {} unless headers.respond_to?(:env)
|
|
99
|
+
|
|
100
|
+
raw_session = headers.env["rack.session"]
|
|
101
|
+
return {} unless raw_session
|
|
102
|
+
|
|
103
|
+
session_hash = raw_session.to_hash
|
|
104
|
+
filter_parameters(session_hash)
|
|
105
|
+
rescue
|
|
106
|
+
{ error: "Could not serialize session" }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def extract_user(headers)
|
|
110
|
+
return nil unless headers.respond_to?(:env)
|
|
111
|
+
|
|
112
|
+
warden = headers.env["warden"]
|
|
113
|
+
if warden && warden.user
|
|
114
|
+
user = warden.user
|
|
115
|
+
{
|
|
116
|
+
id: user.respond_to?(:id) ? user.id : nil,
|
|
117
|
+
email: user.respond_to?(:email) ? user.email : nil,
|
|
118
|
+
class: user.class.name
|
|
119
|
+
}
|
|
120
|
+
else
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
rescue
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def filter_parameters(params)
|
|
128
|
+
if defined?(::ActiveSupport::ParameterFilter) && defined?(::Rails.application)
|
|
129
|
+
filter = ::ActiveSupport::ParameterFilter.new(::Rails.application.config.filter_parameters)
|
|
130
|
+
return filter.filter(params)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if defined?(::ActionDispatch::Http::ParameterFilter) && defined?(::Rails.application)
|
|
134
|
+
filter = ::ActionDispatch::Http::ParameterFilter.new(::Rails.application.config.filter_parameters)
|
|
135
|
+
return filter.filter(params)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sensitive = %w[password password_confirmation token secret credit_card]
|
|
139
|
+
params.select { |k, _v| !sensitive.include?(k.to_s) }
|
|
140
|
+
rescue
|
|
141
|
+
{ error: "Could not filter parameters" }
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/notifications"
|
|
4
|
+
|
|
5
|
+
module RailswatchGem
|
|
6
|
+
module Instrumentation
|
|
7
|
+
class ScheduledTasksInstrumenter
|
|
8
|
+
def initialize(client, config)
|
|
9
|
+
@client = client
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start
|
|
14
|
+
# Only instrument Rake if it is loaded.
|
|
15
|
+
# This prevents errors if the gem is used in a context where Rake isn't present.
|
|
16
|
+
if defined?(::Rake::Task)
|
|
17
|
+
patch_rake_tasks
|
|
18
|
+
|
|
19
|
+
ActiveSupport::Notifications.subscribe("task.rake") do |*args|
|
|
20
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
|
21
|
+
process_event(event)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def patch_rake_tasks
|
|
29
|
+
# Ensure we only patch once
|
|
30
|
+
return if ::Rake::Task.include?(RakeTaskPatch)
|
|
31
|
+
|
|
32
|
+
::Rake::Task.prepend(RakeTaskPatch)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def process_event(event)
|
|
36
|
+
payload = event.payload
|
|
37
|
+
task_name = payload[:name]
|
|
38
|
+
|
|
39
|
+
# Optional: Ignore internal Railswatch tasks to prevent noise
|
|
40
|
+
return if task_name.to_s.start_with?("railswatch:")
|
|
41
|
+
|
|
42
|
+
data = {
|
|
43
|
+
event_type: "scheduled_task",
|
|
44
|
+
# FIX: Handle Float timestamps safely
|
|
45
|
+
timestamp: Time.at(event.end).utc.iso8601,
|
|
46
|
+
|
|
47
|
+
# Task Identity
|
|
48
|
+
name: task_name,
|
|
49
|
+
args: payload[:args].to_a, # Rake args come as a wrapper, convert to array
|
|
50
|
+
|
|
51
|
+
# Performance
|
|
52
|
+
duration_ms: event.duration.round(2)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Error handling
|
|
56
|
+
if payload[:exception]
|
|
57
|
+
data[:error_class] = payload[:exception].first.to_s
|
|
58
|
+
data[:error_message] = payload[:exception].last.to_s
|
|
59
|
+
data[:status] = "failed"
|
|
60
|
+
else
|
|
61
|
+
data[:status] = "success"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@client.record(data)
|
|
65
|
+
rescue => e
|
|
66
|
+
warn "RailswatchGem: Failed to process scheduled task event: #{e.message}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# ----------------------------------------------------------------
|
|
70
|
+
# Internal Patch for Rake::Task
|
|
71
|
+
# ----------------------------------------------------------------
|
|
72
|
+
module RakeTaskPatch
|
|
73
|
+
def execute(args = nil)
|
|
74
|
+
# We explicitly capture 'name' here because it's available on the task instance
|
|
75
|
+
ActiveSupport::Notifications.instrument("task.rake", { name: name, args: args }) do
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module RailswatchGem
|
|
6
|
+
module Middleware
|
|
7
|
+
class RequestContext
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(env)
|
|
13
|
+
# 1. Try ActionDispatch's ID (standard Rails)
|
|
14
|
+
# 2. Try HTTP header (load balancers)
|
|
15
|
+
# 3. Fallback to random UUID
|
|
16
|
+
req_id = env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"] || SecureRandom.uuid
|
|
17
|
+
|
|
18
|
+
# Store in thread local so LogsInstrumenter can read it deeper in the stack
|
|
19
|
+
Thread.current[:railswatch_request_id] = req_id
|
|
20
|
+
|
|
21
|
+
@app.call(env)
|
|
22
|
+
ensure
|
|
23
|
+
# Clean up to prevent leakage between requests in threaded web servers (Puma/Unicorn)
|
|
24
|
+
Thread.current[:railswatch_request_id] = nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
require_relative "middleware/request_context"
|
|
5
|
+
|
|
6
|
+
module RailswatchGem
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
# Insert our middleware at the very top (index 0) to ensure we catch everything,
|
|
9
|
+
# including potential errors in other middlewares.
|
|
10
|
+
initializer "railswatch.middleware" do |app|
|
|
11
|
+
app.middleware.insert_before 0, RailswatchGem::Middleware::RequestContext
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Automatically boot the instrumentation when Rails finishes initializing.
|
|
15
|
+
config.after_initialize do
|
|
16
|
+
# Only boot if the user hasn't disabled auto_boot in their config
|
|
17
|
+
if RailswatchGem.configuration.auto_boot
|
|
18
|
+
RailswatchGem.boot!
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "railswatch_gem/version"
|
|
4
|
+
require_relative "railswatch_gem/configuration"
|
|
5
|
+
require_relative "railswatch_gem/client"
|
|
6
|
+
require_relative "railswatch_gem/instrumentation/registry"
|
|
7
|
+
require_relative "railswatch_gem/instrumentation/requests_instrumenter"
|
|
8
|
+
require_relative "railswatch_gem/instrumentation/queries_instrumenter"
|
|
9
|
+
require_relative "railswatch_gem/instrumentation/jobs_instrumenter"
|
|
10
|
+
require_relative "railswatch_gem/instrumentation/cache_instrumenter"
|
|
11
|
+
require_relative "railswatch_gem/instrumentation/mail_instrumenter"
|
|
12
|
+
require_relative "railswatch_gem/instrumentation/outgoing_requests_instrumenter"
|
|
13
|
+
require_relative "railswatch_gem/instrumentation/scheduled_tasks_instrumenter"
|
|
14
|
+
require_relative "railswatch_gem/instrumentation/commands_instrumenter"
|
|
15
|
+
require_relative "railswatch_gem/instrumentation/notifications_instrumenter"
|
|
16
|
+
require_relative "railswatch_gem/instrumentation/logs_instrumenter"
|
|
17
|
+
require_relative "railswatch_gem/instrumentation/errors_instrumenter"
|
|
18
|
+
require_relative "railswatch_gem/instrumentation/models_instrumenter"
|
|
19
|
+
require_relative "railswatch_gem/middleware/request_context"
|
|
20
|
+
require_relative "railswatch_gem/helpers"
|
|
21
|
+
|
|
22
|
+
# Load the Railtie if we are inside a Rails application.
|
|
23
|
+
# We wrap this in a rescue block because sometimes 'Rails' is defined
|
|
24
|
+
# (e.g. in test mocks or specific environments) but the 'rails/railtie' file
|
|
25
|
+
# is not actually available in the load path.
|
|
26
|
+
if defined?(Rails)
|
|
27
|
+
begin
|
|
28
|
+
require_relative "railswatch_gem/railtie"
|
|
29
|
+
rescue LoadError
|
|
30
|
+
# Rails constant was defined, but we couldn't load rails/railtie.
|
|
31
|
+
# Proceeding without Railtie integration.
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module RailswatchGem
|
|
36
|
+
class << self
|
|
37
|
+
def configure
|
|
38
|
+
yield(configuration) if block_given?
|
|
39
|
+
|
|
40
|
+
# Automatically boot if configured, or allow manual booting
|
|
41
|
+
# Ideally, you might want to call boot! explicitly in a Railtie
|
|
42
|
+
boot! if configuration.auto_boot && !defined?(Rails) # If Rails, Railtie handles it
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def configuration
|
|
46
|
+
@configuration ||= Configuration.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def client
|
|
50
|
+
@client ||= Client.new(
|
|
51
|
+
ingest_url: configuration.ingest_url,
|
|
52
|
+
env_token: configuration.env_token,
|
|
53
|
+
batch_size: configuration.batch_size,
|
|
54
|
+
flush_interval: configuration.flush_interval
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def instrumentation_registry
|
|
59
|
+
@instrumentation_registry ||= Instrumentation::Registry.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Safe to call multiple times
|
|
63
|
+
def boot!
|
|
64
|
+
return if @booted
|
|
65
|
+
|
|
66
|
+
instrumentation_registry.boot!(client, configuration)
|
|
67
|
+
@booted = true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def record(event)
|
|
71
|
+
# Ensure client is initialized
|
|
72
|
+
client.record(event)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/manual_test.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
require "json"
|
|
3
|
+
require "thread"
|
|
4
|
+
require "logger"
|
|
5
|
+
|
|
6
|
+
# Since we are running this script standalone (outside of Rails), we need to
|
|
7
|
+
# manually load ActiveSupport parts that the gem expects to be present.
|
|
8
|
+
# Ideally, your gem files should require what they need, but in a Rails gem
|
|
9
|
+
# it is common to assume these are available.
|
|
10
|
+
require "active_support"
|
|
11
|
+
require "active_support/concern"
|
|
12
|
+
require "active_support/core_ext"
|
|
13
|
+
require "active_support/notifications"
|
|
14
|
+
|
|
15
|
+
# --- MOCK RAILS ENVIRONMENT ---
|
|
16
|
+
# The LogsInstrumenter and ErrorsInstrumenter expect Rails to be present
|
|
17
|
+
# and configured. We mock the minimal requirements here.
|
|
18
|
+
unless defined?(Rails)
|
|
19
|
+
module Rails; end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Add logger if missing (ActiveSupport defines the Module, but not the method)
|
|
23
|
+
unless Rails.respond_to?(:logger)
|
|
24
|
+
def Rails.logger
|
|
25
|
+
@logger ||= Logger.new($stdout)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Add error reporter mock for ErrorsInstrumenter
|
|
30
|
+
unless Rails.respond_to?(:error)
|
|
31
|
+
def Rails.error
|
|
32
|
+
@error_mock ||= Class.new do
|
|
33
|
+
def subscribe(subscriber); end
|
|
34
|
+
end.new
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Add application config mock for RequestsInstrumenter (parameter filtering)
|
|
39
|
+
unless Rails.respond_to?(:application)
|
|
40
|
+
def Rails.application
|
|
41
|
+
Struct.new(:config).new(
|
|
42
|
+
Struct.new(:filter_parameters).new([])
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
# -----------------------------
|
|
47
|
+
|
|
48
|
+
# 1. Load your gem files (adjust paths if necessary)
|
|
49
|
+
require_relative "lib/railswatch_gem"
|
|
50
|
+
|
|
51
|
+
# 2. Spin up a tiny TCP server to act as the "Ingestion API"
|
|
52
|
+
# This lets us see exactly what the gem sends over the network.
|
|
53
|
+
server = TCPServer.new(0) # 0 assigns a random available port
|
|
54
|
+
port = server.addr[1]
|
|
55
|
+
puts "--- 📡 Mock Ingestion Server started on port #{port} ---"
|
|
56
|
+
|
|
57
|
+
server_thread = Thread.new do
|
|
58
|
+
loop do
|
|
59
|
+
client = server.accept
|
|
60
|
+
|
|
61
|
+
# Read the HTTP headers and body
|
|
62
|
+
request_lines = []
|
|
63
|
+
while (line = client.gets) && line !~ /^\s*$/
|
|
64
|
+
request_lines << line
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Parse Content-Length to read the body
|
|
68
|
+
content_length = request_lines.find { |l| l =~ /^Content-Length:/ }&.split(":")&.last&.to_i || 0
|
|
69
|
+
body = client.read(content_length)
|
|
70
|
+
|
|
71
|
+
# Acknowledge receipt to the gem (202 Accepted)
|
|
72
|
+
client.print "HTTP/1.1 202 Accepted\r\n"
|
|
73
|
+
client.print "Content-Type: application/json\r\n"
|
|
74
|
+
client.print "\r\n"
|
|
75
|
+
client.print '{"status":"ok"}'
|
|
76
|
+
client.close
|
|
77
|
+
|
|
78
|
+
puts "\n--- 📦 Payload Received ---"
|
|
79
|
+
puts JSON.pretty_generate(JSON.parse(body))
|
|
80
|
+
puts "---------------------------\n"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# 3. Configure the Gem to point to our mock server
|
|
85
|
+
RailswatchGem.configure do |config|
|
|
86
|
+
config.ingest_url = "http://localhost:#{port}/ingest"
|
|
87
|
+
config.env_token = "test-token-123"
|
|
88
|
+
config.batch_size = 1 # Flush immediately for testing
|
|
89
|
+
config.flush_interval = 0.1 # Check queue rapidly
|
|
90
|
+
config.auto_boot = true # Ensure systems are go
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Manually boot since we aren't in Rails
|
|
94
|
+
RailswatchGem.boot!
|
|
95
|
+
|
|
96
|
+
# 4. Trigger an event using your new helper
|
|
97
|
+
puts "firing event..."
|
|
98
|
+
RailswatchGem.rw("Hello, World!", { user_id: 42, role: "admin" })
|
|
99
|
+
|
|
100
|
+
# 5. Wait a moment for the thread to flush
|
|
101
|
+
sleep 2
|
|
102
|
+
|
|
103
|
+
puts "Done."
|
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: railswatch_gem
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Tyler Hammett
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Railswatch Gem
|
|
14
|
+
email:
|
|
15
|
+
- thammett@railswatch.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- CHANGELOG.md
|
|
21
|
+
- LICENSE.txt
|
|
22
|
+
- README.md
|
|
23
|
+
- Rakefile
|
|
24
|
+
- lib/railswatch_gem.rb
|
|
25
|
+
- lib/railswatch_gem/client.rb
|
|
26
|
+
- lib/railswatch_gem/configuration.rb
|
|
27
|
+
- lib/railswatch_gem/helpers.rb
|
|
28
|
+
- lib/railswatch_gem/instrumentation/cache_instrumenter.rb
|
|
29
|
+
- lib/railswatch_gem/instrumentation/commands_instrumenter.rb
|
|
30
|
+
- lib/railswatch_gem/instrumentation/errors_instrumenter.rb
|
|
31
|
+
- lib/railswatch_gem/instrumentation/jobs_instrumenter.rb
|
|
32
|
+
- lib/railswatch_gem/instrumentation/logs_instrumenter.rb
|
|
33
|
+
- lib/railswatch_gem/instrumentation/mail_instrumenter.rb
|
|
34
|
+
- lib/railswatch_gem/instrumentation/models_instrumenter.rb
|
|
35
|
+
- lib/railswatch_gem/instrumentation/notifications_instrumenter.rb
|
|
36
|
+
- lib/railswatch_gem/instrumentation/outgoing_requests_instrumenter.rb
|
|
37
|
+
- lib/railswatch_gem/instrumentation/queries_instrumenter.rb
|
|
38
|
+
- lib/railswatch_gem/instrumentation/registry.rb
|
|
39
|
+
- lib/railswatch_gem/instrumentation/requests_instrumenter.rb
|
|
40
|
+
- lib/railswatch_gem/instrumentation/scheduled_tasks_instrumenter.rb
|
|
41
|
+
- lib/railswatch_gem/middleware/request_context.rb
|
|
42
|
+
- lib/railswatch_gem/railtie.rb
|
|
43
|
+
- lib/railswatch_gem/version.rb
|
|
44
|
+
- manual_test.rb
|
|
45
|
+
- sig/railswatch_gem.rbs
|
|
46
|
+
homepage: https://github.com/railswatch/railswatch_gem
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
homepage_uri: https://github.com/railswatch/railswatch_gem
|
|
51
|
+
source_code_uri: https://github.com/railswatch/railswatch_gem
|
|
52
|
+
changelog_uri: https://github.com/railswatch/railswatch_gem/blob/main/CHANGELOG.md
|
|
53
|
+
post_install_message:
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.2.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.4.19
|
|
69
|
+
signing_key:
|
|
70
|
+
specification_version: 4
|
|
71
|
+
summary: Railswatch Gem
|
|
72
|
+
test_files: []
|