Module: Shipeasy::SDK::See

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

Defined Under Namespace

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.



113
114
115
116
117
118
119
120
121
122
123
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
# File 'lib/shipeasy/sdk/see.rb', line 113

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)


93
94
95
96
97
98
# File 'lib/shipeasy/sdk/see.rb', line 93

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.



83
84
85
86
87
88
89
90
91
# File 'lib/shipeasy/sdk/see.rb', line 83

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.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/shipeasy/sdk/see.rb', line 56

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



185
186
187
188
189
190
191
192
193
# File 'lib/shipeasy/sdk/see.rb', line 185

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



48
49
50
51
# File 'lib/shipeasy/sdk/see.rb', line 48

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