Module: Kumi::Core::Analyzer::Debug

Defined in:
lib/kumi/core/analyzer/debug.rb

Defined Under Namespace

Modules: Loggable

Constant Summary collapse

KEY =
:kumi_debug_log

Class Method Summary collapse

Class Method Details

.debug(id, **fields) ⇒ Object



62
63
64
# File 'lib/kumi/core/analyzer/debug.rb', line 62

def debug(id, **fields)
  log(level: :debug, id: id, **fields)
end

.diff_state(before, after) ⇒ Object

State diffing



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/kumi/core/analyzer/debug.rb', line 79

def diff_state(before, after)
  changes = {}

  all_keys = (before.keys + after.keys).uniq
  all_keys.each do |key|
    if !before.key?(key)
      changes[key] = { type: :added, value: truncate(after[key]) }
    elsif !after.key?(key)
      changes[key] = { type: :removed, value: truncate(before[key]) }
    elsif before[key] != after[key]
      changes[key] = {
        type: :changed,
        before: truncate(before[key]),
        after: truncate(after[key])
      }
    end
  end

  changes
end

.drain_logObject



34
35
36
37
38
# File 'lib/kumi/core/analyzer/debug.rb', line 34

def drain_log
  stash = Thread.current[KEY]
  Thread.current[KEY] = nil
  (stash && stash[:events]) || []
end

.emit(pass:, diff:, elapsed_ms:, logs:) ⇒ Object

Emit debug event



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/kumi/core/analyzer/debug.rb', line 101

def emit(pass:, diff:, elapsed_ms:, logs:)
  payload = {
    ts: Time.now.utc.iso8601,
    pass: pass,
    elapsed_ms: elapsed_ms,
    diff: diff,
    logs: logs
  }

  if output_path && !output_path.empty?
    File.open(output_path, "a") { |f| f.puts(JSON.dump(payload)) }
  else
    $stdout.puts "\n=== STATE #{pass} (#{elapsed_ms}ms) ==="
    $stdout.puts JSON.pretty_generate(payload)
  end
end

.enabled?Boolean



13
14
15
# File 'lib/kumi/core/analyzer/debug.rb', line 13

def enabled?
  ENV["KUMI_DEBUG_STATE"] == "1"
end

.info(id, **fields) ⇒ Object



58
59
60
# File 'lib/kumi/core/analyzer/debug.rb', line 58

def info(id, **fields)
  log(level: :info, id: id, **fields)
end

.log(level:, id:, method: nil, **fields) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/kumi/core/analyzer/debug.rb', line 40

def log(level:, id:, method: nil, **fields)
  buf = Thread.current[KEY]
  return unless buf

  loc = caller_locations(1, 1)&.first
  meth = method || loc&.base_label
  buf[:events] << {
    ts: Time.now.utc.iso8601,
    pass: buf[:pass],
    level: level,
    id: id,
    method: meth,
    file: loc&.path,
    line: loc&.lineno,
    **fields
  }
end

.max_depthObject



21
22
23
# File 'lib/kumi/core/analyzer/debug.rb', line 21

def max_depth
  (ENV["KUMI_DEBUG_MAX_DEPTH"] || "5").to_i
end

.max_itemsObject



25
26
27
# File 'lib/kumi/core/analyzer/debug.rb', line 25

def max_items
  (ENV["KUMI_DEBUG_MAX_ITEMS"] || "100").to_i
end

.output_pathObject



17
18
19
# File 'lib/kumi/core/analyzer/debug.rb', line 17

def output_path
  ENV.fetch("KUMI_DEBUG_OUTPUT_PATH", nil)
end

.reset_log(pass:) ⇒ Object

Log buffer management



30
31
32
# File 'lib/kumi/core/analyzer/debug.rb', line 30

def reset_log(pass:)
  Thread.current[KEY] = { pass: pass, events: [] }
end

.trace(id, **start_fields) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
# File 'lib/kumi/core/analyzer/debug.rb', line 66

def trace(id, **start_fields)
  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  info(:"#{id}_start", **start_fields)
  yield.tap do |_ret|
    dt = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2)
    info(:"#{id}_finish", ms: dt)
  end
rescue StandardError => e
  log(level: :error, id: :"#{id}_error", error: e.class.name, message: e.message)
  raise
end