Class: Alplus::Client
- Inherits:
-
Object
- Object
- Alplus::Client
- Defined in:
- lib/alplus/client.rb
Overview
Orchestrates one configuration's capture pipeline: builds the wire item, wraps it in an envelope, and hands it to the background worker (or, in test mode, sends it synchronously through the in-memory transport).
Every public method is fail-safe: a bug anywhere in envelope building or dispatch is caught and logged, never raised into the host app (issue #14 story 8). The event id is always generated and returned first, so a caller can show "reference id err_..." even if the event was dropped.
Instance Attribute Summary collapse
-
#transport ⇒ Object
readonly
Returns the value of attribute transport.
Instance Method Summary collapse
-
#capture_exception(exception, level: "error", context: nil, contexts: nil, tags: nil, breadcrumbs: nil, user: Alplus::UNSET, mechanism: "generic", fingerprint: nil) ⇒ Object
The enabled/sampled gate runs BEFORE dedup registration (issue #15 defect): a sampled-out or disabled capture must not occupy the dedup slot, or it would suppress the NEXT (real) in-window capture of the same error.
- #capture_message(message, level: "info", context: nil, contexts: nil, tags: nil, breadcrumbs: nil, user: Alplus::UNSET, mechanism: "generic", fingerprint: nil) ⇒ Object
-
#flush(timeout: 2) ⇒ Object
Flushes BOTH delivery lanes (issue #12: error and session are independent workers).
-
#initialize(config, sleeper: config.sleeper) ⇒ Client
constructor
sleeper:is forwarded to the defaultTransport(never used whenconfig.transport/config.test_modesupply a different one) purely as a test seam: with the realKernel#sleep, a spec exercising a retried send would otherwise wait on real backoff wall-clock time. -
#notify_log_breadcrumb(crumb) ⇒ Object
Offers a log-line breadcrumb to every exception item currently inside its post-error log window on this thread (issue #47).
-
#report_session(session) ⇒ Object
Reports a closed
Session(issue #12) toPOST /e/sessions, on its OWN backgroundWorker(own queue, own thread) so a slow/failing/e/errorsdelivery can never delay or drop a queued session, or vice versa -- seeWorker's class doc.
Constructor Details
#initialize(config, sleeper: config.sleeper) ⇒ Client
sleeper: is forwarded to the default Transport (never used when
config.transport/config.test_mode supply a different one) purely
as a test seam: with the real Kernel#sleep, a spec exercising a
retried send would otherwise wait on real backoff wall-clock time.
Defaults to config.sleeper so Alplus.configure { |c| c.sleeper = ... }
reaches the module-level singleton client too, not just a directly
constructed Client.new.
20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/alplus/client.rb', line 20 def initialize(config, sleeper: config.sleeper) @config = config @transport = config.transport || (config.test_mode ? TestTransport.new : Transport.new(config, sleeper: sleeper)) @worker = Worker.new(config, @transport, kind: :error) # A SEPARATE worker (own queue, own thread) from `@worker` (issue # #12 fix): see `Worker`'s class doc for why sharing one lane with # error delivery was a defect (head-of-line blocking + silent drops # under an error storm, exactly when crash-free data matters). @session_worker = Worker.new(config, @transport, kind: :session) @pending_window = PendingWindow.new(config.resolved_post_error_log_window_ms) { |item| deliver_item(item) } end |
Instance Attribute Details
#transport ⇒ Object (readonly)
Returns the value of attribute transport.
34 35 36 |
# File 'lib/alplus/client.rb', line 34 def transport @transport end |
Instance Method Details
#capture_exception(exception, level: "error", context: nil, contexts: nil, tags: nil, breadcrumbs: nil, user: Alplus::UNSET, mechanism: "generic", fingerprint: nil) ⇒ Object
The enabled/sampled gate runs BEFORE dedup registration (issue #15 defect): a sampled-out or disabled capture must not occupy the dedup slot, or it would suppress the NEXT (real) in-window capture of the same error.
Dedup (issue #15) then runs BEFORE the scope merge / envelope build:
the same exception object captured twice within the dedup window
(e.g. by RackMiddleware auto-capture and a manual rescue further up
the stack) returns the first call's id and is never re-queued — see
Dedup.
contexts:/fingerprint: and the ambient Scope (set_user,
set_tag, set_context, add_breadcrumb) are issue #17: an explicit
per-call user:/tags:/contexts:/breadcrumbs: here wins over
whatever the ambient scope carries, field-by-field — see
ScopeMerge.merge. user: defaults to the Alplus::UNSET sentinel,
not nil, so a caller CAN pass user: nil to explicitly clear the
ambient user for one capture.
54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/alplus/client.rb', line 54 def capture_exception(exception, level: "error", context: nil, contexts: nil, tags: nil, breadcrumbs: nil, user: Alplus::UNSET, mechanism: "generic", fingerprint: nil) mark_session_outcome(level) fresh_id = Id.generate_event_id return fresh_id unless enabled? return fresh_id if excluded?(exception) resolved = Dedup.resolve(exception, fresh_id) return resolved[:id] if resolved[:duplicate] id = resolved[:id] dispatch(id) { build_item(:exception_item, exception: exception, id: id, level: level, context: context, contexts: contexts, tags: , breadcrumbs: , user: user, mechanism: mechanism, fingerprint: fingerprint) } id end |
#capture_message(message, level: "info", context: nil, contexts: nil, tags: nil, breadcrumbs: nil, user: Alplus::UNSET, mechanism: "generic", fingerprint: nil) ⇒ Object
68 69 70 71 72 73 74 75 |
# File 'lib/alplus/client.rb', line 68 def (, level: "info", context: nil, contexts: nil, tags: nil, breadcrumbs: nil, user: Alplus::UNSET, mechanism: "generic", fingerprint: nil) mark_session_outcome(level) id = Id.generate_event_id return id unless enabled? dispatch(id) { build_item(:message_item, message: , id: id, level: level, context: context, contexts: contexts, tags: , breadcrumbs: , user: user, mechanism: mechanism, fingerprint: fingerprint) } id end |
#flush(timeout: 2) ⇒ Object
Flushes BOTH delivery lanes (issue #12: error and session are
independent workers). A slow/stuck error lane still bounds this call
by timeout for the session lane's own drain -- each Worker#flush
call gets the full timeout budget rather than splitting it, since a
caller flushing wants both drained, not a race between them.
82 83 84 85 86 87 88 89 90 91 |
# File 'lib/alplus/client.rb', line 82 def flush(timeout: 2) # Seal the post-error log window first: flush means "send now with # whatever after-lines were collected so far", never "wait". @pending_window.seal_all! error_flushed = @worker.flush(timeout: timeout) session_flushed = @session_worker.flush(timeout: timeout) error_flushed && session_flushed rescue StandardError false end |
#notify_log_breadcrumb(crumb) ⇒ Object
Offers a log-line breadcrumb to every exception item currently inside its post-error log window on this thread (issue #47). Never raises.
95 96 97 |
# File 'lib/alplus/client.rb', line 95 def (crumb) @pending_window.(crumb) end |
#report_session(session) ⇒ Object
Reports a closed Session (issue #12) to POST /e/sessions, on its
OWN background Worker (own queue, own thread) so a slow/failing
/e/errors delivery can never delay or drop a queued session, or
vice versa -- see Worker's class doc. Unlike capture_exception/
capture_message, never sampled or deduped — an accurate crash-free
percentage needs every session counted, not a sample of them. Only
gated on config.valid? (configured + enabled). Fail-safe: never
raises.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/alplus/client.rb', line 107 def report_session(session) return false unless @config.valid? envelope = Envelope.wrap(config: @config, item: Envelope.session_item(session: session, config: @config)) if @config.test_mode @transport.send_envelope(envelope, kind: :session) else @session_worker.enqueue(envelope) end rescue StandardError => e @config.logger&.warn("[alplus] session report failed internally; dropped: #{e.class}: #{e.}") false end |