Module: Shipeasy::SDK::See

Defined in:
lib/shipeasy/sdk/see.rb

Defined Under Namespace

Modules: Context Classes: Built, Chain, ControlFlowChain, ControlFlowTail, Limiter, NullChain, Violation

Constant Summary collapse

SEE_MAX_MESSAGE =

---- Limits (mirror core.ts; kept in sync with the worker's /collect) ----

500
SEE_MAX_STACK =
8000
SEE_MAX_SUBJECT =

used for subject, outcome, error_type

200
SEE_MAX_EXTRA_VALUE =
200
SEE_MAX_EXTRA_KEYS =
20
SEE_DEDUP_WINDOW_MS =
30_000
SEE_MAX_PER_PROCESS =
25
DEFAULT_SUBJECT =

Default consequence parts when a chain omits them.

"app".freeze
DEFAULT_OUTCOME =
"hit an error".freeze
EXPECTED_IVAR =

Marker attribute stamped onto an exception by control_flow_exception().

:@__shipeasy_see_expected

Class Method Summary collapse

Class Method Details

.build_event(problem, subject, outcome, extras, sdk_version:, env:) ⇒ Object

Build the type:"error" event accepted by POST /collect.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/shipeasy/sdk/see.rb', line 124

def build_event(problem, subject, outcome, extras, sdk_version:, env:)
  stack = nil

  if problem.is_a?(Violation)
    error_type = problem.name
    message    = problem.name
    kind       = "violation"
  elsif problem.is_a?(Exception)
    error_type = problem.class.name || "Error"
    message    = (problem.message.to_s.empty? ? error_type : problem.message)
    bt = problem.backtrace
    stack = bt.join("\n") if bt && !bt.empty?
    kind = "caught"
  else
    error_type = "Error"
    message    = problem.to_s
    kind       = "caught"
  end

  ev = {
    "type"        => "error",
    "kind"        => kind,
    "error_type"  => truncate(error_type, SEE_MAX_SUBJECT),
    "message"     => truncate(message, SEE_MAX_MESSAGE),
    "subject"     => truncate(subject, SEE_MAX_SUBJECT),
    "outcome"     => truncate(outcome, SEE_MAX_SUBJECT),
    "side"        => "server",
    "sdk_version" => sdk_version,
    "ts"          => (Time.now.to_f * 1000).to_i,
  }
  ev["stack"] = truncate(stack, SEE_MAX_STACK) if stack
  clean = sanitize_extras(extras)
  ev["extras"] = clean if clean
  ev["env"] = env if env && !env.to_s.empty?
  ev
end

.expected?(err) ⇒ Boolean

Returns:

  • (Boolean)


104
105
106
107
108
109
# File 'lib/shipeasy/sdk/see.rb', line 104

def expected?(err)
  err.instance_variable_defined?(EXPECTED_IVAR) &&
    !err.instance_variable_get(EXPECTED_IVAR).nil?
rescue StandardError
  false
end

.mark_expected(err, because, extras = nil) ⇒ Object

Best-effort stamp marking an exception as expected control flow.



94
95
96
97
98
99
100
101
102
# File 'lib/shipeasy/sdk/see.rb', line 94

def mark_expected(err, because, extras = nil)
  mark = { "because" => because.to_s }
  clean = sanitize_extras(extras)
  mark["extras"] = clean if clean
  err.instance_variable_set(EXPECTED_IVAR, mark)
rescue StandardError
  # Frozen / builtin objects that reject ivars: best effort only.
  nil
end

.sanitize_extras(extras) ⇒ Object

Drop nil values, keep only String/Numeric(finite)/boolean, truncate string values to 200 chars, cap at 20 keys (insertion order). Returns nil if nothing is kept. Keys are stringified.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/shipeasy/sdk/see.rb', line 67

def sanitize_extras(extras)
  return nil unless extras.is_a?(Hash)
  return nil if extras.empty?

  out = {}
  extras.each do |k, v|
    break if out.size >= SEE_MAX_EXTRA_KEYS
    next if v.nil?

    case v
    when true, false
      out[k.to_s] = v
    when String
      out[k.to_s] = truncate(v, SEE_MAX_EXTRA_VALUE)
    when Numeric
      # Reject NaN / Infinity (not representable in JSON).
      next if v.respond_to?(:finite?) && !v.finite?

      out[k.to_s] = v
    else
      next
    end
  end
  out.empty? ? nil : out
end

.top_stack_line(stack) ⇒ Object



196
197
198
199
200
201
202
203
204
# File 'lib/shipeasy/sdk/see.rb', line 196

def top_stack_line(stack)
  return "" if stack.nil? || stack.empty?

  stack.each_line do |line|
    s = line.strip
    return s[0, 200] if s.start_with?("File ") || s.start_with?("at ") || s.include?("line ") || s.include?(":in ")
  end
  ""
end

.truncate(str, limit) ⇒ Object



59
60
61
62
# File 'lib/shipeasy/sdk/see.rb', line 59

def truncate(str, limit)
  s = str.to_s
  s.length <= limit ? s : s[0, limit]
end