├── .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 |
--------------------------------------------------------------------------------