├── .github
└── workflows
│ └── ruby.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── console
└── setup
├── examples
├── console.jpg
└── slack.jpg
├── lib
├── query_track.rb
├── query_track
│ ├── event_processor.rb
│ ├── filters.rb
│ ├── notifications
│ │ ├── custom.rb
│ │ ├── log.rb
│ │ └── slack.rb
│ ├── settings.rb
│ ├── trace.rb
│ └── version.rb
└── subscriber.rb
├── query_track.gemspec
└── spec
├── query_track
├── event_processor_spec.rb
├── filters_spec.rb
├── notifications
│ ├── custom_spec.rb
│ └── slack_spec.rb
└── trace_spec.rb
├── query_track_spec.rb
└── spec_helper.rb
/.github/workflows/ruby.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | include:
10 | - ruby: 3.3
11 | steps:
12 | - uses: actions/checkout@v4
13 | - uses: ruby/setup-ruby@v1
14 | with:
15 | ruby-version: ${{ matrix.ruby }}
16 | bundler-cache: true
17 | - run: bundle exec rspec
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 |
10 | # rspec failure tracking
11 | .rspec_status
12 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | DisplayCopNames: true
3 | DisplayStyleGuide: true
4 | StyleGuideCopsOnly: true
5 | TargetRubyVersion: 2.5
6 |
7 | Metrics/LineLength:
8 | Max: 120
9 |
10 | Documentation:
11 | Enabled: false
12 |
13 | FrozenStringLiteralComment:
14 | Enabled: false
15 |
16 | Style/FileName:
17 | Enabled: false
18 |
19 | Metrics/ModuleLength:
20 | Exclude:
21 | - spec/**/*_spec.rb
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v0.0.10 2020-08-07
2 |
3 | Added custom app directory ([evanboho](https://github.com/kirillshevch/query_track/pull/60))
4 |
5 | # v0.0.9 2019-12-21
6 |
7 | Added custom handles ([kirillshevch](https://github.com/kirillshevch/query_track/pull/29))
8 |
9 | # v0.0.8 2019-08-17
10 |
11 | Added enabled/disable config ([kirillshevch](https://github.com/kirillshevch/query_track/pull/13))
12 |
13 | # v0.0.7 2019-08-01
14 |
15 | fixed view of duration seconds ([kirillshevch](https://github.com/kirillshevch/query_track/pull/8))
16 |
17 | # v0.0.6 2019-08-01
18 |
19 | repush of 0.0.5
20 |
21 | # v0.0.5 2019-08-01
22 |
23 | *yanked*
24 |
25 | # v0.0.5 2019-07-31
26 |
27 | Use #find instead of #map .include?(false). ([kirillshevch](https://github.com/kirillshevch/query_track/pull/7))
28 |
29 | # v0.0.4 2019-07-22
30 |
31 | Added temp solution for async slack notifications. ([kirillshevch](https://github.com/kirillshevch/query_track/pull/5))
32 |
33 | # v0.0.3 2019-07-22
34 |
35 | Added backtrace filters. ([kirillshevch](https://github.com/kirillshevch/query_track/pull/3))
36 |
37 | # v0.0.2 2019-07-22
38 |
39 | Add console log, fix disabling of slack notifications. ([kirillshevch](https://github.com/kirillshevch/query_track/pull/2))
40 |
41 | # v0.0.1 2019-07-20
42 |
43 | Initial release with event processing, slack notifications and duration limit. ([kirillshevch](https://github.com/kirillshevch/query_track/pull/1))
44 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4 |
5 | # Specify your gem's dependencies in query_track.gemspec
6 | gemspec
7 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | query_track (0.0.14)
5 | activesupport
6 | dry-configurable
7 | slack_hook
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | activesupport (7.1.3)
13 | base64
14 | bigdecimal
15 | concurrent-ruby (~> 1.0, >= 1.0.2)
16 | connection_pool (>= 2.2.5)
17 | drb
18 | i18n (>= 1.6, < 2)
19 | minitest (>= 5.1)
20 | mutex_m
21 | tzinfo (~> 2.0)
22 | ast (2.4.2)
23 | base64 (0.2.0)
24 | bigdecimal (3.1.6)
25 | byebug (11.1.3)
26 | concurrent-ruby (1.2.3)
27 | connection_pool (2.4.1)
28 | diff-lcs (1.5.1)
29 | drb (2.2.0)
30 | ruby2_keywords
31 | dry-configurable (1.1.0)
32 | dry-core (~> 1.0, < 2)
33 | zeitwerk (~> 2.6)
34 | dry-core (1.0.1)
35 | concurrent-ruby (~> 1.0)
36 | zeitwerk (~> 2.6)
37 | i18n (1.14.1)
38 | concurrent-ruby (~> 1.0)
39 | json (2.7.1)
40 | language_server-protocol (3.17.0.3)
41 | minitest (5.22.2)
42 | mutex_m (0.2.0)
43 | parallel (1.24.0)
44 | parser (3.3.0.5)
45 | ast (~> 2.4.1)
46 | racc
47 | racc (1.7.3)
48 | rainbow (3.1.1)
49 | rake (13.1.0)
50 | regexp_parser (2.9.0)
51 | rexml (3.2.6)
52 | rspec (3.13.0)
53 | rspec-core (~> 3.13.0)
54 | rspec-expectations (~> 3.13.0)
55 | rspec-mocks (~> 3.13.0)
56 | rspec-core (3.13.0)
57 | rspec-support (~> 3.13.0)
58 | rspec-expectations (3.13.0)
59 | diff-lcs (>= 1.2.0, < 2.0)
60 | rspec-support (~> 3.13.0)
61 | rspec-mocks (3.13.0)
62 | diff-lcs (>= 1.2.0, < 2.0)
63 | rspec-support (~> 3.13.0)
64 | rspec-support (3.13.0)
65 | rubocop (1.60.2)
66 | json (~> 2.3)
67 | language_server-protocol (>= 3.17.0)
68 | parallel (~> 1.10)
69 | parser (>= 3.3.0.2)
70 | rainbow (>= 2.2.2, < 4.0)
71 | regexp_parser (>= 1.8, < 3.0)
72 | rexml (>= 3.2.5, < 4.0)
73 | rubocop-ast (>= 1.30.0, < 2.0)
74 | ruby-progressbar (~> 1.7)
75 | unicode-display_width (>= 2.4.0, < 3.0)
76 | rubocop-ast (1.30.0)
77 | parser (>= 3.2.1.0)
78 | ruby-progressbar (1.13.0)
79 | ruby2_keywords (0.0.5)
80 | slack_hook (0.1.2)
81 | tzinfo (2.0.6)
82 | concurrent-ruby (~> 1.0)
83 | unicode-display_width (2.5.0)
84 | zeitwerk (2.6.13)
85 |
86 | PLATFORMS
87 | ruby
88 |
89 | DEPENDENCIES
90 | bundler
91 | byebug
92 | query_track!
93 | rake
94 | rspec
95 | rubocop
96 |
97 | BUNDLED WITH
98 | 2.4.19
99 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Kirill Shevchenko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QueryTrack
2 |
3 | [](https://badge.fury.io/rb/query_track)
4 | [](https://codeclimate.com/github/kirillshevch/query_track/maintainability)
5 |
6 | Tool for finding time-consuming database queries for ActiveRecord-based Rails Apps. Provides Slack notifications with backtrace, raw SQL, time duration, etc.
7 |
8 | ## Installation
9 |
10 | Add this line to your application's `Gemfile` and then execute `bundle install`:
11 |
12 | ```ruby
13 | gem 'query_track'
14 | ```
15 |
16 | ## Usage
17 |
18 | ### SQL Duration Limit
19 |
20 | Specify SQL duration query limit (in seconds):
21 |
22 | ```ruby
23 | QueryTrack::Settings.configure do |config|
24 | config.duration = 0.5
25 | end
26 | ```
27 |
28 | ### Console Log
29 |
30 | Enable console logs from config:
31 |
32 | ```ruby
33 | QueryTrack::Settings.configure do |config|
34 | config.duration = 0.5
35 | config.logs = true
36 | end
37 | ```
38 |
39 | #
40 |
41 | ### Filters
42 |
43 | To avoid noisy warnings from used gems, and places where fat queries are justified, you can filters SQL by backtrace.
44 | For example, you have installed `activeadmin` and want to skip everything from `app/admin`:
45 |
46 | ```ruby
47 | QueryTrack::Settings.configure do |config|
48 | config.duration = 0.5
49 | config.filters = ['app/admin']
50 | end
51 | ```
52 | ### App Directory
53 |
54 | QueryTrack finds the trace by filtering the caller by the app directory.
55 | By default, the app directory is set to 'app', the default for Rails apps.
56 | For apps that have a non-stanard app directory, this can be set with the `app_dir` config field:
57 |
58 | ```ruby
59 | QueryTrack::Settings.configure do |config|
60 | config.duration = 0.5
61 | config.app_dir = 'backend'
62 | end
63 | ```
64 |
65 | ### Enable/Disable toggle
66 |
67 | Enable/disable with ENV variables to turn it on/off without code push. By default *QueryTrack* is enabled.
68 |
69 | ```ruby
70 | QueryTrack::Settings.configure do |config|
71 | config.duration = 0.5
72 | config.enabled = ENV['QUERY_TRACK_ENABLED']
73 | end
74 | ```
75 |
76 | ### Slack Notifications
77 |
78 | To receive notifications about slow queries into Slack, you need to install [incoming-webhooks](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks) and put link into config file:
79 |
80 | ```ruby
81 | QueryTrack::Settings.configure do |config|
82 | config.duration = 0.5
83 | config.notifications.slack = 'https://hooks.slack.com/services/T0000000/B0000000/C0000000'
84 | end
85 | ```
86 |
87 | #
88 |
89 | ## Custom Notifications (Handlers)
90 |
91 | You can write your own handler for slow queries. Send data to any source(for e.g. to logs storage) or make notification for another source(Email, Messengers, etc.)
92 |
93 | ```ruby
94 | QueryTrack::Settings.configure do |config|
95 | config.duration = 0.5
96 | config.notifications.custom_handler = -> (sql, duration, trace) {
97 | # data processing...
98 | }
99 | end
100 | ```
101 |
102 | ## Production Usage Notes
103 |
104 | When [QueryTrack](https://github.com/kirillshevch/query_track/blob/master/lib/query_track/notifications/slack.rb#L21) send slack hooks, request is executed in separate thread. So there should be no synchronous delays.
105 |
106 | Subscription to SQL events and checking duration time actually take insignificant time in milliseconds.
107 |
108 | If your project is horizontally scaled, you can install `query_track` for one of the node to avoid performance degrade for whole application.
109 |
110 | ## Contributing
111 |
112 | Bug reports and pull requests are welcome on GitHub at https://github.com/kirillshevch/query_track.
113 |
114 | ## License
115 |
116 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
117 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rspec/core/rake_task'
3 |
4 | RSpec::Core::RakeTask.new(:spec)
5 |
6 | task default: :spec
7 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'bundler/setup'
4 | require 'query_track'
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require 'irb'
14 | IRB.start(__FILE__)
15 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/examples/console.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kirillshevch/query_track/15261a340e0b61a805a629247bb9b681d83c143f/examples/console.jpg
--------------------------------------------------------------------------------
/examples/slack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kirillshevch/query_track/15261a340e0b61a805a629247bb9b681d83c143f/examples/slack.jpg
--------------------------------------------------------------------------------
/lib/query_track.rb:
--------------------------------------------------------------------------------
1 | require 'dry-configurable'
2 | require 'active_support/notifications'
3 | require 'slack_hook'
4 | require 'query_track/version'
5 | require 'query_track/settings'
6 | require 'query_track/trace'
7 | require 'query_track/filters'
8 | require 'query_track/notifications/custom'
9 | require 'query_track/notifications/slack'
10 | require 'query_track/notifications/log'
11 | require 'query_track/event_processor'
12 | require 'subscriber'
13 | require 'logger'
14 |
15 | module QueryTrack
16 | class << self
17 | attr_writer :logger
18 |
19 | def logger
20 | @logger ||= Logger.new($stdout).tap do |log|
21 | log.progname = self.name
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/query_track/event_processor.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | class EventProcessor
3 | attr_reader :event, :subscriber
4 |
5 | def initialize(event, subscriber)
6 | @event = event
7 | @subscriber = subscriber
8 | end
9 |
10 | def call
11 | unsubscribe
12 | return unless QueryTrack::Settings.config.duration && QueryTrack::Settings.config.enabled
13 |
14 | return if under_filter?(caller)
15 |
16 | if duration_seconds > QueryTrack::Settings.config.duration
17 | QueryTrack::Notifications::Slack.new(event.payload[:sql], duration_seconds).call
18 | QueryTrack::Notifications::Log.new(event.payload[:sql], duration_seconds).call
19 | QueryTrack::Notifications::Custom.new(event.payload[:sql], duration_seconds).call
20 | end
21 | end
22 |
23 | private
24 |
25 | def unsubscribe
26 | return if QueryTrack::Settings.config.enabled
27 |
28 | ActiveSupport::Notifications.unsubscribe(subscriber)
29 | end
30 |
31 | def duration_seconds
32 | event.duration / 1000
33 | end
34 |
35 | def under_filter?(trace)
36 | QueryTrack::Filters.new(caller).call
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/lib/query_track/filters.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | class Filters
3 | attr_reader :full_trace
4 |
5 | def initialize(full_trace)
6 | @full_trace = full_trace
7 | end
8 |
9 | def call
10 | QueryTrack::Settings.config.filters.find do |filter|
11 | full_trace.select { |v| v =~ %r{#{filter}} }[0]
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/query_track/notifications/custom.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | module Notifications
3 | class Custom
4 | attr_reader :code, :duration
5 |
6 | def initialize(code, duration)
7 | @code = code.strip
8 | @duration = duration
9 | end
10 |
11 | def call
12 | return unless QueryTrack::Settings.config.notifications.custom_handler
13 |
14 | trace = QueryTrack::Trace.new(caller).call
15 |
16 | QueryTrack::Settings.config.notifications.custom_handler.call(code, duration, trace)
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/query_track/notifications/log.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | module Notifications
3 | class Log
4 | attr_reader :code, :duration
5 |
6 | def initialize(code, duration)
7 | @code = code.strip
8 | @duration = duration
9 | end
10 |
11 | def call
12 | return unless QueryTrack::Settings.config.logs
13 |
14 | trace = QueryTrack::Trace.new(caller).call
15 |
16 | QueryTrack.logger.info "\n#{code}\nDuration: #{duration}s\nTrace: #{trace}"
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/query_track/notifications/slack.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | module Notifications
3 | class Slack
4 | attr_reader :code, :duration, :webhook_url
5 |
6 | def initialize(code, duration)
7 | @code = code.strip
8 | @duration = duration
9 | @webhook_url = QueryTrack::Settings.config.notifications.slack
10 | end
11 |
12 | def call
13 | return if webhook_url.nil? || webhook_url.empty?
14 |
15 | slack_hook = SlackHook::Incoming.new(webhook_url)
16 |
17 | trace = QueryTrack::Trace.new(caller).call
18 |
19 | payload = { blocks: blocks(trace) }
20 |
21 | Thread.new { slack_hook.post(payload) }
22 | end
23 |
24 | private
25 |
26 | def blocks(trace)
27 | [
28 | {
29 | "type": 'divider'
30 | },
31 | {
32 | "type": 'section',
33 | "text": {
34 | "type": 'mrkdwn',
35 | "text": "```#{code}```"
36 | }
37 | },
38 | {
39 | "type": 'context',
40 | "elements": [
41 | {
42 | "type": 'mrkdwn',
43 | "text": "Duration: *#{duration}s* "
44 | }
45 | ]
46 | },
47 | {
48 | "type": 'context',
49 | "elements": [
50 | {
51 | "type": 'mrkdwn',
52 | "text": "#{trace}"
53 | }
54 | ]
55 | }
56 | ]
57 | end
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/lib/query_track/settings.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | class Settings
3 | extend ::Dry::Configurable
4 |
5 | setting :duration
6 |
7 | setting :notifications do
8 | setting :slack, default: ''
9 | setting :custom_handler
10 | end
11 |
12 | setting :logs, default: false
13 |
14 | setting :filters, default: []
15 |
16 | setting :enabled, default: true
17 |
18 | setting :app_dir, default: 'app'
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/query_track/trace.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | class Trace
3 | attr_reader :full_trace
4 |
5 | def initialize(full_trace)
6 | @full_trace = full_trace
7 | end
8 |
9 | def call
10 | full_trace.select { |v| v =~ %r{#{QueryTrack::Settings.config.app_dir}/} }[0]
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/query_track/version.rb:
--------------------------------------------------------------------------------
1 | module QueryTrack
2 | VERSION = '0.0.14'
3 | end
4 |
--------------------------------------------------------------------------------
/lib/subscriber.rb:
--------------------------------------------------------------------------------
1 | subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
2 | event = ActiveSupport::Notifications::Event.new(*args)
3 |
4 | QueryTrack::EventProcessor.new(event, subscriber).call
5 | end
6 |
--------------------------------------------------------------------------------
/query_track.gemspec:
--------------------------------------------------------------------------------
1 | lib = File.expand_path('../lib', __FILE__)
2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3 | require 'query_track/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = 'query_track'
7 | spec.version = QueryTrack::VERSION
8 | spec.authors = ['Kirill Shevchenko']
9 | spec.email = ['hello@kirillshevch.com']
10 |
11 | spec.summary = 'Finding time-consuming database queries for ActiveRecord-based Rails Apps'
12 | spec.description = spec.summary
13 | spec.homepage = 'https://github.com/kirillshevch/query_track'
14 | spec.license = 'MIT'
15 |
16 | # Specify which files should be added to the gem when it is released.
17 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20 | end
21 | spec.bindir = 'exe'
22 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23 | spec.require_paths = ['lib']
24 |
25 | spec.add_dependency 'activesupport'
26 | spec.add_dependency 'slack_hook'
27 | spec.add_dependency 'dry-configurable'
28 |
29 | spec.add_development_dependency 'bundler'
30 | spec.add_development_dependency 'rubocop'
31 | spec.add_development_dependency 'rake'
32 | spec.add_development_dependency 'rspec'
33 | spec.add_development_dependency 'byebug'
34 | end
35 |
--------------------------------------------------------------------------------
/spec/query_track/event_processor_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe QueryTrack::EventProcessor do
2 | let(:payload) do ['sql.active_record', Time.now, Time.now, '1e07319',
3 | {
4 | sql: "SET client_min_messages TO 'warning'",
5 | name: 'SCHEMA',
6 | binds: [],
7 | type_casted_binds: [],
8 | statement_name: nil,
9 | connection_id: 1
10 | }
11 | ] end
12 |
13 | let!(:event) { ActiveSupport::Notifications::Event.new(*payload) }
14 | let(:subscriber) { double }
15 |
16 | subject { described_class.new(event, subscriber) }
17 |
18 | context 'skipping case' do
19 | it 'should return if duration not specified' do
20 | expect(event).not_to receive(:duration)
21 | subject.call
22 | end
23 | end
24 |
25 | context 'processing' do
26 | before do
27 | QueryTrack::Settings.configure do |config|
28 | config.duration = 1.0
29 | config.logs = true
30 | end
31 | end
32 |
33 | it 'should process event if duration specified' do
34 | allow(event).to receive(:duration).and_return(1)
35 | expect(event).to receive(:duration)
36 | subject.call
37 | end
38 |
39 | it 'should call logs if enabled' do
40 | allow(event).to receive(:duration).and_return(5000) # value in milliseconds
41 | mock_logger = double('log')
42 | allow(mock_logger).to receive(:call)
43 | expect(QueryTrack::Notifications::Log).to receive(:new).and_return(mock_logger)
44 | subject.call
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/query_track/filters_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe QueryTrack::Filters do
2 | let(:fake_trace) { ['app/admin/user.rb', 'lib/config/query_track.rb'] }
3 |
4 | context 'has content under filter' do
5 | before do
6 | QueryTrack::Settings.configure do |config|
7 | config.duration = 1.0
8 | config.filters = ['app/admin']
9 | end
10 | end
11 |
12 | it 'should return true' do
13 | expect(QueryTrack::Filters.new(fake_trace).call).to be_truthy
14 | end
15 | end
16 |
17 | context 'has no content under filter' do
18 | before do
19 | QueryTrack::Settings.configure do |config|
20 | config.duration = 1.0
21 | config.filters = ['app/models']
22 | end
23 | end
24 |
25 | it 'should return true' do
26 | expect(QueryTrack::Filters.new(fake_trace).call).to be_falsey
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/spec/query_track/notifications/custom_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe QueryTrack::Notifications::Custom do
2 | let(:code) { 'COMMIT' }
3 | let(:duration) { 5 }
4 |
5 | subject { described_class.new(code, duration) }
6 |
7 | context 'custom handler not specified' do
8 | before do
9 | QueryTrack::Settings.configure do |config|
10 | config.duration = 1.0
11 | end
12 | end
13 |
14 | it 'should not return anything' do
15 | expect(subject.call).to be_nil
16 | end
17 | end
18 |
19 | context 'custom handler specified' do
20 | before do
21 | QueryTrack::Settings.configure do |config|
22 | config.duration = 1.0
23 | config.notifications.custom_handler = -> (sql, duration, trace) {}
24 | end
25 | end
26 |
27 | it 'should call custom handler' do
28 | expect(QueryTrack::Settings.config.notifications.custom_handler).to receive(:call)
29 | subject.call
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/spec/query_track/notifications/slack_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe QueryTrack::Notifications::Slack do
2 | let(:code) { 'COMMIT' }
3 | let(:duration) { 5 }
4 | let(:slack_stub) { double }
5 |
6 | subject { described_class.new(code, duration) }
7 |
8 | context 'slack not specified' do
9 | before do
10 | QueryTrack::Settings.configure do |config|
11 | config.duration = 1.0
12 | end
13 | end
14 |
15 | it 'should post notification to slack' do
16 | #allow(SlackHook::Incoming).to receive(:new).and_return(slack_stub)
17 | expect(slack_stub).not_to receive(:post)
18 | subject.call
19 | end
20 | end
21 |
22 | context 'slack specified' do
23 | before do
24 | QueryTrack::Settings.configure do |config|
25 | config.duration = 1.0
26 | config.notifications.slack = 'https://hooks.slack.com/services/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'
27 | end
28 | end
29 |
30 | it 'should post notification to slack' do
31 | allow(SlackHook::Incoming).to receive(:new).and_return(slack_stub)
32 | expect(Thread).to receive(:new)
33 | subject.call
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/spec/query_track/trace_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe QueryTrack::Trace do
2 | let(:app_dir_path) { 'app/admin/user.rb' }
3 | let(:fake_trace) { [app_dir_path, 'lib/config/query_track.rb'] }
4 |
5 | before do
6 | QueryTrack::Settings.configure do |config|
7 | config.duration = 1.0
8 | end
9 | end
10 |
11 | context 'has default app directory' do
12 | it 'should return path matching app directory' do
13 | expect(QueryTrack::Trace.new(fake_trace).call).to eq app_dir_path
14 | end
15 | end
16 |
17 | context 'has override app directory' do
18 | let(:app_dir_path) { 'backend/models/user.rb' }
19 |
20 | before do
21 | QueryTrack::Settings.configure do |config|
22 | config.app_dir = 'backend'
23 | end
24 | end
25 |
26 | it 'should return path matching app directory' do
27 | expect(QueryTrack::Trace.new(fake_trace).call).to eq app_dir_path
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/query_track_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe QueryTrack do
2 | it 'has a version number' do
3 | expect(QueryTrack::VERSION).not_to be nil
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'bundler/setup'
2 | require 'query_track'
3 |
4 | RSpec.configure do |config|
5 | # Enable flags like --only-failures and --next-failure
6 | config.example_status_persistence_file_path = '.rspec_status'
7 |
8 | # Disable RSpec exposing methods globally on `Module` and `main`
9 | config.disable_monkey_patching!
10 |
11 | config.expect_with :rspec do |c|
12 | c.syntax = :expect
13 | end
14 | end
15 |
--------------------------------------------------------------------------------