Class: Errsight::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/errsight/client.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Client

Returns a new instance of Client.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/errsight/client.rb', line 8

def initialize(config)
  @config              = config
  @queue               = []
  @mutex               = Mutex.new
  @pid                 = Process.pid
  @shutdown            = false
  # Mutex + CV used to interrupt the flush worker's between-tick wait
  # on shutdown. Plain Thread#wakeup has a lost-wakeup race: if
  # shutdown! fires before the thread has entered its first sleep,
  # wakeup is dropped and the thread sleeps the full flush_interval.
  # CV#wait + setting the flag inside the same lock has no such race.
  @sleep_mutex         = Mutex.new
  @sleep_cv            = ConditionVariable.new
  # When the API returns 429, we set this to a Time and skip sends in
  # flush! until it elapses. Replaces a per-call `sleep retry_after`
  # that used to park the flush thread for up to 60s, during which new
  # events spilled past max_queue_size and got silently dropped.
  @rate_limited_until  = nil
  start_flush_worker
end

Class Method Details

.cert_storeObject

Cached once at process boot — set_default_paths reads the system trust store from disk and is expensive to redo per request.



168
169
170
# File 'lib/errsight/client.rb', line 168

def self.cert_store
  @cert_store ||= OpenSSL::X509::Store.new.tap(&:set_default_paths)
end

Instance Method Details

#enqueue(event) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/errsight/client.rb', line 29

def enqueue(event)
  # Fork-safety gate: see detect_fork! comment. Cheapest possible check
  # since enqueue is on the hot path of every captured event.
  detect_fork! if @pid != Process.pid

  @mutex.synchronize do
    if @queue.size >= @config.max_queue_size
      @config.logger&.warn("[Errsight] Queue full (#{@config.max_queue_size}), dropping event")
      return
    end
    @queue << event
  end
end

#flush!Object



43
44
45
46
47
48
49
50
51
# File 'lib/errsight/client.rb', line 43

def flush!
  return if rate_limited?
  events = nil
  @mutex.synchronize do
    return if @queue.empty?
    events = @queue.slice!(0, @config.batch_size)
  end
  send_events(events) if events&.any?
end

#shutdown!Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/errsight/client.rb', line 53

def shutdown!
  # Set the flag and signal under the same lock the flush worker uses
  # for its CV#wait. This atomically transfers the shutdown signal:
  # either the worker hasn't entered wait yet (and will see @shutdown
  # immediately) or it's in wait (and the broadcast unblocks it).
  @sleep_mutex.synchronize do
    @shutdown = true
    @sleep_cv.broadcast
  end
  thread = @flush_thread
  # Bound total shutdown time. If a flush is stuck on a hung HTTP
  # request, kill it rather than block the host's signal handling.
  thread&.join(@config.shutdown_timeout)
  thread&.kill if thread&.alive?
  # The thread does a final drain on graceful exit; do one here too in
  # case we had to kill it, or in case shutdown! was called when no
  # flush thread was alive.
  flush! rescue nil
  # Lazy init guard: @http_mutex may be nil if no request ever fired,
  # in which case the old code raised NoMethodError on shutdown.
  http_mutex.synchronize { @http&.finish if @http&.started? }
rescue StandardError
  # best-effort
end