callstacking-rails 0.1.19 → 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: 122755f46a3f89ee6c310aed0ede48453bf7ff7f92c50f9809fe7466ad49159b
4
- data.tar.gz: adfdbd3892e3c6cc5b1e869ce8a1d63185610347b3079a1de8cf6ebb8009eff3
3
+ metadata.gz: 1da66ff58acecfefd9f52e259cb9d69eb54e8328a4fee36ed130e26f968d61a2
4
+ data.tar.gz: 2d8bc0b54633d9a17e607ee1c7006d7d728e999c5a5d5c6bfc93dd9090a1a19e
5
5
  SHA512:
6
- metadata.gz: baa19130df5d47f45d0df4d2a0055edb1d5a2bbb74bdff1919a20de8bd6e6fa5fe6ad8224dddf8a6b973128301f007b76f094d076e0753c1b5257f3b5852c9ac
7
- data.tar.gz: 22e7fc256542ea2f13de0f400b350a74a2e115c96610734f0db0e492d416b48d6dd19f26ea68b40a4ffac6ef3a515f27a21604b3a0fe0dca2e43f6017bf1693f
6
+ metadata.gz: 737cf8e5ddf9467a6ce6ca2ae1ebeee369f5e9200b7893440fb923ecbe21638ac0ec0430ab909cddd29910b5a4bc83d04436fd2cf304109b88d7d64768232789
7
+ data.tar.gz: 8f8afdfc52b192dd8e688e5f28dc8f93306241bc4347cc3c90144d7a6b7b9f210b0d9eb6037b288e6bbc4c68554e71456261fd736e5ea5e5fde6402c29051d53
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Callstacking::Rails
2
+ [![Build Status](https://github.com/callstacking/callstacking-rails/actions/workflows/ci.yml/badge.svg)](https://github.com/callstacking/callstacking-rails/actions/workflows/ci.yml)
2
3
 
3
4
  Call Stacking is a rolling, checkpoint debugger for Rails. It records all of the critical method calls within your app, along with their important context (param/argument/return/local variable values).
4
5
 
@@ -1,33 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "callstacking/rails/setup"
5
- require "callstacking/rails/settings"
4
+ require "callstacking/rails"
6
5
 
7
- include Callstacking::Rails::Settings
8
-
9
- action = ARGV[0]&.downcase&.strip
6
+ action = Callstacking::Rails::Cli.action(ARGV)
7
+ settings = Callstacking::Rails::Settings.new
10
8
 
11
9
  if action.nil?
12
10
  Callstacking::Rails::Setup.instructions
13
11
  exit!(1)
14
12
  end
15
13
 
16
- read_settings
17
-
18
- case action
19
- when 'register'
20
- puts "Open the following URL to register:\n\n"
21
- puts " #{settings[:url]}/users/sign_up\n\n"
22
-
23
- when 'setup'
24
- Callstacking::Rails::Setup.new.start
25
-
26
- when 'enable'
27
- Callstacking::Rails::Setup.new.enable_disable
28
- puts "Call Stacking tracing enabled (#{Callstacking::Rails::Env.environment})"
29
-
30
- when 'disable'
31
- Callstacking::Rails::Setup.new.enable_disable(enabled: false)
32
- puts "Call Stacking tracing disabled (#{Callstacking::Rails::Env.environment})"
33
- end
14
+ cli = Callstacking::Rails::Cli.new(action, settings)
15
+ cli.run
@@ -0,0 +1,50 @@
1
+ module Callstacking
2
+ module Rails
3
+ class Cli
4
+ REGISTER = 'register'
5
+ SETUP = 'setup'
6
+ ENABLE = 'enable'
7
+ DISABLE = 'disable'
8
+
9
+ attr_reader :action, :settings
10
+
11
+ def initialize(action, settings)
12
+ @action = action
13
+ @settings = settings
14
+ end
15
+
16
+ def run
17
+ parse_options
18
+ end
19
+
20
+ def self.action(args)
21
+ args[0]&.downcase&.strip
22
+ end
23
+
24
+ private
25
+
26
+ def parse_options
27
+ case action
28
+ when REGISTER
29
+ puts "Open the following URL to register:\n\n"
30
+ puts " #{settings.url}/users/sign_up\n\n"
31
+ REGISTER
32
+
33
+ when SETUP
34
+ Callstacking::Rails::Setup.new.start
35
+ SETUP
36
+
37
+ when ENABLE
38
+ settings.enable_disable
39
+ puts "Call Stacking tracing enabled (#{Callstacking::Rails::Env.environment})"
40
+ ENABLE
41
+
42
+ when DISABLE
43
+ settings.enable_disable(enabled: false)
44
+ puts "Call Stacking tracing disabled (#{Callstacking::Rails::Env.environment})"
45
+ DISABLE
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,4 @@
1
1
  require 'json'
2
- require "callstacking/rails/client/base"
3
2
 
4
3
  module Callstacking
5
4
  module Rails
@@ -1,6 +1,5 @@
1
1
  require 'faraday'
2
2
  require 'faraday/follow_redirects'
3
- require "callstacking/rails/settings"
4
3
 
5
4
  module Callstacking
6
5
  module Rails
@@ -8,12 +7,12 @@ module Callstacking
8
7
  class Error < StandardError; end
9
8
 
10
9
  class Base
11
- include Callstacking::Rails::Settings
10
+ attr_reader :url, :auth_token
12
11
 
13
- def initialize
14
- read_settings
12
+ def initialize(url, auth_token)
13
+ @url = url
14
+ @auth_token = auth_token
15
15
  end
16
-
17
16
  def connection
18
17
  # https://github.com/lostisland/awesome-faraday
19
18
  @connection ||= Faraday.new(url) do |c|
@@ -25,7 +24,7 @@ module Callstacking
25
24
  c.request :json # This will set the "Content-Type" header to application/json and call .to_json on the body
26
25
  c.adapter Faraday.default_adapter
27
26
 
28
- if auth_token?
27
+ if auth_token.present?
29
28
  c.request :authorization, :Bearer, auth_token
30
29
  end
31
30
  end
@@ -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
  })
@@ -1,4 +1,6 @@
1
1
  require "rails"
2
+ require "active_support/cache"
3
+ require "callstacking/rails/env"
2
4
  require "callstacking/rails/trace"
3
5
  require "callstacking/rails/instrument"
4
6
  require 'callstacking/rails/spans'
@@ -8,20 +10,23 @@ require "callstacking/rails/loader"
8
10
  require "callstacking/rails/client/base"
9
11
  require "callstacking/rails/client/authenticate"
10
12
  require "callstacking/rails/client/trace"
13
+ require "callstacking/rails/cli"
11
14
  require "callstacking/rails/traces_helper"
15
+ require "callstacking/rails/time_based_uuid"
12
16
 
13
17
  module Callstacking
14
18
  module Rails
15
19
  class Engine < ::Rails::Engine
16
- cattr_accessor :spans, :trace
20
+ EXCLUDED_TEST_CLASSES = ['test/dummy/app/models/salutation.rb'].freeze
21
+
22
+ cattr_accessor :spans, :trace, :settings, :instrumenter, :loader
17
23
 
18
- include Settings
19
24
  isolate_namespace Callstacking::Rails
20
25
 
21
- read_settings
22
-
26
+ @@settings||=Callstacking::Rails::Settings.new
23
27
  @@spans||=Spans.new
24
28
  @@trace||=Trace.new(@@spans)
29
+ @@instrumenter||=Instrument.new(@@spans)
25
30
 
26
31
  initializer "engine_name.assets.precompile" do |app|
27
32
  app.config.assets.precompile << "checkpoint_rails_manifest.js"
@@ -31,19 +36,38 @@ module Callstacking
31
36
  ActiveSupport.on_load :action_controller do
32
37
  include Callstacking::Rails::TracesHelper
33
38
  end
39
+ end
40
+
41
+ config.after_initialize do
42
+ if @@settings.enabled?
43
+ puts "Call Stacking enabled (#{Callstacking::Rails::Env.environment})"
44
+
45
+ ActionController::Base.send :after_action do
46
+ inject_hud(@@settings)
47
+ end
34
48
 
35
- Callstacking::Rails::Loader.new.on_load(@@spans) if enabled?
49
+ @@loader = Callstacking::Rails::Loader.new(@@instrumenter,
50
+ excluded: @@settings.excluded + EXCLUDED_TEST_CLASSES)
51
+ @@loader.on_load
52
+
53
+ @@trace.request_tracing
54
+ else
55
+ puts "Call Stacking disabled (#{Callstacking::Rails::Env.environment})"
56
+ end
36
57
  end
37
58
 
38
- initializer :append_before_action do
39
- ActionController::Base.send :after_action, :inject_hud
59
+ def self.start_tracing
60
+ return false if @@settings.disabled?
61
+
62
+ @@instrumenter.enable!(@@loader.klasses)
63
+ true
40
64
  end
41
65
 
42
- if enabled?
43
- puts "Call Stacking enabled (#{Callstacking::Rails::Env.environment})"
44
- trace.tracing
45
- else
46
- puts "Call Stacking disabled (#{Callstacking::Rails::Env.environment})"
66
+ def self.stop_tracing
67
+ return false if @@settings.disabled?
68
+
69
+ @@instrumenter.disable!
70
+ true
47
71
  end
48
72
  end
49
73
  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
@@ -1,18 +1,22 @@
1
+ require "active_support/cache"
1
2
  require "active_support/concern"
2
3
  require "active_support/core_ext/class/attribute_accessors"
3
- require "callstacking/rails/env"
4
4
 
5
5
  module Callstacking
6
6
  module Rails
7
- module Settings
8
- extend ActiveSupport::Concern
9
-
10
- included do |base|
11
- attr_accessor :settings
12
- end
7
+ class Settings
8
+ attr_accessor :settings
9
+ attr_reader :client
13
10
 
14
11
  SETTINGS_FILE = "#{Dir.home}/.callstacking"
15
12
  PRODUCTION_URL = "https://callstacking.com"
13
+ ENV_KEY = 'CALLSTACKING_ENABLED'
14
+ CACHE_KEY = :callstacking_enabled
15
+
16
+ def initialize
17
+ read_settings
18
+ @client = Callstacking::Rails::Client::Authenticate.new(url, auth_token)
19
+ end
16
20
 
17
21
  def url
18
22
  settings[:url] || PRODUCTION_URL
@@ -30,18 +34,67 @@ module Callstacking
30
34
  File.write(SETTINGS_FILE, new_settings.to_yaml)
31
35
  end
32
36
 
37
+ def self.enable!
38
+ ::Rails.cache.write(CACHE_KEY, true)
39
+ end
40
+
41
+ def self.disable!
42
+ ::Rails.cache.write(CACHE_KEY, false)
43
+ end
44
+
33
45
  def enabled?
46
+ return ::Rails.cache.read(CACHE_KEY) unless ::Rails.cache.read(CACHE_KEY).nil?
47
+ return false if ENV[ENV_KEY] == 'false'
34
48
  return false if settings.nil?
35
- return false if ENV['CALLSTACKING_ENABLED'] == 'false'
49
+
36
50
  settings[:enabled]
37
51
  end
38
52
 
53
+ def excluded
54
+ settings[:excluded] || []
55
+ end
56
+
39
57
  def disabled?
40
58
  !enabled?
41
59
  end
42
60
 
61
+ def save(email, password, url)
62
+ props = { auth_token: '',
63
+ url: url,
64
+ enabled: true
65
+ }
66
+
67
+ props = { Callstacking::Rails::Env.environment => {
68
+ settings: props
69
+ } }
70
+
71
+ write_settings(complete_settings.merge(props))
72
+
73
+ props[Callstacking::Rails::Env.environment][:settings][:auth_token] = token(email, password)
74
+
75
+ write_settings(complete_settings.merge(props))
76
+
77
+ read_settings
78
+ end
79
+
80
+ def enable_disable(enabled: true)
81
+ settings[:enabled] = enabled
82
+
83
+ props = { Callstacking::Rails::Env.environment => {
84
+ settings: settings
85
+ } }
86
+
87
+ write_settings(complete_settings.merge(props))
88
+ end
89
+
90
+ private
91
+
92
+ def token(email, password)
93
+ client.login(email, password)
94
+ end
95
+
43
96
  def read_settings
44
- @@settings = @settings = complete_settings.dig(::Callstacking::Rails::Env.environment, :settings) || {}
97
+ @settings = complete_settings.dig(::Callstacking::Rails::Env.environment, :settings) || {}
45
98
  rescue StandardError => e
46
99
  puts e.full_message
47
100
  puts e.backtrace.join("\n")
@@ -1,53 +1,33 @@
1
1
  require 'yaml'
2
- require "callstacking/rails/settings"
3
- require "callstacking/rails/client/authenticate"
4
- require "callstacking/rails/env"
5
2
  require 'io/console'
6
3
 
7
4
  module Callstacking
8
5
  module Rails
9
6
  class Setup
10
- include ::Callstacking::Rails::Settings
11
- extend ::Callstacking::Rails::Settings
12
-
13
- attr_accessor :client
7
+ attr_accessor :settings
14
8
 
15
9
  def initialize
16
- read_settings
17
- end
18
-
19
- def client
20
- @client = Callstacking::Rails::Client::Authenticate.new
10
+ @settings = Callstacking::Rails::Settings.new
21
11
  end
22
12
 
23
13
  def start
24
14
  puts "Login to callstacking.com"
25
15
  puts
26
16
 
27
- email = prompt("Enter email:")
17
+ email = prompt("Enter email:", echo: true)
28
18
  password = prompt("Enter password:", echo: false)
29
19
 
30
- save(email, password, url)
20
+ settings.save(email, password, url)
31
21
 
32
22
  puts "Authentication successful."
33
- puts "Settings saved to #{SETTINGS_FILE}"
23
+ puts "Settings saved to #{Callstacking::Rails::Settings::SETTINGS_FILE}"
34
24
  true
35
25
  rescue StandardError => e
36
- puts "Problem authenticating: #{e.message}"
26
+ puts "Error authenticating: #{e.message}"
37
27
  puts e.backtrace.join("\n")
38
28
  false
39
29
  end
40
30
 
41
- def enable_disable(enabled: true)
42
- settings[:enabled] = enabled
43
-
44
- props = { Callstacking::Rails::Env.environment => {
45
- settings: settings
46
- } }
47
-
48
- write_settings(complete_settings.merge(props))
49
- end
50
-
51
31
  def prompt(label, echo: true)
52
32
  puts label
53
33
 
@@ -58,31 +38,7 @@ module Callstacking
58
38
  value
59
39
  end
60
40
 
61
- def token(email, password)
62
- client.login(email, password)
63
- end
64
-
65
- def save(email, password, url)
66
- props = { auth_token: '',
67
- url: url,
68
- enabled: true
69
- }
70
-
71
- props = { Callstacking::Rails::Env.environment => {
72
- settings: props
73
- } }
74
-
75
- write_settings(complete_settings.merge(props))
76
-
77
- props[Callstacking::Rails::Env.environment][:settings][:auth_token] = token(email, password)
78
-
79
- write_settings(complete_settings.merge(props))
80
-
81
- read_settings
82
- end
83
-
84
41
  def self.instructions
85
- read_settings
86
42
  puts "loading environment #{Callstacking::Rails::Env.environment}"
87
43
  puts
88
44
  puts "Usage: "
@@ -94,7 +50,7 @@ module Callstacking
94
50
  puts " > callstacking-rails setup"
95
51
  puts
96
52
  puts " Interactively prompts you for your callstacking.com username/password."
97
- puts " Stores auth details in #{SETTINGS_FILE}"
53
+ puts " Stores auth details in #{Callstacking::Rails::Settings::SETTINGS_FILE}"
98
54
  puts
99
55
  puts " > callstacking-rails enable"
100
56
  puts
@@ -107,19 +63,23 @@ module Callstacking
107
63
  puts " You can have multiple environments."
108
64
  puts " The default is #{Callstacking::Rails::Env::DEFAULT_ENVIRONMENT}."
109
65
  puts
110
- puts " The #{Callstacking::Rails::Env.environment}: section in the #{SETTINGS_FILE} contains your credentials."
66
+ puts " The #{Callstacking::Rails::Env.environment}: section in the #{Callstacking::Rails::Settings::SETTINGS_FILE} contains your credentials."
111
67
  puts " By setting the RAILS_ENV environment you can maintain multiple settings."
112
68
  puts
113
69
  puts "Questions? Create an issue: https://github.com/callstacking/callstacking-rails/issues"
70
+
71
+ :instructions
114
72
  end
115
73
 
116
74
  private
117
75
 
118
76
  def url
119
- if Callstacking::Rails::Env.production? && ENV['CALLSTACKING_RAILS_LOCAL_TEST'].nil?
120
- PRODUCTION_URL
77
+ if (Callstacking::Rails::Env.production? || ::Rails.env.test?) &&
78
+ ENV['CALLSTACKING_RAILS_LOCAL_TEST'].nil?
79
+ Callstacking::Rails::Settings::PRODUCTION_URL
121
80
  else
122
- prompt("Enter URL for #{Callstacking::Rails::Env.environment} API calls [#{PRODUCTION_URL}]:") || PRODUCTION_URL
81
+ prompt("Enter URL for #{Callstacking::Rails::Env.environment} API calls [#{Callstacking::Rails::Settings::PRODUCTION_URL}]:") ||
82
+ Callstacking::Rails::Settings::PRODUCTION_URL
123
83
  end
124
84
  end
125
85
  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
@@ -1,57 +1,72 @@
1
1
  require "rails"
2
2
  require "active_support/concern"
3
- require "callstacking/rails/client/base"
4
- require "callstacking/rails/client/authenticate"
5
- require "callstacking/rails/client/trace"
6
- require "callstacking/rails/settings"
7
3
 
8
4
  module Callstacking
9
5
  module Rails
10
6
  class Trace
11
- include Callstacking::Rails::Settings
12
-
13
7
  attr_accessor :spans, :client, :lock
14
- cattr_accessor :current_request_id
8
+ attr_reader :settings
9
+ cattr_accessor :current_trace_id
10
+ cattr_accessor :current_tuid
11
+
12
+ ICON = '💥'
13
+ MAX_TRACE_ENTRIES = 3000
15
14
 
16
15
  def initialize(spans)
17
16
  @traces = []
18
17
  @spans = spans
18
+ @settings = Callstacking::Rails::Settings.new
19
19
 
20
20
  @lock = Mutex.new
21
- @client = Callstacking::Rails::Client::Trace.new
22
- end
21
+ @client = Callstacking::Rails::Client::Trace.new(settings.url, settings.auth_token)
23
22
 
24
- def tracing
25
- read_settings
23
+ init_uuids(nil, nil)
24
+ init_callbacks(nil)
25
+ end
26
26
 
27
+ def request_tracing
28
+ tuid = nil
27
29
  trace_id = nil
28
- max_trace_entries = nil
29
30
 
30
- ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
31
- trace_id, max_trace_entries = start_request(payload[:request]&.request_id, payload[:method], payload[:controller],
32
- payload[:action], payload[:format], ::Rails.root,
33
- payload[:request]&.original_url || payload[:path],
34
- payload[:headers], payload[:params])
35
- 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)
36
34
 
37
- @spans.on_call_entry do |nesting_level, order_num, klass, method_name, arguments, path, line_no|
38
- 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])
39
40
  end
40
41
 
41
- @spans.on_call_return do |coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val|
42
- create_call_return(coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, @traces)
43
- end
44
-
45
- ActiveSupport::Notifications.subscribe("process_action.action_controller") do |name, start, finish, id, payload|
46
- 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],
47
45
  payload[:action], payload[:format],
48
46
  payload[:request]&.original_url || payload[:path],
49
- trace_id, @traces, max_trace_entries)
47
+ @traces, MAX_TRACE_ENTRIES)
50
48
  end
51
49
  end
52
50
 
53
51
  private
54
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
+
55
70
  def completed_request_message(method, controller, action, format)
56
71
  "Completed request: #{method} #{controller}##{action} as #{format}"
57
72
  end
@@ -60,9 +75,10 @@ module Callstacking
60
75
  "Started request: #{method} #{controller}##{action} as #{format}"
61
76
  end
62
77
 
63
- 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)
64
79
  lock.synchronize do
65
- traces << { type: 'TraceCallReturn',
80
+ traces << { tuid: tuid,
81
+ type: 'TraceCallReturn',
66
82
  order_num: order_num,
67
83
  nesting_level: nesting_level,
68
84
  local_variables: {},
@@ -78,9 +94,10 @@ module Callstacking
78
94
  end
79
95
  end
80
96
 
81
- 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)
82
98
  lock.synchronize do
83
- traces << { type: 'TraceCallEntry',
99
+ traces << { tuid: tuid,
100
+ type: 'TraceCallEntry',
84
101
  order_num: order_num,
85
102
  nesting_level: nesting_level,
86
103
  args: arguments,
@@ -96,9 +113,10 @@ module Callstacking
96
113
  end
97
114
  end
98
115
 
99
- def create_message(message, order_num, traces)
116
+ def create_message(tuid, message, order_num, traces)
100
117
  lock.synchronize do
101
- traces << { type: 'TraceMessage',
118
+ traces << { tuid: tuid,
119
+ type: 'TraceMessage',
102
120
  order_num: order_num,
103
121
  nesting_level: 0,
104
122
  message: message,
@@ -118,30 +136,28 @@ module Callstacking
118
136
  lock.synchronize do
119
137
  return if traces.empty?
120
138
 
121
- client.upsert(trace_id, { trace_entries: traces })
139
+ client.upsert(trace_id,
140
+ { trace_entries: traces })
122
141
  traces.clear
123
142
  end
124
143
  end
125
- 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)
126
145
  return if do_not_track_request?(original_url, format)
127
-
128
- request_id = request_id || SecureRandom.uuid
129
- Callstacking::Rails::Trace.current_request_id = request_id
130
146
 
131
- trace_id, _interval, max_trace_entries = client.create(method, controller, action, format,
132
- path, original_url, request_id, headers,
133
- params)
147
+ client.create(trace_id, tuid,
148
+ method, controller,
149
+ action, format,
150
+ path, original_url,
151
+ headers, params)
134
152
 
135
153
  print_trace_url(trace_id)
136
154
 
137
- create_message(start_request_message(method, controller, action, format),
155
+ create_message(tuid, start_request_message(method, controller, action, format),
138
156
  spans.increment_order_num, @traces)
139
-
140
- return trace_id, max_trace_entries
141
157
  end
142
158
 
143
159
  def print_trace_url(trace_id)
144
- url = "#{settings[:url]}/traces/#{trace_id}"
160
+ url = "#{settings.url}/traces/#{trace_id}"
145
161
 
146
162
  puts "*" * url.size
147
163
  puts url
@@ -150,13 +166,13 @@ module Callstacking
150
166
  url
151
167
  end
152
168
 
153
- 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)
154
170
  if do_not_track_request?(original_url, format)
155
171
  traces.clear
156
172
  return
157
173
  end
158
174
 
159
- create_message(completed_request_message(method, controller, action, format),
175
+ create_message(tuid, completed_request_message(method, controller, action, format),
160
176
  spans.increment_order_num, traces)
161
177
 
162
178
  send_traces!(trace_id, traces[0..max_trace_entries])
@@ -7,12 +7,9 @@ module Callstacking
7
7
  include ActionView::Helpers::TagHelper
8
8
  include ActionView::Helpers::JavaScriptHelper
9
9
  include ActionView::Context
10
- include Callstacking::Rails::Settings
11
10
 
12
- def hud
13
- read_settings
14
-
15
- frame_url = "#{url || Callstacking::Rails::Settings::PRODUCTION_URL}/traces/#{Callstacking::Rails::Trace.current_request_id}/print"
11
+ def hud(url)
12
+ frame_url = "#{url || Callstacking::Rails::Settings::PRODUCTION_URL}/traces/#{Callstacking::Rails::Trace.current_trace_id}/print"
16
13
 
17
14
  body = []
18
15
  body << (content_tag( :div, data: { turbo:false },
@@ -20,7 +17,7 @@ module Callstacking
20
17
  padding: 0px; position: fixed; height: 50px; width: 40px; cursor: pointer;',
21
18
  onclick: 'document.getElementById("callstacking-debugger").style.display = "unset";
22
19
  document.getElementById("callstacking-close").style.display = "unset";') do
23
- "<span title='ctrl-d'><center>💥</center></span>".html_safe
20
+ "<span title='ctrl-d'><center>#{Callstacking::Rails::Trace::ICON}</center></span>".html_safe
24
21
  end)
25
22
 
26
23
  body << (content_tag(:iframe, src: frame_url, id: 'callstacking-debugger', data: { turbo:false },
@@ -45,11 +42,8 @@ module Callstacking
45
42
  body.join
46
43
  end
47
44
 
48
- def inject_hud
49
- read_settings
50
- return unless enabled?
51
-
52
- response.body = response.body.sub(/<\/body>/i, "#{hud}</body>")
45
+ def inject_hud(settings)
46
+ response.body = response.body.sub(/<\/body>/i, "#{hud(settings.url)}</body>")
53
47
  end
54
48
  end
55
49
  end
@@ -1,5 +1,5 @@
1
1
  module Callstacking
2
2
  module Rails
3
- VERSION = "0.1.19"
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.19
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-09 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
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-silence
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
55
83
  description: Quickly visualize which methods call which, their parameters, and return
56
84
  values.
57
85
  email:
@@ -74,6 +102,7 @@ files:
74
102
  - config/routes.rb
75
103
  - exe/callstacking-rails
76
104
  - lib/callstacking/rails.rb
105
+ - lib/callstacking/rails/cli.rb
77
106
  - lib/callstacking/rails/client/authenticate.rb
78
107
  - lib/callstacking/rails/client/base.rb
79
108
  - lib/callstacking/rails/client/trace.rb
@@ -84,6 +113,7 @@ files:
84
113
  - lib/callstacking/rails/settings.rb
85
114
  - lib/callstacking/rails/setup.rb
86
115
  - lib/callstacking/rails/spans.rb
116
+ - lib/callstacking/rails/time_based_uuid.rb
87
117
  - lib/callstacking/rails/trace.rb
88
118
  - lib/callstacking/rails/traces_helper.rb
89
119
  - lib/callstacking/rails/version.rb