activerabbit-ai 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/active_rabbit/client/configuration.rb +7 -0
- data/lib/active_rabbit/client/log_forwarder.rb +144 -0
- data/lib/active_rabbit/client/railtie.rb +19 -0
- data/lib/active_rabbit/client/version.rb +1 -1
- data/lib/active_rabbit/client.rb +9 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f857d4340fe645c130c5b892b533cd4fcb7daec16f4616402dfff2b41e8fe81
|
|
4
|
+
data.tar.gz: 864bb6b6bacbec9e8c3777400f340977df074af989c4b7130f773876c3b8d32a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: afcfc21570337a0b77e70ef7da3b7f78465c0d5eb9ba9e4cefd6491728065754385dd742b93f7c97d465fc571171dc27d5e448b547639030770b268879b4f59f
|
|
7
|
+
data.tar.gz: 2687df8f73f2e4dec19ce5e55af23f92c6b66649392d341dcc9309bc8acb23300180e56fa7594286d2abed5e8ec3024a8f1016accff91665e09627691cc190e7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.4] - 2026-04-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Log forwarding**: New `LogForwarder` buffers Rails log lines and sends them in batches to `POST /api/v1/logs`. Enable with `config.enable_logs = true` in the ActiveRabbit initializer.
|
|
9
|
+
- **Configuration**: `logs_flush_interval`, `logs_batch_size`, and optional `logs_source` for tuning batching and labeling forwarded logs.
|
|
10
|
+
- **Rails integration**: Railtie registers the forwarder with `ActiveSupport::BroadcastLogger` so `Rails.logger` output is forwarded without changing application code. Flush/shutdown hooks keep the forwarder aligned with the app lifecycle.
|
|
11
|
+
|
|
5
12
|
## [0.6.3] - 2026-03-30
|
|
6
13
|
|
|
7
14
|
### Added
|
|
@@ -11,6 +11,7 @@ module ActiveRabbit
|
|
|
11
11
|
attr_accessor :batch_size, :flush_interval, :queue_size
|
|
12
12
|
attr_accessor :enable_performance_monitoring, :enable_n_plus_one_detection
|
|
13
13
|
attr_accessor :enable_pii_scrubbing, :pii_fields
|
|
14
|
+
attr_accessor :enable_logs, :logs_flush_interval, :logs_batch_size, :logs_source
|
|
14
15
|
attr_accessor :ignored_exceptions, :ignored_user_agents, :ignore_404
|
|
15
16
|
attr_accessor :release, :server_name, :logger
|
|
16
17
|
attr_accessor :before_send_event, :before_send_exception
|
|
@@ -40,8 +41,14 @@ module ActiveRabbit
|
|
|
40
41
|
@enable_performance_monitoring = true
|
|
41
42
|
@enable_n_plus_one_detection = true
|
|
42
43
|
@enable_pii_scrubbing = true
|
|
44
|
+
@enable_logs = false
|
|
43
45
|
@disable_console_logs = true
|
|
44
46
|
|
|
47
|
+
# Log forwarding
|
|
48
|
+
@logs_flush_interval = 5
|
|
49
|
+
@logs_batch_size = 100
|
|
50
|
+
@logs_source = nil
|
|
51
|
+
|
|
45
52
|
# PII scrubbing
|
|
46
53
|
@pii_fields = %w[
|
|
47
54
|
password password_confirmation token secret key
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "logger"
|
|
6
|
+
|
|
7
|
+
module ActiveRabbit
|
|
8
|
+
module Client
|
|
9
|
+
# Logger subclass that buffers log entries and forwards them in batches to
|
|
10
|
+
# the ActiveRabbit POST /api/v1/logs endpoint.
|
|
11
|
+
#
|
|
12
|
+
# Plugged into ActiveSupport::BroadcastLogger automatically by the Railtie
|
|
13
|
+
# when `config.enable_logs = true`, so every Rails.logger call is forwarded
|
|
14
|
+
# without touching application code.
|
|
15
|
+
class LogForwarder < ::Logger
|
|
16
|
+
SEVERITY_MAP = { 0 => "debug", 1 => "info", 2 => "warn",
|
|
17
|
+
3 => "error", 4 => "fatal", 5 => "info" }.freeze
|
|
18
|
+
|
|
19
|
+
MAX_MESSAGE_LEN = 10_000
|
|
20
|
+
|
|
21
|
+
def initialize(configuration)
|
|
22
|
+
@configuration = configuration
|
|
23
|
+
@buffer = []
|
|
24
|
+
@mutex = Mutex.new
|
|
25
|
+
@uri = URI("#{configuration.api_url.chomp('/')}/api/v1/logs")
|
|
26
|
+
@stopped = false
|
|
27
|
+
|
|
28
|
+
super(File::NULL, level: ::Logger::DEBUG)
|
|
29
|
+
self.formatter = proc { |*, msg| msg }
|
|
30
|
+
|
|
31
|
+
start_flusher
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# -- Logger interface --------------------------------------------------
|
|
35
|
+
|
|
36
|
+
def add(severity, message = nil, progname = nil, &block)
|
|
37
|
+
return true if Thread.current[:_ar_log_sending]
|
|
38
|
+
|
|
39
|
+
severity ||= UNKNOWN
|
|
40
|
+
return true if severity < level
|
|
41
|
+
|
|
42
|
+
message = block&.call if message.nil? && block
|
|
43
|
+
message ||= progname
|
|
44
|
+
return true if message.nil?
|
|
45
|
+
|
|
46
|
+
msg = message.to_s.strip
|
|
47
|
+
return true if msg.empty?
|
|
48
|
+
msg = msg[0, MAX_MESSAGE_LEN] if msg.length > MAX_MESSAGE_LEN
|
|
49
|
+
|
|
50
|
+
entry = build_entry(severity, msg)
|
|
51
|
+
@mutex.synchronize { @buffer << entry }
|
|
52
|
+
async_flush if buffer_size >= batch_size
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def flush
|
|
57
|
+
entries = swap_buffer
|
|
58
|
+
send_batch(entries) if entries&.any?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def stop
|
|
62
|
+
@stopped = true
|
|
63
|
+
flush
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def build_entry(severity, message)
|
|
69
|
+
ctx = Thread.current[:active_rabbit_request_context]
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
level: SEVERITY_MAP[severity] || "info",
|
|
73
|
+
message: message,
|
|
74
|
+
source: @configuration.logs_source || "rails",
|
|
75
|
+
environment: @configuration.environment || (defined?(Rails) ? Rails.env.to_s : "production"),
|
|
76
|
+
occurred_at: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%6NZ"),
|
|
77
|
+
request_id: ctx&.dig(:request_id),
|
|
78
|
+
context: { pid: Process.pid }
|
|
79
|
+
}.compact
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def batch_size
|
|
83
|
+
@configuration.logs_batch_size || 100
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def flush_interval
|
|
87
|
+
@configuration.logs_flush_interval || 5
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def buffer_size
|
|
91
|
+
@mutex.synchronize { @buffer.size }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def swap_buffer
|
|
95
|
+
@mutex.synchronize do
|
|
96
|
+
return nil if @buffer.empty?
|
|
97
|
+
out = @buffer.dup
|
|
98
|
+
@buffer.clear
|
|
99
|
+
out
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def async_flush
|
|
104
|
+
Thread.new { flush }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def start_flusher
|
|
108
|
+
Thread.new do
|
|
109
|
+
loop do
|
|
110
|
+
sleep flush_interval
|
|
111
|
+
break if @stopped
|
|
112
|
+
flush
|
|
113
|
+
rescue StandardError
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def send_batch(entries)
|
|
120
|
+
return if entries.nil? || entries.empty?
|
|
121
|
+
|
|
122
|
+
Thread.current[:_ar_log_sending] = true
|
|
123
|
+
|
|
124
|
+
entries.each_slice(1000) do |chunk|
|
|
125
|
+
http = Net::HTTP.new(@uri.host, @uri.port)
|
|
126
|
+
http.use_ssl = (@uri.scheme == "https")
|
|
127
|
+
http.open_timeout = 5
|
|
128
|
+
http.read_timeout = 10
|
|
129
|
+
|
|
130
|
+
req = Net::HTTP::Post.new(@uri)
|
|
131
|
+
req["Content-Type"] = "application/json"
|
|
132
|
+
req["X-Project-Token"] = @configuration.api_key
|
|
133
|
+
req.body = { entries: chunk }.to_json
|
|
134
|
+
|
|
135
|
+
http.request(req)
|
|
136
|
+
end
|
|
137
|
+
rescue StandardError
|
|
138
|
+
nil
|
|
139
|
+
ensure
|
|
140
|
+
Thread.current[:_ar_log_sending] = false
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -325,6 +325,25 @@ module ActiveRabbit
|
|
|
325
325
|
ar_log(:info, "[ActiveRabbit] Middleware configured successfully")
|
|
326
326
|
end
|
|
327
327
|
|
|
328
|
+
initializer "active_rabbit.log_forwarding", after: :initialize_logger do |app|
|
|
329
|
+
app.config.after_initialize do
|
|
330
|
+
cfg = ActiveRabbit::Client.configuration
|
|
331
|
+
next unless cfg&.enable_logs
|
|
332
|
+
next unless ActiveRabbit::Client.configured?
|
|
333
|
+
|
|
334
|
+
forwarder = ActiveRabbit::Client.log_forwarder
|
|
335
|
+
next unless forwarder
|
|
336
|
+
|
|
337
|
+
if Rails.logger.is_a?(ActiveSupport::BroadcastLogger)
|
|
338
|
+
Rails.logger.broadcast_to(forwarder)
|
|
339
|
+
else
|
|
340
|
+
Rails.logger = ActiveSupport::BroadcastLogger.new(Rails.logger, forwarder)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
ar_log(:info, "[ActiveRabbit] Log forwarding enabled")
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
328
347
|
initializer "active_rabbit.error_reporter" do |app|
|
|
329
348
|
# DISABLED: Rails error reporter creates duplicate events because middleware already catches all errors
|
|
330
349
|
# The middleware provides better context and catches errors at the right level
|
data/lib/active_rabbit/client.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "client/performance_monitor"
|
|
|
9
9
|
require_relative "client/n_plus_one_detector"
|
|
10
10
|
require_relative "client/pii_scrubber"
|
|
11
11
|
require_relative "client/error_reporter"
|
|
12
|
+
require_relative "client/log_forwarder"
|
|
12
13
|
|
|
13
14
|
# Rails integration (optional)
|
|
14
15
|
begin
|
|
@@ -117,6 +118,7 @@ module ActiveRabbit
|
|
|
117
118
|
event_processor.flush
|
|
118
119
|
exception_tracker.flush
|
|
119
120
|
performance_monitor.flush
|
|
121
|
+
@log_forwarder&.flush
|
|
120
122
|
end
|
|
121
123
|
|
|
122
124
|
# Shutdown the client gracefully
|
|
@@ -124,10 +126,17 @@ module ActiveRabbit
|
|
|
124
126
|
return unless configured?
|
|
125
127
|
|
|
126
128
|
flush
|
|
129
|
+
@log_forwarder&.stop
|
|
127
130
|
event_processor.shutdown
|
|
128
131
|
http_client.shutdown
|
|
129
132
|
end
|
|
130
133
|
|
|
134
|
+
# Returns the LogForwarder instance (created on first access when enabled).
|
|
135
|
+
def log_forwarder
|
|
136
|
+
return nil unless configuration&.enable_logs
|
|
137
|
+
@log_forwarder ||= LogForwarder.new(configuration)
|
|
138
|
+
end
|
|
139
|
+
|
|
131
140
|
# Manual capture convenience for non-Rails contexts
|
|
132
141
|
def capture_exception(exception, context: {}, user_id: nil, tags: {})
|
|
133
142
|
track_exception(exception, context: context, user_id: user_id, tags: tags)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerabbit-ai
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Shapalov
|
|
@@ -95,6 +95,7 @@ files:
|
|
|
95
95
|
- lib/active_rabbit/client/event_processor.rb
|
|
96
96
|
- lib/active_rabbit/client/exception_tracker.rb
|
|
97
97
|
- lib/active_rabbit/client/http_client.rb
|
|
98
|
+
- lib/active_rabbit/client/log_forwarder.rb
|
|
98
99
|
- lib/active_rabbit/client/n_plus_one_detector.rb
|
|
99
100
|
- lib/active_rabbit/client/performance_monitor.rb
|
|
100
101
|
- lib/active_rabbit/client/pii_scrubber.rb
|