Module: Koderift::Rails::Instrumentation

Defined in:
lib/koderift/rails/instrumentation.rb

Class Method Summary collapse

Class Method Details

.captureObject

Subscribes to Rails notifications during a request to capture partial render times, SQL query times, and breadcrumbs. Returns a hash of captured data to be merged into the log payload.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/koderift/rails/instrumentation.rb', line 12

def self.capture
  partials       = []
  queries        = []
  search_queries = []
  breadcrumbs    = []
  config         = Koderift::Rails.configuration

  partial_sub = ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
    event      = ActiveSupport::Notifications::Event.new(*args)
    payload    = event.payload
    duration   = (payload[:duration] || event.duration).round
    identifier = payload[:identifier].to_s
                        .sub(rails_root_prefix, '')
                        .sub(%r{\A/app/views/}, '')

    partials << { partial: identifier, duration_ms: duration }
    breadcrumbs << {
      type:        'render',
      message:     identifier,
      duration_ms: duration,
      timestamp:   Time.now.utc.iso8601
    }
  end

  query_sub = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
    event   = ActiveSupport::Notifications::Event.new(*args)
    payload = event.payload
    next if payload[:name].to_s.match?(/\A(CACHE|SCHEMA)/)

    duration = (payload[:duration] || event.duration).round
    sql      = payload[:sql].to_s.squish.truncate(300)

    queries << { sql: sql, duration_ms: duration }
    breadcrumbs << {
      type:        'sql',
      message:     sql,
      duration_ms: duration,
      timestamp:   Time.now.utc.iso8601
    }
  end

  # Single-model search: fired by Model.search(...)
  # payload: { name: "Admin10 Search", query: {...} }
  search_sub = ActiveSupport::Notifications.subscribe('search.searchkick') do |*args|
    event    = ActiveSupport::Notifications::Event.new(*args)
    payload  = event.payload
    duration = (payload[:duration] || event.duration).round

    index = payload[:name].to_s.sub(/\s*Search\z/i, '').strip
    index = 'unknown' if index.blank?

    search_queries << { index: index, duration_ms: duration }
  end

  # Multi search: fired by Searchkick.multi_search([...])
  # payload: { name: "Multi Search", body: "..." }
  # Duration covers the entire batch — record as a single entry.
  multi_search_sub = ActiveSupport::Notifications.subscribe('multi_search.searchkick') do |*args|
    event    = ActiveSupport::Notifications::Event.new(*args)
    duration = event.duration.round

    search_queries << { index: 'multi_search', duration_ms: duration }
  end

  external_calls = []
  Thread.current[:koderift_external_calls]  = external_calls
  Thread.current[:koderift_external_config] = config

  begin
    yield
  ensure
    ActiveSupport::Notifications.unsubscribe(partial_sub)
    ActiveSupport::Notifications.unsubscribe(query_sub)
    ActiveSupport::Notifications.unsubscribe(search_sub)
    ActiveSupport::Notifications.unsubscribe(multi_search_sub)
    Thread.current[:koderift_external_calls]  = nil
    Thread.current[:koderift_external_config] = nil
  end

  spans = []

  partials.each_with_index do |p, i|
    spans << { type: 'partial', name: p[:partial], duration_ms: p[:duration_ms], sequence: i }
  end

  queries.each_with_index do |q, i|
    spans << { type: 'sql', name: q[:sql], duration_ms: q[:duration_ms], sequence: i }
  end

  search_queries.each_with_index do |s, i|
    spans << { type: 'search', name: s[:index], duration_ms: s[:duration_ms], sequence: i }
  end

  external_calls.each_with_index do |c, i|
    spans << {
      type:        'http',
      name:        "#{c[:host]}#{c[:endpoint]}",
      duration_ms: c[:duration_ms],
      detail:      { method: c[:method], status: c[:status], trace_id: c[:trace_id] }.compact.to_json,
      sequence:    i
    }
  end

  {
    spans:         spans,
    query_count:   queries.size,
    partial_count: partials.size,
    search_count:  search_queries.size,
    breadcrumbs:   breadcrumbs.last(config.max_breadcrumbs)
  }
end

.rails_root_prefixObject



124
125
126
# File 'lib/koderift/rails/instrumentation.rb', line 124

def self.rails_root_prefix
  defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root ? ::Rails.root.to_s : ''
end