activetracker 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +11 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +160 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +399 -0
  10. data/Rakefile +21 -0
  11. data/THOUGHTS.md +50 -0
  12. data/activetracker.gemspec +49 -0
  13. data/app/assets/config/active_tracker_manifest.js +4 -0
  14. data/app/assets/images/active_tracker/.keep +0 -0
  15. data/app/assets/images/active_tracker/logo.svg +6 -0
  16. data/app/assets/images/active_tracker/reload.svg +10 -0
  17. data/app/assets/javascripts/active_tracker/active_tracker.js +15 -0
  18. data/app/assets/javascripts/active_tracker/tabs.js +12 -0
  19. data/app/assets/javascripts/active_tracker/tags.js +18 -0
  20. data/app/assets/javascripts/active_tracker/zepto.js +2 -0
  21. data/app/assets/stylesheets/active_tracker/active_tracker.css.scss +21 -0
  22. data/app/assets/stylesheets/active_tracker/tailwind.min.scss +1 -0
  23. data/app/controllers/active_tracker/base_controller.rb +22 -0
  24. data/app/controllers/active_tracker/dashboard_controller.rb +10 -0
  25. data/app/controllers/active_tracker/exceptions_controller.rb +41 -0
  26. data/app/controllers/active_tracker/queries_controller.rb +39 -0
  27. data/app/controllers/active_tracker/requests_controller.rb +41 -0
  28. data/app/helpers/active_tracker/application_helper.rb +4 -0
  29. data/app/helpers/active_tracker/images_helper.rb +8 -0
  30. data/app/helpers/active_tracker/output_helper.rb +35 -0
  31. data/app/helpers/active_tracker/pagination_helper.rb +16 -0
  32. data/app/jobs/active_tracker/application_job.rb +4 -0
  33. data/app/mailers/active_tracker/application_mailer.rb +6 -0
  34. data/app/models/active_tracker/application_record.rb +5 -0
  35. data/app/views/active_tracker/common/_empty.html.erb +9 -0
  36. data/app/views/active_tracker/common/_pagination.html.erb +22 -0
  37. data/app/views/active_tracker/common/_plugin_nav.html.erb +10 -0
  38. data/app/views/active_tracker/dashboard/index.html.erb +18 -0
  39. data/app/views/active_tracker/exceptions/index.html.erb +44 -0
  40. data/app/views/active_tracker/exceptions/show.html.erb +56 -0
  41. data/app/views/active_tracker/queries/index.html.erb +38 -0
  42. data/app/views/active_tracker/queries/show.html.erb +39 -0
  43. data/app/views/active_tracker/requests/_request.html.erb +30 -0
  44. data/app/views/active_tracker/requests/index.html.erb +26 -0
  45. data/app/views/active_tracker/requests/show.html.erb +81 -0
  46. data/app/views/layouts/active_tracker/active_tracker.html.erb +46 -0
  47. data/bin/console +14 -0
  48. data/bin/rails +25 -0
  49. data/bin/setup +8 -0
  50. data/doc/logo.md +11 -0
  51. data/integration/generators/installer.rb +7 -0
  52. data/integration/templates/initializer.rb +32 -0
  53. data/lib/active_tracker.rb +41 -0
  54. data/lib/active_tracker/configuration.rb +67 -0
  55. data/lib/active_tracker/engine.rb +24 -0
  56. data/lib/active_tracker/exception_capturer.rb +38 -0
  57. data/lib/active_tracker/model.rb +153 -0
  58. data/lib/active_tracker/output_capturer.rb +37 -0
  59. data/lib/active_tracker/plugin.rb +4 -0
  60. data/lib/active_tracker/plugin/base.rb +9 -0
  61. data/lib/active_tracker/plugin/exception.rb +113 -0
  62. data/lib/active_tracker/plugin/query.rb +128 -0
  63. data/lib/active_tracker/plugin/request.rb +163 -0
  64. data/lib/active_tracker/rails_logger.rb +120 -0
  65. data/lib/active_tracker/router.rb +16 -0
  66. data/lib/active_tracker/version.rb +3 -0
  67. data/lib/activetracker.rb +3 -0
  68. data/lib/tasks/active_tracker_tasks.rake +12 -0
  69. data/spec/activetracker_spec.rb +36 -0
  70. data/spec/dummy/Rakefile +6 -0
  71. data/spec/dummy/app/assets/config/manifest.js +3 -0
  72. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  73. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  74. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  75. data/spec/dummy/app/javascript/packs/application.js +15 -0
  76. data/spec/dummy/app/jobs/application_job.rb +7 -0
  77. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  78. data/spec/dummy/app/models/application_record.rb +3 -0
  79. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  80. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  81. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  82. data/spec/dummy/bin/rails +4 -0
  83. data/spec/dummy/bin/rake +4 -0
  84. data/spec/dummy/bin/setup +33 -0
  85. data/spec/dummy/config.ru +5 -0
  86. data/spec/dummy/config/application.rb +37 -0
  87. data/spec/dummy/config/boot.rb +5 -0
  88. data/spec/dummy/config/database.yml +54 -0
  89. data/spec/dummy/config/environment.rb +5 -0
  90. data/spec/dummy/config/environments/development.rb +62 -0
  91. data/spec/dummy/config/environments/production.rb +107 -0
  92. data/spec/dummy/config/environments/test.rb +48 -0
  93. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  94. data/spec/dummy/config/initializers/assets.rb +12 -0
  95. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  96. data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
  97. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  98. data/spec/dummy/config/initializers/disable_remote_forms.rb +1 -0
  99. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  100. data/spec/dummy/config/initializers/inflections.rb +16 -0
  101. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  102. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  103. data/spec/dummy/config/locales/en.yml +33 -0
  104. data/spec/dummy/config/puma.rb +38 -0
  105. data/spec/dummy/config/routes.rb +3 -0
  106. data/spec/dummy/config/spring.rb +6 -0
  107. data/spec/dummy/config/storage.yml +34 -0
  108. data/spec/dummy/public/404.html +67 -0
  109. data/spec/dummy/public/422.html +67 -0
  110. data/spec/dummy/public/500.html +66 -0
  111. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  112. data/spec/dummy/public/apple-touch-icon.png +0 -0
  113. data/spec/dummy/public/favicon.ico +0 -0
  114. data/spec/lib/active_tracker/model_spec.rb +38 -0
  115. data/spec/spec_helper.rb +18 -0
  116. metadata +348 -0
@@ -0,0 +1,4 @@
1
+ module ActiveTracker
2
+ module Plugin
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveTracker
2
+ module Plugin
3
+ class Base
4
+ def self.nav_url
5
+ "/" + ActiveTracker::Configuration.mountpoint + "/" + resources_name.to_s
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,113 @@
1
+ module ActiveTracker
2
+ module Plugin
3
+ class Exception < Base
4
+ def self.register
5
+ Rails.application.middleware.insert_after BetterErrors::Middleware, ActiveTracker::ExceptionCapturer
6
+
7
+ @@registered = true
8
+ end
9
+
10
+ def self.registered?
11
+ @@registered rescue false
12
+ end
13
+
14
+ def self.resources_name
15
+ :exceptions
16
+ end
17
+
18
+ def self.statistics
19
+ ret = []
20
+ @exceptions = ActiveTracker::Model.all("Exception")
21
+ num_exceptions = @exceptions.count
22
+ exceptions_last_day = @exceptions.select {|e| e.log_at >= 1.day.ago}.count
23
+ exceptions_last_30_minutes = @exceptions.select {|e| e.log_at >= 30.minutes.ago}.count
24
+
25
+ ret << {plugin: self, label: "Last 24 hours", value: exceptions_last_day}
26
+ if exceptions_last_30_minutes == 0
27
+ ret << {plugin: self, label: "Last 30 min", value: exceptions_last_30_minutes}
28
+ else
29
+ ret << {plugin: self, label: "Last 30 min", value: exceptions_last_30_minutes, error: true}
30
+ end
31
+
32
+ ret
33
+ end
34
+
35
+ def self.nav_svg
36
+ svg = <<~EOF
37
+ <svg width="16" height="16" viewBox="0 0 16 16" class="fill-current" xmlns="http://www.w3.org/2000/svg">
38
+ <path d="M13.7656 2.76562L12.1406 4.39062L12.9688 5.21875C13.2625 5.5125 13.2625 5.9875 12.9688 6.27812L12.425 6.82188C12.7937 7.6375 13 8.54375 13 9.49687C13 13.0875 10.0906 15.9969 6.5 15.9969C2.90937 15.9969 0 13.0906 0 9.5C0 5.90938 2.90937 3 6.5 3C7.45312 3 8.35938 3.20625 9.175 3.575L9.71875 3.03125C10.0125 2.7375 10.4875 2.7375 10.7781 3.03125L11.6062 3.85938L13.2312 2.23438L13.7656 2.76562ZM15.625 1.875H14.875C14.6687 1.875 14.5 2.04375 14.5 2.25C14.5 2.45625 14.6687 2.625 14.875 2.625H15.625C15.8313 2.625 16 2.45625 16 2.25C16 2.04375 15.8313 1.875 15.625 1.875ZM13.75 0C13.5437 0 13.375 0.16875 13.375 0.375V1.125C13.375 1.33125 13.5437 1.5 13.75 1.5C13.9563 1.5 14.125 1.33125 14.125 1.125V0.375C14.125 0.16875 13.9563 0 13.75 0ZM14.8094 1.71875L15.3406 1.1875C15.4875 1.04062 15.4875 0.803125 15.3406 0.65625C15.1937 0.509375 14.9563 0.509375 14.8094 0.65625L14.2781 1.1875C14.1312 1.33438 14.1312 1.57187 14.2781 1.71875C14.4281 1.86563 14.6656 1.86563 14.8094 1.71875ZM12.6906 1.71875C12.8375 1.86563 13.075 1.86563 13.2219 1.71875C13.3688 1.57187 13.3688 1.33438 13.2219 1.1875L12.6906 0.65625C12.5437 0.509375 12.3063 0.509375 12.1594 0.65625C12.0125 0.803125 12.0125 1.04062 12.1594 1.1875L12.6906 1.71875ZM14.8094 2.78125C14.6625 2.63438 14.425 2.63438 14.2781 2.78125C14.1312 2.92812 14.1312 3.16563 14.2781 3.3125L14.8094 3.84375C14.9563 3.99062 15.1937 3.99062 15.3406 3.84375C15.4875 3.69688 15.4875 3.45937 15.3406 3.3125L14.8094 2.78125ZM3.5 8.5C3.5 7.39687 4.39687 6.5 5.5 6.5C5.775 6.5 6 6.275 6 6C6 5.725 5.775 5.5 5.5 5.5C3.84687 5.5 2.5 6.84688 2.5 8.5C2.5 8.775 2.725 9 3 9C3.275 9 3.5 8.775 3.5 8.5Z"/>
39
+ </svg>
40
+ EOF
41
+ svg.html_safe
42
+ end
43
+
44
+ def self.nav_title
45
+ "Exceptions"
46
+ end
47
+
48
+ def self.filters=(value)
49
+ @filters = value
50
+ end
51
+
52
+ def self.filters
53
+ @filters ||= []
54
+ end
55
+
56
+ def self.exception_capture(class_name, message, backtrace)
57
+ return if filter_exception?(class_name)
58
+
59
+ tags = {
60
+ class_name: class_name,
61
+ backtrace_hash: Digest::SHA2.hexdigest(backtrace.first.to_s),
62
+ }
63
+
64
+ ActiveTracker::Model.find_or_create("Exception", tags:tags, data_type: "full") do |obj|
65
+ if obj.persisted
66
+ ActiveTracker::Model.delete(obj.key)
67
+ end
68
+ obj.data ||= {}
69
+ obj.data["count"] = (obj.data["count"] || 0) + 1
70
+ # Enough for most git commits to be referenced
71
+ # so should be fine for exception hashes within an application
72
+ obj.id = "E" + Digest::SHA2.hexdigest(tags.inspect)[0,8]
73
+ obj.expiry = 7.days
74
+ obj.log_at = Time.now
75
+
76
+ obj.data["backtrace"] = backtrace
77
+ obj.data["message"] = message
78
+
79
+ obj.data["at_requests"] ||= []
80
+ if ActiveTracker::Plugin::Request.registered?
81
+ id = ActiveTracker::Plugin::Request.current_tags[:id] rescue nil
82
+ obj.data["at_requests"].prepend(id) if id.present?
83
+ obj.data["at_requests"] = obj.data["at_requests"][0,20]
84
+ ActiveTracker::Plugin::Request.current_tags[:at_exceptions] ||= []
85
+ ActiveTracker::Plugin::Request.current_tags[:at_exceptions] << obj.id
86
+ end
87
+ end
88
+ end
89
+
90
+ def self.filter_exception?(class_name)
91
+ ActiveTracker::Plugin::Exception.filters.each do |filter|
92
+ if filter.is_a?(Regexp)
93
+ if filter.match(class_name)
94
+ return true
95
+ end
96
+ elsif filter.is_a?(String)
97
+ if class_name == filter
98
+ return true
99
+ end
100
+ elsif filter.is_a?(Exception)
101
+ if class_name == filter.class.name
102
+ return true
103
+ end
104
+ end
105
+ end
106
+
107
+ false
108
+ end
109
+
110
+
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,128 @@
1
+ require 'digest'
2
+
3
+ module ActiveTracker
4
+ module Plugin
5
+ class Query < Base
6
+ def self.register
7
+ ActiveSupport::Notifications.subscribe "sql.active_record" do |*args|
8
+ event = ActiveSupport::Notifications::Event.new(*args)
9
+ capture_query(event)
10
+ end
11
+
12
+ @@registered = true
13
+ end
14
+
15
+ def self.registered?
16
+ @@registered rescue false
17
+ end
18
+
19
+ def self.resources_name
20
+ :queries
21
+ end
22
+
23
+ def self.nav_svg
24
+ svg = <<~EOF
25
+ <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="database" class="fill-current" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16"><path d="M448 73.143v45.714C448 159.143 347.667 192 224 192S0 159.143 0 118.857V73.143C0 32.857 100.333 0 224 0s224 32.857 224 73.143zM448 176v102.857C448 319.143 347.667 352 224 352S0 319.143 0 278.857V176c48.125 33.143 136.208 48.572 224 48.572S399.874 209.143 448 176zm0 160v102.857C448 479.143 347.667 512 224 512S0 479.143 0 438.857V336c48.125 33.143 136.208 48.572 224 48.572S399.874 369.143 448 336z"></path></svg>
26
+ EOF
27
+ svg.html_safe
28
+ end
29
+
30
+ def self.nav_title
31
+ "Queries"
32
+ end
33
+
34
+ def self.statistics
35
+ ret = []
36
+ queries = ActiveTracker::Model.all("Query")
37
+ queries = queries.select {|e| e.log_at >= 60.minutes.ago}
38
+
39
+ num_queries = 0
40
+ slow_queries = 0
41
+ total_duration = 0
42
+
43
+ queries.each do |query|
44
+ actual_query = ActiveTracker::Model.find(query.key)
45
+ next unless actual_query
46
+ slow_queries += actual_query.count if actual_query.last_duration > self.min_slow_duration_ms
47
+ num_queries += actual_query.count
48
+ total_duration += actual_query.last_duration * actual_query.count
49
+ end
50
+
51
+ ret << {plugin: self, label: "Queries/hour", value: num_queries}
52
+ if slow_queries == 0
53
+ ret << {plugin: self, label: "Slow queries/hour", value: slow_queries}
54
+ else
55
+ ret << {plugin: self, label: "Slow queries/hour", value: slow_queries, error: true}
56
+ end
57
+ ret << {plugin: self, label: "Avg time/query", value: "%.2fms" % (total_duration/num_queries)} if num_queries > 0
58
+
59
+ ret
60
+ end
61
+
62
+ def self.filters=(value)
63
+ @filters = value
64
+ end
65
+
66
+ def self.filters
67
+ @filters ||= ["SCHEMA", /^$/]
68
+ end
69
+
70
+ def self.min_slow_duration_ms=(value)
71
+ @min_slow_duration_ms = value
72
+ end
73
+
74
+ def self.min_slow_duration_ms
75
+ @min_slow_duration_ms ||= 100
76
+ end
77
+
78
+ def self.capture_query(event)
79
+ tags = {
80
+ sql: event.payload[:sql],
81
+ name: event.payload[:name],
82
+ }
83
+
84
+ return if filter_query?(tags)
85
+
86
+ ActiveTracker::Model.find_or_create("Query", tags:tags, data_type: "full") do |obj|
87
+ if obj.persisted
88
+ ActiveTracker::Model.delete(obj.key)
89
+ end
90
+ obj.data ||= {}
91
+ obj.data["last_duration"] = event.duration
92
+ obj.data["count"] = (obj.data["count"] || 0) + 1
93
+ # Enough for most git commits to be referenced
94
+ # so should be fine for SQL query hashes within an application
95
+ obj.id = "Q" + Digest::SHA2.hexdigest(tags.inspect)[0,8]
96
+ obj.expiry = 7.days
97
+ obj.log_at = Time.now
98
+
99
+ obj.data["at_requests"] ||= []
100
+ if ActiveTracker::Plugin::Request.registered?
101
+ id = ActiveTracker::Plugin::Request.current_tags[:id] rescue nil
102
+ obj.data["at_requests"].prepend(id) if id.present?
103
+ obj.data["at_requests"] = obj.data["at_requests"][0,20]
104
+ ActiveTracker::Plugin::Request.current_tags[:at_queries] ||= []
105
+ ActiveTracker::Plugin::Request.current_tags[:at_queries] << obj.id
106
+ end
107
+ end
108
+ end
109
+
110
+ def self.filter_query?(details)
111
+ ActiveTracker::Plugin::Query.filters.each do |filter|
112
+ if filter.is_a?(Regexp)
113
+ if filter.match(details[:sql] || "") || filter.match(details[:name] || "")
114
+ return true
115
+ end
116
+ else
117
+ if (details[:sql] || "").include?(filter) || (details[:name] || "").include?(filter)
118
+ return true
119
+ end
120
+ end
121
+ end
122
+
123
+ false
124
+ end
125
+
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,163 @@
1
+ module ActiveTracker
2
+ module Plugin
3
+ class Request < Base
4
+ def self.register
5
+ @logger = ActiveTracker::RailsLogger.new
6
+ @logger.level = Rails.logger.level
7
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(@logger))
8
+
9
+ ActiveSupport::Notifications.subscribe "start_processing.action_controller" do |event|
10
+ clear_context
11
+ @logger.reset
12
+ tag_current(id: SecureRandom.uuid)
13
+ @output = ""
14
+ end
15
+
16
+ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
17
+ event = ActiveSupport::Notifications::Event.new(*args)
18
+ request_processed(event)
19
+ end
20
+
21
+ Rails.application.middleware.insert_before Rack::Sendfile, ActiveTracker::OutputCapturer
22
+
23
+ @@registered = true
24
+ end
25
+
26
+ def self.registered?
27
+ @@registered rescue false
28
+ end
29
+
30
+ def self.resources_name
31
+ :requests
32
+ end
33
+
34
+ def self.nav_svg
35
+ svg = <<~EOF
36
+ <svg width="16" height="16" viewBox="0 0 16 16" class="fill-current" xmlns="http://www.w3.org/2000/svg">
37
+ <path d="M16 1.5V10.5C16 11.3281 15.3281 12 14.5 12H13V5.5C13 4.12187 11.8781 3 10.5 3H4V1.5C4 0.671875 4.67188 0 5.5 0H14.5C15.3281 0 16 0.671875 16 1.5ZM12 5.5V14.5C12 15.3281 11.3281 16 10.5 16H1.5C0.671875 16 0 15.3281 0 14.5V5.5C0 4.67188 0.671875 4 1.5 4H10.5C11.3281 4 12 4.67188 12 5.5ZM9.875 6.375C9.875 6.16875 9.70625 6 9.5 6H2.375C2.16875 6 2 6.16875 2 6.375V8H9.875V6.375Z" />
38
+ </svg>
39
+ EOF
40
+ svg.html_safe
41
+ end
42
+
43
+ def self.nav_title
44
+ "Requests"
45
+ end
46
+
47
+ def self.statistics
48
+ ret = []
49
+ @requests = ActiveTracker::Model.all("Request")
50
+ @requests = @requests.select {|e| e.log_at >= 60.minutes.ago}
51
+
52
+ num_requests = @requests.count
53
+
54
+ percentage_error = 0
55
+ avg_milliseconds = 0
56
+ if num_requests > 0
57
+ num_errors = @requests.map {|r| r.tags[:status][0]}.select {|s| s=="4" || s=="5"}.count
58
+ percentage_error = num_errors / @requests.count.to_f * 100.0
59
+ avg_milliseconds = @requests.map {|r| r.tags[:duration].to_i}.sum / num_requests
60
+ end
61
+
62
+
63
+ ret << {plugin: self, label: "Requests/hour", value: num_requests}
64
+ if percentage_error < 1.0
65
+ ret << {plugin: self, label: "Error percentage", value: "%.1f%%" % percentage_error}
66
+ else
67
+ ret << {plugin: self, label: "Error percentage", value: "%.1f%%" % percentage_error, error: true}
68
+ end
69
+ ret << {plugin: self, label: "Avg time/request", value: "#{avg_milliseconds}ms"} if avg_milliseconds
70
+
71
+ ret
72
+ end
73
+
74
+ def self.filters=(value)
75
+ @filters = value
76
+ end
77
+
78
+ def self.filters
79
+ @filters ||= ["/#{ActiveTracker::Configuration.mountpoint}"]
80
+ end
81
+
82
+ def self.clear_context
83
+ @tags = {}
84
+ @redactions = []
85
+ end
86
+
87
+ def self.tag_current(tags = {})
88
+ @tags = current_tags.merge(tags)
89
+ end
90
+
91
+ def self.current_tags
92
+ @tags || {}
93
+ end
94
+
95
+ def self.redact(value)
96
+ @redactions << value
97
+ end
98
+
99
+ def self.app_name
100
+ ENV["APP_NAME"] || Rails.application.class.parent.to_s
101
+ end
102
+
103
+ def self.request_processed(event)
104
+ tag_current status: event.payload[:status]
105
+ tag_current duration: "#{@duration.to_i}ms"
106
+ tag_current url: event.payload[:path]
107
+ tag_current method: event.payload[:method]
108
+ tag_current app: app_name
109
+ end
110
+
111
+ def self.output_capture(output)
112
+ @output = output
113
+ end
114
+
115
+ def self.record_duration(duration)
116
+ return if ActiveTracker::Plugin::Request.current_tags[:url] && filter_request?(ActiveTracker::Plugin::Request.current_tags[:url])
117
+
118
+ @duration = duration
119
+ log = @logger.lines[0, 65535] rescue ""
120
+
121
+ _, status, duration = (@logger&.lines || "").force_encoding("UTF-8").match(/Completed (\d+) .*? in (\d+)ms/m).to_a
122
+ tag_current status: status
123
+ tag_current duration: "#{duration.to_i}ms"
124
+
125
+ log = apply_redactions(log)
126
+ @output = apply_redactions(@output)
127
+
128
+ ActiveTracker::Model.save("Request", {log: log, output: @output},
129
+ tags: ActiveTracker::Plugin::Request.current_tags,
130
+ data_type: "full",
131
+ expiry: 7.days,
132
+ log_at: Time.now
133
+ ) if ActiveTracker::Plugin::Request.current_tags.any? && ActiveTracker::Plugin::Request.current_tags[:id].present?
134
+
135
+ clear_context
136
+ end
137
+
138
+ def self.filter_request?(path)
139
+ ActiveTracker::Plugin::Request.filters.each do |filter|
140
+ if filter.is_a?(Regexp)
141
+ if filter.match(path)
142
+ return true
143
+ end
144
+ else
145
+ if path.start_with?(filter)
146
+ return true
147
+ end
148
+ end
149
+ end
150
+
151
+ false
152
+ end
153
+
154
+ def self.apply_redactions(value)
155
+ (@redactions || []).each do |redaction|
156
+ value = value.gsub(redaction, "[REDACTED]")
157
+ end
158
+ value
159
+ end
160
+
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,120 @@
1
+ require "active_support/core_ext/time/conversions"
2
+ require "active_support/core_ext/object/blank"
3
+ require "action_dispatch/http/request"
4
+ require "rack/body_proxy"
5
+
6
+ module ActiveTracker
7
+ class RailsLogger
8
+ def initialize
9
+ reset
10
+ end
11
+
12
+ def reset
13
+ @lines = ""
14
+ end
15
+
16
+ def lines
17
+ @lines
18
+ end
19
+
20
+ def add(severity, message = nil, progname = nil)
21
+ severity ||= UNKNOWN
22
+ return true if severity < level
23
+
24
+ if message.nil?
25
+ if block_given?
26
+ message = yield
27
+ else
28
+ message = progname
29
+ end
30
+ end
31
+
32
+ @lines << "#{message}\n"
33
+ true
34
+ end
35
+ alias log add
36
+
37
+ def debug?; @level <= DEBUG; end
38
+ def debug!; self.level = DEBUG; end
39
+ def info?; @level <= INFO; end
40
+ def info!; self.level = INFO; end
41
+ def warn?; @level <= WARN; end
42
+ def warn!; self.level = WARN; end
43
+ def error?; @level <= ERROR; end
44
+ def error!; self.level = ERROR; end
45
+ def fatal?; @level <= FATAL; end
46
+ def fatal!; self.level = FATAL; end
47
+
48
+ def debug(message)
49
+ message = yield if block_given?
50
+ return if message.blank?
51
+ add(DEBUG, message)
52
+ end
53
+
54
+ def info(message = "")
55
+ message = yield if block_given?
56
+ return if message.blank?
57
+ add(INFO, message)
58
+
59
+ # matches = message.match(/Completed (\d+) .*? in (\d+)ms/)
60
+ # if matches
61
+ # @status = matches[1]
62
+ # @time_taken = matches[2]
63
+ # end
64
+ # @lines << message if info? && @recording
65
+ end
66
+
67
+ def warn(message = "")
68
+ message = yield if block_given?
69
+ return if message.blank?
70
+ add(WARN, message)
71
+ end
72
+
73
+ def error(message = "")
74
+ message = yield if block_given?
75
+ return if message.blank?
76
+ add(ERROR, message)
77
+ end
78
+
79
+ def fatal(message = "")
80
+ message = yield if block_given?
81
+ return if message.blank?
82
+ add(FATAL, message)
83
+ end
84
+
85
+ def level
86
+ @level
87
+ end
88
+
89
+ def level=(severity)
90
+ if severity.is_a?(Integer)
91
+ @level = severity
92
+ else
93
+ case severity.to_s.downcase
94
+ when 'debug'
95
+ @level = DEBUG
96
+ when 'info'
97
+ @level = INFO
98
+ when 'warn'
99
+ @level = WARN
100
+ when 'error'
101
+ @level = ERROR
102
+ when 'fatal'
103
+ @level = FATAL
104
+ when 'unknown'
105
+ @level = UNKNOWN
106
+ else
107
+ raise ArgumentError, "invalid log level: #{severity}"
108
+ end
109
+ end
110
+ end
111
+
112
+ def formatter
113
+ ActiveSupport::Logger::SimpleFormatter
114
+ end
115
+
116
+ def silence(*args)
117
+ yield self
118
+ end
119
+ end
120
+ end