factorial-debugbar 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
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!(SecureRandom.uuid)
13
+
14
+ res = @app.call(env)
15
+
16
+ # TODO: Remove this if statement?
17
+ # We check meta because the frontend doesn't support request without meta yet.
18
+ # It might happen with ActionController::Live where the following code
19
+ # will run BEFORE ActionControllerEventSubscriber.process_action is called
20
+ if Debugbar::Current.request&.meta
21
+ # filename = "#{Time.now.to_i}--#{Debugbar::Current.request.meta.dig(:params, :controller)}_#{Debugbar::Current.request.meta.dig(:params, :action).gsub('/', '_')}.json"
22
+ # File.open(Rails.root.join('_requests', filename), "w") do |f|
23
+ # f.write(Debugbar::Current.request.to_json)
24
+ # end
25
+
26
+ RequestBuffer.push(Debugbar::Current.pop_request!)
27
+
28
+ # TODO: Refactor has not having ActionCable might more common than I thought
29
+ if Debugbar.connected? && defined?(ActionCable)
30
+ data = RequestBuffer.all.map(&:to_h)
31
+ ActionCable.server.broadcast("debugbar_channel", data)
32
+ end
33
+ end
34
+
35
+ res
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,99 @@
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, source)
44
+ @messages << {id: SecureRandom.hex(8), msg: msg, extra: extra, source: source}
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
+ return nil unless response&.body
91
+
92
+ {
93
+ status: response.status,
94
+ headers: response.headers.to_h.transform_keys { |s| s.split('-').map(&:capitalize).join('-') },
95
+ body: response.body,
96
+ }
97
+ end
98
+ end
99
+ 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,168 @@
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 #{adapter_name(adapter)}"
86
+ # end
87
+ # end
88
+ # end
89
+ # # subscribe_log_level :enqueue_all, :info
90
+
91
+ private
92
+
93
+ # See ActiveJob.adapter_name for orignal implementation. This is redefined here
94
+ # to be compatible with the older Rails version.
95
+ def adapter_name(adapter) # :nodoc:
96
+ return adapter.queue_adapter_name if adapter.respond_to?(:queue_adapter_name)
97
+
98
+ adapter_class = adapter.is_a?(Module) ? adapter : adapter.class
99
+ "#{adapter_class.name.demodulize.delete_suffix('Adapter')}"
100
+ end
101
+
102
+ def queue_name(event)
103
+ adapter_name(event.payload[:adapter]) + "(#{event.payload[:job].queue_name})"
104
+ end
105
+
106
+ def args_info(job)
107
+ if job.class.log_arguments? && job.arguments.any?
108
+ " with arguments: " +
109
+ job.arguments.map { |arg| format(arg).inspect }.join(", ")
110
+ else
111
+ ""
112
+ end
113
+ end
114
+
115
+ def format(arg)
116
+ case arg
117
+ when Hash
118
+ arg.transform_values { |value| format(value) }
119
+ when Array
120
+ arg.map { |value| format(value) }
121
+ when GlobalID::Identification
122
+ arg.to_global_id rescue arg
123
+ else
124
+ arg
125
+ end
126
+ end
127
+
128
+ def scheduled_at(job)
129
+ Time.at(job.scheduled_at).utc if job.scheduled_at
130
+ end
131
+
132
+ def logger
133
+ ActiveJob::Base.logger
134
+ end
135
+
136
+ def info(progname = nil, &block)
137
+ return unless super
138
+
139
+ if ActiveJob.verbose_enqueue_logs
140
+ log_enqueue_source
141
+ end
142
+ end
143
+
144
+ def error(progname = nil, &block)
145
+ return unless super
146
+
147
+ if ActiveJob.verbose_enqueue_logs
148
+ log_enqueue_source
149
+ end
150
+ end
151
+
152
+ def log_enqueue_source
153
+ extract_enqueue_source_location(caller)
154
+ end
155
+
156
+ def extract_enqueue_source_location(locations)
157
+ backtrace_cleaner.clean(locations.lazy).first
158
+ end
159
+
160
+ def enqueued_jobs_message(adapter, enqueued_jobs)
161
+ enqueued_count = enqueued_jobs.size
162
+ job_classes_counts = enqueued_jobs.map(&:class).tally.sort_by { |_k, v| -v }
163
+ "Enqueued #{enqueued_count} #{'job'.pluralize(enqueued_count)} to #{adapter_name(adapter)}"\
164
+ " (#{job_classes_counts.map { |klass, count| "#{count} #{klass}" }.join(', ')})"
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,49 @@
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
+ # TODO: Restore binds when I can figure out how to get something in poayload[:binds]
27
+ # end
28
+
29
+ Debugbar::Tracker.add_query({
30
+ id: event.transaction_id,
31
+ title: title,
32
+ name: payload[:name],
33
+ sql: sql,
34
+ cached: payload[:cached].present?,
35
+ async: payload[:async],
36
+ duration: event.duration.round(1),
37
+ lock_wait: payload[:lock_wait]&.round(1),
38
+ binds: binds,
39
+ source: query_source_location
40
+ })
41
+ end
42
+
43
+ def query_source_location
44
+ str = backtrace_cleaner.clean(caller(3))[0]
45
+ str&.split(":in")&.map { |s| s.delete_prefix("#{Rails.root}/").strip.tr("`'", '' ) } # Duplicated in Debugbar.msg
46
+ end
47
+ end
48
+ end
49
+ 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.3.3"
5
+ end
data/lib/debugbar.rb ADDED
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
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
+ return if Debugbar::Current.ignore?
20
+
21
+ if Current.request
22
+ return Current.request.send("#{m}=", val)
23
+ end
24
+
25
+ if Current.request.nil? && ENV["DEBUGBAR_VERBOSE_MODE"] == "true"
26
+ # TODO: Much, much better logging needed
27
+ puts "The current request is not set yet. Was trying to set #{m} = #{val.class.name}."
28
+ pp val
29
+ nil
30
+ end
31
+ end
32
+ end
33
+
34
+ METHODS.each do |m|
35
+ define_method(m) do |*args, &block|
36
+ return if Debugbar::Current.ignore?
37
+
38
+ if Current.request
39
+ return Current.request.send(m, *args, &block)
40
+ end
41
+
42
+ if Current.request.nil? && ENV["DEBUGBAR_VERBOSE_MODE"] == "true"
43
+ puts "The current request is not set yet. Was trying to call #{m}(#{args.map{ _1.class.name}.join(',')})."
44
+ pp args
45
+ nil
46
+ end
47
+ end
48
+ end
49
+
50
+ def msg(msg, extra, source)
51
+ if Current.request.nil?
52
+ puts "The current request is not set yet. Printing to STDOUT instead."
53
+ puts msg, extra, source
54
+ else
55
+ Current.request.add_msg(msg, extra, source)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ include Tracker
62
+
63
+ class << self
64
+ def config
65
+ @config ||= Config.new(enabled: true)
66
+ end
67
+
68
+ def configure
69
+ yield config
70
+ end
71
+
72
+ def connect!
73
+ @connected = true
74
+ end
75
+
76
+ def disconnect!
77
+ @connected = false
78
+ end
79
+
80
+ def connected?
81
+ @connected
82
+ end
83
+
84
+ def msg(msg, *extra)
85
+ source = caller.first&.split(":in")&.map { |s| s.delete_prefix("#{Rails.root}/").strip.tr("`'", '' ) }
86
+ Tracker.msg(msg, extra, source)
87
+ end
88
+ end
89
+ end
90
+
91
+ require 'debugbar/engine'
data/public/.gitignore ADDED
File without changes