dontbugme 0.1.0

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +174 -0
  4. data/app/controllers/dontbugme/traces_controller.rb +45 -0
  5. data/app/views/dontbugme/traces/diff.html.erb +18 -0
  6. data/app/views/dontbugme/traces/index.html.erb +30 -0
  7. data/app/views/dontbugme/traces/show.html.erb +15 -0
  8. data/app/views/layouts/dontbugme/application.html.erb +56 -0
  9. data/bin/dontbugme +5 -0
  10. data/lib/dontbugme/cleanup_job.rb +17 -0
  11. data/lib/dontbugme/cli.rb +171 -0
  12. data/lib/dontbugme/config/routes.rb +7 -0
  13. data/lib/dontbugme/configuration.rb +147 -0
  14. data/lib/dontbugme/context.rb +25 -0
  15. data/lib/dontbugme/correlation.rb +25 -0
  16. data/lib/dontbugme/engine.rb +11 -0
  17. data/lib/dontbugme/formatters/diff.rb +187 -0
  18. data/lib/dontbugme/formatters/json.rb +11 -0
  19. data/lib/dontbugme/formatters/timeline.rb +119 -0
  20. data/lib/dontbugme/middleware/rack.rb +37 -0
  21. data/lib/dontbugme/middleware/sidekiq.rb +31 -0
  22. data/lib/dontbugme/middleware/sidekiq_client.rb +14 -0
  23. data/lib/dontbugme/railtie.rb +47 -0
  24. data/lib/dontbugme/recorder.rb +70 -0
  25. data/lib/dontbugme/source_location.rb +44 -0
  26. data/lib/dontbugme/span.rb +70 -0
  27. data/lib/dontbugme/span_collection.rb +40 -0
  28. data/lib/dontbugme/store/async.rb +45 -0
  29. data/lib/dontbugme/store/base.rb +23 -0
  30. data/lib/dontbugme/store/memory.rb +61 -0
  31. data/lib/dontbugme/store/postgresql.rb +186 -0
  32. data/lib/dontbugme/store/sqlite.rb +148 -0
  33. data/lib/dontbugme/subscribers/action_mailer.rb +53 -0
  34. data/lib/dontbugme/subscribers/active_job.rb +44 -0
  35. data/lib/dontbugme/subscribers/active_record.rb +81 -0
  36. data/lib/dontbugme/subscribers/base.rb +19 -0
  37. data/lib/dontbugme/subscribers/cache.rb +54 -0
  38. data/lib/dontbugme/subscribers/net_http.rb +87 -0
  39. data/lib/dontbugme/subscribers/redis.rb +63 -0
  40. data/lib/dontbugme/trace.rb +142 -0
  41. data/lib/dontbugme/version.rb +5 -0
  42. data/lib/dontbugme.rb +118 -0
  43. data/lib/generators/dontbugme/install/install_generator.rb +17 -0
  44. data/lib/generators/dontbugme/install/templates/dontbugme.rb +17 -0
  45. metadata +164 -0
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Subscribers
5
+ class ActionMailer
6
+ EVENT = 'deliver.action_mailer'
7
+
8
+ def self.subscribe
9
+ return unless defined?(::ActionMailer::Base)
10
+
11
+ ::ActiveSupport::Notifications.subscribe(EVENT) do |*args|
12
+ call(*args)
13
+ end
14
+ end
15
+
16
+ def call(_name, start, finish, _id, payload)
17
+ return unless Context.active?
18
+ return unless Dontbugme.config.recording?
19
+
20
+ duration_ms = ((finish - start) * 1000).round(2)
21
+ mailer = payload[:mailer] || payload['mailer']
22
+ action = payload[:action] || payload['action']
23
+ message_id = payload[:message_id] || payload['message_id']
24
+
25
+ detail = "#{mailer}##{action}"
26
+ payload_data = {
27
+ mailer: mailer,
28
+ action: action,
29
+ message_id: message_id
30
+ }
31
+ payload_data[:to] = payload[:to] if payload[:to]
32
+ payload_data[:subject] = truncate(payload[:subject].to_s, 100) if payload[:subject]
33
+
34
+ Recorder.add_span(
35
+ category: :mailer,
36
+ operation: 'deliver',
37
+ detail: detail,
38
+ payload: payload_data,
39
+ duration_ms: duration_ms,
40
+ started_at: start
41
+ )
42
+ end
43
+
44
+ private
45
+
46
+ def truncate(str, max)
47
+ return str if str.length <= max
48
+
49
+ "#{str[0, max]}..."
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Subscribers
5
+ class ActiveJob
6
+ EVENT = 'enqueue.active_job'
7
+
8
+ def self.subscribe
9
+ return unless defined?(::ActiveJob::Base)
10
+
11
+ ::ActiveSupport::Notifications.subscribe(EVENT) do |*args|
12
+ call(*args)
13
+ end
14
+ end
15
+
16
+ def call(_name, start, finish, _id, payload)
17
+ return unless Context.active?
18
+ return unless Dontbugme.config.recording?
19
+
20
+ duration_ms = ((finish - start) * 1000).round(2)
21
+ job_class = payload[:job]&.class&.name || payload[:job_class] || payload['job_class']
22
+ queue = payload[:queue] || payload['queue']
23
+ args = payload[:args] || payload['args'] || []
24
+
25
+ detail = "ENQUEUE #{job_class}"
26
+ payload_data = {
27
+ job: job_class,
28
+ queue: queue,
29
+ args: args.is_a?(Array) ? args.first(5) : args
30
+ }
31
+ payload_data[:scheduled_at] = payload[:scheduled_at] if payload[:scheduled_at]
32
+
33
+ Recorder.add_span(
34
+ category: :enqueue,
35
+ operation: 'ENQUEUE',
36
+ detail: detail,
37
+ payload: payload_data,
38
+ duration_ms: duration_ms,
39
+ started_at: start
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Subscribers
5
+ class ActiveRecord < Base
6
+ EVENT = 'sql.active_record'
7
+
8
+ def self.subscribe
9
+ return unless defined?(::ActiveRecord::Base)
10
+
11
+ ::ActiveSupport::Notifications.subscribe(EVENT) do |*args|
12
+ call(*args)
13
+ end
14
+ end
15
+
16
+ def call(_name, start, finish, _id, payload)
17
+ return unless Context.active?
18
+
19
+ config = Dontbugme.config
20
+ return unless config.recording?
21
+
22
+ duration_ms = ((finish - start) * 1000).round(2)
23
+ sql = payload[:sql] || payload['sql'] || ''
24
+ binds = config.capture_sql_binds ? process_binds(payload) : []
25
+
26
+ operation = extract_operation(sql)
27
+ payload_data = {
28
+ name: payload[:name] || payload['name'],
29
+ connection_id: payload[:connection_id] || payload['connection_id']
30
+ }
31
+ payload_data[:binds] = binds if binds.any?
32
+
33
+ Recorder.add_span(
34
+ category: :sql,
35
+ operation: operation,
36
+ detail: sql,
37
+ payload: payload_data,
38
+ duration_ms: duration_ms,
39
+ started_at: start
40
+ )
41
+ end
42
+
43
+ private
44
+
45
+ def extract_operation(sql)
46
+ return 'UNKNOWN' if sql.nil? || sql.empty?
47
+
48
+ sql = sql.strip.upcase
49
+ if sql.start_with?('SELECT') then 'SELECT'
50
+ elsif sql.start_with?('INSERT') then 'INSERT'
51
+ elsif sql.start_with?('UPDATE') then 'UPDATE'
52
+ elsif sql.start_with?('DELETE') then 'DELETE'
53
+ elsif sql.start_with?('BEGIN') then 'BEGIN'
54
+ elsif sql.start_with?('COMMIT') then 'COMMIT'
55
+ elsif sql.start_with?('ROLLBACK') then 'ROLLBACK'
56
+ elsif sql.start_with?('SAVEPOINT') then 'SAVEPOINT'
57
+ elsif sql.start_with?('RELEASE') then 'RELEASE'
58
+ else 'OTHER'
59
+ end
60
+ end
61
+
62
+ def process_binds(payload)
63
+ binds = payload[:binds] || payload['binds'] || []
64
+ type_casted = payload[:type_casted_binds] || payload['type_casted_binds'] || binds
65
+ max_size = Dontbugme.config.max_sql_bind_size
66
+
67
+ Array(type_casted).map do |val|
68
+ truncate_value(val, max_size)
69
+ end
70
+ end
71
+
72
+ def truncate_value(val, max_size)
73
+ str = val.to_s
74
+ return str if str.bytesize <= max_size
75
+
76
+ truncated = str.byteslice(0, max_size)
77
+ "#{truncated}[truncated, #{str.bytesize}B]"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Subscribers
5
+ class Base
6
+ def self.subscribe
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def self.call(*args)
11
+ new.call(*args)
12
+ end
13
+
14
+ def call(_name, _start, _finish, _id, _payload)
15
+ raise NotImplementedError
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Subscribers
5
+ class Cache
6
+ EVENTS = %w[
7
+ cache_read.active_support
8
+ cache_write.active_support
9
+ cache_delete.active_support
10
+ cache_exist?.active_support
11
+ ].freeze
12
+
13
+ def self.subscribe
14
+ return unless defined?(ActiveSupport::Cache::Store)
15
+
16
+ EVENTS.each do |event|
17
+ ::ActiveSupport::Notifications.subscribe(event) do |*args|
18
+ new.call(*args)
19
+ end
20
+ end
21
+ end
22
+
23
+ def call(name, start, finish, _id, payload)
24
+ return unless Context.active?
25
+ return unless Dontbugme.config.recording?
26
+
27
+ duration_ms = ((finish - start) * 1000).round(2)
28
+ operation = payload[:operation] || payload['operation'] || extract_operation(name)
29
+ key = payload[:key] || payload['key']
30
+ hit = payload[:hit] if payload.key?(:hit) || payload.key?('hit')
31
+
32
+ detail = "cache #{operation} #{key}"
33
+ payload_data = { key: key }
34
+ payload_data[:hit] = hit unless hit.nil?
35
+ payload_data[:super_operation] = payload[:super_operation] if payload[:super_operation]
36
+
37
+ Recorder.add_span(
38
+ category: :cache,
39
+ operation: operation.to_s.upcase,
40
+ detail: detail,
41
+ payload: payload_data,
42
+ duration_ms: duration_ms,
43
+ started_at: start
44
+ )
45
+ end
46
+
47
+ private
48
+
49
+ def extract_operation(event_name)
50
+ event_name.to_s.split('.').first
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Subscribers
5
+ class NetHttp
6
+ def self.subscribe
7
+ return unless defined?(Net::HTTP)
8
+
9
+ Net::HTTP.prepend(Instrumentation)
10
+ end
11
+
12
+ module Instrumentation
13
+ def request(req, body = nil, &block)
14
+ return super unless Dontbugme::Context.active?
15
+ return super unless Dontbugme.config.recording?
16
+
17
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
18
+ start_wall = Time.now
19
+ response = super
20
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time).round(2)
21
+
22
+ record_span(req, response, start_wall, duration_ms)
23
+ response
24
+ rescue StandardError => e
25
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time).round(2)
26
+ record_span(req, nil, start_wall, duration_ms, error: e)
27
+ raise
28
+ end
29
+
30
+ private
31
+
32
+ def record_span(req, response, start_wall, duration_ms, error: nil)
33
+ config = Dontbugme.config
34
+ uri = build_uri(req)
35
+ method = req.method
36
+ detail = "#{method} #{uri}"
37
+ payload = {
38
+ method: method,
39
+ url: uri,
40
+ status: response&.code&.to_i
41
+ }
42
+ payload[:error] = error.message if error
43
+
44
+ if config.capture_http_headers&.any?
45
+ payload[:request_headers] = capture_headers(req, config.capture_http_headers)
46
+ end
47
+
48
+ Dontbugme::Recorder.add_span(
49
+ category: :http,
50
+ operation: method,
51
+ detail: detail,
52
+ payload: payload,
53
+ duration_ms: duration_ms,
54
+ started_at: start_wall
55
+ )
56
+ end
57
+
58
+ def build_uri(req)
59
+ path = req.path.to_s.empty? ? '/' : req.path
60
+ "#{use_ssl? ? 'https' : 'http'}://#{address}:#{port}#{path}"
61
+ end
62
+
63
+ def capture_headers(req, header_names)
64
+ result = {}
65
+ max = Dontbugme.config.max_http_body_size
66
+ header_map = {
67
+ 'content_type' => 'Content-Type',
68
+ 'authorization_type' => 'Authorization',
69
+ 'authorization' => 'Authorization'
70
+ }
71
+ header_names.each do |name|
72
+ key = header_map[name.to_s.downcase] || name.to_s.split('_').map(&:capitalize).join('-')
73
+ val = req[key]
74
+ result[name.to_s] = truncate(val.to_s, max) if val
75
+ end
76
+ result
77
+ end
78
+
79
+ def truncate(str, max)
80
+ return str if str.bytesize <= max
81
+
82
+ "#{str.byteslice(0, max)}[truncated]"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Subscribers
5
+ class Redis
6
+ def self.subscribe
7
+ return unless defined?(::Redis)
8
+ return unless defined?(::Redis::Client)
9
+
10
+ ::Redis::Client.prepend(Instrumentation)
11
+ end
12
+
13
+ module Instrumentation
14
+ def call(command, &block)
15
+ return super unless Dontbugme::Context.active?
16
+ return super unless Dontbugme.config.recording?
17
+
18
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
19
+ start_wall = Time.now
20
+ result = super
21
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time).round(2)
22
+
23
+ record_span(command, start_wall, duration_ms)
24
+ result
25
+ rescue StandardError => e
26
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_time).round(2)
27
+ record_span(command, start_wall, duration_ms, error: e)
28
+ raise
29
+ end
30
+
31
+ private
32
+
33
+ def record_span(command, start_wall, duration_ms, error: nil)
34
+ cmd = Array(command).map(&:to_s)
35
+ operation = cmd.first&.upcase || 'UNKNOWN'
36
+ detail = cmd.join(' ')
37
+ config = Dontbugme.config
38
+
39
+ payload = { command: operation }
40
+ if config.capture_redis_values && cmd.size > 1
41
+ payload[:args] = cmd[1..].map { |a| truncate(a, config.max_redis_value_size) }
42
+ end
43
+ payload[:error] = error.message if error
44
+
45
+ Dontbugme::Recorder.add_span(
46
+ category: :redis,
47
+ operation: operation,
48
+ detail: detail,
49
+ payload: payload,
50
+ duration_ms: duration_ms,
51
+ started_at: start_wall
52
+ )
53
+ end
54
+
55
+ def truncate(str, max)
56
+ return str if str.to_s.bytesize <= max
57
+
58
+ "#{str.to_s.byteslice(0, max)}[truncated]"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Dontbugme
6
+ class Trace
7
+ attr_reader :id, :kind, :identifier, :metadata, :correlation_id
8
+ attr_accessor :status, :error, :truncated_spans_count
9
+
10
+ def initialize(kind:, identifier:, metadata: {})
11
+ @id = "tr_#{SecureRandom.hex(6)}"
12
+ @kind = kind.to_sym
13
+ @identifier = identifier.to_s
14
+ @metadata = metadata
15
+ @correlation_id = metadata[:correlation_id] || metadata['correlation_id']
16
+ @spans = []
17
+ @started_at_utc = Time.now.utc
18
+ @started_at_monotonic = now_monotonic_ms
19
+ @finished_at = nil
20
+ @status = :success
21
+ @error = nil
22
+ @truncated_spans_count = 0
23
+ end
24
+
25
+ def merge_tags!(**tags)
26
+ @metadata.merge!(tags.transform_keys(&:to_sym))
27
+ end
28
+
29
+ def add_span(span)
30
+ config = Dontbugme.config
31
+ max_spans = config.max_spans_per_trace
32
+
33
+ if @spans.size >= max_spans
34
+ @truncated_spans_count += 1
35
+ return
36
+ end
37
+
38
+ @spans << span
39
+ end
40
+
41
+ def spans
42
+ @span_collection ||= SpanCollection.new(@spans)
43
+ end
44
+
45
+ def raw_spans
46
+ @spans.freeze
47
+ end
48
+
49
+ def finish!(error: nil)
50
+ @finished_at = now_monotonic_ms
51
+ @status = error ? :error : :success
52
+ @error = error ? format_error(error) : nil
53
+ end
54
+
55
+ def started_at_utc
56
+ @started_at_utc
57
+ end
58
+
59
+ # Monotonic start time, used for computing span offsets during recording
60
+ def started_at_monotonic
61
+ @started_at_monotonic
62
+ end
63
+
64
+ # Wall-clock start time for computing span offsets from ActiveSupport::Notifications
65
+ def started_at_time
66
+ @started_at_utc
67
+ end
68
+
69
+ def duration_ms
70
+ return @duration_ms_stored if defined?(@duration_ms_stored) && @duration_ms_stored
71
+ return nil unless @finished_at
72
+
73
+ (@finished_at - @started_at_monotonic).round(2)
74
+ end
75
+
76
+ def to_h
77
+ {
78
+ id: id,
79
+ kind: kind,
80
+ identifier: identifier,
81
+ started_at: format_time(started_at_utc),
82
+ finished_at: @finished_at ? format_time(Time.at(@finished_at / 1000.0).utc) : nil,
83
+ duration_ms: duration_ms,
84
+ status: status,
85
+ error: error,
86
+ metadata: metadata,
87
+ correlation_id: correlation_id,
88
+ spans: raw_spans.map(&:to_h),
89
+ truncated_spans_count: truncated_spans_count
90
+ }
91
+ end
92
+
93
+ def self.from_h(hash)
94
+ trace = allocate
95
+ trace.instance_variable_set(:@id, hash[:id] || hash['id'])
96
+ trace.instance_variable_set(:@kind, (hash[:kind] || hash['kind']).to_sym)
97
+ trace.instance_variable_set(:@identifier, hash[:identifier] || hash['identifier'])
98
+ trace.instance_variable_set(:@metadata, (hash[:metadata] || hash['metadata'] || {}).transform_keys(&:to_sym))
99
+ trace.instance_variable_set(:@correlation_id, hash[:correlation_id] || hash['correlation_id'])
100
+ trace.instance_variable_set(:@status, (hash[:status] || hash['status'] || :success).to_sym)
101
+ trace.instance_variable_set(:@error, hash[:error] || hash['error'])
102
+ trace.instance_variable_set(:@truncated_spans_count, hash[:truncated_spans_count] || hash['truncated_spans_count'] || 0)
103
+
104
+ spans_data = hash[:spans] || hash['spans'] || []
105
+ trace.instance_variable_set(:@spans, spans_data.map { |s| Span.from_h(s) })
106
+
107
+ started = hash[:started_at] || hash['started_at']
108
+ trace.instance_variable_set(:@started_at_utc, started ? Time.parse(started.to_s) : nil)
109
+ trace.instance_variable_set(:@started_at_monotonic, 0)
110
+ trace.instance_variable_set(:@finished_at, nil)
111
+ trace.instance_variable_set(:@duration_ms_stored, hash[:duration_ms] || hash['duration_ms'])
112
+ trace
113
+ end
114
+
115
+ # Convenience for trace.spans by category
116
+ def spans_by_category(cat)
117
+ spans.select { |s| s.category == cat.to_sym }
118
+ end
119
+
120
+ def to_timeline(only: nil, slow: nil)
121
+ Formatters::Timeline.format(self, only: only, slow: slow)
122
+ end
123
+
124
+ private
125
+
126
+ def now_monotonic_ms
127
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
128
+ end
129
+
130
+ def format_time(t)
131
+ t.respond_to?(:iso8601) ? t.iso8601(3) : t.utc.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
132
+ end
133
+
134
+ def format_error(err)
135
+ {
136
+ class: err.class.name,
137
+ message: err.message,
138
+ backtrace: err.backtrace&.first(20)
139
+ }
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ VERSION = '0.1.0'
5
+ end
data/lib/dontbugme.rb ADDED
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Dontbugme
6
+ class Error < StandardError; end
7
+
8
+ class << self
9
+ attr_writer :config, :store
10
+
11
+ def config
12
+ @config ||= Configuration.new
13
+ end
14
+
15
+ def configure
16
+ yield config
17
+ end
18
+
19
+ def store
20
+ @store ||= build_store
21
+ end
22
+
23
+ def trace(identifier, metadata: {}, &block)
24
+ Recorder.record(kind: :custom, identifier: identifier, metadata: metadata, &block)
25
+ end
26
+
27
+ def span(name, payload: {}, &block)
28
+ return yield unless Context.active?
29
+
30
+ start_mono = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
31
+ start_wall = Time.now
32
+ result = yield
33
+ duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - start_mono).round(2)
34
+
35
+ Recorder.add_span(
36
+ category: :custom,
37
+ operation: 'span',
38
+ detail: name.to_s,
39
+ payload: payload,
40
+ duration_ms: duration_ms,
41
+ started_at: start_wall
42
+ )
43
+ result
44
+ end
45
+
46
+ def snapshot(data)
47
+ return unless Context.active?
48
+
49
+ payload = data.is_a?(Hash) ? data : { value: data }
50
+ Recorder.add_span(
51
+ category: :snapshot,
52
+ operation: 'snapshot',
53
+ detail: 'snapshot',
54
+ payload: payload,
55
+ duration_ms: 0,
56
+ started_at: Time.now
57
+ )
58
+ end
59
+
60
+ def tag(**metadata)
61
+ return unless Context.active?
62
+
63
+ Context.current&.merge_tags!(**metadata)
64
+ end
65
+
66
+ private
67
+
68
+ def build_store
69
+ store = case config.store
70
+ when :sqlite
71
+ Store::Sqlite.new(path: config.sqlite_path)
72
+ when :memory
73
+ Store::Memory.new
74
+ when :postgresql
75
+ conn = config.postgresql_connection || (defined?(ActiveRecord::Base) && ActiveRecord::Base.connection)
76
+ conn ? Store::Postgresql.new(connection: conn) : Store::Sqlite.new(path: config.sqlite_path)
77
+ else
78
+ Store::Sqlite.new(path: config.sqlite_path)
79
+ end
80
+ config.async_store ? Store::Async.new(store) : store
81
+ end
82
+ end
83
+ end
84
+
85
+ require 'dontbugme/version'
86
+ require 'dontbugme/configuration'
87
+ require 'dontbugme/span'
88
+ require 'dontbugme/span_collection'
89
+ require 'dontbugme/trace'
90
+ require 'dontbugme/context'
91
+ require 'dontbugme/source_location'
92
+ require 'dontbugme/recorder'
93
+ require 'dontbugme/subscribers/base'
94
+ require 'dontbugme/subscribers/active_record'
95
+ require 'dontbugme/subscribers/net_http'
96
+ require 'dontbugme/subscribers/redis'
97
+ require 'dontbugme/subscribers/cache'
98
+ require 'dontbugme/subscribers/action_mailer'
99
+ require 'dontbugme/subscribers/active_job'
100
+ require 'dontbugme/store/base'
101
+ require 'dontbugme/store/memory'
102
+ require 'dontbugme/store/sqlite'
103
+ require 'dontbugme/store/postgresql'
104
+ require 'dontbugme/store/async'
105
+ require 'dontbugme/cleanup_job'
106
+ require 'dontbugme/correlation'
107
+ require 'dontbugme/middleware/sidekiq'
108
+ require 'dontbugme/middleware/sidekiq_client'
109
+ require 'dontbugme/middleware/rack'
110
+ require 'dontbugme/formatters/timeline'
111
+ require 'dontbugme/formatters/json'
112
+ require 'dontbugme/formatters/diff'
113
+ require 'dontbugme/cli'
114
+
115
+ # Load Railtie when Rails is present (must be after all other requires)
116
+ if defined?(Rails)
117
+ require 'dontbugme/railtie'
118
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dontbugme
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ def add_route
9
+ route "mount Dontbugme::Engine, at: '/inspector' if Dontbugme.config.enable_web_ui"
10
+ end
11
+
12
+ def create_initializer
13
+ template 'dontbugme.rb', 'config/initializers/dontbugme.rb'
14
+ end
15
+ end
16
+ end
17
+ end