railswatch 1.0.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 (138) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +485 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/railswatch_manifest.js +0 -0
  6. data/app/assets/images/activity.svg +13 -0
  7. data/app/assets/images/bot.svg +1 -0
  8. data/app/assets/images/close.svg +13 -0
  9. data/app/assets/images/details.svg +3 -0
  10. data/app/assets/images/download.svg +3 -0
  11. data/app/assets/images/export.svg +13 -0
  12. data/app/assets/images/external.svg +1 -0
  13. data/app/assets/images/git.svg +1 -0
  14. data/app/assets/images/github.svg +1 -0
  15. data/app/assets/images/home.svg +16 -0
  16. data/app/assets/images/import.svg +13 -0
  17. data/app/assets/images/menu.svg +16 -0
  18. data/app/assets/images/moon.svg +3 -0
  19. data/app/assets/images/stat.svg +1 -0
  20. data/app/assets/images/sun.svg +4 -0
  21. data/app/assets/images/user.svg +1 -0
  22. data/app/controllers/railswatch/base_controller.rb +35 -0
  23. data/app/controllers/railswatch/concerns/csv_exportable.rb +31 -0
  24. data/app/controllers/railswatch/railswatch_controller.rb +183 -0
  25. data/app/engine_assets/javascripts/apex_ext.js +30 -0
  26. data/app/engine_assets/javascripts/application.js +9 -0
  27. data/app/engine_assets/javascripts/autoupdate.js +79 -0
  28. data/app/engine_assets/javascripts/charts.js +279 -0
  29. data/app/engine_assets/javascripts/navbar.js +11 -0
  30. data/app/engine_assets/javascripts/panel.js +43 -0
  31. data/app/engine_assets/javascripts/table.js +12 -0
  32. data/app/engine_assets/javascripts/theme.js +43 -0
  33. data/app/engine_assets/stylesheets/panel.css +111 -0
  34. data/app/engine_assets/stylesheets/responsive.css +102 -0
  35. data/app/engine_assets/stylesheets/style.css +960 -0
  36. data/app/helpers/railswatch/railswatch_helper.rb +338 -0
  37. data/app/views/railswatch/_panel.html.erb +15 -0
  38. data/app/views/railswatch/layouts/railswatch.html.erb +81 -0
  39. data/app/views/railswatch/railswatch/_card.html.erb +7 -0
  40. data/app/views/railswatch/railswatch/_chart.html.erb +13 -0
  41. data/app/views/railswatch/railswatch/_crashes_table_content.html.erb +62 -0
  42. data/app/views/railswatch/railswatch/_custom_events_table_content.html.erb +27 -0
  43. data/app/views/railswatch/railswatch/_delayed_job_table_content.html.erb +52 -0
  44. data/app/views/railswatch/railswatch/_export.html.erb +4 -0
  45. data/app/views/railswatch/railswatch/_grape_requests_table_content.html.erb +31 -0
  46. data/app/views/railswatch/railswatch/_overview.html.erb +124 -0
  47. data/app/views/railswatch/railswatch/_rake_tasks_table_content.html.erb +25 -0
  48. data/app/views/railswatch/railswatch/_recent_requests_table_content.html.erb +28 -0
  49. data/app/views/railswatch/railswatch/_recent_row.html.erb +41 -0
  50. data/app/views/railswatch/railswatch/_requests_table_content.html.erb +51 -0
  51. data/app/views/railswatch/railswatch/_sidekiq_jobs_table_content.html.erb +50 -0
  52. data/app/views/railswatch/railswatch/_summary.html.erb +50 -0
  53. data/app/views/railswatch/railswatch/_table.html.erb +30 -0
  54. data/app/views/railswatch/railswatch/_trace.html.erb +78 -0
  55. data/app/views/railswatch/railswatch/crashes.html.erb +2 -0
  56. data/app/views/railswatch/railswatch/custom.html.erb +6 -0
  57. data/app/views/railswatch/railswatch/delayed_job.html.erb +6 -0
  58. data/app/views/railswatch/railswatch/grape.html.erb +6 -0
  59. data/app/views/railswatch/railswatch/index.html.erb +9 -0
  60. data/app/views/railswatch/railswatch/rake.html.erb +6 -0
  61. data/app/views/railswatch/railswatch/recent.html.erb +2 -0
  62. data/app/views/railswatch/railswatch/requests.html.erb +2 -0
  63. data/app/views/railswatch/railswatch/resources.html.erb +28 -0
  64. data/app/views/railswatch/railswatch/sidekiq.html.erb +6 -0
  65. data/app/views/railswatch/railswatch/slow.html.erb +2 -0
  66. data/app/views/railswatch/railswatch/summary.js.erb +3 -0
  67. data/app/views/railswatch/railswatch/trace.js.erb +9 -0
  68. data/app/views/railswatch/shared/_header.html.erb +39 -0
  69. data/app/views/railswatch/shared/_page_header.html.erb +23 -0
  70. data/config/routes.rb +27 -0
  71. data/lib/generators/railswatch/install/USAGE +19 -0
  72. data/lib/generators/railswatch/install/install_generator.rb +46 -0
  73. data/lib/generators/railswatch/install/templates/create_railswatch_tables.rb +140 -0
  74. data/lib/generators/railswatch/install/templates/initializer.rb +87 -0
  75. data/lib/railswatch/data_source.rb +106 -0
  76. data/lib/railswatch/engine.rb +103 -0
  77. data/lib/railswatch/events/record.rb +63 -0
  78. data/lib/railswatch/extensions/trace.rb +14 -0
  79. data/lib/railswatch/extensions/trace_db.rb +21 -0
  80. data/lib/railswatch/gems/custom_ext.rb +38 -0
  81. data/lib/railswatch/gems/delayed_job_ext.rb +70 -0
  82. data/lib/railswatch/gems/grape_ext.rb +64 -0
  83. data/lib/railswatch/gems/rake_ext.rb +69 -0
  84. data/lib/railswatch/gems/sidekiq_ext.rb +55 -0
  85. data/lib/railswatch/instrument/metrics_collector.rb +70 -0
  86. data/lib/railswatch/interface.rb +9 -0
  87. data/lib/railswatch/models/application_record.rb +31 -0
  88. data/lib/railswatch/models/base_record.rb +59 -0
  89. data/lib/railswatch/models/collection.rb +37 -0
  90. data/lib/railswatch/models/custom_record.rb +32 -0
  91. data/lib/railswatch/models/delayed_job_record.rb +39 -0
  92. data/lib/railswatch/models/event_record.rb +11 -0
  93. data/lib/railswatch/models/grape_record.rb +61 -0
  94. data/lib/railswatch/models/rake_record.rb +33 -0
  95. data/lib/railswatch/models/request_record.rb +105 -0
  96. data/lib/railswatch/models/resource_record.rb +33 -0
  97. data/lib/railswatch/models/sidekiq_record.rb +41 -0
  98. data/lib/railswatch/models/trace_record.rb +21 -0
  99. data/lib/railswatch/pruner.rb +47 -0
  100. data/lib/railswatch/rails/middleware.rb +117 -0
  101. data/lib/railswatch/rails/query_builder.rb +20 -0
  102. data/lib/railswatch/reports/annotations_report.rb +13 -0
  103. data/lib/railswatch/reports/base_report.rb +48 -0
  104. data/lib/railswatch/reports/breakdown_report.rb +11 -0
  105. data/lib/railswatch/reports/crash_report.rb +11 -0
  106. data/lib/railswatch/reports/overview_report.rb +88 -0
  107. data/lib/railswatch/reports/percentile_report.rb +16 -0
  108. data/lib/railswatch/reports/recent_requests_report.rb +21 -0
  109. data/lib/railswatch/reports/requests_report.rb +75 -0
  110. data/lib/railswatch/reports/resources_report.rb +42 -0
  111. data/lib/railswatch/reports/response_time_report.rb +27 -0
  112. data/lib/railswatch/reports/slow_requests_report.rb +21 -0
  113. data/lib/railswatch/reports/throughput_report.rb +16 -0
  114. data/lib/railswatch/reports/trace_report.rb +17 -0
  115. data/lib/railswatch/system_monitor/resources_monitor.rb +88 -0
  116. data/lib/railswatch/thread/current_request.rb +37 -0
  117. data/lib/railswatch/utils.rb +58 -0
  118. data/lib/railswatch/version.rb +7 -0
  119. data/lib/railswatch/widgets/base.rb +17 -0
  120. data/lib/railswatch/widgets/card.rb +19 -0
  121. data/lib/railswatch/widgets/chart.rb +33 -0
  122. data/lib/railswatch/widgets/crashes_table.rb +27 -0
  123. data/lib/railswatch/widgets/custom_events_table.rb +48 -0
  124. data/lib/railswatch/widgets/delayed_job_table.rb +31 -0
  125. data/lib/railswatch/widgets/grape_requests_table.rb +31 -0
  126. data/lib/railswatch/widgets/percentile_card.rb +23 -0
  127. data/lib/railswatch/widgets/rake_tasks_table.rb +31 -0
  128. data/lib/railswatch/widgets/recent_requests_table.rb +35 -0
  129. data/lib/railswatch/widgets/requests_table.rb +27 -0
  130. data/lib/railswatch/widgets/resource_chart.rb +116 -0
  131. data/lib/railswatch/widgets/response_time_chart.rb +29 -0
  132. data/lib/railswatch/widgets/sidekiq_jobs_table.rb +31 -0
  133. data/lib/railswatch/widgets/slow_requests_table.rb +33 -0
  134. data/lib/railswatch/widgets/table.rb +43 -0
  135. data/lib/railswatch/widgets/throughput_chart.rb +29 -0
  136. data/lib/railswatch.rb +184 -0
  137. data/lib/tasks/railswatch.rake +9 -0
  138. metadata +445 -0
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Gems
5
+ class DelayedJobExt
6
+ class Plugin < ::Delayed::Plugin
7
+ callbacks do |lifecycle|
8
+ lifecycle.around(:invoke_job) do |job, *args, &block|
9
+ now = Railswatch::Utils.time
10
+ error = nil
11
+ block.call(job, *args)
12
+ status = 'success'
13
+ rescue Exception => e # rubocop:disable Lint/RescueException
14
+ status = 'error'
15
+ error = e
16
+ raise e
17
+ ensure
18
+ Railswatch::Gems::DelayedJobExt::Plugin.persist(job, now, status, error)
19
+ end
20
+ end
21
+
22
+ def self.persist(job, now, status, error) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
23
+ meta_data = meta(job.payload_object)
24
+ error_bt = error ? error.backtrace&.take(20)&.join("\n") : nil
25
+ record = Railswatch::Models::DelayedJobRecord.new(
26
+ jid: job.id,
27
+ duration: (Railswatch::Utils.time - now) * 1000,
28
+ datetime: now.strftime(Railswatch::FORMAT),
29
+ datetimei: now.to_i,
30
+ source_type: meta_data[0],
31
+ class_name: meta_data[1],
32
+ method_name: meta_data[2],
33
+ status: status,
34
+ job_args: filtered_args(job.payload_object),
35
+ error_message: error&.message,
36
+ error_backtrace: error_bt
37
+ )
38
+ record.save
39
+ CurrentRequest.cleanup
40
+ end
41
+
42
+ # [source_type, class_name, method_name]
43
+ def self.meta(payload_object) # rubocop:disable Metrics/MethodLength
44
+ if payload_object.is_a?(::Delayed::PerformableMethod)
45
+ if payload_object.object.is_a?(Module)
46
+ [:class_method, payload_object.object.name, payload_object.method_name.to_s]
47
+ else
48
+ [:instance_method, payload_object.object.class.name, payload_object.method_name.to_s]
49
+ end
50
+ else
51
+ [:instance_method, payload_object.class.name, 'perform']
52
+ end
53
+ rescue StandardError
54
+ %i[unknown unknown unknown]
55
+ end
56
+
57
+ def self.filtered_args(payload_object)
58
+ raw = payload_object.args if payload_object.respond_to?(:args)
59
+ Railswatch::Utils.filter_params({ 'args' => Array.wrap(raw) })['args']
60
+ rescue StandardError
61
+ nil
62
+ end
63
+ end
64
+
65
+ def self.init
66
+ ::Delayed::Worker.plugins += [::Railswatch::Gems::DelayedJobExt::Plugin]
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Gems
5
+ class GrapeExt
6
+ class << self
7
+ def init
8
+ ActiveSupport::Notifications.subscribe(/grape/) do |name, start, finish, _id, payload|
9
+ handle_grape_notification(name, start, finish, payload)
10
+ end
11
+ end
12
+
13
+ def handle_grape_notification(name, start, finish, payload)
14
+ req = setup_grape_request
15
+ now = Railswatch::Utils.time
16
+ set_record_fields(req, now)
17
+ set_timing_field(req.record, name, start, finish)
18
+ set_payload_fields(req.record, payload[:env]) if payload[:env]
19
+ save_and_cleanup_if_needed(req, name, payload)
20
+ end
21
+
22
+ def setup_grape_request
23
+ req = CurrentRequest.current
24
+ req.ignore.add(:monitoring)
25
+ req.data ||= {}
26
+ req.record ||= Railswatch::Models::GrapeRecord.new(request_id: req.request_id)
27
+ req
28
+ end
29
+
30
+ def set_record_fields(req, now)
31
+ req.record.datetimei ||= now.to_i
32
+ req.record.datetime ||= now.strftime(Railswatch::FORMAT)
33
+ end
34
+
35
+ def save_and_cleanup_if_needed(req, name, payload)
36
+ return unless name == 'format_response.grape' || name_grape_expect_no_content?(req, name, payload)
37
+
38
+ req.record.save
39
+ CurrentRequest.cleanup
40
+ end
41
+
42
+ def name_grape_expect_no_content?(req, name, payload)
43
+ expects_no_content = Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(req.record.status.to_i)
44
+ name == 'endpoint_run.grape' && (payload[:endpoint]&.body.nil? || expects_no_content)
45
+ end
46
+
47
+ def set_timing_field(record, name, start, finish)
48
+ return unless ['endpoint_render.grape', 'endpoint_run.grape', 'format_response.grape'].include?(name)
49
+
50
+ record.send("#{name.tr('.', '_')}=", (finish - start) * 1000)
51
+ end
52
+
53
+ def set_payload_fields(record, env)
54
+ endpoint = env['api.endpoint']
55
+
56
+ record.status = endpoint&.status || env['api.response.status']
57
+ record.format = env['api.format']
58
+ record.method = env['REQUEST_METHOD']
59
+ record.path = env['PATH_INFO']
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Gems
5
+ class RakeExt
6
+ class << self
7
+ def init # rubocop:disable Metrics/MethodLength
8
+ ::Rake::Task.class_eval do
9
+ next if method_defined?(:invoke_with_railswatch)
10
+
11
+ def invoke_with_railswatch(*args)
12
+ now = Railswatch::Utils.time
13
+ status = 'success'
14
+ invoke_without_new_railswatch(*args)
15
+ rescue Exception => e # rubocop:disable Lint/RescueException
16
+ status = 'error'
17
+ raise(e)
18
+ ensure
19
+ Railswatch::Gems::RakeExt.store_invocation(self, args, now, status)
20
+ end
21
+
22
+ alias_method :invoke_without_new_railswatch, :invoke
23
+ alias_method :invoke, :invoke_with_railswatch
24
+
25
+ def invoke(*args) # rubocop:disable Lint/DuplicateMethods
26
+ invoke_with_railswatch(*args)
27
+ end
28
+ end
29
+ end
30
+
31
+ def find_task_name(*args)
32
+ (ARGV + args).compact
33
+ end
34
+
35
+ def store_invocation(task, args, started_at, status)
36
+ return if Railswatch.skipable_rake_tasks.include?(task.name)
37
+ return unless monitoring_storage_available?
38
+
39
+ build_rake_record(task, args, started_at, status).save
40
+ ensure
41
+ CurrentRequest.cleanup
42
+ end
43
+
44
+ def resolved_task_name(task, args)
45
+ task_info = find_task_name(*args)
46
+ task_info.empty? ? [task.name] : task_info
47
+ end
48
+
49
+ def monitoring_storage_available?
50
+ Railswatch::Models::RakeRecord.connection.data_source_exists?(
51
+ Railswatch::Models::RakeRecord.table_name
52
+ )
53
+ rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
54
+ false
55
+ end
56
+
57
+ def build_rake_record(task, args, started_at, status)
58
+ Railswatch::Models::RakeRecord.new(
59
+ task: resolved_task_name(task, args),
60
+ datetime: started_at.strftime(Railswatch::FORMAT),
61
+ datetimei: started_at.to_i,
62
+ duration: (Railswatch::Utils.time - started_at) * 1000,
63
+ status: status
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Gems
5
+ class SidekiqExt
6
+ def initialize(options = nil); end
7
+
8
+ def call(worker, msg, queue) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
9
+ now = Railswatch::Utils.time
10
+ payload = msg.is_a?(Hash) ? msg : {}
11
+ queue_name = queue.respond_to?(:call) ? nil : queue
12
+ record = build_record(worker, payload, queue_name, now)
13
+
14
+ result = yield
15
+ record.status = 'success'
16
+ result
17
+ rescue Exception => e # rubocop:disable Lint/RescueException
18
+ record.status = 'exception'
19
+ record.message = e.message
20
+ record.error_backtrace = e.backtrace&.take(20)&.join("\n")
21
+ raise e
22
+ ensure
23
+ persist_record(record, now)
24
+ end
25
+
26
+ private
27
+
28
+ def persist_record(record, started_at)
29
+ return unless record
30
+
31
+ record.duration = (Railswatch::Utils.time - started_at) * 1000
32
+ record.save
33
+ CurrentRequest.cleanup
34
+ end
35
+
36
+ def build_record(worker, payload, queue_name, now) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
37
+ filtered = Railswatch::Utils.filter_params({ 'args' => Array.wrap(payload['args']) })
38
+ Railswatch::Models::SidekiqRecord.new(
39
+ enqueued_ati: present_to_i(payload['enqueued_at']),
40
+ datetimei: present_to_i(payload['created_at']),
41
+ jid: payload['jid'].presence || SecureRandom.hex(12),
42
+ queue: queue_name.presence || 'default',
43
+ start_timei: now.to_i,
44
+ datetime: now.strftime(Railswatch::FORMAT),
45
+ worker: payload['wrapped'].presence || worker.to_s,
46
+ job_args: filtered['args']
47
+ )
48
+ end
49
+
50
+ def present_to_i(val)
51
+ val.present? ? val.to_i : nil
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Instrument
5
+ class MetricsCollector
6
+ # payload
7
+ # {
8
+ # controller: "PostsController",
9
+ # action: "index",
10
+ # params: {"action" => "index", "controller" => "posts"},
11
+ # headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>,
12
+ # format: :html,
13
+ # method: "GET",
14
+ # path: "/posts",
15
+ # status: 200,
16
+ # view_runtime: 46.848,
17
+ # db_runtime: 0.157
18
+ # }
19
+
20
+ def call(event_name, started, finished, event_id, payload)
21
+ return if Railswatch.skip
22
+ return if CurrentRequest.current.data
23
+
24
+ return if ignored_event?(payload)
25
+
26
+ CurrentRequest.current.data = build_record(
27
+ event_name: event_name,
28
+ started: started,
29
+ finished: finished,
30
+ event_id: event_id,
31
+ payload: payload
32
+ )
33
+ end
34
+
35
+ private
36
+
37
+ def ignored_event?(payload)
38
+ return true if payload[:controller].blank?
39
+
40
+ endpoint = "#{payload[:controller]}##{payload[:action]}"
41
+ Railswatch.ignored_endpoints.include?(endpoint) ||
42
+ Railswatch.ignored_paths.any? { |path| payload[:path].start_with?(path) }
43
+ end
44
+
45
+ def build_record(event_name:, started:, finished:, event_id:, payload:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
46
+ event = notification_event(event_name, started, finished, event_id, payload)
47
+ finished_at = finished.is_a?(Time) ? finished.utc : Time.at(finished).utc
48
+ {
49
+ controller: payload[:controller],
50
+ action: payload[:action],
51
+ format: payload[:format],
52
+ status: payload[:status],
53
+ datetime: finished_at.strftime(Railswatch::FORMAT),
54
+ datetimei: finished_at.to_i,
55
+ method: payload[:method],
56
+ path: payload[:path],
57
+ view_runtime: payload[:view_runtime],
58
+ db_runtime: payload[:db_runtime],
59
+ duration: event.duration,
60
+ exception: payload[:exception],
61
+ exception_object: payload[:exception_object]
62
+ }
63
+ end
64
+
65
+ def notification_event(event_name, started, finished, event_id, payload)
66
+ ActiveSupport::Notifications::Event.new(event_name, started, finished, event_id, payload)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Interface
5
+ def create_event(name:, datetime: Railswatch::Utils.time, options: {})
6
+ Railswatch::Events::Record.create(name: name, datetimei: datetime.to_i, options: options)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Models
5
+ class ApplicationRecord < ActiveRecord::Base
6
+ self.abstract_class = true
7
+
8
+ class << self
9
+ def reset_storage_connection!
10
+ target = Railswatch.database_connection_name.presence&.to_s
11
+ if target.blank?
12
+ remove_connection
13
+ else
14
+ establish_connection(resolve_connection_name(target).to_sym)
15
+ end
16
+ rescue ActiveRecord::ConnectionNotEstablished
17
+ nil
18
+ end
19
+
20
+ private
21
+
22
+ def resolve_connection_name(target)
23
+ configs = ActiveRecord::Base.configurations.configs_for(env_name: ::Rails.env)
24
+ return target if configs.any? { |config| config.name == target }
25
+
26
+ "#{::Rails.env}_#{target}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Railswatch
6
+ module Models
7
+ class BaseRecord < ApplicationRecord
8
+ self.abstract_class = true
9
+
10
+ before_validation :normalize_occurred_at!
11
+
12
+ def datetime
13
+ occurred_at&.utc&.strftime(Railswatch::FORMAT)
14
+ end
15
+
16
+ def datetime=(value)
17
+ @legacy_datetime = value
18
+ end
19
+
20
+ def datetimei
21
+ occurred_at&.to_i
22
+ end
23
+
24
+ def datetimei=(value)
25
+ @legacy_datetimei = value&.to_i
26
+ end
27
+
28
+ def value
29
+ payload_hash
30
+ end
31
+
32
+ def duration
33
+ value['duration']
34
+ end
35
+
36
+ private
37
+
38
+ def payload_hash
39
+ {}
40
+ end
41
+
42
+ def ms(value)
43
+ "#{value.to_f.round(1)} ms" if value
44
+ end
45
+
46
+ def normalize_occurred_at!
47
+ self.occurred_at ||= if @legacy_datetimei.present?
48
+ Time.at(@legacy_datetimei, in: '+00:00')
49
+ elsif @legacy_datetime.present?
50
+ Time.strptime(@legacy_datetime, Railswatch::FORMAT).utc
51
+ else
52
+ Railswatch::Utils.time
53
+ end
54
+ rescue ArgumentError
55
+ self.occurred_at ||= Railswatch::Utils.time
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Models
5
+ class Collection
6
+ attr_reader :data
7
+
8
+ def initialize
9
+ @data = []
10
+ end
11
+
12
+ def add(record)
13
+ @data << record
14
+ end
15
+
16
+ def group_by(type)
17
+ case type
18
+ when :controller_action, :controller_action_format, :datetime, :path
19
+ fetch_values @data.group_by(&type)
20
+ else
21
+ {}
22
+ end
23
+ end
24
+
25
+ def fetch_values(groupped_collection)
26
+ result = {}
27
+ groupped_collection.each do |key, records|
28
+ result[key] ||= []
29
+ records.each do |record|
30
+ result[key] << record.value
31
+ end
32
+ end
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Models
5
+ class CustomRecord < BaseRecord
6
+ self.table_name = 'railswatch_custom_records'
7
+
8
+ def duration
9
+ duration_ms
10
+ end
11
+
12
+ def duration=(value)
13
+ self.duration_ms = value
14
+ end
15
+
16
+ def payload_hash
17
+ { 'duration' => duration_ms }
18
+ end
19
+
20
+ def record_hash
21
+ {
22
+ tag_name: tag_name,
23
+ namespace_name: namespace_name,
24
+ status: status,
25
+ datetimei: occurred_at.to_i,
26
+ datetime: occurred_at,
27
+ duration: duration_ms
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Models
5
+ class DelayedJobRecord < BaseRecord
6
+ self.table_name = 'railswatch_delayed_job_records'
7
+
8
+ serialize :job_args, coder: JSON
9
+
10
+ def duration
11
+ duration_ms
12
+ end
13
+
14
+ def duration=(value)
15
+ self.duration_ms = value
16
+ end
17
+
18
+ def payload_hash
19
+ { 'duration' => duration_ms }
20
+ end
21
+
22
+ def record_hash # rubocop:disable Metrics/MethodLength
23
+ {
24
+ jid: jid,
25
+ datetime: occurred_at,
26
+ datetimei: occurred_at.to_i,
27
+ duration: duration_ms,
28
+ status: status,
29
+ source_type: source_type,
30
+ class_name: class_name,
31
+ method_name: method_name,
32
+ job_args: job_args,
33
+ error_message: error_message,
34
+ error_backtrace: error_backtrace
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Models
5
+ class EventRecord < BaseRecord
6
+ self.table_name = 'railswatch_event_records'
7
+
8
+ serialize :options, coder: JSON
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Models
5
+ class GrapeRecord < BaseRecord
6
+ self.table_name = 'railswatch_grape_records'
7
+
8
+ def method
9
+ http_method
10
+ end
11
+
12
+ def method=(value)
13
+ self.http_method = value
14
+ end
15
+
16
+ def endpoint_render_grape
17
+ endpoint_render_grape_ms
18
+ end
19
+
20
+ def endpoint_render_grape=(value)
21
+ self.endpoint_render_grape_ms = value
22
+ end
23
+
24
+ def endpoint_run_grape
25
+ endpoint_run_grape_ms
26
+ end
27
+
28
+ def endpoint_run_grape=(value)
29
+ self.endpoint_run_grape_ms = value
30
+ end
31
+
32
+ def format_response_grape
33
+ format_response_grape_ms
34
+ end
35
+
36
+ def format_response_grape=(value)
37
+ self.format_response_grape_ms = value
38
+ end
39
+
40
+ def payload_hash
41
+ {
42
+ 'endpoint_render.grape' => endpoint_render_grape_ms,
43
+ 'endpoint_run.grape' => endpoint_run_grape_ms,
44
+ 'format_response.grape' => format_response_grape_ms
45
+ }
46
+ end
47
+
48
+ def record_hash
49
+ {
50
+ format: self.format,
51
+ status: status,
52
+ method: http_method,
53
+ path: path,
54
+ datetime: occurred_at,
55
+ datetimei: occurred_at.to_i,
56
+ request_id: request_id
57
+ }.merge(payload_hash)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railswatch
4
+ module Models
5
+ class RakeRecord < BaseRecord
6
+ self.table_name = 'railswatch_rake_records'
7
+
8
+ serialize :task, coder: JSON
9
+
10
+ def duration
11
+ duration_ms
12
+ end
13
+
14
+ def duration=(value)
15
+ self.duration_ms = value
16
+ end
17
+
18
+ def payload_hash
19
+ { 'duration' => duration_ms }
20
+ end
21
+
22
+ def record_hash
23
+ {
24
+ task: Array.wrap(task),
25
+ datetime: occurred_at,
26
+ datetimei: occurred_at.to_i,
27
+ duration: duration_ms,
28
+ status: status
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end