activetracker 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +160 -0
- data/LICENSE.txt +21 -0
- data/README.md +399 -0
- data/Rakefile +21 -0
- data/THOUGHTS.md +50 -0
- data/activetracker.gemspec +49 -0
- data/app/assets/config/active_tracker_manifest.js +4 -0
- data/app/assets/images/active_tracker/.keep +0 -0
- data/app/assets/images/active_tracker/logo.svg +6 -0
- data/app/assets/images/active_tracker/reload.svg +10 -0
- data/app/assets/javascripts/active_tracker/active_tracker.js +15 -0
- data/app/assets/javascripts/active_tracker/tabs.js +12 -0
- data/app/assets/javascripts/active_tracker/tags.js +18 -0
- data/app/assets/javascripts/active_tracker/zepto.js +2 -0
- data/app/assets/stylesheets/active_tracker/active_tracker.css.scss +21 -0
- data/app/assets/stylesheets/active_tracker/tailwind.min.scss +1 -0
- data/app/controllers/active_tracker/base_controller.rb +22 -0
- data/app/controllers/active_tracker/dashboard_controller.rb +10 -0
- data/app/controllers/active_tracker/exceptions_controller.rb +41 -0
- data/app/controllers/active_tracker/queries_controller.rb +39 -0
- data/app/controllers/active_tracker/requests_controller.rb +41 -0
- data/app/helpers/active_tracker/application_helper.rb +4 -0
- data/app/helpers/active_tracker/images_helper.rb +8 -0
- data/app/helpers/active_tracker/output_helper.rb +35 -0
- data/app/helpers/active_tracker/pagination_helper.rb +16 -0
- data/app/jobs/active_tracker/application_job.rb +4 -0
- data/app/mailers/active_tracker/application_mailer.rb +6 -0
- data/app/models/active_tracker/application_record.rb +5 -0
- data/app/views/active_tracker/common/_empty.html.erb +9 -0
- data/app/views/active_tracker/common/_pagination.html.erb +22 -0
- data/app/views/active_tracker/common/_plugin_nav.html.erb +10 -0
- data/app/views/active_tracker/dashboard/index.html.erb +18 -0
- data/app/views/active_tracker/exceptions/index.html.erb +44 -0
- data/app/views/active_tracker/exceptions/show.html.erb +56 -0
- data/app/views/active_tracker/queries/index.html.erb +38 -0
- data/app/views/active_tracker/queries/show.html.erb +39 -0
- data/app/views/active_tracker/requests/_request.html.erb +30 -0
- data/app/views/active_tracker/requests/index.html.erb +26 -0
- data/app/views/active_tracker/requests/show.html.erb +81 -0
- data/app/views/layouts/active_tracker/active_tracker.html.erb +46 -0
- data/bin/console +14 -0
- data/bin/rails +25 -0
- data/bin/setup +8 -0
- data/doc/logo.md +11 -0
- data/integration/generators/installer.rb +7 -0
- data/integration/templates/initializer.rb +32 -0
- data/lib/active_tracker.rb +41 -0
- data/lib/active_tracker/configuration.rb +67 -0
- data/lib/active_tracker/engine.rb +24 -0
- data/lib/active_tracker/exception_capturer.rb +38 -0
- data/lib/active_tracker/model.rb +153 -0
- data/lib/active_tracker/output_capturer.rb +37 -0
- data/lib/active_tracker/plugin.rb +4 -0
- data/lib/active_tracker/plugin/base.rb +9 -0
- data/lib/active_tracker/plugin/exception.rb +113 -0
- data/lib/active_tracker/plugin/query.rb +128 -0
- data/lib/active_tracker/plugin/request.rb +163 -0
- data/lib/active_tracker/rails_logger.rb +120 -0
- data/lib/active_tracker/router.rb +16 -0
- data/lib/active_tracker/version.rb +3 -0
- data/lib/activetracker.rb +3 -0
- data/lib/tasks/active_tracker_tasks.rake +12 -0
- data/spec/activetracker_spec.rb +36 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/javascript/packs/application.js +15 -0
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +33 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +37 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +54 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +62 -0
- data/spec/dummy/config/environments/production.rb +107 -0
- data/spec/dummy/config/environments/test.rb +48 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +12 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/disable_remote_forms.rb +1 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/puma.rb +38 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/lib/active_tracker/model_spec.rb +38 -0
- data/spec/spec_helper.rb +18 -0
- metadata +348 -0
@@ -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
|