Class: Errsight::Scope
- Inherits:
-
Object
- Object
- Errsight::Scope
- Defined in:
- lib/errsight/scope.rb
Overview
Holds the user, tags, and breadcrumbs that should be attached to events captured while this scope is on top of the hub stack.
A scope is owned by a single thread of execution (a Rails request, a Sidekiq job, or an ad-hoc Errsight.with_scope block). Pushing a new scope forks a deep copy of the parent so child mutations don’t bleed back up the stack.
Breadcrumbs are split into two ring buffers — manual app crumbs and auto-collected DB crumbs — so a high-query request can’t evict the user’s manual context. The public ‘breadcrumbs` accessor returns a merged, timestamp-sorted view; consumers see one stream.
Constant Summary collapse
- MAX_USER_BREADCRUMBS =
50- MAX_DB_BREADCRUMBS =
30- BREADCRUMB_LIMIT =
Back-compat alias for callers that referenced the old single-cap name.
MAX_USER_BREADCRUMBS
Instance Attribute Summary collapse
-
#tags ⇒ Object
readonly
Returns the value of attribute tags.
-
#user ⇒ Object
readonly
Returns the value of attribute user.
Class Method Summary collapse
Instance Method Summary collapse
- #add_breadcrumb(category:, message:, level: :info, data: nil) ⇒ Object
-
#add_db_breadcrumb(message:, data: nil) ⇒ Object
Internal API for auto-instrumentation (sql.active_record subscriber today; future http subscribers will use the same ring or get their own).
-
#breadcrumbs ⇒ Object
Merged, timestamp-sorted view across both rings.
- #clear_breadcrumbs ⇒ Object
- #clear_tags ⇒ Object
- #clear_user ⇒ Object
-
#dup ⇒ Object
Deep-ish copy used when pushing a child scope.
-
#initialize ⇒ Scope
constructor
A new instance of Scope.
-
#merge!(other) ⇒ Object
Overlay another scope’s state onto this one.
- #set_tag(key, value) ⇒ Object
- #set_tags(tags) ⇒ Object
- #set_user(user) ⇒ Object
-
#to_h ⇒ Object
Serialize for cross-process propagation (Sidekiq client middleware will stash this in the job payload so the server middleware can rehydrate it before the job runs).
Constructor Details
#initialize ⇒ Scope
Returns a new instance of Scope.
22 23 24 25 26 27 |
# File 'lib/errsight/scope.rb', line 22 def initialize @user = nil @tags = {} @user_breadcrumbs = [] @db_breadcrumbs = [] end |
Instance Attribute Details
#tags ⇒ Object (readonly)
Returns the value of attribute tags.
20 21 22 |
# File 'lib/errsight/scope.rb', line 20 def @tags end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
20 21 22 |
# File 'lib/errsight/scope.rb', line 20 def user @user end |
Class Method Details
.from_h(hash) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/errsight/scope.rb', line 130 def self.from_h(hash) scope = new return scope unless hash.is_a?(Hash) = hash["tags"].is_a?(Hash) ? hash["tags"].transform_keys(&:to_s).transform_values(&:to_s) : {} # dup each crumb so the rehydrated scope doesn't alias entries inside # the caller's job payload — add_breadcrumb mutates @user_breadcrumbs # in place and we don't want that mutation to flow back into job args. crumbs = hash["breadcrumbs"].is_a?(Array) ? hash["breadcrumbs"].map { |c| c.is_a?(Hash) ? c.dup : c } : [] # send: replace_state is protected so external callers can't reach in, # but a class-method factory needs to bypass that to build a new scope. # DB breadcrumbs are intentionally not propagated; they start empty. scope.send(:replace_state, hash["user"], , crumbs, []) scope end |
Instance Method Details
#add_breadcrumb(category:, message:, level: :info, data: nil) ⇒ Object
60 61 62 63 |
# File 'lib/errsight/scope.rb', line 60 def (category:, message:, level: :info, data: nil) @user_breadcrumbs << build_crumb(category, , level, data) @user_breadcrumbs.shift while @user_breadcrumbs.size > MAX_USER_BREADCRUMBS end |
#add_db_breadcrumb(message:, data: nil) ⇒ Object
Internal API for auto-instrumentation (sql.active_record subscriber today; future http subscribers will use the same ring or get their own). Separate cap from manual crumbs so a runaway-query request can’t push out the app code’s own context.
69 70 71 72 |
# File 'lib/errsight/scope.rb', line 69 def (message:, data: nil) @db_breadcrumbs << build_crumb("db", , :info, data) @db_breadcrumbs.shift while @db_breadcrumbs.size > MAX_DB_BREADCRUMBS end |
#breadcrumbs ⇒ Object
Merged, timestamp-sorted view across both rings. ISO-8601 strings sort lexicographically the same as chronologically, so a string sort is correct without parsing back into Time.
32 33 34 35 36 |
# File 'lib/errsight/scope.rb', line 32 def return @user_breadcrumbs if @db_breadcrumbs.empty? return @db_breadcrumbs if @user_breadcrumbs.empty? (@user_breadcrumbs + @db_breadcrumbs).sort_by { |b| b[:timestamp] } end |
#clear_breadcrumbs ⇒ Object
74 75 76 77 |
# File 'lib/errsight/scope.rb', line 74 def @user_breadcrumbs = [] @db_breadcrumbs = [] end |
#clear_tags ⇒ Object
56 57 58 |
# File 'lib/errsight/scope.rb', line 56 def @tags = {} end |
#clear_user ⇒ Object
42 43 44 |
# File 'lib/errsight/scope.rb', line 42 def clear_user @user = nil end |
#dup ⇒ Object
Deep-ish copy used when pushing a child scope. Hashes/arrays are dup’d so child mutations don’t bleed back up the stack.
104 105 106 107 108 109 110 111 112 |
# File 'lib/errsight/scope.rb', line 104 def dup copy = Scope.new copy.send(:replace_state, @user&.dup, @tags.dup, @user_breadcrumbs.map(&:dup), @db_breadcrumbs.map(&:dup)) copy end |
#merge!(other) ⇒ Object
Overlay another scope’s state onto this one. Used by Sidekiq server middleware to layer payload scope (user/tags shipped by the enqueuer) on top of process-wide root state (e.g. Errsight.set_tag(“region”,…) called once at boot). User from ‘other` wins; tags are merged with `other` taking precedence on key collisions; breadcrumbs are appended in order and clipped to the limit.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/errsight/scope.rb', line 85 def merge!(other) return self unless other.is_a?(Scope) @user = other.user if other.user @tags.merge!(other.) unless other..empty? other_user = other.instance_variable_get(:@user_breadcrumbs) other_db = other.instance_variable_get(:@db_breadcrumbs) unless other_user.empty? @user_breadcrumbs.concat(other_user.map(&:dup)) @user_breadcrumbs.shift while @user_breadcrumbs.size > MAX_USER_BREADCRUMBS end unless other_db.empty? @db_breadcrumbs.concat(other_db.map(&:dup)) @db_breadcrumbs.shift while @db_breadcrumbs.size > MAX_DB_BREADCRUMBS end self end |
#set_tag(key, value) ⇒ Object
46 47 48 49 |
# File 'lib/errsight/scope.rb', line 46 def set_tag(key, value) return if key.nil? @tags[key.to_s] = value.to_s end |
#set_tags(tags) ⇒ Object
51 52 53 54 |
# File 'lib/errsight/scope.rb', line 51 def () return unless .is_a?(Hash) .each { |k, v| set_tag(k, v) } end |
#set_user(user) ⇒ Object
38 39 40 |
# File 'lib/errsight/scope.rb', line 38 def set_user(user) @user = user.is_a?(Hash) ? user : nil end |
#to_h ⇒ Object
Serialize for cross-process propagation (Sidekiq client middleware will stash this in the job payload so the server middleware can rehydrate it before the job runs).
Only manual user breadcrumbs travel across process boundaries. The receiving worker collects its own DB breadcrumbs from its own queries — propagating the parent’s would mix DB events from two unrelated connection states and confuse debugging.
122 123 124 125 126 127 128 |
# File 'lib/errsight/scope.rb', line 122 def to_h hash = {} hash["user"] = @user unless @user.nil? hash["tags"] = @tags unless @tags.empty? hash["breadcrumbs"] = @user_breadcrumbs unless @user_breadcrumbs.empty? hash end |