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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d02e83fd5992873b3b7aabb322ab9e3c914b91e7e64b4a80be335007561bc404
4
- data.tar.gz: d60e55a0fcab7c981d7b7a214cff142825c613e7d32ddf15533bccd451cb6657
3
+ metadata.gz: 8f857d4340fe645c130c5b892b533cd4fcb7daec16f4616402dfff2b41e8fe81
4
+ data.tar.gz: 864bb6b6bacbec9e8c3777400f340977df074af989c4b7130f773876c3b8d32a
5
5
  SHA512:
6
- metadata.gz: 32a57acfde857e55a97932f56ae1a449f04a7e51e7ad8903d2cf63380ddd338cf0930d18785559f2bf358250470ce1890f503dde670aaebddf1756f6b5675c2f
7
- data.tar.gz: dad613e6edf130e26c185b2b17670366400c4a40a36ebf0d8017a77189f01c1647c0cbd3ef7f810727a47beccf46b5f482394b681424982ad64a020eb1bcd768
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
@@ -1,5 +1,5 @@
1
1
  module ActiveRabbit
2
2
  module Client
3
- VERSION = "0.6.3"
3
+ VERSION = "0.6.4"
4
4
  end
5
5
  end
@@ -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.3
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