4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | # Specify your gem's dependencies in callstacking-rails.gemspec.
5 | gemspec
6 |
7 | gem "sqlite3"
8 |
9 | gem "sprockets-rails"
10 |
11 | # Start debugger with binding.b [https://github.com/ruby/debug]
12 | # gem "debug", ">= 1.0.0"
13 |
--------------------------------------------------------------------------------
/test/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | mount Callstacking::Rails::Engine => "/callstacking-rails"
3 |
4 | resources :application, only: :index
5 |
6 | get '/hello', to: 'application#hello'
7 | get '/bounjor', to: 'application#bounjor'
8 | get '/hallo', to: 'application#hallo'
9 |
10 | root to: "application#index"
11 | end
12 |
--------------------------------------------------------------------------------
/test/dummy/app/models/salutation.rb:
--------------------------------------------------------------------------------
1 | class Salutation
2 | def hello(name)
3 | "hello #{name}"
4 | end
5 |
6 | def self.hello(name)
7 | "hi #{name}"
8 | end
9 |
10 | def hi(first_name, last_name:)
11 | "hi #{first_name} #{last_name}"
12 | end
13 |
14 | def self.hi(first_name:, last_name:)
15 | "hi #{first_name} #{last_name}"
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/exe/callstacking-rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "callstacking/rails"
5 |
6 | action = Callstacking::Rails::Cli.action(ARGV)
7 | settings = Callstacking::Rails::Settings.new
8 |
9 | if action.nil?
10 | Callstacking::Rails::Setup.instructions
11 | exit!(1)
12 | end
13 |
14 | cli = Callstacking::Rails::Cli.new(action, settings)
15 | cli.run
--------------------------------------------------------------------------------
/test/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= stylesheet_link_tag "application" %>
10 |
11 |
12 |
13 | <%= yield %>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | # CI-specific setup
6 | if [ -n "$GITHUB_ACTIONS" ]; then
7 | bundle config path vendor/bundle
8 | bundle config jobs 4
9 | bundle config retry 3
10 | git config --global user.name 'GitHub Actions'
11 | git config --global user.email 'github-actions@example.com'
12 | fi
13 |
14 | gem install bundler --conservative
15 | bundle check || bundle install
--------------------------------------------------------------------------------
/test/callstacking/rails/settings_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'minitest/autorun'
4 |
5 | module Callstacking
6 | module Rails
7 | class SettingsTest < Minitest::Test
8 | def setup
9 | @subject = Callstacking::Rails::Settings.new
10 | end
11 |
12 | def test_read_settings
13 | assert_equal @subject.url, Callstacking::Rails::Settings::PRODUCTION_URL
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of
4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
5 | # notations and behaviors.
6 | Rails.application.config.filter_parameters += [
7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/env.rb:
--------------------------------------------------------------------------------
1 | require "active_support/inflector"
2 |
3 | module Callstacking
4 | module Rails
5 | class Env
6 | DEFAULT_ENVIRONMENT = 'development'.freeze
7 |
8 | cattr_accessor :environment
9 |
10 | @@environment = (ENV['RAILS_ENV'] || DEFAULT_ENVIRONMENT).parameterize(separator: '_').to_sym
11 |
12 | def self.production?
13 | @@environment == DEFAULT_ENVIRONMENT.parameterize(separator: '_').to_sym
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/client/authenticate.rb:
--------------------------------------------------------------------------------
1 | require 'json'
2 |
3 | module Callstacking
4 | module Rails
5 | module Client
6 | class Error < StandardError; end
7 |
8 | class Authenticate < Base
9 | URL = "/api/v1/auth.json"
10 |
11 | def login(email, password)
12 | resp = post(URL, email: email, password: password)
13 |
14 | raise Faraday::UnauthorizedError if resp&.body.nil?
15 |
16 | body = resp&.body || {}
17 | body["token"]
18 | end
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/logger.rb:
--------------------------------------------------------------------------------
1 | module Callstacking
2 | module Rails
3 | class Logger
4 | def self.log(message)
5 | puts message
6 |
7 | if ENV['GITHUB_OUTPUT'].present?
8 | File.open(ENV['GITHUB_OUTPUT'], 'a') do |file|
9 | # Write your progress output to the file
10 | # This could be inside a loop or condition, depending on your needs
11 | file.puts "::set-output name=progress_output::#{message}"
12 | end
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = "1.0"
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in the app/assets
11 | # folder are already added.
12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
13 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails gems
3 | # installed from the root of your application.
4 |
5 | ENGINE_ROOT = File.expand_path("..", __dir__)
6 | ENGINE_PATH = File.expand_path("../lib/callstacking/rails/engine", __dir__)
7 | APP_PATH = File.expand_path("../test/dummy/config/application", __dir__)
8 |
9 | # Set up gems listed in the Gemfile.
10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
11 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
12 |
13 | require "rails/all"
14 | require "rails/engine/commands"
15 |
--------------------------------------------------------------------------------
/test/dummy/app/views/application/index.html.erb:
--------------------------------------------------------------------------------
1 | Salutation: <%= @salutation %>
2 |
3 | ::Rails.cache.read(CACHE_KEY): <%= ::Rails.cache.read(Callstacking::Rails::Settings::CACHE_KEY) %>
4 |
5 | ENV[ENV_KEY]: <%= ENV[Callstacking::Rails::Settings::ENV_KEY] %>
6 |
7 | settings.nil?: <%= @settings.settings.nil? %>
8 |
9 | Rails env: <%= Rails.env.to_s %>
10 |
11 | Callstacking env: <%= Callstacking::Rails::Env.environment %>
12 |
13 | Settings: <%= @settings.settings %>
14 |
15 | Enabled: <%= @settings.enabled? %>
16 |
17 | Instrumented klasses: <%= ::Callstacking::Rails::Engine.loader.klasses.to_a.inspect %>
--------------------------------------------------------------------------------
/test/callstacking/rails/setup_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'minitest/autorun'
4 | require 'callstacking/rails/setup'
5 |
6 | module Callstacking
7 | module Rails
8 | class SetupTest < Minitest::Test
9 | def setup
10 | @subject = Callstacking::Rails::Setup.new
11 | Callstacking::Rails::Settings.any_instance.stubs(:save).returns(true)
12 | end
13 |
14 | def test_start
15 | @subject.stubs(:prompt).returns('value')
16 |
17 | assert_equal true, @subject.start
18 | end
19 | def test_instructions
20 | assert_equal :instructions, Callstacking::Rails::Setup.instructions
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/test/callstacking/rails/exe_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'minitest/autorun'
4 |
5 | module Callstacking
6 | module Rails
7 | class ExeTest < Minitest::Test
8 | def test_register
9 | test_run('./exe/callstacking-rails register')
10 | end
11 |
12 | def test_setup
13 | # test_run('./exe/callstacking-rails setup')
14 | end
15 |
16 | def test_enable
17 | test_run('./exe/callstacking-rails enable')
18 | end
19 |
20 | def test_disable
21 | test_run('./exe/callstacking-rails disable')
22 | end
23 |
24 | private
25 | def test_run(command)
26 | refute_match /Error/, `#{command}`
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/test/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem "sqlite3"
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, "\\1en"
8 | # inflect.singular /^(ox)en/i, "\\1"
9 | # inflect.irregular "person", "people"
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym "RESTful"
16 | # end
17 |
--------------------------------------------------------------------------------
/test/integration/navigation_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class NavigationTest < ActionDispatch::IntegrationTest
4 | test "HUD is loaded" do
5 | get '/?debug=1'
6 | assert_match(/Hello/, @response.body)
7 | assert_match(/iframe/, @response.body)
8 | assert_match(/#{Callstacking::Rails::Trace::ICON}/, @response.body)
9 | assert_match(/#{Callstacking::Rails::Settings::PRODUCTION_URL}\/traces/, @response.body)
10 | end
11 |
12 | test "HUD is not loaded" do
13 | get '/'
14 | assert_match(/Hello/, @response.body)
15 | assert_no_match(/iframe/, @response.body)
16 | assert_no_match(/#{Callstacking::Rails::Trace::ICON}/, @response.body)
17 | assert_no_match(/#{Callstacking::Rails::Settings::PRODUCTION_URL}\/traces/, @response.body)
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/test/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/callstacking/rails/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/test/callstacking/rails/trace_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'minitest/autorun'
4 | require 'callstacking/rails/settings'
5 |
6 | module Callstacking
7 | module Rails
8 | class TraceTest < Minitest::Test
9 | def setup
10 | @spans = Spans.new
11 | @subject = Callstacking::Rails::Trace.new(@spans)
12 | end
13 |
14 | def test_do_not_track_request
15 | assert_equal true,
16 | @subject.send(:do_not_track_request?, 'http://localhost:3000/assets/application.css', 'text/css')
17 | assert_equal true,
18 | @subject.send(:do_not_track_request?, 'http://localhost:3000/health', '*/*')
19 | assert_equal false,
20 | @subject.send(:do_not_track_request?, 'http://localhost:3000/users', 'text/html')
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/time_based_uuid.rb:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/test/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 | require "callstacking/rails"
9 |
10 | module Dummy
11 | class Application < Rails::Application
12 | config.load_defaults Rails::VERSION::STRING.to_f
13 |
14 | # For compatibility with applications that use this config
15 | config.action_controller.include_all_helpers = false
16 |
17 | # Configuration for the application, engines, and railties goes here.
18 | #
19 | # These settings can be overridden in specific environments using the files
20 | # in config/environments, which are processed later.
21 | #
22 | # config.time_zone = "Central Time (US & Canada)"
23 | # config.eager_load_paths << Rails.root.join("extras")
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/test/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t "hello"
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t("hello") %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # "true": "foo"
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/test/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | include Callstacking::Rails::Helpers::InstrumentHelper
3 |
4 | before_action :include_settings
5 |
6 | prepend_around_action :callstacking_setup, if: -> { params[:debug] == '1' }
7 |
8 | def index
9 | @salutation = 'Hello from index'
10 | end
11 |
12 | def hello
13 | @salutation = 'Hello from hello'
14 |
15 | 10.times do
16 | English.new.hello
17 | sleep_rand
18 | end
19 |
20 | render :index
21 | end
22 |
23 | def bounjor
24 | @salutation = 'Bounjour de bounjour'
25 |
26 | 10.times do
27 | French.new.bounjor
28 | sleep_rand
29 | end
30 |
31 | render :index
32 | end
33 |
34 | def hallo
35 | @salutation = 'Hallo von hallo'
36 |
37 | 10.times do
38 | German.new.hallo
39 | sleep_rand
40 | end
41 |
42 | render :index
43 | end
44 |
45 | private
46 | def include_settings
47 | @settings = Callstacking::Rails::Settings.new
48 | @settings.url
49 | end
50 |
51 | def sleep_rand
52 | sleep rand(0.0..2.0)
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/test/dummy/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path("..", __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts "== Installing dependencies =="
17 | system! "gem install bundler --conservative"
18 | system("bundle check") || system!("bundle install")
19 |
20 | # puts "\n== Copying sample files =="
21 | # unless File.exist?("config/database.yml")
22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml"
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! "bin/rails db:prepare"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/test/dummy/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy.
4 | # See the Securing Rails Applications Guide for more information:
5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header
6 |
7 | # Rails.application.configure do
8 | # config.content_security_policy do |policy|
9 | # policy.default_src :self, :https
10 | # policy.font_src :self, :https, :data
11 | # policy.img_src :self, :https, :data
12 | # policy.object_src :none
13 | # policy.script_src :self, :https
14 | # policy.style_src :self, :https
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 | #
19 | # # Generate session nonces for permitted importmap and inline scripts
20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
21 | # config.content_security_policy_nonce_directives = %w(script-src)
22 | #
23 | # # Report violations without enforcing the policy.
24 | # # config.content_security_policy_report_only = true
25 | # end
26 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/helpers/instrument_helper.rb:
--------------------------------------------------------------------------------
1 | module Callstacking
2 | module Rails
3 | module Helpers
4 | module InstrumentHelper
5 | extend ActiveSupport::Concern
6 | def callstacking_setup
7 | exception = nil
8 | @last_callstacking_sample = Time.now.utc
9 | Callstacking::Rails::Engine.start_tracing(self)
10 |
11 | yield
12 | rescue Exception => e
13 | @last_callstacking_exception = Time.now.utc
14 | exception = e
15 | raise e
16 | ensure
17 | Callstacking::Rails::Engine.stop_tracing(self, exception)
18 | end
19 | end
20 |
21 | def callstcking_sample_trace?
22 | if @last_callstacking_exception.present? && @last_callstacking_exception < 1.minute.ago
23 | @last_callstacking_exception = nil
24 | return true
25 | end
26 |
27 | false
28 | end
29 |
30 | def callstacking_followup_exception_trace?
31 | if @last_callstacking_sample.present? && @last_callstacking_sample < 1.hour.ago
32 | @last_callstacking_exception = nil
33 | return true
34 | end
35 |
36 | false
37 | end
38 | end
39 | end
40 | end
--------------------------------------------------------------------------------
/test/callstacking/rails/cli_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'minitest/autorun'
4 |
5 | module Callstacking
6 | module Rails
7 | class CliTest < Minitest::Test
8 |
9 | def setup
10 | @settings = Callstacking::Rails::Settings.new
11 | Callstacking::Rails::Setup.any_instance.stubs(:start).returns(true)
12 | end
13 |
14 | def test_register
15 | @subject = Callstacking::Rails::Cli.new(Callstacking::Rails::Cli::REGISTER, @settings)
16 | assert_equal @subject.run, Callstacking::Rails::Cli::REGISTER
17 | end
18 |
19 | def test_setup
20 | @subject = Callstacking::Rails::Cli.new(Callstacking::Rails::Cli::SETUP, @settings)
21 | assert_equal @subject.run, Callstacking::Rails::Cli::SETUP
22 | end
23 |
24 | def test_enable
25 | @subject = Callstacking::Rails::Cli.new(Callstacking::Rails::Cli::ENABLE, @settings)
26 | assert_equal @subject.run, Callstacking::Rails::Cli::ENABLE
27 | end
28 |
29 | def test_disable
30 | @subject = Callstacking::Rails::Cli.new(Callstacking::Rails::Cli::DISABLE, @settings)
31 | assert_equal @subject.run, Callstacking::Rails::Cli::DISABLE
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/test/dummy/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket-<%= Rails.env %>
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket-<%= Rails.env %>
23 |
24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name-<%= Rails.env %>
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/cli.rb:
--------------------------------------------------------------------------------
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
--------------------------------------------------------------------------------
/callstacking-rails.gemspec:
--------------------------------------------------------------------------------
1 | require_relative "lib/callstacking/rails/version"
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "callstacking-rails"
5 | spec.version = Callstacking::Rails::VERSION
6 | spec.authors = ["Jim Jones"]
7 | spec.email = ["jim.jones1@gmail.com"]
8 | spec.homepage = "https://github.com/callstacking/callstacking-rails"
9 | spec.summary = "Quickly visualize which methods call which, their parameters, and return values."
10 | spec.description = "Quickly visualize which methods call which, their parameters, and return values."
11 | spec.license = "GPL-3.0-or-later"
12 | spec.bindir = "exe"
13 |
14 | spec.metadata["homepage_uri"] = spec.homepage
15 | spec.metadata["source_code_uri"] = "https://github.com/callstacking/callstacking-rails"
16 |
17 | spec.files = Dir.chdir(File.expand_path(__dir__)) do
18 | Dir["{app,config,db,lib,exe}/**/*", "LICENSE", "Rakefile", "README.md"]
19 | end
20 |
21 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22 |
23 | spec.add_dependency "rails", ">= 4"
24 | spec.add_dependency "faraday", '>= 1.10.3'
25 | spec.add_dependency 'faraday-follow_redirects'
26 | spec.add_dependency 'method_source'
27 | spec.add_development_dependency 'mocha'
28 | spec.add_development_dependency 'minitest-silence'
29 | end
30 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/client/trace.rb:
--------------------------------------------------------------------------------
1 | require "callstacking/rails/client/base"
2 |
3 | module Callstacking
4 | module Rails
5 | module Client
6 | class Trace < Base
7 | CREATE_URL = "/api/v1/traces.json"
8 | UPDATE_URL = "/api/v1/traces/:id.json"
9 | SHOW_URL = "/api/v1/traces/:id.json"
10 |
11 | def initialize(url, auth_token)
12 | super
13 |
14 | # All requests for trace and trace entry creation are async
15 | # join by the client side generated tuid
16 | @async = true
17 | end
18 |
19 | def create(request_id, tuid, method_name, klass, action_name, format_name, root_path, url, headers, params)
20 | post(CREATE_URL,
21 | {},
22 | {
23 | request_id: request_id,
24 | tuid: tuid,
25 | method_name: method_name,
26 | klass: klass,
27 | action_name: action_name,
28 | format_name: format_name,
29 | root_path: root_path,
30 | url: url,
31 | h: headers.to_h,
32 | p: params.to_h,
33 | })
34 |
35 | nil
36 | end
37 |
38 | def upsert(trace_id, traces)
39 | patch(UPDATE_URL.gsub(':id', trace_id), {}, traces)
40 | end
41 |
42 | def show(trace_id, params = {})
43 | get(SHOW_URL.gsub(':id', trace_id), params)
44 | end
45 | end
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # https://github.com/scenic-views/scenic/blob/main/.github/workflows/ci.yml
2 |
3 | name: CI
4 |
5 | on:
6 | push:
7 | branches: main
8 | pull_request:
9 | branches: "*"
10 |
11 | jobs:
12 | build:
13 | name: Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}
14 |
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | ruby: ["2.7", "3.0", "3.1", "3.2"]
19 | rails: ["6.1", "7.0"]
20 | continue-on-error: [false]
21 |
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@v3
27 |
28 | - name: Install Ruby ${{ matrix.ruby }}
29 | uses: ruby/setup-ruby@v1
30 | with:
31 | ruby-version: ${{ matrix.ruby }}
32 |
33 | - name: Generate lockfile
34 | run: bundle lock
35 |
36 | - name: Cache dependencies
37 | uses: actions/cache@v3
38 | with:
39 | path: vendor/bundle
40 | key: bundle-${{ hashFiles('Gemfile.lock') }}
41 |
42 | - name: Set up Call Stacking client
43 | run: bin/setup
44 |
45 | - name: Run tests
46 | run: bundle exec rake app:test:all
47 | continue-on-error: ${{ matrix.continue-on-error }}
48 | env:
49 | GITHUB_OUTPUT: ${{ github.action_path }}/output.txt
50 | CALLSTACKING_API_TOKEN: ${{ secrets.CALLSTACKING_API_TOKEN }}
51 |
52 | - name: Display progress
53 | run: echo "Application logging ${{ steps.run_script.outputs.progress_output }}"
54 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # Configure Rails Environment
2 | require 'callstacking/rails/settings'
3 | require 'callstacking/rails/env'
4 | require 'callstacking/rails/client/base'
5 | require 'callstacking/rails/client/authenticate'
6 | require "callstacking/rails/logger"
7 |
8 | ENV["RAILS_ENV"] = "test"
9 |
10 | # https://github.com/Shopify/minitest-silence
11 | ENV["CI"] = "true"
12 |
13 | Callstacking::Rails::Settings.new.save('test@test.com',
14 | 'testing123',
15 | Callstacking::Rails::Settings::PRODUCTION_URL)
16 |
17 | # ENV[Callstacking::Rails::Settings::ENV_KEY] = 'false'
18 |
19 |
20 | require_relative "../test/dummy/config/environment"
21 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
22 | ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__)
23 | require "rails/test_help"
24 |
25 | # Load fixtures from the engine
26 | if ActiveSupport::TestCase.respond_to?(:fixture_path=)
27 | ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
28 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
29 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
30 | ActiveSupport::TestCase.fixtures :all
31 | end
32 |
33 | def module_and_method_exist?(module_name, method_name)
34 | Object.const_defined?(module_name.to_sym) &&
35 | module_name.constantize.method_defined?(method_name.to_sym)
36 | end
37 |
38 | require 'mocha/minitest'
--------------------------------------------------------------------------------
/lib/callstacking/rails/loader.rb:
--------------------------------------------------------------------------------
1 | require "rails"
2 | require "callstacking/rails/logger"
3 |
4 | module Callstacking
5 | module Rails
6 | class Loader
7 | attr_accessor :instrumenter, :klasses, :excluded, :settings
8 | def initialize(instrumenter, excluded: [])
9 | @excluded = excluded
10 | @instrumenter = instrumenter
11 | @klasses = Set.new
12 | @settings = Callstacking::Rails::Settings.new
13 |
14 | preloaded_klasses
15 | end
16 |
17 | def preloaded_klasses
18 | ObjectSpace.each_object(Module){|ob| filter_klass(ob, (Object.const_source_location(ob.to_s)&.first rescue nil))}
19 | end
20 |
21 | def on_load
22 | trace = TracePoint.new(:end) do |tp|
23 | klass = tp.self
24 | path = tp.path
25 |
26 | filter_klass(klass, path)
27 | end
28 |
29 | trace.enable
30 | end
31 |
32 | def reset!
33 | instrumenter.instrument_method(ActionView::PartialRenderer, :render, application_level: false)
34 | instrumenter.instrument_method(ActionView::TemplateRenderer, :render, application_level: false)
35 | end
36 |
37 | private
38 | def filter_klass(klass, path)
39 | return if klass.nil? || path.nil?
40 | return if path == false
41 |
42 | excluded_klass = excluded.any? { |ex| path =~ /#{ex}/ }
43 |
44 | if path =~ /#{::Rails.root.to_s}/ &&
45 | !klasses.include?(klass) &&
46 | !excluded_klass
47 | instrumenter.instrument_klass(klass) if settings.enabled?
48 | klasses << klass
49 | end
50 | end
51 | end
52 | end
53 | end
--------------------------------------------------------------------------------
/lib/callstacking/rails/spans.rb:
--------------------------------------------------------------------------------
1 | module Callstacking
2 | module Rails
3 | class Spans
4 | attr_accessor :order_num, :nesting_level, :previous_entry
5 | attr_accessor :call_entry_callback, :call_return_callback
6 |
7 | def initialize
8 | reset
9 | end
10 |
11 | def increment_order_num
12 | @order_num+=1
13 | @order_num
14 | end
15 |
16 | def increment_nesting_level
17 | @nesting_level+=1
18 | @nesting_level
19 | end
20 |
21 | def call_entry(klass, method_name, arguments, path, line_no, method_source)
22 | @nesting_level+=1
23 | @previous_entry = previous_event(klass, method_name)
24 | @call_entry_callback.call(@nesting_level, increment_order_num, klass, method_name, arguments, path, line_no, method_source)
25 | end
26 |
27 | def call_return(klass, method_name, path, line_no, return_val, method_source)
28 | @call_return_callback.call(coupled_callee(klass, method_name), @nesting_level,
29 | increment_order_num, klass, method_name, path, line_no, return_val, method_source)
30 | @nesting_level-=1
31 | end
32 |
33 | def on_call_entry(&block)
34 | @call_entry_callback = block
35 | end
36 |
37 | def on_call_return(&block)
38 | @call_return_callback = block
39 | end
40 |
41 | def reset
42 | @nesting_level = -1
43 | @order_num = -1
44 | @previous_entry = nil
45 | end
46 |
47 | private
48 | def previous_event(klass, method_name)
49 | "#{klass}:#{method_name}"
50 | end
51 |
52 | def coupled_callee(klass, method_name)
53 | previous_entry == previous_event(klass, method_name)
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/test/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
12 | # terminating a worker in development environments.
13 | #
14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
15 |
16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
17 | #
18 | port ENV.fetch("PORT") { 3000 }
19 |
20 | # Specifies the `environment` that Puma will run in.
21 | #
22 | environment ENV.fetch("RAILS_ENV") { "development" }
23 |
24 | # Specifies the `pidfile` that Puma will use.
25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
26 |
27 | # Specifies the number of `workers` to boot in clustered mode.
28 | # Workers are forked web server processes. If using threads and workers together
29 | # the concurrency of the application would be max `threads` * `workers`.
30 | # Workers do not work on JRuby or Windows (both of which do not support
31 | # processes).
32 | #
33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
34 |
35 | # Use the `preload_app!` method when specifying a `workers` number.
36 | # This directive tells Puma to first boot the application and load code
37 | # before forking the application. This takes advantage of Copy On Write
38 | # process behavior so workers use less memory.
39 | #
40 | # preload_app!
41 |
42 | # Allow puma to be restarted by `bin/rails restart` command.
43 | plugin :tmp_restart
44 |
--------------------------------------------------------------------------------
/test/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/integration/thread_safety_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | # CALLSTACKING_API_TOKEN required for these integration tests. Full end-to-end.
4 | # https://github.com/callstacking/callstacking-rails/settings/secrets/actions
5 | class ThreadSafetyTest < ActionDispatch::IntegrationTest
6 | TEST_URL = "http://www.example.com"
7 | MAX_RETRIES = 50
8 |
9 | # Test initiates multiple http requests and makes multiple method calls in parallel for each of the requests.
10 | # The results are validated against the Call Stacking server, ensuring that none of
11 | # the trace values are intermixed.
12 | test "concurrent tracing" do
13 | ::Callstacking::Rails::Trace.trace_log_clear
14 |
15 | urls = {'/hello?debug=1' => 'English',
16 | '/bounjor?debug=1' => 'French',
17 | '/hallo?debug=1' => 'German'}
18 |
19 | settings = Callstacking::Rails::Settings.new
20 | client = Callstacking::Rails::Client::Trace.new(settings.url, settings.auth_token)
21 | client.async = false # Since we'll already be running in a thread
22 |
23 | threads = urls.keys.collect do |url|
24 | Thread.new do
25 | get url
26 | end
27 | end
28 | threads.each(&:join)
29 |
30 | ::Callstacking::Rails::Trace.trace_log.each do |trace_id, url|
31 | params = url.gsub(TEST_URL, '')
32 |
33 | # Retry until the trace is available.
34 | retry_count = 0
35 | json = {'trace_entries' => []}
36 | while json['trace_entries'].empty? && retry_count <= MAX_RETRIES
37 | response = client.show(trace_id)
38 | json = response.body
39 |
40 | sleep 1
41 | retry_count+=1
42 | end
43 |
44 | ::Callstacking::Rails::Logger.log "url: #{url} -- json: #{json.inspect}"
45 |
46 | sleep 10
47 |
48 | entry_classes = json['trace_entries'].find_all do |trace_entry|
49 | urls.values.include?(trace_entry['klass'])
50 | end
51 |
52 | entry_classes.each do |trace_entry|
53 | assert_equal urls[params], trace_entry['klass']
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/client/base.rb:
--------------------------------------------------------------------------------
1 | require 'faraday'
2 | require 'faraday/follow_redirects'
3 |
4 | module Callstacking
5 | module Rails
6 | module Client
7 | class Error < StandardError; end
8 |
9 | class Base
10 | attr_accessor :async, :threads
11 | attr_reader :url, :auth_token
12 |
13 | def initialize(url, auth_token)
14 | @url = url
15 | @auth_token = auth_token
16 |
17 | @threads = []
18 | @async = false
19 | end
20 | def connection
21 | # https://github.com/lostisland/awesome-faraday
22 | @connection ||= Faraday.new(url) do |c|
23 | c.response :json
24 | c.response :follow_redirects
25 | c.use Faraday::Response::RaiseError # raise exceptions on 40x, 50x responses
26 | c.request :json # This will set the "Content-Type" header to application/json and call .to_json on the body
27 | c.adapter Faraday.default_adapter
28 | c.options.timeout = 120
29 |
30 | if auth_token.present?
31 | c.request :authorization, :Bearer, auth_token
32 | end
33 | end
34 | end
35 |
36 | def get(url, params = {}, headers = {})
37 | if async
38 | threads << Thread.new do
39 | connection.get(url, params, headers)
40 | end
41 | else
42 | connection.get(url, params, headers)
43 | end
44 | ensure
45 | Faraday.default_connection.close if async
46 | end
47 |
48 | def post(url, params = {}, body = {}, headers_ext = {})
49 | r(:post, url, params, body, headers_ext)
50 | end
51 |
52 | def patch(url, params = {}, body = {}, headers_ext = {})
53 | r(:patch, url, params, body, headers_ext)
54 | end
55 |
56 | def r(action, url, params = {}, body = {}, _headers_ext = {})
57 | if async
58 | threads << Thread.new do
59 | connection.send(action, url) do |req|
60 | req.params.merge!(params)
61 | req.body = body
62 | end
63 | end
64 | else
65 | connection.send(action, url) do |req|
66 | req.params.merge!(params)
67 | req.body = body
68 | end
69 | end
70 | ensure
71 | Faraday.default_connection.close if async
72 | end
73 | end
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | # Turn false under Spring and add config.action_view.cache_template_loading = true.
12 | config.cache_classes = true
13 |
14 | # Eager loading loads your whole application. When running a single test locally,
15 | # this probably isn't necessary. It's a good idea to do in a continuous integration
16 | # system, or in some way before deploying your code.
17 | config.eager_load = ENV["CI"].present?
18 |
19 | # Configure public file server for tests with Cache-Control for performance.
20 | config.public_file_server.enabled = true
21 | config.public_file_server.headers = {
22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}"
23 | }
24 |
25 | # Show full error reports and disable caching.
26 | config.consider_all_requests_local = true
27 | config.action_controller.perform_caching = false
28 | config.cache_store = :null_store
29 |
30 | # Raise exceptions instead of rendering exception templates.
31 | config.action_dispatch.show_exceptions = false
32 |
33 | # Disable request forgery protection in test environment.
34 | config.action_controller.allow_forgery_protection = false
35 |
36 | # Store uploaded files on the local file system in a temporary directory.
37 | config.active_storage.service = :test
38 |
39 | config.action_mailer.perform_caching = false
40 |
41 | # Tell Action Mailer not to deliver emails to the real world.
42 | # The :test delivery method accumulates sent emails in the
43 | # ActionMailer::Base.deliveries array.
44 | config.action_mailer.delivery_method = :test
45 |
46 | # Print deprecation notices to the stderr.
47 | config.active_support.deprecation = :stderr
48 |
49 | # Raise exceptions for disallowed deprecations.
50 | config.active_support.disallowed_deprecation = :raise
51 |
52 | # Tell Active Support which deprecation messages to disallow.
53 | config.active_support.disallowed_deprecation_warnings = []
54 |
55 | # Raises error for missing translations.
56 | # config.i18n.raise_on_missing_translations = true
57 |
58 | # Annotate rendered view with file names.
59 | # config.action_view.annotate_rendered_view_with_filenames = true
60 | end
61 |
--------------------------------------------------------------------------------
/test/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable server timing
18 | config.server_timing = true
19 |
20 | # Enable/disable caching. By default caching is disabled.
21 | # Run rails dev:cache to toggle caching.
22 | if ::Rails.root.join("tmp/caching-dev.txt").exist?
23 | config.action_controller.perform_caching = true
24 | config.action_controller.enable_fragment_cache_logging = true
25 |
26 | config.cache_store = :memory_store
27 | config.public_file_server.headers = {
28 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
29 | }
30 | else
31 | config.action_controller.perform_caching = false
32 |
33 | config.cache_store = :null_store
34 | end
35 |
36 | # Store uploaded files on the local file system (see config/storage.yml for options).
37 | config.active_storage.service = :local
38 |
39 | # Don't care if the mailer can't send.
40 | config.action_mailer.raise_delivery_errors = false
41 |
42 | config.action_mailer.perform_caching = false
43 |
44 | # Print deprecation notices to the Rails logger.
45 | config.active_support.deprecation = :log
46 |
47 | # Raise exceptions for disallowed deprecations.
48 | config.active_support.disallowed_deprecation = :raise
49 |
50 | # Tell Active Support which deprecation messages to disallow.
51 | config.active_support.disallowed_deprecation_warnings = []
52 |
53 | # Raise an error on page load if there are pending migrations.
54 | config.active_record.migration_error = :page_load
55 |
56 | # Highlight code that triggered database queries in logs.
57 | config.active_record.verbose_query_logs = true
58 |
59 | # Suppress logger output for asset requests.
60 | config.assets.quiet = true
61 |
62 | # Raises error for missing translations.
63 | # config.i18n.raise_on_missing_translations = true
64 |
65 | # Annotate rendered view with file names.
66 | # config.action_view.annotate_rendered_view_with_filenames = true
67 |
68 | # Uncomment if you wish to allow Action Cable access from any origin.
69 | # config.action_cable.disable_request_forgery_protection = true
70 | end
71 |
--------------------------------------------------------------------------------
/lib/callstacking/rails/helpers/heads_up_display_helper.rb:
--------------------------------------------------------------------------------
1 | require 'action_view/helpers'
2 | require "action_view/context.rb"
3 |
4 | module Callstacking
5 | module Rails
6 | module Helpers
7 | module HeadsUpDisplayHelper
8 | include ActionView::Helpers::TagHelper
9 | include ActionView::Helpers::JavaScriptHelper
10 | include ActionView::Context
11 |
12 | def hud(url)
13 | frame_url = "#{url || Callstacking::Rails::Settings::PRODUCTION_URL}/traces/#{Callstacking::Rails::Trace.current_trace_id}/print"
14 |
15 | body = []
16 | body << (content_tag(:div, data: { turbo: false },
17 | style: 'top: 50%; right: 10px; font-size: 24pt; :hover{text-shadow: 1px 1px 2px #000000};
18 | padding: 0px; position: fixed; height: 50px; width: 40px; cursor: pointer;',
19 | onclick: 'document.getElementById("callstacking-debugger").style.display = "unset";
20 | document.getElementById("callstacking-close").style.display = "unset";') do
21 | "