sapience 0.2.4 → 0.2.5
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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +21 -0
- data/.simplecov +19 -16
- data/.travis.yml +3 -3
- data/CHANGELOG.md +4 -0
- data/Gemfile +0 -8
- data/README.md +5 -0
- data/config/default.yml +4 -0
- data/docker-compose.yml +14 -0
- data/lib/sapience/appender/stream.rb +0 -1
- data/lib/sapience/base.rb +34 -4
- data/lib/sapience/configuration.rb +41 -20
- data/lib/sapience/core_ext/hash.rb +10 -7
- data/lib/sapience/core_ext/symbol.rb +15 -0
- data/lib/sapience/core_ext/thread.rb +1 -0
- data/lib/sapience/extensions/action_cable/tagged_logger_proxy.rb +2 -0
- data/lib/sapience/extensions/action_controller/live.rb +2 -0
- data/lib/sapience/extensions/action_controller/log_subscriber.rb +76 -121
- data/lib/sapience/extensions/action_dispatch/debug_exceptions.rb +2 -0
- data/lib/sapience/extensions/action_view/log_subscriber.rb +16 -6
- data/lib/sapience/extensions/action_view/streaming_template_renderer.rb +2 -0
- data/lib/sapience/extensions/active_job/logging.rb +1 -1
- data/lib/sapience/extensions/active_model_serializers/logging.rb +7 -2
- data/lib/sapience/extensions/active_record/log_subscriber.rb +45 -29
- data/lib/sapience/extensions/rails/rack/logger.rb +1 -0
- data/lib/sapience/extensions/rails/rack/logger_info_as_debug.rb +2 -0
- data/lib/sapience/formatters/color.rb +0 -1
- data/lib/sapience/formatters/default.rb +0 -1
- data/lib/sapience/formatters/json.rb +0 -1
- data/lib/sapience/formatters/raw.rb +0 -1
- data/lib/sapience/log.rb +54 -35
- data/lib/sapience/logger.rb +50 -71
- data/lib/sapience/rails.rb +17 -20
- data/lib/sapience/sapience.rb +23 -27
- data/lib/sapience/subscriber.rb +1 -13
- data/lib/sapience/version.rb +1 -1
- data/lib/sapience.rb +4 -3
- data/sapience.gemspec +6 -1
- data/test_app/Gemfile +7 -0
- data/test_app/Rakefile +2 -0
- data/test_app/app/models/post.rb +1 -1
- data/test_app/app/views/posts/_form.html.slim +18 -0
- data/test_app/app/views/posts/edit.html.slim +8 -0
- data/test_app/app/views/posts/index.html.slim +25 -0
- data/test_app/app/views/posts/new.html.slim +5 -0
- data/test_app/app/views/posts/show.html.slim +15 -0
- data/test_app/app/workers/test_worker.rb +17 -0
- data/test_app/bin/sneakers +10 -0
- data/test_app/config/initializers/sneakers.rb +15 -0
- data/test_app/config/sapience_example.yml +24 -0
- data/test_app/db/migrate/{20160812093621_create_posts.rb → 20160902141445_create_posts.rb} +1 -1
- data/test_app/db/schema.rb +1 -1
- data/test_app/lib/external_sneaker.rb +46 -0
- data/test_app/spec/factories/posts.rb +7 -0
- data/test_app/spec/factories/users.rb +8 -0
- data/test_app/spec/rails_helper.rb +8 -3
- data/test_app/spec/requests/posts_spec.rb +2 -1
- data/test_app/spec/views/posts/edit.html.slim_spec.rb +17 -0
- data/test_app/spec/views/posts/index.html.slim_spec.rb +17 -0
- data/test_app/spec/views/posts/new.html.slim_spec.rb +17 -0
- data/test_app/spec/views/posts/show.html.slim_spec.rb +14 -0
- data/test_app/spec/workers/test_worker_spec.rb +36 -0
- data/test_app.simplecov +19 -0
- metadata +95 -15
- data/.coveralls.yml +0 -1
- data/lib/sapience/extensions/action_controller/log_subscriber_processing.rb +0 -24
- data/test_app/app/views/posts/_form.html.erb +0 -32
- data/test_app/app/views/posts/create.html.erb +0 -2
- data/test_app/app/views/posts/destroy.html.erb +0 -2
- data/test_app/app/views/posts/edit.html.erb +0 -6
- data/test_app/app/views/posts/index.html.erb +0 -31
- data/test_app/app/views/posts/new.html.erb +0 -5
- data/test_app/app/views/posts/show.html.erb +0 -19
- data/test_app/app/views/posts/update.html.erb +0 -2
@@ -1,127 +1,82 @@
|
|
1
1
|
require "action_controller/log_subscriber"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
3
|
+
module Sapience
|
4
|
+
module Extensions
|
5
|
+
module ActionController
|
6
|
+
class LogSubscriber < ::ActionController::LogSubscriber # rubocop:disable ClassLength
|
7
|
+
alias_method :orig_start_processing, :start_processing
|
8
|
+
alias_method :orig_process_action, :process_action
|
9
|
+
|
10
|
+
# Log as debug to hide Processing messages in production
|
11
|
+
def start_processing(event)
|
12
|
+
debug { "Processing ##{event.payload[:action]}" }
|
13
|
+
end
|
14
|
+
|
15
|
+
def process_action(event) # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
|
16
|
+
return unless logger.info?
|
17
|
+
data = request(event.payload)
|
18
|
+
data.merge! runtimes(event)
|
19
|
+
data.merge! exception(event.payload)
|
20
|
+
info(data)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def request(payload) # rubocop:disable AbcSize
|
26
|
+
{
|
27
|
+
method: payload[:method].upcase,
|
28
|
+
request_path: request_path(payload),
|
29
|
+
format: format(payload),
|
30
|
+
status: payload[:status].to_i,
|
31
|
+
controller: payload[:params]["controller"],
|
32
|
+
action: payload[:params]["action"],
|
33
|
+
host: Sapience.config.host,
|
34
|
+
route: "#{payload[:params].delete("controller")}##{payload[:params]["action"]}",
|
35
|
+
message: "Completed ##{payload[:params].delete("action")}",
|
36
|
+
tags: Sapience.tags,
|
37
|
+
params: payload[:params],
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def format(payload)
|
42
|
+
if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR == 0
|
43
|
+
payload[:formats].first
|
44
|
+
else
|
45
|
+
payload[:format]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def runtimes(event)
|
50
|
+
{
|
51
|
+
total: event.duration,
|
52
|
+
view: event.payload[:view_runtime],
|
53
|
+
db: event.payload[:db_runtime],
|
54
|
+
}.each_with_object({}) do |(name, runtime), runtimes|
|
55
|
+
runtimes[:runtimes] ||= {}
|
56
|
+
runtimes[:runtimes][name] = runtime.to_f.round(2) if runtime
|
57
|
+
runtimes
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Monkey patching to enable exception logging
|
62
|
+
def exception(payload)
|
63
|
+
if payload[:exception]
|
64
|
+
exception, message = payload[:exception]
|
65
|
+
message ||= exception.message
|
66
|
+
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception)
|
67
|
+
backtrace = $ERROR_INFO.try(:backtrace).try(:first)
|
68
|
+
backtrace ||= exception.backtrace.first
|
69
|
+
message = "#{exception}\n#{message}\n#{backtrace}"
|
70
|
+
{ status: status, error: message }
|
71
|
+
else
|
72
|
+
{}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def request_path(payload)
|
77
|
+
payload[:path].split("?").first
|
78
|
+
end
|
29
79
|
end
|
30
|
-
|
31
|
-
payload[:message] = "Completed ##{payload[:action]}"
|
32
|
-
payload[:status_message] = Rack::Utils::HTTP_STATUS_CODES[payload[:status]] if payload[:status].present?
|
33
|
-
payload[:duration] = event.duration
|
34
|
-
# Causes excessive log output with Rails 5 RC1
|
35
|
-
payload.delete(:headers)
|
36
|
-
payload
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def halted_callback(event)
|
41
|
-
controller_logger(event).info do
|
42
|
-
"Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def send_file(event)
|
47
|
-
controller_logger(event).info("Sent file") { { path: event.payload[:path], duration: event.duration } }
|
48
|
-
end
|
49
|
-
|
50
|
-
def redirect_to(event)
|
51
|
-
controller_logger(event).info("Redirected to") { { location: event.payload[:location] } }
|
52
|
-
end
|
53
|
-
|
54
|
-
def send_data(event)
|
55
|
-
controller_logger(event).info("Sent data") { { file_name: event.payload[:filename], duration: event.duration } }
|
56
|
-
end
|
57
|
-
|
58
|
-
def unpermitted_parameters(event)
|
59
|
-
controller_logger(event).debug do
|
60
|
-
unpermitted_keys = event.payload[:keys]
|
61
|
-
"Unpermitted parameter#{"s" if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
# Returns the logger for the supplied event.
|
68
|
-
# Returns ActionController::Base.logger if no controller is present
|
69
|
-
def controller_logger(event)
|
70
|
-
if (controller = event.payload[:controller])
|
71
|
-
begin
|
72
|
-
controller.constantize.logger
|
73
|
-
rescue NameError
|
74
|
-
ActionController::Base.logger
|
75
|
-
end
|
76
|
-
else
|
77
|
-
ActionController::Base.logger
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def extract_path(path)
|
82
|
-
index = path.index("?")
|
83
|
-
index ? path[0, index] : path
|
84
|
-
end
|
85
|
-
|
86
|
-
def write_fragment(event)
|
87
|
-
controller_logger(event).info do
|
88
|
-
key_or_path = event.payload[:key] || event.payload[:path]
|
89
|
-
{ message: "Write fragment #{key_or_path}", duration: event.duration }
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def read_fragment(event)
|
94
|
-
controller_logger(event).info do
|
95
|
-
key_or_path = event.payload[:key] || event.payload[:path]
|
96
|
-
{ message: "Read fragment #{key_or_path}", duration: event.duration }
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def exist_fragment(event)
|
101
|
-
controller_logger(event).info do
|
102
|
-
key_or_path = event.payload[:key] || event.payload[:path]
|
103
|
-
{ message: "Exist fragment #{key_or_path}", duration: event.duration }
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def expire_fragment(event)
|
108
|
-
controller_logger(event).info do
|
109
|
-
key_or_path = event.payload[:key] || event.payload[:path]
|
110
|
-
{ message: "Expire fragment #{key_or_path}", duration: event.duration }
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def expire_page(event)
|
115
|
-
controller_logger(event).info do
|
116
|
-
key_or_path = event.payload[:key] || event.payload[:path]
|
117
|
-
{ message: "Expire page #{key_or_path}", duration: event.duration }
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def write_page(event)
|
122
|
-
controller_logger(event).info do
|
123
|
-
key_or_path = event.payload[:key] || event.payload[:path]
|
124
|
-
{ message: "Write page #{key_or_path}", duration: event.duration }
|
125
80
|
end
|
126
81
|
end
|
127
82
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
class ActionDispatch::DebugExceptions # rubocop:disable ClassAndModuleChildren
|
4
4
|
private
|
5
5
|
|
6
|
+
alias_method :orig_log_error, :log_error
|
7
|
+
|
6
8
|
def log_error(_request, wrapper)
|
7
9
|
ActiveSupport::Deprecation.silence do
|
8
10
|
ActionController::Base.logger.fatal(wrapper.exception)
|
@@ -1,9 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require "action_view/log_subscriber"
|
2
|
+
|
3
|
+
module Sapience
|
4
|
+
module Extensions
|
5
|
+
module ActionView
|
6
|
+
class LogSubscriber < ::ActionView::LogSubscriber
|
7
|
+
include Sapience::Loggable
|
8
|
+
|
9
|
+
def info(message = nil, &block)
|
10
|
+
logger.debug(message, &block)
|
11
|
+
end
|
5
12
|
|
6
|
-
|
7
|
-
|
13
|
+
def info?
|
14
|
+
logger.debug?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
8
18
|
end
|
9
19
|
end
|
@@ -1,14 +1,19 @@
|
|
1
1
|
# Patch ActiveModelSerializers logger
|
2
|
+
require "active_model_serializers"
|
2
3
|
require "active_model_serializers/logging"
|
3
4
|
|
4
5
|
module ActiveModelSerializers::Logging # rubocop:disable ClassAndModuleChildren
|
5
|
-
|
6
|
+
def self.included(base)
|
7
|
+
base.send(:include, Sapience::Loggable)
|
8
|
+
end
|
6
9
|
|
7
10
|
private
|
8
11
|
|
9
|
-
alias_method :
|
12
|
+
alias_method :orig_tag_logger, :tag_logger
|
10
13
|
|
11
14
|
def tag_logger(*tags, &block)
|
12
15
|
logger.tagged(*tags, &block)
|
13
16
|
end
|
14
17
|
end
|
18
|
+
|
19
|
+
ActiveModelSerializers.send(:include, Sapience::Loggable)
|
@@ -1,35 +1,51 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
log = {
|
14
|
-
message: name,
|
15
|
-
sql: payload[:sql],
|
16
|
-
duration: event.duration,
|
17
|
-
}
|
18
|
-
unless (payload[:binds] || []).empty?
|
19
|
-
log[:binds] = binds = {}
|
20
|
-
# Changed with Rails 5
|
21
|
-
if Rails.version.to_i >= 5
|
22
|
-
payload[:binds].each do |attr|
|
23
|
-
attr_name, value = render_bind(attr)
|
24
|
-
binds[attr_name] = value
|
1
|
+
require "active_support/notifications"
|
2
|
+
require "active_record/log_subscriber"
|
3
|
+
|
4
|
+
module Sapience
|
5
|
+
module Extensions
|
6
|
+
module ActiveRecord
|
7
|
+
class LogSubscriber < ::ActiveRecord::LogSubscriber
|
8
|
+
include Sapience::Loggable
|
9
|
+
|
10
|
+
def identity(event)
|
11
|
+
lsevent = logstash_event(event)
|
12
|
+
logger << lsevent.to_json + "\n" if logger && lsevent
|
25
13
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
14
|
+
alias_method :sql, :identity
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def logstash_event(event)
|
19
|
+
data = event.payload
|
20
|
+
|
21
|
+
return if "SCHEMA" == data[:name]
|
22
|
+
|
23
|
+
data.merge! runtimes(event)
|
24
|
+
data.merge! extract_sql(data)
|
25
|
+
|
26
|
+
data.merge! tags(data)
|
27
|
+
debug(data)
|
28
|
+
end
|
29
|
+
|
30
|
+
def runtimes(event)
|
31
|
+
if event.duration
|
32
|
+
{ duration: event.duration.to_f.round(2) }
|
33
|
+
else
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def extract_sql(data)
|
39
|
+
{ sql: data[:sql].squeeze(" ") }
|
40
|
+
end
|
41
|
+
|
42
|
+
def tags(data)
|
43
|
+
tags = Sapience.tags.dup
|
44
|
+
tags.push("request")
|
45
|
+
tags.push("exception") if data[:exception]
|
46
|
+
{ tags: tags }
|
30
47
|
end
|
31
48
|
end
|
32
49
|
end
|
33
|
-
debug(log)
|
34
50
|
end
|
35
51
|
end
|
data/lib/sapience/log.rb
CHANGED
@@ -38,7 +38,7 @@ module Sapience
|
|
38
38
|
# Object supplied when measure_x was called
|
39
39
|
#
|
40
40
|
# backtrace [Array<String>]
|
41
|
-
# The
|
41
|
+
# The backtrace_level captured at source when the log level >= Sapience.config.backtrace_level
|
42
42
|
#
|
43
43
|
# metric_amount [Numeric]
|
44
44
|
# Used for numeric or counter metrics.
|
@@ -47,34 +47,17 @@ module Sapience
|
|
47
47
|
# rubocop:disable LineLength
|
48
48
|
Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount) do
|
49
49
|
MAX_EXCEPTIONS_TO_UNWRAP = 5
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
exceptions = []
|
55
|
-
ex = exception
|
56
|
-
while !ex.nil? && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
|
57
|
-
exceptions << ex
|
58
|
-
yield(ex, depth)
|
59
|
-
|
60
|
-
depth += 1
|
61
|
-
ex =
|
62
|
-
if ex.respond_to?(:cause) && ex.cause
|
63
|
-
ex.cause
|
64
|
-
elsif ex.respond_to?(:continued_exception) && ex.continued_exception
|
65
|
-
ex.continued_exception
|
66
|
-
elsif ex.respond_to?(:original_exception) && ex.original_exception
|
67
|
-
ex.original_exception
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
50
|
+
MILLISECONDS_IN_SECOND = 1_000
|
51
|
+
MILLISECONDS_IN_MINUTE = 60_000
|
52
|
+
MILLISECONDS_IN_HOUR = 3_600_000
|
53
|
+
MILLISECONDS_IN_DAY = 86_400_000
|
71
54
|
|
72
55
|
# Returns [String] the exception backtrace including all of the child / caused by exceptions
|
73
56
|
def backtrace_to_s
|
74
57
|
trace = ""
|
75
58
|
each_exception do |exception, i|
|
76
59
|
if i == 0
|
77
|
-
trace
|
60
|
+
trace << (exception.backtrace || []).join("\n")
|
78
61
|
else
|
79
62
|
trace << "\nCause: #{exception.class.name}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
|
80
63
|
end
|
@@ -90,19 +73,28 @@ module Sapience
|
|
90
73
|
end
|
91
74
|
|
92
75
|
# Returns [String] the duration in human readable form
|
93
|
-
def duration_human # rubocop:disable AbcSize
|
76
|
+
def duration_human # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
|
94
77
|
return nil unless duration
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
78
|
+
days, ms = duration.divmod(MILLISECONDS_IN_DAY)
|
79
|
+
hours, ms = ms.divmod(MILLISECONDS_IN_HOUR)
|
80
|
+
minutes, ms = ms.divmod(MILLISECONDS_IN_MINUTE)
|
81
|
+
seconds, ms = ms.divmod(MILLISECONDS_IN_SECOND)
|
82
|
+
|
83
|
+
str = ""
|
84
|
+
str << "#{days}d" if days > 0
|
85
|
+
str << " #{hours}h" if hours > 0
|
86
|
+
str << " #{minutes}m" if minutes > 0
|
87
|
+
str << " #{seconds}s" if seconds > 0
|
88
|
+
str << " #{ms}ms" if ms > 0
|
89
|
+
|
90
|
+
if days > 0 || hours > 0 || minutes > 0
|
91
|
+
str.strip
|
104
92
|
else
|
105
|
-
|
93
|
+
if seconds >= 1.0
|
94
|
+
format "%.3fs", duration / MILLISECONDS_IN_SECOND.to_f
|
95
|
+
else
|
96
|
+
duration_to_s
|
97
|
+
end
|
106
98
|
end
|
107
99
|
end
|
108
100
|
|
@@ -210,7 +202,7 @@ module Sapience
|
|
210
202
|
message: exception.message,
|
211
203
|
stack_trace: exception.backtrace,
|
212
204
|
}
|
213
|
-
root
|
205
|
+
root = root[name]
|
214
206
|
end
|
215
207
|
end
|
216
208
|
|
@@ -218,6 +210,33 @@ module Sapience
|
|
218
210
|
h[:metric] = metric if metric
|
219
211
|
h
|
220
212
|
end
|
213
|
+
|
214
|
+
private
|
215
|
+
|
216
|
+
# Call the block for exception and any nested exception
|
217
|
+
def each_exception # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
218
|
+
# With thanks to https://github.com/bugsnag/bugsnag-ruby/blob/6348306e44323eee347896843d16c690cd7c4362/lib/bugsnag/notification.rb#L81
|
219
|
+
depth = 0
|
220
|
+
exceptions = []
|
221
|
+
ex = exception
|
222
|
+
while !ex.nil? && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
|
223
|
+
exceptions << ex
|
224
|
+
yield(ex, depth)
|
225
|
+
|
226
|
+
depth += 1
|
227
|
+
ex =
|
228
|
+
# continued_exception is only used by REXML
|
229
|
+
# original_exception is deprecated in Rails 5+
|
230
|
+
# Not worth testing to thoroughly?
|
231
|
+
if ex.respond_to?(:cause) && ex.cause
|
232
|
+
ex.cause
|
233
|
+
elsif ex.respond_to?(:continued_exception) && ex.continued_exception
|
234
|
+
ex.continued_exception
|
235
|
+
elsif ex.respond_to?(:original_exception) && ex.original_exception
|
236
|
+
ex.original_exception
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
221
240
|
end
|
222
241
|
# rubocop:enable LineLength
|
223
242
|
end
|