callstacking-rails 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf64a69817d5a94e205db95cd79dbff2c15d1190ed001bb49d1113aa0ada96b4
4
- data.tar.gz: 4a37b31f1d9faac2de735ea799fd7fda9151ff2300de7401ad23d9dc8c21fd5e
3
+ metadata.gz: 1da66ff58acecfefd9f52e259cb9d69eb54e8328a4fee36ed130e26f968d61a2
4
+ data.tar.gz: 2d8bc0b54633d9a17e607ee1c7006d7d728e999c5a5d5c6bfc93dd9090a1a19e
5
5
  SHA512:
6
- metadata.gz: 2739c75f8347b76baa6f509f4f5c31c87e3de649490d3c1d3f4c9237062b44e90cb8a0cf47cf4d025b1e9b0539834b0e8641cd6ef6a2302854f0610bf0a3775e
7
- data.tar.gz: acecb1df5835c9b3992501e506042c1693364c326545e36d6b0fd6549b48afba0af43808a4a730135ea988bbd4f6a38c14c361d4467361456f668e5bda7fba1e
6
+ metadata.gz: 737cf8e5ddf9467a6ce6ca2ae1ebeee369f5e9200b7893440fb923ecbe21638ac0ec0430ab909cddd29910b5a4bc83d04436fd2cf304109b88d7d64768232789
7
+ data.tar.gz: 8f8afdfc52b192dd8e688e5f28dc8f93306241bc4347cc3c90144d7a6b7b9f210b0d9eb6037b288e6bbc4c68554e71456261fd736e5ea5e5fde6402c29051d53
@@ -7,16 +7,18 @@ module Callstacking
7
7
  CREATE_URL = "/api/v1/traces.json"
8
8
  UPDATE_URL = "/api/v1/traces/:id.json"
9
9
 
10
- def create(method_name, klass, action_name, format_name, root_path, url, request_id, headers, params)
10
+ def create(request_id, tuid, method_name, klass, action_name, format_name, root_path, url, headers, params)
11
11
  resp = post(CREATE_URL,
12
12
  {},
13
- { method_name: method_name,
13
+ {
14
+ request_id: request_id,
15
+ tuid: tuid,
16
+ method_name: method_name,
14
17
  klass: klass,
15
18
  action_name: action_name,
16
19
  format_name: format_name,
17
20
  root_path: root_path,
18
21
  url: url,
19
- request_id: request_id,
20
22
  h: headers.to_h,
21
23
  p: params.to_h,
22
24
  })
@@ -12,17 +12,21 @@ require "callstacking/rails/client/authenticate"
12
12
  require "callstacking/rails/client/trace"
13
13
  require "callstacking/rails/cli"
14
14
  require "callstacking/rails/traces_helper"
15
+ require "callstacking/rails/time_based_uuid"
15
16
 
16
17
  module Callstacking
17
18
  module Rails
18
19
  class Engine < ::Rails::Engine
19
- cattr_accessor :spans, :trace, :settings
20
+ EXCLUDED_TEST_CLASSES = ['test/dummy/app/models/salutation.rb'].freeze
21
+
22
+ cattr_accessor :spans, :trace, :settings, :instrumenter, :loader
20
23
 
21
24
  isolate_namespace Callstacking::Rails
22
25
 
23
26
  @@settings||=Callstacking::Rails::Settings.new
24
27
  @@spans||=Spans.new
25
28
  @@trace||=Trace.new(@@spans)
29
+ @@instrumenter||=Instrument.new(@@spans)
26
30
 
27
31
  initializer "engine_name.assets.precompile" do |app|
28
32
  app.config.assets.precompile << "checkpoint_rails_manifest.js"
@@ -42,13 +46,29 @@ module Callstacking
42
46
  inject_hud(@@settings)
43
47
  end
44
48
 
45
- Callstacking::Rails::Loader.new.on_load(@@spans)
49
+ @@loader = Callstacking::Rails::Loader.new(@@instrumenter,
50
+ excluded: @@settings.excluded + EXCLUDED_TEST_CLASSES)
51
+ @@loader.on_load
46
52
 
47
- @@trace.tracing
53
+ @@trace.request_tracing
48
54
  else
49
55
  puts "Call Stacking disabled (#{Callstacking::Rails::Env.environment})"
50
56
  end
51
57
  end
58
+
59
+ def self.start_tracing
60
+ return false if @@settings.disabled?
61
+
62
+ @@instrumenter.enable!(@@loader.klasses)
63
+ true
64
+ end
65
+
66
+ def self.stop_tracing
67
+ return false if @@settings.disabled?
68
+
69
+ @@instrumenter.disable!
70
+ true
71
+ end
52
72
  end
53
73
  end
54
74
  end
@@ -4,16 +4,17 @@ require 'rails'
4
4
  module Callstacking
5
5
  module Rails
6
6
  class Instrument
7
- attr_accessor :spans, :klass
8
- attr_reader :root
7
+ attr_accessor :spans
8
+ attr_reader :root, :settings, :span_modules
9
9
 
10
- def initialize(spans, klass)
10
+ def initialize(spans)
11
11
  @spans = spans
12
- @klass = klass
12
+ @span_modules = Set.new
13
+ @settings = Callstacking::Rails::Settings.new
13
14
  @root = Regexp.new(::Rails.root.to_s)
14
15
  end
15
16
 
16
- def instrument_method(method_name, application_level: true)
17
+ def instrument_method(klass, method_name, application_level: true)
17
18
  method_path = (klass.instance_method(method_name).source_location.first rescue nil) ||
18
19
  (klass.method(method_name).source_location.first rescue nil)
19
20
 
@@ -22,7 +23,7 @@ module Callstacking
22
23
 
23
24
  return if method_path =~ /initializer/i
24
25
 
25
- tmp_module = find_or_initialize_module
26
+ tmp_module = find_or_initialize_module(klass)
26
27
 
27
28
  return if tmp_module.nil? ||
28
29
  tmp_module.instance_methods.include?(method_name) ||
@@ -76,31 +77,22 @@ module Callstacking
76
77
  new_method
77
78
  end
78
79
 
79
- def find_or_initialize_module
80
- name = klass&.name rescue nil
81
- return if name.nil?
82
-
83
- module_name = "#{klass.name.gsub('::', '')}Span"
84
- module_index = klass.ancestors.map(&:to_s).index(module_name)
85
-
86
- unless module_index
87
- new_module = Object.const_set(module_name, Module.new)
88
-
89
- new_module.instance_variable_set("@klass", klass)
90
- new_module.instance_variable_set("@spans", spans)
91
-
92
- klass.prepend new_module
93
- klass.singleton_class.prepend new_module if klass.class == Module
94
-
95
- return find_or_initialize_module
80
+ def enable!(klasses)
81
+ klasses.each do |klass|
82
+ instrument_klass(klass, application_level: true)
83
+ end
84
+ end
85
+ def disable!
86
+ span_modules.each do |mod|
87
+ mod.instance_methods.each do |method_name|
88
+ mod.remove_method(method_name)
89
+ end
96
90
  end
97
-
98
- klass.ancestors[module_index]
99
91
  end
100
92
 
101
- def instrument_klass(application_level: true)
102
- relevant = all_methods - filtered
103
- relevant.each { |method| instrument_method(method, application_level: application_level) }
93
+ def instrument_klass(klass, application_level: true)
94
+ relevant = all_methods(klass) - filtered
95
+ relevant.each { |method| instrument_method(klass, method, application_level: application_level) }
104
96
  end
105
97
 
106
98
  def self.arguments_for(m, args)
@@ -118,9 +110,31 @@ module Callstacking
118
110
  end
119
111
 
120
112
  private
113
+ def find_or_initialize_module(klass)
114
+ name = klass&.name rescue nil
115
+ return if name.nil?
116
+
117
+ module_name = "#{klass.name.gsub('::', '')}Span"
118
+ module_index = klass.ancestors.map(&:to_s).index(module_name)
119
+
120
+ unless module_index
121
+ new_module = Object.const_set(module_name, Module.new)
122
+ span_modules << new_module
123
+
124
+ new_module.instance_variable_set("@klass", klass)
125
+ new_module.instance_variable_set("@spans", spans)
126
+
127
+ klass.prepend new_module
128
+ klass.singleton_class.prepend new_module if klass.class == Module
129
+
130
+ return find_or_initialize_module(klass)
131
+ end
132
+
133
+ klass.ancestors[module_index]
134
+ end
121
135
 
122
- def all_methods
123
- @all_methods ||= (klass.instance_methods +
136
+ def all_methods(klass)
137
+ (klass.instance_methods +
124
138
  klass.private_instance_methods(false) +
125
139
  klass.protected_instance_methods(false) +
126
140
  klass.methods +
@@ -3,26 +3,32 @@ require "rails"
3
3
  module Callstacking
4
4
  module Rails
5
5
  class Loader
6
- attr_accessor :loader, :root, :once
7
- def initialize
6
+ attr_accessor :root, :spans, :instrumenter, :klasses, :excluded
7
+ def initialize(instrumenter, excluded: [])
8
8
  @root = Regexp.new(::Rails.root.to_s)
9
+ @spans = spans
10
+ @excluded = excluded
11
+ @instrumenter = instrumenter
12
+ @klasses = Set.new
9
13
  end
10
14
 
11
- def on_load(spans)
15
+ def on_load
12
16
  trace = TracePoint.new(:end) do |tp|
13
17
  klass = tp.self
14
18
  path = tp.path
15
19
 
16
- Instrument.new(spans, klass).instrument_klass if path =~ root
20
+ exclude = excluded.any? { |ex| path =~ /#{ex}/ }
21
+
22
+ if path =~ root && !exclude
23
+ instrumenter.instrument_klass(klass)
24
+ klasses << klass
25
+ end
17
26
  end
18
27
 
19
28
  trace.enable
20
29
 
21
- Instrument.new(spans, ActionView::PartialRenderer).instrument_method(:render,
22
- application_level: false)
23
-
24
- Instrument.new(spans, ActionView::TemplateRenderer).instrument_method( :render,
25
- application_level: false)
30
+ instrumenter.instrument_method(ActionView::PartialRenderer, :render, application_level: false)
31
+ instrumenter.instrument_method(ActionView::TemplateRenderer, :render, application_level: false)
26
32
  end
27
33
  end
28
34
  end
@@ -34,11 +34,11 @@ module Callstacking
34
34
  File.write(SETTINGS_FILE, new_settings.to_yaml)
35
35
  end
36
36
 
37
- def enable!
37
+ def self.enable!
38
38
  ::Rails.cache.write(CACHE_KEY, true)
39
39
  end
40
40
 
41
- def disable!
41
+ def self.disable!
42
42
  ::Rails.cache.write(CACHE_KEY, false)
43
43
  end
44
44
 
@@ -50,6 +50,10 @@ module Callstacking
50
50
  settings[:enabled]
51
51
  end
52
52
 
53
+ def excluded
54
+ settings[:excluded] || []
55
+ end
56
+
53
57
  def disabled?
54
58
  !enabled?
55
59
  end
@@ -0,0 +1,25 @@
1
+ require 'securerandom'
2
+
3
+ class TimeBasedUUID
4
+ EPOCH_OFFSET = 1468418800000 # A custom epoch, it could be the UNIX timestamp when the application was created (in milliseconds)
5
+ MAX_INT8_VALUE = 9223372036854775807
6
+
7
+ def self.generate
8
+ # Get the current time in milliseconds
9
+ current_time = (Time.now.to_f * 1000).to_i
10
+
11
+ # Subtract the custom epoch to reduce the timestamp size
12
+ timestamp = current_time - EPOCH_OFFSET
13
+
14
+ # Generate a random 64-bit number using SecureRandom
15
+ random_bits = SecureRandom.random_number(1 << 64)
16
+
17
+ # Combine the timestamp and the random bits
18
+ uuid = (timestamp << 64) | random_bits
19
+
20
+ # Ensure the UUID fits into a PostgreSQL int8 column
21
+ uuid = uuid % MAX_INT8_VALUE if uuid > MAX_INT8_VALUE
22
+
23
+ uuid
24
+ end
25
+ end
@@ -6,9 +6,11 @@ module Callstacking
6
6
  class Trace
7
7
  attr_accessor :spans, :client, :lock
8
8
  attr_reader :settings
9
- cattr_accessor :current_request_id
9
+ cattr_accessor :current_trace_id
10
+ cattr_accessor :current_tuid
10
11
 
11
12
  ICON = '💥'
13
+ MAX_TRACE_ENTRIES = 3000
12
14
 
13
15
  def initialize(spans)
14
16
  @traces = []
@@ -17,37 +19,54 @@ module Callstacking
17
19
 
18
20
  @lock = Mutex.new
19
21
  @client = Callstacking::Rails::Client::Trace.new(settings.url, settings.auth_token)
22
+
23
+ init_uuids(nil, nil)
24
+ init_callbacks(nil)
20
25
  end
21
26
 
22
- def tracing
27
+ def request_tracing
28
+ tuid = nil
23
29
  trace_id = nil
24
- max_trace_entries = nil
25
30
 
26
- ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
27
- trace_id, max_trace_entries = start_request(payload[:request]&.request_id, payload[:method], payload[:controller],
28
- payload[:action], payload[:format], ::Rails.root,
29
- payload[:request]&.original_url || payload[:path],
30
- payload[:headers], payload[:params])
31
- end
31
+ ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |_name, _start, _finish, _id, payload|
32
+ trace_id, tuid = init_uuids(payload[:request]&.request_id || SecureRandom.uuid, TimeBasedUUID.generate)
33
+ init_callbacks(tuid)
32
34
 
33
- @spans.on_call_entry do |nesting_level, order_num, klass, method_name, arguments, path, line_no|
34
- create_call_entry(nesting_level, order_num, klass, method_name, arguments, path, line_no, @traces)
35
+ start_request(trace_id, tuid,
36
+ payload[:method], payload[:controller],
37
+ payload[:action], payload[:format], ::Rails.root,
38
+ payload[:request]&.original_url || payload[:path],
39
+ payload[:headers], payload[:params])
35
40
  end
36
41
 
37
- @spans.on_call_return do |coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val|
38
- create_call_return(coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, @traces)
39
- end
40
-
41
- ActiveSupport::Notifications.subscribe("process_action.action_controller") do |name, start, finish, id, payload|
42
- complete_request(payload[:method], payload[:controller],
42
+ ActiveSupport::Notifications.subscribe("process_action.action_controller") do |_name, _start, _finish, _id, payload|
43
+ complete_request(trace_id, tuid,
44
+ payload[:method], payload[:controller],
43
45
  payload[:action], payload[:format],
44
46
  payload[:request]&.original_url || payload[:path],
45
- trace_id, @traces, max_trace_entries)
47
+ @traces, MAX_TRACE_ENTRIES)
46
48
  end
47
49
  end
48
50
 
49
51
  private
50
52
 
53
+ def init_callbacks(tuid)
54
+ @spans.on_call_entry do |nesting_level, order_num, klass, method_name, arguments, path, line_no|
55
+ create_call_entry(tuid, nesting_level, order_num, klass, method_name, arguments, path, line_no, @traces)
56
+ end
57
+
58
+ @spans.on_call_return do |coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val|
59
+ create_call_return(tuid, coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, @traces)
60
+ end
61
+ end
62
+
63
+ def init_uuids(trace_id, tuid)
64
+ Callstacking::Rails::Trace.current_trace_id = trace_id
65
+ Callstacking::Rails::Trace.current_tuid = tuid
66
+
67
+ return trace_id, tuid
68
+ end
69
+
51
70
  def completed_request_message(method, controller, action, format)
52
71
  "Completed request: #{method} #{controller}##{action} as #{format}"
53
72
  end
@@ -56,9 +75,10 @@ module Callstacking
56
75
  "Started request: #{method} #{controller}##{action} as #{format}"
57
76
  end
58
77
 
59
- def create_call_return(coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, traces)
78
+ def create_call_return(tuid, coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, traces)
60
79
  lock.synchronize do
61
- traces << { type: 'TraceCallReturn',
80
+ traces << { tuid: tuid,
81
+ type: 'TraceCallReturn',
62
82
  order_num: order_num,
63
83
  nesting_level: nesting_level,
64
84
  local_variables: {},
@@ -74,9 +94,10 @@ module Callstacking
74
94
  end
75
95
  end
76
96
 
77
- def create_call_entry(nesting_level, order_num, klass, method_name, arguments, path, line_no, traces)
97
+ def create_call_entry(tuid, nesting_level, order_num, klass, method_name, arguments, path, line_no, traces)
78
98
  lock.synchronize do
79
- traces << { type: 'TraceCallEntry',
99
+ traces << { tuid: tuid,
100
+ type: 'TraceCallEntry',
80
101
  order_num: order_num,
81
102
  nesting_level: nesting_level,
82
103
  args: arguments,
@@ -92,9 +113,10 @@ module Callstacking
92
113
  end
93
114
  end
94
115
 
95
- def create_message(message, order_num, traces)
116
+ def create_message(tuid, message, order_num, traces)
96
117
  lock.synchronize do
97
- traces << { type: 'TraceMessage',
118
+ traces << { tuid: tuid,
119
+ type: 'TraceMessage',
98
120
  order_num: order_num,
99
121
  nesting_level: 0,
100
122
  message: message,
@@ -114,26 +136,24 @@ module Callstacking
114
136
  lock.synchronize do
115
137
  return if traces.empty?
116
138
 
117
- client.upsert(trace_id, { trace_entries: traces })
139
+ client.upsert(trace_id,
140
+ { trace_entries: traces })
118
141
  traces.clear
119
142
  end
120
143
  end
121
- def start_request(request_id, method, controller, action, format, path, original_url, headers, params)
144
+ def start_request(trace_id, tuid, method, controller, action, format, path, original_url, headers, params)
122
145
  return if do_not_track_request?(original_url, format)
123
-
124
- request_id = request_id || SecureRandom.uuid
125
- Callstacking::Rails::Trace.current_request_id = request_id
126
146
 
127
- trace_id, _interval, max_trace_entries = client.create(method, controller, action, format,
128
- path, original_url, request_id, headers,
129
- params)
147
+ client.create(trace_id, tuid,
148
+ method, controller,
149
+ action, format,
150
+ path, original_url,
151
+ headers, params)
130
152
 
131
153
  print_trace_url(trace_id)
132
154
 
133
- create_message(start_request_message(method, controller, action, format),
155
+ create_message(tuid, start_request_message(method, controller, action, format),
134
156
  spans.increment_order_num, @traces)
135
-
136
- return trace_id, max_trace_entries
137
157
  end
138
158
 
139
159
  def print_trace_url(trace_id)
@@ -146,13 +166,13 @@ module Callstacking
146
166
  url
147
167
  end
148
168
 
149
- def complete_request(method, controller, action, format, original_url, trace_id, traces, max_trace_entries)
169
+ def complete_request(trace_id, tuid, method, controller, action, format, original_url, traces, max_trace_entries)
150
170
  if do_not_track_request?(original_url, format)
151
171
  traces.clear
152
172
  return
153
173
  end
154
174
 
155
- create_message(completed_request_message(method, controller, action, format),
175
+ create_message(tuid, completed_request_message(method, controller, action, format),
156
176
  spans.increment_order_num, traces)
157
177
 
158
178
  send_traces!(trace_id, traces[0..max_trace_entries])
@@ -9,7 +9,7 @@ module Callstacking
9
9
  include ActionView::Context
10
10
 
11
11
  def hud(url)
12
- frame_url = "#{url || Callstacking::Rails::Settings::PRODUCTION_URL}/traces/#{Callstacking::Rails::Trace.current_request_id}/print"
12
+ frame_url = "#{url || Callstacking::Rails::Settings::PRODUCTION_URL}/traces/#{Callstacking::Rails::Trace.current_trace_id}/print"
13
13
 
14
14
  body = []
15
15
  body << (content_tag( :div, data: { turbo:false },
@@ -1,5 +1,5 @@
1
1
  module Callstacking
2
2
  module Rails
3
- VERSION = "0.1.20"
3
+ VERSION = "0.1.21"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: callstacking-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.20
4
+ version: 0.1.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Jones
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-12 00:00:00.000000000 Z
11
+ date: 2023-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -113,6 +113,7 @@ files:
113
113
  - lib/callstacking/rails/settings.rb
114
114
  - lib/callstacking/rails/setup.rb
115
115
  - lib/callstacking/rails/spans.rb
116
+ - lib/callstacking/rails/time_based_uuid.rb
116
117
  - lib/callstacking/rails/trace.rb
117
118
  - lib/callstacking/rails/traces_helper.rb
118
119
  - lib/callstacking/rails/version.rb