├── .github └── workflows │ └── test.yml ├── .rspec ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── changelog.md ├── lib ├── rails_tracepoint_stack.rb └── rails_tracepoint_stack │ ├── configuration.rb │ ├── filter │ ├── custom_trace_selector_filter.rb │ ├── gem_path.rb │ ├── rb_config.rb │ ├── trace_from_dependencies_filter.rb │ ├── trace_from_ruby_code_filter.rb │ └── trace_to_ignore_filter.rb │ ├── log_formatter.rb │ ├── logger.rb │ ├── trace.rb │ ├── trace_filter.rb │ ├── tracer.rb │ └── version.rb ├── rails_tracepoint_stack.gemspec └── spec ├── rails_tracepoint_stack ├── filter │ ├── custom_trace_selector_filter_spec.rb │ ├── trace_from_dependencies_filter_spec.rb │ ├── trace_from_ruby_code_filter_spec.rb │ └── trace_to_ignore_filter_spec.rb ├── log_formatter_spec.rb ├── logger_spec.rb ├── trace_filter_spec.rb ├── trace_spec.rb └── tracer_spec.rb ├── rails_tracepoint_stack_spec.rb ├── shared └── tracer_examples.rb └── spec_helper.rb /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | rspec: 13 | name: RSpec / Ruby ${{ matrix.ruby-version }} 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | ruby-version: [3.0, 3.1, 3.2, 3.3, 3.4] 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Set up Ruby 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: ${{ matrix.ruby-version }} 25 | 26 | - name: Install dependencies 27 | run: bundle install 28 | 29 | - name: Run tests 30 | run: bundle exec rspec 31 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | rails_tracepoint_stack (0.3.4) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | ast (2.4.2) 10 | diff-lcs (1.5.1) 11 | json (2.7.2) 12 | language_server-protocol (3.17.0.3) 13 | lint_roller (1.1.0) 14 | parallel (1.25.1) 15 | parser (3.3.4.0) 16 | ast (~> 2.4.1) 17 | racc 18 | racc (1.8.0) 19 | rainbow (3.1.1) 20 | rake (13.2.1) 21 | regexp_parser (2.9.2) 22 | rexml (3.3.2) 23 | strscan 24 | rspec (3.13.0) 25 | rspec-core (~> 3.13.0) 26 | rspec-expectations (~> 3.13.0) 27 | rspec-mocks (~> 3.13.0) 28 | rspec-core (3.13.0) 29 | rspec-support (~> 3.13.0) 30 | rspec-expectations (3.13.1) 31 | diff-lcs (>= 1.2.0, < 2.0) 32 | rspec-support (~> 3.13.0) 33 | rspec-mocks (3.13.1) 34 | diff-lcs (>= 1.2.0, < 2.0) 35 | rspec-support (~> 3.13.0) 36 | rspec-support (3.13.1) 37 | rubocop (1.64.1) 38 | json (~> 2.3) 39 | language_server-protocol (>= 3.17.0) 40 | parallel (~> 1.10) 41 | parser (>= 3.3.0.2) 42 | rainbow (>= 2.2.2, < 4.0) 43 | regexp_parser (>= 1.8, < 3.0) 44 | rexml (>= 3.2.5, < 4.0) 45 | rubocop-ast (>= 1.31.1, < 2.0) 46 | ruby-progressbar (~> 1.7) 47 | unicode-display_width (>= 2.4.0, < 3.0) 48 | rubocop-ast (1.31.3) 49 | parser (>= 3.3.1.0) 50 | rubocop-performance (1.21.1) 51 | rubocop (>= 1.48.1, < 2.0) 52 | rubocop-ast (>= 1.31.1, < 2.0) 53 | ruby-progressbar (1.13.0) 54 | standard (1.39.2) 55 | language_server-protocol (~> 3.17.0.2) 56 | lint_roller (~> 1.0) 57 | rubocop (~> 1.64.0) 58 | standard-custom (~> 1.0.0) 59 | standard-performance (~> 1.4) 60 | standard-custom (1.0.2) 61 | lint_roller (~> 1.0) 62 | rubocop (~> 1.50) 63 | standard-performance (1.4.0) 64 | lint_roller (~> 1.1) 65 | rubocop-performance (~> 1.21.0) 66 | strscan (3.1.0) 67 | unicode-display_width (2.5.0) 68 | 69 | PLATFORMS 70 | arm64-darwin-23 71 | x86_64-linux 72 | 73 | DEPENDENCIES 74 | rails_tracepoint_stack! 75 | rake (~> 13.0, >= 13.0.0) 76 | rspec (~> 3.0, >= 3.0.0) 77 | standard (~> 1.39, >= 1.39.1) 78 | 79 | BUNDLED WITH 80 | 2.5.16 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | This project aims to create a logger for method calls in Ruby , excluding methods from gems, internal classes, etc. 4 | 5 | By utilizing Ruby's `TracePoint` functionality, it allows monitoring and displaying the methods called during the application's execution, filtering to show only the methods defined in the application's own code. 6 | 7 | ## Install 8 | 9 | ```bash 10 | gem install rails_tracepoint_stack 11 | ``` 12 | 13 | set env 14 | ```bash 15 | RAILS_TRACEPOINT_STACK_ENABLED=true 16 | ``` 17 | if you wanna enable it globally on your Rails app 18 | 19 | ## Usage 20 | 21 | Global use: 22 | 23 | By using the global tracing, just configuring `RAILS_TRACEPOINT_STACK_ENABLED` as true, you can have a sample scenario and output as: 24 | 25 | ```ruby 26 | module Foo 27 | def puts_message(message) 28 | puts message 29 | end 30 | end 31 | 32 | class Bar 33 | include Foo 34 | 35 | def initialize(message:) 36 | @message = message 37 | end 38 | 39 | def perform 40 | puts_message(@message) 41 | end 42 | end 43 | ``` 44 | 45 | then 46 | 47 | ```ruby 48 | Bar.new(message: "Hello World").call 49 | ``` 50 | 51 | will produce an output (by default saved on `log/rails_tracepoint_stack.log`) as: 52 | 53 | ```json 54 | called: Bar#initialize in COMPLETE_PATH_TO_YOUR_FILE/app/services/bar.rb:METHOD_LINE with params: {:message=>"Hello World"} 55 | called: Bar#perform in COMPLETE_PATH_TO_YOUR_FILE/app/services/bar.rb:METHOD_LINE with params: {} 56 | called: Foo#puts_message in COMPLETE_PATH_TO_YOUR_FILE/app/services/foo:METHOD_LINE with params: {:message=>"Hello World"} 57 | ``` 58 | **Enabling Locally with .enable_trace** 59 | 60 | You can also enable the tracer locally (even with `RAILS_TRACEPOINT_STACK_ENABLED` not defined) by passing a block of code to be traced: 61 | 62 | ```ruby 63 | class Bar 64 | include Foo 65 | 66 | def initialize(message:) 67 | @message = message 68 | end 69 | 70 | def perform 71 | RailsTracepointStack.enable_trace do 72 | puts_message(@message) 73 | end 74 | end 75 | end 76 | ``` 77 | wich will produce a similar result 78 | 79 | ## Configuration 80 | 81 | You can also implement custom configuration for `RailsTracepointStack` by passing your custom configurations as follows: 82 | 83 | | Configuration | Description | 84 | |--------------------------------|-----------------------------------------------------------------------------------------------| 85 | | `file_path_to_filter_patterns` | Include configuration allowing filter traces only when the origin file path matches a pattern, Example: `/services\/foo.rb/` | 86 | | `ignore_patterns` | Pass a regex pattern to filter (ignore) matched traces. Example: `/services\/foo.rb/` | 87 | | `log_format` | Inform what kind of format you wanna to use, text (default), or json | 88 | | `log_external_sources` | Log the external sources to the file, like libraries, gems and bundler | 89 | | `logger` | Pass your custom logger. Example: `Rails.logger`. If not used, logs are saved in `log/rails_tracepoint_stack.log`. | 90 | 91 | Complete example: 92 | 93 | ```ruby 94 | # config/initializers/rails_tracepoint_stack.rb 95 | 96 | RailsTracepointStack.configure do |config| 97 | config.file_path_to_filter_patterns << /services\/foo.rb/ 98 | config.ignore_patterns << /services\/foo.rb/ 99 | config.log_format = :text 100 | config.log_external_sources = false 101 | config.logger = YourLogger 102 | end 103 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.4 4 | 5 | **Changes** 6 | 7 | - Fix flaky tests by @danielmbrasil [PR #27](https://github.com/carlosdanielpohlod/rails_tracepoint_stack/pull/27) 8 | 9 | - Huge refactor of filters class, separating in modules by filter type [PR #18](https://github.com/carlosdanielpohlod/rails_tracepoint_stack/pull/18) 10 | 11 | - Some other code refactors 12 | 13 | ## 0.3.3 14 | 15 | **Changes** 16 | 17 | - Add autoload of all lib files on test_helper 18 | - Fix some tests 19 | 20 | **BugFix** 21 | 22 | - Fixed the Configuration module not loading the default value for the configuration attributes 23 | 24 | ## 0.3.1 25 | 26 | **Changes:** 27 | 28 | - Add the ability to include the external sources to the log using `config.log_external_sources = true` 29 | 30 | ## 0.3.0 31 | 32 | **Changes:** 33 | 34 | - Refactor classes, formatting a trace using a value object class `RailsTracepointStack::Trace` 35 | - include configuration `log_format` option, allowing choose an output as `text` or `json` 36 | - Include configuration `file_path_to_filter_patterns`, allowing filter traces only when the origin file path matches a pattern 37 | - Improve test coverage 38 | 39 | ## 0.2.1 40 | 41 | **Changes:** 42 | 43 | - Update the ENV enable to be more semantic 44 | - Add the VERSION constant module 45 | - Sorted the files inside of gemspec 46 | - Fix the depencies on the gemspec 47 | - Add the "log_format" configuration support for text and json formats 48 | 49 | ## 0.2.0 50 | 51 | **Changes:** 52 | 53 | - Refactor by separating Logger and Filter into their own classes. 54 | 55 | - Introduce `RailsTracepointStack.configure`, which allows ignoring traces with a custom pattern and customizing the logs output. Example: 56 | 57 | ```ruby 58 | RailsTracepointStack.configure do |config| 59 | config.ignore_patterns << /services\/foo.rb/ 60 | config.logger = YourLogger 61 | end 62 | ``` 63 | 64 | The default log destination is a file located on `log/rails_tracepoint_stack.log` 65 | 66 | - Add The possibility of enable the tracer locally, by calling: 67 | 68 | ```ruby 69 | class Foo 70 | def bar 71 | RailsTracepointStack.enable_trace do 72 | p "your code" 73 | end 74 | end 75 | end 76 | ``` 77 | 78 | - Add Rspec and Rake development dependencies, and add partial test coverage. 79 | 80 | ## 0.1.4 81 | 82 | **Changes:** 83 | 84 | - Ignore logs containing `gems/bundler`. 85 | - Require ruby >= 3.0. 86 | 87 | **Breaking Changes:** 88 | 89 | - To enable logs catch, it is necessary to set `RAILS_TRACEPOINT_STACK` as `true`. 90 | -------------------------------------------------------------------------------- /lib/rails_tracepoint_stack.rb: -------------------------------------------------------------------------------- 1 | require 'rails_tracepoint_stack/configuration' 2 | require 'rails_tracepoint_stack/log_formatter' 3 | require 'rails_tracepoint_stack/tracer' 4 | 5 | $rails_tracer_rtps = nil 6 | 7 | module RailsTracepointStack 8 | class << self 9 | attr_writer :configuration, :logger 10 | 11 | def configuration 12 | @configuration ||= RailsTracepointStack::Configuration.new 13 | end 14 | end 15 | 16 | def self.configure 17 | yield(configuration) 18 | end 19 | 20 | def self.enable_trace 21 | raise ArgumentError, "Block not given to #enable_trace" unless block_given? 22 | 23 | tracer = RailsTracepointStack::Tracer.new 24 | tracer.enable 25 | yield 26 | ensure 27 | tracer.disable 28 | end 29 | end 30 | 31 | if ENV.fetch("RAILS_TRACEPOINT_STACK_ENABLED", "false") == "true" 32 | $rails_tracer_rtps = RailsTracepointStack::Tracer.new 33 | $rails_tracer_rtps.enable 34 | 35 | at_exit do 36 | $rails_tracer_rtps.disable 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/rails_tracepoint_stack/configuration.rb: -------------------------------------------------------------------------------- 1 | module RailsTracepointStack 2 | class Configuration 3 | attr_accessor :file_path_to_filter_patterns, 4 | :ignore_patterns, 5 | :log_format, 6 | :log_external_sources, 7 | :logger 8 | 9 | def initialize 10 | @file_path_to_filter_patterns = [] 11 | @ignore_patterns = [] 12 | @log_format = :text 13 | @log_external_sources = false 14 | @logger = nil 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rails_tracepoint_stack/filter/custom_trace_selector_filter.rb: -------------------------------------------------------------------------------- 1 | module RailsTracepointStack 2 | module Filter 3 | module CustomTraceSelectorFilter 4 | def is_a_trace_required_to_watch_by_the_custom_configs?(trace:) 5 | return false unless RailsTracepointStack.configuration.file_path_to_filter_patterns.any? 6 | 7 | filter_match_a_custom_pattern_to_be_not_ignored?(trace) 8 | end 9 | 10 | private 11 | 12 | def filter_match_a_custom_pattern_to_be_not_ignored?(trace) 13 | RailsTracepointStack.configuration.file_path_to_filter_patterns.any? do |pattern| 14 | trace.file_path.match?(pattern) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/rails_tracepoint_stack/filter/gem_path.rb: -------------------------------------------------------------------------------- 1 | module RailsTracepointStack 2 | module Filter 3 | class GemPath 4 | def self.full_gem_path 5 | @full_gem_path ||= Bundler.load.specs.map(&:full_gem_path) 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rails_tracepoint_stack/filter/rb_config.rb: -------------------------------------------------------------------------------- 1 | module RailsTracepointStack 2 | module Filter 3 | class RbConfig 4 | def self.ruby_lib_path 5 | @ruby_lib_path ||= ::RbConfig::CONFIG["rubylibdir"] 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rails_tracepoint_stack/filter/trace_from_dependencies_filter.rb: -------------------------------------------------------------------------------- 1 | require 'rails_tracepoint_stack/filter/gem_path' 2 | require 'rails_tracepoint_stack/filter/rb_config' 3 | 4 | module RailsTracepointStack 5 | module Filter 6 | module TraceFromDependenciesFilter 7 | def should_ignore_because_is_a_internal_dependency?(trace:) 8 | return false if not_ignore_external_source_traces? 9 | 10 | file_path_starts_with_gem_path?(trace) || 11 | file_path_starts_with_ruby_lib_path?(trace) || 12 | file_path_include_bundler_gems_path?(trace) 13 | end 14 | 15 | private 16 | 17 | def file_path_starts_with_gem_path?(trace) 18 | gem_paths.any? { |path| trace.file_path.start_with?(path) } 19 | end 20 | 21 | def file_path_starts_with_ruby_lib_path?(trace) 22 | trace.file_path.start_with?(ruby_lib_path) 23 | end 24 | 25 | def file_path_include_bundler_gems_path?(trace) 26 | trace.file_path.include?("gems/bundler") 27 | end 28 | 29 | def gem_paths 30 | @gem_paths ||= RailsTracepointStack::Filter::GemPath.full_gem_path 31 | end 32 | 33 | def ruby_lib_path 34 | @ruby_lib_path ||= RailsTracepointStack::Filter::RbConfig.ruby_lib_path 35 | end 36 | 37 | def not_ignore_external_source_traces? 38 | RailsTracepointStack.configuration.log_external_sources 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rails_tracepoint_stack/filter/trace_from_ruby_code_filter.rb: -------------------------------------------------------------------------------- 1 | module RailsTracepointStack 2 | module Filter 3 | module TraceFromRubyCodeFilter 4 | def should_ignore_because_is_ruby_trace?(trace:) 5 | return false if not_ignore_external_source_traces? 6 | 7 | trace.file_path.start_with?(' 3.0", ">= 3.0.0" 33 | s.add_development_dependency "rake", "~> 13.0", ">= 13.0.0" 34 | s.add_development_dependency "standard", "~> 1.39", ">= 1.39.1" 35 | end 36 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/filter/custom_trace_selector_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe RailsTracepointStack::Filter::CustomTraceSelectorFilter do 4 | include RailsTracepointStack::Filter::CustomTraceSelectorFilter 5 | 6 | describe '.is_a_trace_required_to_watch_by_the_custom_configs?' do 7 | subject { is_a_trace_required_to_watch_by_the_custom_configs?(trace: trace) } 8 | 9 | context "when the trace's file path matches a custom pattern" do 10 | let(:file_path) { 'app/controllers/posts_controller' } 11 | let(:trace) { instance_double(RailsTracepointStack::Trace, file_path: file_path) } 12 | 13 | before do 14 | allow(RailsTracepointStack.configuration) 15 | .to receive(:file_path_to_filter_patterns) 16 | .and_return([/app\/controllers/]) 17 | end 18 | 19 | it { is_expected.to be_truthy } 20 | end 21 | 22 | context "when the trace's file path does not match a custom pattern" do 23 | let(:file_path) { 'app/models/post.rb' } 24 | let(:trace) { instance_double(RailsTracepointStack::Trace, file_path: file_path) } 25 | 26 | before do 27 | allow(RailsTracepointStack.configuration) 28 | .to receive(:file_path_to_filter_patterns) 29 | .and_return([/app\/controllers/]) 30 | end 31 | 32 | it { is_expected.to be_falsey } 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/filter/trace_from_dependencies_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe RailsTracepointStack::Filter::TraceFromDependenciesFilter do 4 | include RailsTracepointStack::Filter::TraceFromDependenciesFilter 5 | 6 | context 'when trace is from ruby_lib_path' do 7 | before do 8 | allow(RailsTracepointStack::Filter::GemPath) 9 | .to receive(:full_gem_path) 10 | .and_return([]) 11 | 12 | allow(RailsTracepointStack::Filter::RbConfig) 13 | .to receive(:ruby_lib_path) 14 | .and_return('/path/to/ruby/lib') 15 | 16 | allow(RailsTracepointStack.configuration) 17 | .to receive(:log_external_sources) 18 | .and_return(false) 19 | end 20 | 21 | let(:internal_trace) do 22 | instance_double(RailsTracepointStack::Trace, 23 | file_path: '/path/to/ruby/lib' 24 | ) 25 | end 26 | 27 | it 'ignores the trace' do 28 | expect(should_ignore_because_is_a_internal_dependency?(trace: internal_trace)).to be true 29 | end 30 | end 31 | 32 | context 'when trace is from a gem path' do 33 | before do 34 | allow(RailsTracepointStack::Filter::GemPath) 35 | .to receive(:full_gem_path) 36 | .and_return(['/path/to/gem']) 37 | 38 | allow(RailsTracepointStack::Filter::RbConfig) 39 | .to receive(:ruby_lib_path) 40 | .and_return('/path/to/ruby/lib') 41 | 42 | allow(RailsTracepointStack.configuration) 43 | .to receive(:log_external_sources) 44 | .and_return(false) 45 | end 46 | 47 | let(:gem_path_trace) do 48 | instance_double(RailsTracepointStack::Trace, 49 | file_path: '/path/to/gem/some_file.rb' 50 | ) 51 | end 52 | 53 | it 'ignores the trace' do 54 | expect(should_ignore_because_is_a_internal_dependency?(trace: gem_path_trace)) 55 | .to be true 56 | end 57 | end 58 | 59 | context 'when trace is from a ruby lib path' do 60 | let(:ruby_lib_trace) do 61 | instance_double(RailsTracepointStack::Trace, 62 | file_path: '/path/to/ruby/lib/some_file.rb') 63 | end 64 | 65 | before do 66 | allow(RailsTracepointStack::Filter::GemPath) 67 | .to receive(:full_gem_path) 68 | .and_return([]) 69 | 70 | allow(RailsTracepointStack::Filter::RbConfig) 71 | .to receive(:ruby_lib_path) 72 | .and_return('/path/to/ruby/lib') 73 | 74 | allow(RailsTracepointStack.configuration) 75 | .to receive(:log_external_sources) 76 | .and_return(false) 77 | end 78 | 79 | it 'ignores the trace' do 80 | expect(should_ignore_because_is_a_internal_dependency?(trace: ruby_lib_trace)).to be true 81 | end 82 | end 83 | 84 | context "when not ignore external sources" do 85 | let(:external_trace) do 86 | instance_double(RailsTracepointStack::Trace, 87 | file_path: '/path/to/external/source/some_file.rb' 88 | ) 89 | end 90 | 91 | before do 92 | allow(RailsTracepointStack::Filter::GemPath) 93 | .to receive(:full_gem_path) 94 | .and_return([]) 95 | 96 | allow(RailsTracepointStack::Filter::RbConfig) 97 | .to receive(:ruby_lib_path) 98 | .and_return('/path/to/ruby/lib') 99 | 100 | allow(RailsTracepointStack.configuration) 101 | .to receive(:log_external_sources) 102 | .and_return(true) 103 | end 104 | 105 | it 'does not ignore the trace' do 106 | expect(should_ignore_because_is_a_internal_dependency?(trace: external_trace)).to be false 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/filter/trace_from_ruby_code_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe RailsTracepointStack::Filter::TraceFromRubyCodeFilter do 4 | include RailsTracepointStack::Filter::TraceFromRubyCodeFilter 5 | 6 | describe '.should_ignore_because_is_ruby_trace?' do 7 | let(:trace) { double('trace') } 8 | 9 | before do 10 | allow(RailsTracepointStack.configuration).to receive(:log_external_sources).and_return(log_external_sources) 11 | end 12 | 13 | context 'when log_external_sources is true' do 14 | let(:log_external_sources) { true } 15 | 16 | it 'does not ignore any traces' do 17 | allow(trace).to receive(:file_path).and_return('') 18 | expect(should_ignore_because_is_ruby_trace?(trace: trace)).to be false 19 | end 20 | end 21 | 22 | context 'when log_external_sources is false' do 23 | let(:log_external_sources) { false } 24 | 25 | it 'ignores internal and eval traces' do 26 | allow(trace).to receive(:file_path).and_return('') 27 | expect(should_ignore_because_is_ruby_trace?(trace: trace)).to be true 28 | 29 | allow(trace).to receive(:file_path).and_return('(eval)') 30 | expect(should_ignore_because_is_ruby_trace?(trace: trace)).to be true 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/filter/trace_to_ignore_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe RailsTracepointStack::Filter::TraceToIgnoreFilter do 4 | include RailsTracepointStack::Filter::TraceToIgnoreFilter 5 | 6 | describe '.attends_some_custom_pattern_to_ignore?' do 7 | let(:trace) { double('trace', file_path: file_path) } 8 | 9 | before do 10 | allow(RailsTracepointStack.configuration).to receive(:ignore_patterns).and_return(ignore_patterns) 11 | end 12 | 13 | context 'when the trace file path matches an ignore pattern' do 14 | let(:ignore_patterns) { [/ignored_path/, /secret/] } 15 | let(:file_path) { '/some/ignored_path/file.rb' } 16 | 17 | it 'returns true' do 18 | expect(attends_some_custom_pattern_to_ignore?(trace: trace)).to be true 19 | end 20 | end 21 | 22 | context 'when the trace file path does not match any ignore pattern' do 23 | let(:ignore_patterns) { [/ignored_path/, /secret/] } 24 | let(:file_path) { '/some/regular_path/file.rb' } 25 | 26 | it 'returns false' do 27 | expect(attends_some_custom_pattern_to_ignore?(trace: trace)).to be false 28 | end 29 | end 30 | 31 | context 'with multiple ignore patterns' do 32 | let(:ignore_patterns) { [/ignored_path/, /another_path/, /secret/] } 33 | 34 | it 'returns true if the trace file path matches any pattern' do 35 | expect(attends_some_custom_pattern_to_ignore?(trace: double('trace', file_path: '/another_path/file.rb'))).to be true 36 | expect(attends_some_custom_pattern_to_ignore?(trace: double('trace', file_path: '/some/secret/file.rb'))).to be true 37 | end 38 | 39 | it 'returns false if the trace file path does not match any pattern' do 40 | expect(attends_some_custom_pattern_to_ignore?(trace: double('trace', file_path: '/unmatched_path/file.rb'))).to be false 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/log_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "json" 3 | 4 | RSpec.describe RailsTracepointStack::LogFormatter do 5 | let(:trace_double) do 6 | instance_double(RailsTracepointStack::Trace, 7 | class_name: "MyClass", 8 | method_name: :my_method, 9 | file_path: "/path/to/file.rb", 10 | line_number: 42, 11 | params: {key: "value"}) 12 | end 13 | 14 | describe ".message" do 15 | context "when format is :json" do 16 | before do 17 | allow(RailsTracepointStack) 18 | .to receive_message_chain(:configuration, :log_format) 19 | .and_return(:json) 20 | end 21 | 22 | it "returns a JSON formatted string" do 23 | expected_json = { 24 | class: "MyClass", 25 | method_name: :my_method, 26 | path: "/path/to/file.rb", 27 | line: 42, 28 | params: {key: "value"} 29 | }.to_json 30 | 31 | expect(described_class.message(trace_double)).to eq(expected_json) 32 | end 33 | end 34 | 35 | context "when format is not :json" do 36 | before do 37 | allow(RailsTracepointStack) 38 | .to receive_message_chain(:configuration, :log_format) 39 | .and_return(nil) 40 | end 41 | 42 | it "returns a text formatted string" do 43 | expected_text = "called: MyClass#my_method in /path/to/file.rb:42 with params: {:key=>\"value\"}" 44 | expect(described_class.message(trace_double)).to eq(expected_text) 45 | end 46 | end 47 | end 48 | 49 | describe ".text" do 50 | it "returns a text formatted string" do 51 | expected_text = "called: MyClass#my_method in /path/to/file.rb:42 with params: {:key=>\"value\"}" 52 | expect(described_class.text(trace_double)).to eq(expected_text) 53 | end 54 | end 55 | 56 | describe ".json" do 57 | it "returns a JSON formatted string" do 58 | expected_json = { 59 | class: "MyClass", 60 | method_name: :my_method, 61 | path: "/path/to/file.rb", 62 | line: 42, 63 | params: {key: "value"} 64 | }.to_json 65 | 66 | expect(described_class.json(trace_double)).to eq(expected_json) 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/logger_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "fileutils" 3 | 4 | RSpec.describe RailsTracepointStack::Logger do 5 | let(:log_message) { "This is a test log message." } 6 | let(:log_file_path) { "log/rails_tracepoint_stack.log" } 7 | context "when provide a custom logger" do 8 | let(:custom_logger) { double("Logger", info: true) } 9 | before do 10 | RailsTracepointStack.configure do |config| 11 | config.logger = custom_logger 12 | end 13 | end 14 | 15 | it "uses the custom logger to log the message" do 16 | described_class.log(log_message) 17 | 18 | expect(custom_logger) 19 | .to have_received(:info) 20 | .with(log_message) 21 | end 22 | end 23 | 24 | context "when no custom logger is provided" do 25 | before do 26 | RailsTracepointStack.configure do |config| 27 | config.logger = nil 28 | end 29 | 30 | allow(File) 31 | .to receive(:open) 32 | .with("log/rails_tracepoint_stack.log", "a") 33 | end 34 | 35 | it "logs the message to the default log file" do 36 | described_class.log(log_message) 37 | 38 | expect(File).to have_received(:open) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/trace_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe RailsTracepointStack::TraceFilter do 4 | include RailsTracepointStack::TraceFilter 5 | 6 | before do 7 | initialize_gem_configuration! 8 | end 9 | 10 | context 'when trace matches an ignore pattern' do 11 | before do 12 | RailsTracepointStack.configure do |config| 13 | config.ignore_patterns << /ignore_pattern/ 14 | end 15 | 16 | allow(RailsTracepointStack::Filter::GemPath) 17 | .to receive(:full_gem_path) 18 | .and_return([]) 19 | 20 | allow(RailsTracepointStack::Filter::RbConfig) 21 | .to receive(:ruby_lib_path) 22 | .and_return("/path/to/ruby/lib") 23 | end 24 | 25 | let(:pattern_trace) do 26 | instance_double( 27 | RailsTracepointStack::Trace, 28 | file_path: "some_path/ignore_pattern/some_file.rb" 29 | ) 30 | end 31 | 32 | it 'ignores the trace' do 33 | expect(ignore_trace?(trace: pattern_trace)).to be true 34 | end 35 | end 36 | 37 | ## Flaky test, this one fails when running all tests, but works when running only this test 38 | 39 | context 'when trace does not meet any ignore criteria' do 40 | before do 41 | allow(RailsTracepointStack::Filter::GemPath) 42 | .to receive(:full_gem_path) 43 | .and_return([]) 44 | 45 | allow(RailsTracepointStack::Filter::RbConfig) 46 | .to receive(:ruby_lib_path) 47 | .and_return("/path/to/ruby/lib") 48 | end 49 | 50 | let(:normal_trace) do 51 | instance_double(RailsTracepointStack::Trace, 52 | file_path: "some_path/some_file.rb") 53 | end 54 | 55 | it 'does not ignore the trace' do 56 | expect(ignore_trace?(trace: normal_trace)).to be false 57 | end 58 | end 59 | 60 | context "when defined file path to filter patterns" do 61 | context "and trace not matches" do 62 | before do 63 | RailsTracepointStack.configure do |config| 64 | config.file_path_to_filter_patterns << /ignore_pattern/ 65 | end 66 | 67 | allow(RailsTracepointStack::Filter::GemPath) 68 | .to receive(:full_gem_path) 69 | .and_return([]) 70 | 71 | allow(RailsTracepointStack::Filter::RbConfig) 72 | .to receive(:ruby_lib_path) 73 | .and_return("/path/to/ruby/lib") 74 | end 75 | 76 | let(:pattern_trace) do 77 | instance_double( 78 | RailsTracepointStack::Trace, 79 | file_path: "some_path/ignore_pattern/some_file.rb" 80 | ) 81 | end 82 | 83 | it 'ignores the trace' do 84 | expect(ignore_trace?(trace: pattern_trace)).to be false 85 | end 86 | end 87 | 88 | context "and trace matches" do 89 | before do 90 | RailsTracepointStack.configure do |config| 91 | config.file_path_to_filter_patterns << /ignore_pattern/ 92 | end 93 | 94 | allow(RailsTracepointStack::Filter::GemPath) 95 | .to receive(:full_gem_path) 96 | .and_return([]) 97 | 98 | allow(RailsTracepointStack::Filter::RbConfig) 99 | .to receive(:ruby_lib_path) 100 | .and_return("/path/to/ruby/lib") 101 | end 102 | 103 | let(:pattern_trace) do 104 | instance_double( 105 | RailsTracepointStack::Trace, 106 | file_path: "some_path/some_file.rb" 107 | ) 108 | end 109 | 110 | it 'does not ignore the trace' do 111 | expect(ignore_trace?(trace: pattern_trace)).to be false 112 | end 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/trace_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe RailsTracepointStack::Trace do 4 | let(:trace_point_double) do 5 | instance_double("TracePoint", 6 | defined_class: "MyClass", 7 | method_id: :my_method, 8 | path: "/path/to/file.rb", 9 | lineno: 42, 10 | binding: instance_double( 11 | "Binding", 12 | local_variables: ["var"], 13 | local_variable_get: "value" 14 | )) 15 | end 16 | let(:params) { {"var" => "value"} } 17 | subject(:trace) { described_class.new(trace_point: trace_point_double) } 18 | 19 | describe "#initialize" do 20 | it "initializes with trace_point and params" do 21 | expect(trace.params).to eq(params) 22 | end 23 | end 24 | 25 | describe "delegated methods" do 26 | it "returns the class name" do 27 | expect(trace.class_name).to eq("MyClass") 28 | end 29 | 30 | it "returns the method name" do 31 | expect(trace.method_name).to eq(:my_method) 32 | end 33 | 34 | it "returns the file path" do 35 | expect(trace.file_path).to eq("/path/to/file.rb") 36 | end 37 | 38 | it "returns the line number" do 39 | expect(trace.line_number).to eq(42) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack/tracer_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | class Foo 4 | def dummy_method 5 | nil 6 | end 7 | 8 | def dummy_method_with_params(param_1, param_2) 9 | nil 10 | end 11 | end 12 | 13 | RSpec.describe RailsTracepointStack::Tracer do 14 | let(:tracer) { RailsTracepointStack::Tracer.new } 15 | 16 | before do 17 | allow(RailsTracepointStack::Logger) 18 | .to receive(:log) 19 | 20 | allow_any_instance_of(TracePoint) 21 | .to receive(:lineno).and_return(6) 22 | end 23 | 24 | describe "when the log should not be ignored because not match any filter block" do 25 | before do 26 | allow(RailsTracepointStack::Filter::GemPath) 27 | .to receive(:full_gem_path) 28 | .and_return(["/another/path/to/gem"]) 29 | 30 | allow(RailsTracepointStack::Filter::RbConfig) 31 | .to receive(:ruby_lib_path) 32 | .and_return("/path/to/ruby/lib") 33 | 34 | allow_any_instance_of(TracePoint) 35 | .to receive(:path) 36 | .and_return("/app/rails_tracepoint_stack/spec/tracer_spec.rb") 37 | 38 | allow_any_instance_of(RailsTracepointStack::Configuration) 39 | .to receive(:log_format) 40 | .and_return(:text) 41 | end 42 | 43 | include_examples "tracer success examples asserts" 44 | end 45 | 46 | context "when the log sshould be ignored because is a gem dependency" do 47 | before do 48 | allow(RailsTracepointStack::Filter::GemPath) 49 | .to receive(:full_gem_path) 50 | .and_return([]) 51 | 52 | allow(RailsTracepointStack::Filter::RbConfig) 53 | .to receive(:ruby_lib_path) 54 | .and_return('/path/to/ruby/lib') 55 | 56 | allow_any_instance_of(TracePoint) 57 | .to receive(:path) 58 | .and_return("/path/to/ruby/lib") 59 | 60 | RailsTracepointStack.configure do |config| 61 | config.log_external_sources = false 62 | end 63 | end 64 | 65 | it "does not call logger" do 66 | tracer.enable do 67 | Foo.new.dummy_method 68 | end 69 | 70 | expect(RailsTracepointStack::Logger).not_to have_received(:log) 71 | end 72 | end 73 | 74 | context "when the log should be ignored because is a internal dependency" do 75 | before do 76 | allow(RailsTracepointStack::Filter::GemPath) 77 | .to receive(:full_gem_path) 78 | .and_return(['/path/to/gem']) 79 | 80 | allow(RailsTracepointStack::Filter::RbConfig) 81 | .to receive(:ruby_lib_path) 82 | .and_return('/path/to/ruby/lib') 83 | 84 | allow_any_instance_of(TracePoint) 85 | .to receive(:path) 86 | .and_return("/path/to/gem/some_file.rb") 87 | 88 | RailsTracepointStack.configure do |config| 89 | config.log_external_sources = false 90 | end 91 | end 92 | 93 | it 'does not call logger' do 94 | tracer.enable do 95 | Foo.new.dummy_method 96 | end 97 | 98 | expect(RailsTracepointStack::Logger).not_to have_received(:log) 99 | end 100 | end 101 | 102 | context "when the log should not be ignored because is a external dependency" do 103 | before do 104 | allow(RailsTracepointStack::Filter::GemPath) 105 | .to receive(:full_gem_path) 106 | .and_return(['/another/path/to/gem']) 107 | 108 | allow(RailsTracepointStack::Filter::RbConfig) 109 | .to receive(:ruby_lib_path) 110 | .and_return('/path/to/ruby/lib') 111 | 112 | allow_any_instance_of(TracePoint) 113 | .to receive(:path) 114 | .and_return("/another/path/to/gem/some_file.rb") 115 | 116 | RailsTracepointStack.configure do |config| 117 | config.log_external_sources = true 118 | end 119 | end 120 | 121 | it 'calls logger' do 122 | tracer.enable do 123 | Foo.new.dummy_method 124 | end 125 | 126 | expect(RailsTracepointStack::Logger).to have_received(:log) 127 | end 128 | end 129 | 130 | context "when the log attends a custom ignore pattern" do 131 | before do 132 | allow(RailsTracepointStack::Filter::GemPath) 133 | .to receive(:full_gem_path) 134 | .and_return(['/another/path/to/gem']) 135 | 136 | allow(RailsTracepointStack::Filter::RbConfig) 137 | .to receive(:ruby_lib_path) 138 | .and_return('/path/to/ruby/lib') 139 | 140 | allow_any_instance_of(TracePoint) 141 | .to receive(:path) 142 | .and_return("/another/path/to/gem/some_file.rb") 143 | 144 | RailsTracepointStack.configure do |config| 145 | config.ignore_patterns = [/another\/path/] 146 | end 147 | end 148 | 149 | it 'does not call logger' do 150 | tracer.enable do 151 | Foo.new.dummy_method 152 | end 153 | 154 | expect(RailsTracepointStack::Logger).not_to have_received(:log) 155 | end 156 | end 157 | 158 | context "when the log attends a file_path_to_filter_patterns" do 159 | before do 160 | allow(RailsTracepointStack::Filter::GemPath) 161 | .to receive(:full_gem_path) 162 | .and_return(['/another/path/to/gem']) 163 | 164 | allow(RailsTracepointStack::Filter::RbConfig) 165 | .to receive(:ruby_lib_path) 166 | .and_return('/path/to/ruby/lib') 167 | 168 | allow_any_instance_of(TracePoint) 169 | .to receive(:path) 170 | .and_return("/another/path/some_file.rb") 171 | 172 | allow_any_instance_of(RailsTracepointStack::Trace) 173 | .to receive(:file_path) 174 | .and_return("/another/path/some_file.rb") 175 | 176 | RailsTracepointStack.configure do |config| 177 | config.file_path_to_filter_patterns = [/another\/path/] 178 | end 179 | end 180 | 181 | it "calls logger" do 182 | tracer.enable do 183 | Foo.new.dummy_method 184 | end 185 | 186 | expect(RailsTracepointStack::Logger).to have_received(:log) 187 | end 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /spec/rails_tracepoint_stack_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe RailsTracepointStack do 4 | describe '.configure' do 5 | it 'initialize the default configuration attributes' do 6 | expect(described_class.configuration.file_path_to_filter_patterns).to eq([]) 7 | expect(described_class.configuration.ignore_patterns).to eq([]) 8 | expect(described_class.configuration.log_format).to eq(:text) 9 | expect(described_class.configuration.log_external_sources).to eq(false) 10 | expect(described_class.configuration.logger).to eq(nil) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/shared/tracer_examples.rb: -------------------------------------------------------------------------------- 1 | RSpec.shared_examples "tracer success examples asserts" do 2 | context "when log format is text" do 3 | it 'calls logger with correct log' do 4 | tracer.enable do 5 | Foo.new.dummy_method 6 | end 7 | 8 | expect(RailsTracepointStack::Logger) 9 | .to have_received(:log) 10 | .with("called: Foo#dummy_method in /app/rails_tracepoint_stack/spec/tracer_spec.rb:6 with params: {}") 11 | end 12 | end 13 | 14 | context "when log format is json" do 15 | before do 16 | allow(RailsTracepointStack.configuration) 17 | .to receive(:log_format) 18 | .and_return(:json) 19 | end 20 | # TODO: Extract this test to a proper place 21 | it 'calls logger with correct log with json log format' do 22 | tracer.enable do 23 | Foo.new.dummy_method 24 | end 25 | 26 | expect(RailsTracepointStack::Logger) 27 | .to have_received(:log) 28 | .with("{\"class\":\"Foo\",\"method_name\":\"dummy_method\",\"path\":\"/app/rails_tracepoint_stack/spec/tracer_spec.rb\",\"line\":6,\"params\":{}}") 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | Dir[File.join(File.dirname(__FILE__), '../lib/**/*.rb')].sort.each { |file| require file } 2 | Dir[File.join(File.dirname(__FILE__), './shared/**/*.rb')].each { |f| require f } 3 | 4 | def initialize_gem_configuration! 5 | RailsTracepointStack.configuration = RailsTracepointStack::Configuration.new 6 | end 7 | 8 | RSpec.configure do |config| 9 | config.order = :random 10 | config.filter_run_when_matching :focus 11 | config.raise_errors_for_deprecations! 12 | 13 | config.around do |example| 14 | original_config = RailsTracepointStack.configuration.dup 15 | example.run 16 | RailsTracepointStack.configuration = original_config 17 | end 18 | end 19 | --------------------------------------------------------------------------------