debugbar 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5380fa3760a1309aacd1404c077e3e6c78ed94d21bde373eb0f96f90da96abe6
4
+ data.tar.gz: 7e9f86653a25e202c9ebf4c64b8d74f5816aaadbee1f2e2d75b46423fa17a8dd
5
+ SHA512:
6
+ metadata.gz: '08f084f414d3520d4f7f287b3ffcde2a1a322b7bc04492bcf80ca0087b7fe1a853db6dac041bafceaa352944d8c09ed8d12ecf47bfa30cbc03643f29e2573179'
7
+ data.tar.gz: 2836203deb352f9d344d8784423b801d6ca6ff26e7e48adaec4ea58b142434999ef145f32207de68ba80cf499dba137e8f41d507d97bb9f025d84084a205ac65
data/.prettierrc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "trailingComma": "es5",
3
+ "tabWidth": 2,
4
+ "printWidth": 120,
5
+ "semi": false
6
+ }
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Julien Bourdeau
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.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Debugbar for Rails
2
+
3
+ Find all documentation on [debugbar.dev](https://debugbar.dev/)
4
+
5
+ [![debugbar screesnhot](https://debugbar.dev/assets/screenshots/home.png)](https://debugbar.dev/docs/)
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,26 @@
1
+ module Debugbar
2
+ class DebugbarChannel < ActionCable::Channel::Base
3
+ def subscribed
4
+ stream_from "debugbar_channel"
5
+ end
6
+
7
+ def receive(data)
8
+ if data["clear"]
9
+ RequestBuffer.clear!
10
+ end
11
+
12
+ if data["ids"].present?
13
+ RequestBuffer.remove(data["ids"])
14
+ end
15
+
16
+ Debugbar.connect!
17
+
18
+ data = RequestBuffer.all.map(&:to_h)
19
+ ActionCable.server.broadcast("debugbar_channel", data)
20
+ end
21
+
22
+ def unsubscribed
23
+ Debugbar.disconnect!
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ module Debugbar
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module Debugbar
2
+ class AssetsController < ApplicationController
3
+ def js
4
+ render file: File.join(Gem.loaded_specs['debugbar'].full_gem_path, 'public', 'debugbar.js'),
5
+ layout: false,
6
+ content_type: 'text/javascript'
7
+ end
8
+
9
+ private
10
+
11
+ def verify_same_origin_request
12
+ true # YOLO
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ module Debugbar::TagHelpers
2
+ def debugbar_javascript(opt = {})
3
+ html = <<-HTML
4
+ <div id="__debugbar"></div>
5
+ HTML
6
+
7
+ html += <<-HTML
8
+ <script type="text/javascript">
9
+ window._debugbarConfigOptions = #{opt.to_json}
10
+ </script>
11
+ HTML
12
+
13
+ html += <<-HTML
14
+ <script defer src="#{Debugbar.config.prefix}/assets/script"></script>
15
+ HTML
16
+
17
+ raw html
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module Debugbar
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
data/build.sh ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ rm -rf client/dist
4
+ (cd client && npm run build)
5
+ cp client/dist/assets/debugbar-*.js public/debugbar.js
6
+
7
+ gem build debugbar.gemspec
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ Debugbar::Engine.routes.draw do
2
+ mount ActionCable.server => '/cable'
3
+
4
+ # TODO: Silence logs for these routes if `::Rails.application.config.assets.quiet` is true
5
+ get 'assets/style' => "assets#css", format: :css
6
+ get 'assets/script' => "assets#js"
7
+ end
data/debugbar.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/debugbar/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "debugbar"
7
+ spec.version = Debugbar::VERSION
8
+ spec.authors = ["Julien Bourdeau"]
9
+ spec.email = ["julien@debugbar.dev"]
10
+
11
+ spec.summary = "Powerful devtools for Ruby on Rails"
12
+ spec.description = "Get a better understanding of your application performance and behavior (SQL queries, jobs, cache, routes, logs, etc)"
13
+ spec.homepage = "https://debugbar.dev"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/julienbourdeau/debugbar"
19
+ spec.metadata["changelog_uri"] = "https://debugbar.dev/changelog/"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ files_to_remove = %w[
24
+ bin/ test/ spec/ features/ fixtures/
25
+ client/
26
+ .git .circleci appveyor Gemfile package.json package-lock.json
27
+ ]
28
+ spec.files = Dir.chdir(__dir__) do
29
+ `git ls-files -z`.split("\x0").reject do |f|
30
+ (File.expand_path(f) == __FILE__) ||
31
+ f.start_with?(*files_to_remove)
32
+ end
33
+ end
34
+ spec.bindir = "exe"
35
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_dependency "rails" #, ">= 7.1.1"
39
+ spec.add_dependency "actioncable" #, ">= 7.1.1"
40
+
41
+ # For more information and examples about making a new gem, check out our
42
+ # guide at: https://bundler.io/guides/creating_gem.html
43
+ end
@@ -0,0 +1,34 @@
1
+ module Debugbar
2
+ class MemoryBuffer
3
+ def initialize
4
+ @collection = {}
5
+ end
6
+
7
+ def push(request)
8
+ @collection[request.id] = request
9
+ nil
10
+ end
11
+
12
+ def remove(ids)
13
+ ids = Array.wrap(ids)
14
+ ids.each do |id|
15
+ @collection.delete(id)
16
+ end
17
+ :self
18
+ end
19
+
20
+ def all
21
+ @collection.values
22
+ end
23
+
24
+ def each(&block)
25
+ @collection.each(&block)
26
+ :self
27
+ end
28
+
29
+ def clear!
30
+ @collection = {}
31
+ :self
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ module Debugbar
2
+ class NullBuffer
3
+ def push(_request)
4
+ end
5
+
6
+ def remove(_ids)
7
+ end
8
+
9
+ def all
10
+ end
11
+
12
+ def each
13
+ end
14
+
15
+ def clear!
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ module Debugbar
2
+ class RequestBuffer
3
+ class << self
4
+ def init(adapter)
5
+ @enabled = false
6
+ @adapter = adapter
7
+ end
8
+
9
+ def enable!
10
+ @enabled = true
11
+ send_all
12
+ end
13
+
14
+ def disable!
15
+ @enabled = false
16
+ end
17
+
18
+ def send_all
19
+ data = @collection.values.map(&:to_h)
20
+ ActionCable.server.broadcast("debugbar_channel", data)
21
+ end
22
+
23
+ %w(push each all remove clear!).each do |name|
24
+ define_method(name) do |*args, &block|
25
+ ret = @adapter.send(name, *args, &block)
26
+ ret == :self ? self : ret
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ module Debugbar
2
+ class Config
3
+ attr_accessor :enabled, :prefix, :buffer_adapter, :ignore_request,
4
+ :active_record, :action_controller, :active_job,
5
+ :min_log_level
6
+
7
+ alias_method :enabled?, :enabled
8
+
9
+ def initialize(options = {})
10
+ opt = defaults.merge options
11
+ @enabled = opt[:enabled] && !defined?(Rails::Console)
12
+ @prefix = opt[:prefix]
13
+ @active_record = opt[:active_record]
14
+ @action_controller = opt[:action_controller]
15
+ @active_job = opt[:active_job]
16
+ @min_log_level = opt[:min_log_level]
17
+ @buffer_adapter = opt[:buffer_adapter]
18
+ end
19
+
20
+ def defaults
21
+ {
22
+ enabled: Rails.env.development?,
23
+ prefix: "/_debugbar",
24
+ active_record: true,
25
+ action_controller: true,
26
+ active_job: true,
27
+ min_log_level: -1,
28
+ buffer_adapter: :memory,
29
+ }
30
+ end
31
+
32
+ def ignore_request?(env)
33
+ if ignore_request.is_a? Proc
34
+ ignore_request.call(env)
35
+ else
36
+ [Debugbar.config.prefix, "/assets"].any? { |s| env['PATH_INFO'].start_with? s }
37
+ end
38
+ end
39
+
40
+ def use_logger?
41
+ @enabled && @min_log_level >= 0
42
+ end
43
+
44
+ %w(active_record action_controller active_job).each do |name|
45
+ define_method("#{name}?") do
46
+ @enabled && instance_variable_get("@#{name}")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debugbar
4
+ class Current < ActiveSupport::CurrentAttributes
5
+ attribute :request
6
+ attribute :ignore
7
+
8
+ alias_method :ignore?, :ignore
9
+
10
+ def new_request!(request_id)
11
+ self.request = Request.new(request_id)
12
+ end
13
+
14
+ def pop_request!
15
+ request.tap { self.request = nil }
16
+ end
17
+ end
18
+ end
@@ -1,10 +1,93 @@
1
+ require_relative 'config'
2
+ require_relative 'middlewares/track_current_request'
3
+
1
4
  module Debugbar
2
- class Engine < Rails::Engine
3
- # This should fix rails3 rc, and also work with beta
4
- config.autoload_paths << File.expand_path(File.join(File.dirname(__FILE__), "..", "..")) if config.respond_to? :autoload_paths
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace Debugbar
7
+
8
+ initializer 'debugbar.config' do |app|
9
+ app.config.debugbar = ::Debugbar.config
10
+ end
11
+
12
+ initializer 'debugbar.init' do |app|
13
+ adapter = case(app.config.debugbar.buffer_adapter)
14
+ when :memory
15
+ require_relative 'buffers/memory_buffer'
16
+ MemoryBuffer.new
17
+ when :null
18
+ require_relative 'buffers/null_buffer'
19
+ NullBuffer.new
20
+ else
21
+ throw "Invalid RequestBuffer adapter"
22
+ end
23
+
24
+ Debugbar::RequestBuffer.init(adapter)
25
+ end
26
+
27
+ initializer 'debugbar.helper' do
28
+ ActiveSupport.on_load(:action_controller) do
29
+ ActionController::Base.helper(Debugbar::TagHelpers)
30
+ end
31
+
32
+ ActiveSupport.on_load(:action_view) do
33
+ include Debugbar::TagHelpers
34
+ end
35
+ end
36
+
37
+ initializer 'debugbar.inject_middlewares' do |app|
38
+ next unless Debugbar.config.enabled?
39
+ app.middleware.insert_after ActionDispatch::RequestId, Debugbar::TrackCurrentRequest
40
+ end
41
+
42
+ initializer 'debugbar.subscribe' do
43
+ if Debugbar.config.active_record?
44
+ require_relative 'subscribers/active_record'
45
+ subscribe "Debugbar::ActiveRecordEventSubscriber" => "sql.active_record"
46
+ end
47
+
48
+ if Debugbar.config.action_controller?
49
+ require_relative 'subscribers/action_controller'
50
+ subscribe "Debugbar::ActionControllerEventSubscriber" => %w[
51
+ start_processing.action_controller process_action.action_controller
52
+ ]
53
+ end
54
+
55
+ if Debugbar.config.active_job?
56
+ require_relative 'subscribers/active_job'
57
+ subscribe "Debugbar::ActiveJobEventSubscriber" => ["enqueue.active_job", "enqueue_at.active_job"]
58
+ end
59
+
60
+ require_relative 'subscribers/active_support'
61
+ subscribe "Debugbar::ActiveSupportEventSubscriber.cache" => %w(
62
+ cache_read.active_support
63
+ cache_generate.active_support
64
+ cache_fetch_hit.active_support
65
+ cache_write.active_support
66
+ cache_delete.active_support
67
+ cache_exist?.active_support
68
+ )
69
+ end
70
+
71
+ initializer 'debugbar.track_models' do
72
+ next unless Debugbar.config.active_record?
73
+ ActiveSupport.on_load(:active_record) do
74
+ after_initialize do |model|
75
+ Debugbar::Tracker.inc_model(model.class.name)
76
+ end
77
+ end
78
+ end
5
79
 
6
- rake_tasks do
7
- load "debugbar/railties/tasks.rake"
80
+ def subscribe(config)
81
+ config.each do |subscriber, event_names|
82
+ event_names = Array.wrap(event_names)
83
+ class_name, class_method_name = subscriber.split('.')
84
+ event_names.each do |name|
85
+ method_name = class_method_name || name.split('.').first
86
+ ActiveSupport::Notifications.subscribe name do |event|
87
+ class_name.constantize.send method_name, event
88
+ end
89
+ end
90
+ end
8
91
  end
9
92
  end
10
93
  end
@@ -0,0 +1,34 @@
1
+ module Debugbar
2
+ class SimpleLogger < ::Logger
3
+ def initialize(min_level= 2)
4
+ @min_level = min_level
5
+ end
6
+
7
+ def add(severity, message = nil, progname = nil)
8
+ # We normally rely on the Tracker to know if the current request is set or nil and log an error to STDOUT,
9
+ # but in this case, it happens so often that the logs are annoying.
10
+ # In ActiveCable, only the first call goes through the Rake middleware, so every following communication
11
+ # doesn't go through TrackCurrentRequest, which initializes the request.
12
+ return if ::Debugbar::Current.request.nil?
13
+
14
+ return if severity < @min_level
15
+
16
+ if message.nil?
17
+ if block_given?
18
+ message = yield
19
+ else
20
+ message = progname
21
+ progname = nil
22
+ end
23
+ end
24
+
25
+ Debugbar::Tracker.add_log({
26
+ time: Time.now.strftime(Debugbar::TIME_FORMAT),
27
+ severity: severity,
28
+ severity_label: SEV_LABEL[severity],
29
+ message: message,
30
+ progname: progname,
31
+ })
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ module Debugbar
2
+ class TrackCurrentRequest
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ Debugbar::Current.ignore = Debugbar.config.ignore_request?(env)
9
+
10
+ return @app.call(env) if Debugbar::Current.ignore?
11
+
12
+ Debugbar::Current.new_request!(env['action_dispatch.request_id'])
13
+
14
+ res = @app.call(env)
15
+
16
+ # TODO: Remove this if statement?
17
+ if Debugbar::Current.request&.id
18
+ # filename = "#{Time.now.to_i}--#{Debugbar::Current.request.meta.dig(:params, :controller)}_#{Debugbar::Current.request.meta.dig(:params, :action).gsub('/', '_')}.json"
19
+ # File.open(Rails.root.join('_requests', filename), "w") do |f|
20
+ # f.write(Debugbar::Current.request.to_json)
21
+ # end
22
+
23
+ RequestBuffer.push(Debugbar::Current.pop_request!)
24
+
25
+ if Debugbar.connected?
26
+ data = RequestBuffer.all.map(&:to_h)
27
+ ActionCable.server.broadcast("debugbar_channel", data)
28
+ end
29
+ end
30
+
31
+ res
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,98 @@
1
+ module Debugbar
2
+ class Request
3
+ attr_reader :request_id, :meta,
4
+ :models, :queries, :jobs,
5
+ :messages, :cache, :logs
6
+ attr_accessor :request, :response, :headers
7
+
8
+ def initialize(request_id)
9
+ @request_id = request_id
10
+ @models = {}
11
+ @queries = []
12
+ @jobs = []
13
+ @messages = []
14
+ @cache = []
15
+ @logs = []
16
+ end
17
+
18
+ alias_method :id, :request_id
19
+
20
+ def meta=(meta)
21
+ meta.delete(:headers)
22
+ meta.delete(:request)
23
+ meta.delete(:response)
24
+ @meta = meta
25
+ end
26
+
27
+ def inc_model(name)
28
+ if @models[name]
29
+ @models[name] += 1
30
+ else
31
+ @models[name] = 1
32
+ end
33
+ end
34
+
35
+ def add_query(query)
36
+ @queries << query
37
+ end
38
+
39
+ def add_job(job)
40
+ @jobs << job
41
+ end
42
+
43
+ def add_msg(msg, extra)
44
+ @messages << {msg: msg, extra: extra}
45
+ end
46
+
47
+ def add_cache(c)
48
+ @cache << c
49
+ end
50
+
51
+ def add_log(l)
52
+ @logs << l
53
+ end
54
+
55
+ def to_h
56
+ {
57
+ id: request_id,
58
+ meta: meta,
59
+ request: request_hash,
60
+ response: response_hash,
61
+ models: models,
62
+ queries: queries,
63
+ jobs: jobs,
64
+ messages: messages,
65
+ cache: cache,
66
+ logs: logs,
67
+ }
68
+ end
69
+
70
+ def to_json
71
+ JSON.pretty_generate(to_h)
72
+ end
73
+
74
+ private
75
+
76
+ def request_hash
77
+ {
78
+ method: request.method,
79
+ path: request.path,
80
+ format: meta.dig(:format),
81
+ params: meta.dig(:params),
82
+ headers: request.env.select { |k,v| k.start_with? 'HTTP_'} # https://stackoverflow.com/a/55406700/1001125
83
+ .transform_keys { |k| k.sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-') }
84
+ .sort.to_h
85
+ }
86
+ end
87
+
88
+ def response_hash
89
+ return nil if response.nil?
90
+
91
+ {
92
+ status: response.status,
93
+ headers: response.headers.to_h.transform_keys { |s| s.split('-').map(&:capitalize).join('-') },
94
+ body: response.body,
95
+ }
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,33 @@
1
+ module Debugbar
2
+ class ActionControllerEventSubscriber
3
+ class << self
4
+ def start_processing(event)
5
+ return if Debugbar::Current.ignore?
6
+
7
+ Debugbar::Tracker.request = event.payload[:request]
8
+ end
9
+
10
+ def process_action(event)
11
+ return if Debugbar::Current.ignore?
12
+
13
+ meta = event.payload
14
+
15
+ meta.delete :headers
16
+ request = meta.delete :request
17
+ response = meta.delete :response
18
+
19
+ meta.merge!({
20
+ duration: event.duration,
21
+ cpu_time: event.cpu_time,
22
+ idle_time: event.idle_time,
23
+ allocations: event.allocations,
24
+ })
25
+
26
+ Debugbar::Tracker.request = request
27
+ Debugbar::Tracker.response = response
28
+ Debugbar::Tracker.meta = meta
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debugbar
4
+ class ActiveJobEventSubscriber
5
+ class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
6
+
7
+ class << self
8
+ def enqueue(event)
9
+ return if Debugbar::Current.ignore?
10
+
11
+ job = event.payload[:job]
12
+ ex = event.payload[:exception_object] || job.enqueue_error
13
+
14
+ logs = []
15
+ logs << if ex
16
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
17
+ elsif event.payload[:aborted]
18
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
19
+ else
20
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
21
+ end
22
+ logs << log_enqueue_source
23
+
24
+ Debugbar::Tracker.add_job({
25
+ id: job.job_id,
26
+ class: job.class.name,
27
+ queue: queue_name(event),
28
+ args: job.arguments.map { |arg| format(arg) },
29
+ successfully_enqueued: job.successfully_enqueued?,
30
+ scheduled_at: scheduled_at(job),
31
+ logs: logs,
32
+ })
33
+ end
34
+ # subscribe_log_level :enqueue, :info # ???
35
+
36
+ def enqueue_at(event)
37
+ return if Debugbar::Current.ignore?
38
+
39
+ job = event.payload[:job]
40
+ ex = event.payload[:exception_object] || job.enqueue_error
41
+
42
+ logs = []
43
+ logs << if ex
44
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
45
+ elsif event.payload[:aborted]
46
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
47
+ else
48
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(job)}" + args_info(job)
49
+ end
50
+ logs << log_enqueue_source
51
+
52
+ Debugbar::Tracker.add_job({
53
+ id: job.job_id,
54
+ class: job.class.name,
55
+ queue: queue_name(event),
56
+ args: job.arguments.map { |arg| format(arg) },
57
+ successfully_enqueued: job.successfully_enqueued?,
58
+ scheduled_at: scheduled_at(job),
59
+ logs: logs,
60
+ })
61
+ end
62
+ # subscribe_log_level :enqueue_at, :info
63
+
64
+ # def enqueue_all(event)
65
+ # info do
66
+ # jobs = event.payload[:jobs]
67
+ # adapter = event.payload[:adapter]
68
+ # enqueued_count = event.payload[:enqueued_count]
69
+ #
70
+ # if enqueued_count == jobs.size
71
+ # enqueued_jobs_message(adapter, jobs)
72
+ # elsif jobs.any?(&:successfully_enqueued?)
73
+ # enqueued_jobs = jobs.select(&:successfully_enqueued?)
74
+ #
75
+ # failed_enqueue_count = jobs.size - enqueued_count
76
+ # if failed_enqueue_count == 0
77
+ # enqueued_jobs_message(adapter, enqueued_jobs)
78
+ # else
79
+ # "#{enqueued_jobs_message(adapter, enqueued_jobs)}. "\
80
+ # "Failed enqueuing #{failed_enqueue_count} #{'job'.pluralize(failed_enqueue_count)}"
81
+ # end
82
+ # else
83
+ # failed_enqueue_count = jobs.size - enqueued_count
84
+ # "Failed enqueuing #{failed_enqueue_count} #{'job'.pluralize(failed_enqueue_count)} "\
85
+ # "to #{ActiveJob.adapter_name(adapter)}"
86
+ # end
87
+ # end
88
+ # end
89
+ # # subscribe_log_level :enqueue_all, :info
90
+
91
+ private
92
+ def queue_name(event)
93
+ ActiveJob.adapter_name(event.payload[:adapter]) + "(#{event.payload[:job].queue_name})"
94
+ end
95
+
96
+ def args_info(job)
97
+ if job.class.log_arguments? && job.arguments.any?
98
+ " with arguments: " +
99
+ job.arguments.map { |arg| format(arg).inspect }.join(", ")
100
+ else
101
+ ""
102
+ end
103
+ end
104
+
105
+ def format(arg)
106
+ case arg
107
+ when Hash
108
+ arg.transform_values { |value| format(value) }
109
+ when Array
110
+ arg.map { |value| format(value) }
111
+ when GlobalID::Identification
112
+ arg.to_global_id rescue arg
113
+ else
114
+ arg
115
+ end
116
+ end
117
+
118
+ def scheduled_at(job)
119
+ Time.at(job.scheduled_at).utc if job.scheduled_at
120
+ end
121
+
122
+ def logger
123
+ ActiveJob::Base.logger
124
+ end
125
+
126
+ def info(progname = nil, &block)
127
+ return unless super
128
+
129
+ if ActiveJob.verbose_enqueue_logs
130
+ log_enqueue_source
131
+ end
132
+ end
133
+
134
+ def error(progname = nil, &block)
135
+ return unless super
136
+
137
+ if ActiveJob.verbose_enqueue_logs
138
+ log_enqueue_source
139
+ end
140
+ end
141
+
142
+ def log_enqueue_source
143
+ extract_enqueue_source_location(caller)
144
+ end
145
+
146
+ def extract_enqueue_source_location(locations)
147
+ backtrace_cleaner.clean(locations.lazy).first
148
+ end
149
+
150
+ def enqueued_jobs_message(adapter, enqueued_jobs)
151
+ enqueued_count = enqueued_jobs.size
152
+ job_classes_counts = enqueued_jobs.map(&:class).tally.sort_by { |_k, v| -v }
153
+ "Enqueued #{enqueued_count} #{'job'.pluralize(enqueued_count)} to #{ActiveJob.adapter_name(adapter)}"\
154
+ " (#{job_classes_counts.map { |klass, count| "#{count} #{klass}" }.join(', ')})"
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debugbar
4
+ class ActiveRecordEventSubscriber
5
+ class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
6
+
7
+ class << self
8
+ def sql(event)
9
+ return if Debugbar::Current.ignore?
10
+
11
+ payload = event.payload
12
+
13
+ return if payload[:name]&.starts_with? "SCHEMA"
14
+
15
+ title = if payload[:async]
16
+ "ASYNC #{payload[:name]} (#{payload[:lock_wait].round(1)}ms) (db time #{event.duration.round(1)}ms)"
17
+ else
18
+ "#{payload[:name] || "Unnamed"} (#{event.duration.round(1)}ms)"
19
+ end
20
+ title = "CACHE #{title}" if payload[:cached]
21
+
22
+ sql = payload[:sql]&.gsub(/\/\*.*\*\//, "") # remove comments
23
+
24
+ binds = nil
25
+ if payload[:binds]&.any?
26
+ casted_params = type_casted_binds(payload[:type_casted_binds])
27
+
28
+ binds = []
29
+ payload[:binds].each_with_index do |attr, i|
30
+ attribute_name = if attr.respond_to?(:name)
31
+ attr.name
32
+ elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
33
+ attr[i].name
34
+ else
35
+ nil
36
+ end
37
+
38
+ filtered_params = filter(attribute_name, casted_params[i])
39
+
40
+ binds << render_bind(attr, filtered_params)
41
+ end
42
+ binds = binds.inspect
43
+ binds.prepend(" ")
44
+ end
45
+
46
+ Debugbar::Tracker.add_query({
47
+ id: event.transaction_id,
48
+ title: title,
49
+ name: payload[:name],
50
+ sql: sql,
51
+ cached: payload[:cached].present?,
52
+ async: payload[:async],
53
+ duration: event.duration.round(1),
54
+ lock_wait: payload[:lock_wait]&.round(1),
55
+ binds: binds,
56
+ source: query_source_location&.split(":in"),
57
+ })
58
+ end
59
+
60
+ def query_source_location
61
+ backtrace_cleaner.clean(caller(3))[0]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,30 @@
1
+ module Debugbar
2
+ class ActiveSupportEventSubscriber
3
+ class << self
4
+ def cache(event)
5
+ evt = event.payload
6
+ evt[:name] = event.name
7
+ evt[:transaction_id] = event.transaction_id
8
+ evt[:time] = Time.at(event.time).strftime(Debugbar::TIME_FORMAT)
9
+ evt[:label] = case event.name
10
+ when "cache_read.active_support"
11
+ "read"
12
+ when "cache_generate.active_support"
13
+ "generate"
14
+ when "cache_fetch_hit.active_support"
15
+ "fetch hit"
16
+ when "cache_write.active_support"
17
+ "write"
18
+ when "cache_delete.active_support"
19
+ "delete"
20
+ when "cache_exist?.active_support"
21
+ "exist?"
22
+ else
23
+ "unknown"
24
+ end
25
+
26
+ Debugbar::Tracker.add_cache evt
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Debugbar
4
+ VERSION = "0.1.1"
5
+ end
data/lib/debugbar.rb CHANGED
@@ -1,8 +1,82 @@
1
- require 'pathname'
2
- require 'debugbar/engine' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
1
+ # frozen_string_literal: true
3
2
 
4
- module DebugBar
5
- def self.root
6
- Pathname.new(File.join(File.dirname(__FILE__), ".."))
3
+ module Debugbar
4
+ autoload :VERSION, "debugbar/version"
5
+ autoload :Current, "debugbar/current"
6
+ autoload :Request, "debugbar/request"
7
+ autoload :RequestBuffer, "debugbar/buffers/request_buffer"
8
+ autoload :SimpleLogger, "debugbar/loggers/simple_logger"
9
+
10
+ TIME_FORMAT = "%H:%M:%S.%L"
11
+
12
+ module Tracker
13
+ class << self
14
+ SETTERS = %i[request response headers meta].freeze
15
+ METHODS = %i[inc_model add_query add_job add_cache add_log].freeze
16
+
17
+ SETTERS.each do |m|
18
+ define_method("#{m}=") do |val|
19
+ if Current.request.nil?
20
+ # TODO: Much, much better logging needed
21
+ puts "The current request is not set yet. Was trying to set #{m}=[#{val.class.name}]."
22
+ else
23
+ Current.request.send("#{m}=", val)
24
+ end
25
+ end
26
+ end
27
+
28
+ METHODS.each do |m|
29
+ define_method(m) do |*args, &block|
30
+ if Current.request
31
+ return Current.request.send(m, *args, &block)
32
+ end
33
+
34
+ if Current.request.nil? && ENV["DEBUGBAR_VERBOSE_MODE"] == "true"
35
+ puts "The current request is not set yet. Was trying to call #{m}(#{args.map{ _1.class.name}.join(',')})."
36
+ pp args
37
+ nil
38
+ end
39
+ end
40
+ end
41
+
42
+ def msg(msg, *extra)
43
+ if Current.request.nil?
44
+ puts "The current request is not set yet. Printing to STDOUT instead."
45
+ puts msg, extra
46
+ else
47
+ Current.request.add_msg(msg, extra)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ include Tracker
54
+
55
+ class << self
56
+ def config
57
+ @config ||= Config.new(enabled: true)
58
+ end
59
+
60
+ def configure
61
+ yield config
62
+ end
63
+
64
+ def connect!
65
+ @connected = true
66
+ end
67
+
68
+ def disconnect!
69
+ @connected = false
70
+ end
71
+
72
+ def connected?
73
+ @connected
74
+ end
75
+
76
+ def msg(msg, *extra)
77
+ Tracker.msg(msg, *extra)
78
+ end
7
79
  end
8
80
  end
81
+
82
+ require 'debugbar/engine'
data/sig/debugbar.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Debugbar
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata CHANGED
@@ -1,59 +1,103 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: debugbar
3
- version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.0.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
6
5
  platform: ruby
7
- authors:
8
- - Andy Jeffries
9
- autorequire:
10
- bindir: bin
6
+ authors:
7
+ - Julien Bourdeau
8
+ autorequire:
9
+ bindir: exe
11
10
  cert_chain: []
12
-
13
- date: 2011-03-17 00:00:00 +00:00
14
- default_executable:
15
- dependencies: []
16
-
17
- description: Debugbar gem for Rails 3
18
- email: andy@andyjeffries.co.uk
11
+ date: 2024-02-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: actioncable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Get a better understanding of your application performance and behavior
42
+ (SQL queries, jobs, cache, routes, logs, etc)
43
+ email:
44
+ - julien@debugbar.dev
19
45
  executables: []
20
-
21
46
  extensions: []
22
-
23
- extra_rdoc_files:
24
- - README.textile
25
- files:
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".prettierrc"
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - app/channels/debugbar/debugbar_channel.rb
54
+ - app/controllers/debugbar/application_controller.rb
55
+ - app/controllers/debugbar/assets_controller.rb
56
+ - app/helpers/debugbar/tag_helpers.rb
57
+ - app/models/debugbar/application_record.rb
58
+ - build.sh
59
+ - config/routes.rb
60
+ - debugbar.gemspec
26
61
  - lib/debugbar.rb
62
+ - lib/debugbar/buffers/memory_buffer.rb
63
+ - lib/debugbar/buffers/null_buffer.rb
64
+ - lib/debugbar/buffers/request_buffer.rb
65
+ - lib/debugbar/config.rb
66
+ - lib/debugbar/current.rb
27
67
  - lib/debugbar/engine.rb
28
- - lib/debugbar/railties/tasks.rake
29
- - README.textile
30
- has_rdoc: true
31
- homepage: http://andyjeffries.co.uk/
32
- licenses: []
33
-
34
- post_install_message:
35
- rdoc_options:
36
- - --charset=UTF-8
37
- require_paths:
68
+ - lib/debugbar/loggers/simple_logger.rb
69
+ - lib/debugbar/middlewares/track_current_request.rb
70
+ - lib/debugbar/request.rb
71
+ - lib/debugbar/subscribers/action_controller.rb
72
+ - lib/debugbar/subscribers/active_job.rb
73
+ - lib/debugbar/subscribers/active_record.rb
74
+ - lib/debugbar/subscribers/active_support.rb
75
+ - lib/debugbar/version.rb
76
+ - sig/debugbar.rbs
77
+ homepage: https://debugbar.dev
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ homepage_uri: https://debugbar.dev
82
+ source_code_uri: https://github.com/julienbourdeau/debugbar
83
+ changelog_uri: https://debugbar.dev/changelog/
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
38
87
  - lib
39
- required_ruby_version: !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
42
90
  - - ">="
43
- - !ruby/object:Gem::Version
44
- version: "0"
45
- required_rubygems_version: !ruby/object:Gem::Requirement
46
- none: false
47
- requirements:
91
+ - !ruby/object:Gem::Version
92
+ version: 2.6.0
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
48
95
  - - ">="
49
- - !ruby/object:Gem::Version
50
- version: "0"
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
51
98
  requirements: []
52
-
53
- rubyforge_project:
54
- rubygems_version: 1.6.1
55
- signing_key:
56
- specification_version: 3
57
- summary: Debugbar gem for Rails 3
99
+ rubygems_version: 3.4.20
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Powerful devtools for Ruby on Rails
58
103
  test_files: []
59
-
data/README.textile DELETED
@@ -1,18 +0,0 @@
1
- h1. Debug Bar Gem v0.0.1
2
-
3
- h2. Installation
4
-
5
- Add the following to your Gemfile:
6
-
7
- <pre><code>gem 'debugbar'</code></pre>
8
-
9
- And restart your application with:
10
-
11
- <pre><code>bundle install
12
- touch tmp/restart.txt</code></pre>
13
-
14
- h2. Introduction
15
-
16
- DebugBar provides a Django/Symfony like Debug Bar for Ruby on Rails 3.
17
-
18
- This project is only in it's infancy...
@@ -1 +0,0 @@
1
- # Put your custom rake tasks here