sapience 0.1.1
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.simplecov +15 -0
- data/.travis.yml +27 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/sapience/ansi_colors.rb +27 -0
- data/lib/sapience/appender/file.rb +138 -0
- data/lib/sapience/appender/sentry.rb +72 -0
- data/lib/sapience/appender/statsd.rb +68 -0
- data/lib/sapience/appender/wrapper.rb +74 -0
- data/lib/sapience/base.rb +381 -0
- data/lib/sapience/concerns/compatibility.rb +53 -0
- data/lib/sapience/configuration.rb +86 -0
- data/lib/sapience/core_ext/thread.rb +14 -0
- data/lib/sapience/formatters/base.rb +36 -0
- data/lib/sapience/formatters/color.rb +68 -0
- data/lib/sapience/formatters/default.rb +41 -0
- data/lib/sapience/formatters/json.rb +22 -0
- data/lib/sapience/formatters/raw.rb +12 -0
- data/lib/sapience/log.rb +221 -0
- data/lib/sapience/loggable.rb +29 -0
- data/lib/sapience/logger.rb +236 -0
- data/lib/sapience/rails.rb +15 -0
- data/lib/sapience/sapience.rb +357 -0
- data/lib/sapience/subscriber.rb +127 -0
- data/lib/sapience/version.rb +3 -0
- data/lib/sapience.rb +35 -0
- data/sapience.gemspec +39 -0
- data/test_app/.gitignore +42 -0
- data/test_app/.rspec +2 -0
- data/test_app/.ruby-version +1 -0
- data/test_app/Gemfile +36 -0
- data/test_app/README.md +24 -0
- data/test_app/Rakefile +6 -0
- data/test_app/app/assets/config/manifest.js +2 -0
- data/test_app/app/assets/images/.keep +0 -0
- data/test_app/app/assets/javascripts/posts.js +2 -0
- data/test_app/app/assets/stylesheets/application.css +15 -0
- data/test_app/app/assets/stylesheets/posts.css +4 -0
- data/test_app/app/assets/stylesheets/scaffold.css +84 -0
- data/test_app/app/channels/application_cable/channel.rb +4 -0
- data/test_app/app/channels/application_cable/connection.rb +4 -0
- data/test_app/app/controllers/application_controller.rb +3 -0
- data/test_app/app/controllers/concerns/.keep +0 -0
- data/test_app/app/controllers/posts_controller.rb +58 -0
- data/test_app/app/helpers/application_helper.rb +2 -0
- data/test_app/app/helpers/posts_helper.rb +2 -0
- data/test_app/app/jobs/application_job.rb +2 -0
- data/test_app/app/mailers/application_mailer.rb +4 -0
- data/test_app/app/models/application_record.rb +3 -0
- data/test_app/app/models/concerns/.keep +0 -0
- data/test_app/app/models/post.rb +3 -0
- data/test_app/app/models/user.rb +2 -0
- data/test_app/app/views/layouts/application.html.erb +13 -0
- data/test_app/app/views/layouts/mailer.html.erb +13 -0
- data/test_app/app/views/layouts/mailer.text.erb +1 -0
- data/test_app/app/views/posts/_form.html.erb +32 -0
- data/test_app/app/views/posts/create.html.erb +2 -0
- data/test_app/app/views/posts/destroy.html.erb +2 -0
- data/test_app/app/views/posts/edit.html.erb +6 -0
- data/test_app/app/views/posts/index.html.erb +31 -0
- data/test_app/app/views/posts/new.html.erb +5 -0
- data/test_app/app/views/posts/show.html.erb +19 -0
- data/test_app/app/views/posts/update.html.erb +2 -0
- data/test_app/bin/bundle +3 -0
- data/test_app/bin/rails +4 -0
- data/test_app/bin/rake +4 -0
- data/test_app/bin/setup +34 -0
- data/test_app/bin/update +29 -0
- data/test_app/config/application.rb +26 -0
- data/test_app/config/boot.rb +3 -0
- data/test_app/config/cable.yml +9 -0
- data/test_app/config/database.yml +25 -0
- data/test_app/config/environment.rb +5 -0
- data/test_app/config/environments/development.rb +49 -0
- data/test_app/config/environments/production.rb +78 -0
- data/test_app/config/environments/test.rb +42 -0
- data/test_app/config/initializers/application_controller_renderer.rb +6 -0
- data/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test_app/config/initializers/cookies_serializer.rb +5 -0
- data/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/test_app/config/initializers/inflections.rb +16 -0
- data/test_app/config/initializers/mime_types.rb +4 -0
- data/test_app/config/initializers/new_framework_defaults.rb +24 -0
- data/test_app/config/initializers/session_store.rb +3 -0
- data/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/test_app/config/locales/en.yml +23 -0
- data/test_app/config/puma.rb +45 -0
- data/test_app/config/routes.rb +3 -0
- data/test_app/config/secrets.yml +22 -0
- data/test_app/config.ru +5 -0
- data/test_app/db/migrate/20160812092236_create_users.rb +13 -0
- data/test_app/db/migrate/20160812093621_create_posts.rb +11 -0
- data/test_app/db/schema.rb +33 -0
- data/test_app/db/seeds.rb +7 -0
- data/test_app/lib/assets/.keep +0 -0
- data/test_app/lib/tasks/.keep +0 -0
- data/test_app/log/.keep +0 -0
- data/test_app/public/404.html +67 -0
- data/test_app/public/422.html +67 -0
- data/test_app/public/500.html +66 -0
- data/test_app/public/apple-touch-icon-precomposed.png +0 -0
- data/test_app/public/apple-touch-icon.png +0 -0
- data/test_app/public/favicon.ico +0 -0
- data/test_app/public/robots.txt +5 -0
- data/test_app/spec/controllers/posts_controller_spec.rb +7 -0
- data/test_app/spec/helpers/posts_helper_spec.rb +15 -0
- data/test_app/spec/models/post_spec.rb +5 -0
- data/test_app/spec/models/user_spec.rb +5 -0
- data/test_app/spec/rails_helper.rb +23 -0
- data/test_app/spec/requests/posts_spec.rb +10 -0
- data/test_app/spec/routing/posts_routing_spec.rb +39 -0
- data/test_app/spec/spec_helper.rb +60 -0
- data/test_app/tmp/.keep +0 -0
- data/test_app/vendor/assets/stylesheets/.keep +0 -0
- metadata +298 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
module Sapience
|
|
2
|
+
class Base
|
|
3
|
+
# Class name to be logged
|
|
4
|
+
attr_accessor :name, :filter
|
|
5
|
+
|
|
6
|
+
# Set the logging level for this logger
|
|
7
|
+
#
|
|
8
|
+
# Note: This level is only for this particular instance. It does not override
|
|
9
|
+
# the log level in any logging instance or the default log level
|
|
10
|
+
# Sapience.config.default_level
|
|
11
|
+
#
|
|
12
|
+
# Must be one of the values in Sapience::LEVELS, or
|
|
13
|
+
# nil if this logger instance should use the global default level
|
|
14
|
+
def level=(level)
|
|
15
|
+
@level_index = Sapience.config.level_to_index(level)
|
|
16
|
+
@level = Sapience.config.index_to_level(@level_index)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns the current log level if set, otherwise it returns the global
|
|
20
|
+
# default log level
|
|
21
|
+
def level
|
|
22
|
+
@level || Sapience.config.default_level
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Implement the log level calls
|
|
26
|
+
# logger.debug(message, hash|exception=nil, &block)
|
|
27
|
+
#
|
|
28
|
+
# Implement the log level query
|
|
29
|
+
# logger.debug?
|
|
30
|
+
#
|
|
31
|
+
# Parameters:
|
|
32
|
+
# message
|
|
33
|
+
# [String] text message to be logged
|
|
34
|
+
# Should always be supplied unless the result of the supplied block returns
|
|
35
|
+
# a string in which case it will become the logged message
|
|
36
|
+
# Default: nil
|
|
37
|
+
#
|
|
38
|
+
# payload
|
|
39
|
+
# [Hash|Exception] Optional hash payload or an exception to be logged
|
|
40
|
+
# Default: nil
|
|
41
|
+
#
|
|
42
|
+
# exception
|
|
43
|
+
# [Exception] Optional exception to be logged
|
|
44
|
+
# Allows both an exception and a payload to be logged
|
|
45
|
+
# Default: nil
|
|
46
|
+
#
|
|
47
|
+
# Examples:
|
|
48
|
+
# require 'sapience'
|
|
49
|
+
#
|
|
50
|
+
# # Enable trace level logging
|
|
51
|
+
# Sapience.config.default_level = :info
|
|
52
|
+
#
|
|
53
|
+
# # Log to screen
|
|
54
|
+
# Sapience.add_appender(:file, io: STDOUT, formatter: :color)
|
|
55
|
+
#
|
|
56
|
+
# # And log to a file at the same time
|
|
57
|
+
# Sapience.add_appender(:file, file_name: 'application.log', formatter: :color)
|
|
58
|
+
#
|
|
59
|
+
# logger = Sapience['MyApplication']
|
|
60
|
+
# logger.debug("Only display this if log level is set to Debug or lower")
|
|
61
|
+
#
|
|
62
|
+
# # Log information along with a text message
|
|
63
|
+
# logger.info("Request received", user: "joe", duration: 100)
|
|
64
|
+
#
|
|
65
|
+
# # Log an exception
|
|
66
|
+
# logger.info("Parsing received XML", exc)
|
|
67
|
+
#
|
|
68
|
+
Sapience::LEVELS.each_with_index do |level, index|
|
|
69
|
+
class_eval <<-EOT, __FILE__, __LINE__ + 1
|
|
70
|
+
def #{level}(message=nil, payload=nil, exception=nil, &block)
|
|
71
|
+
if level_index <= #{index}
|
|
72
|
+
log_internal(:#{level}, #{index}, message, payload, exception, &block)
|
|
73
|
+
true
|
|
74
|
+
else
|
|
75
|
+
false
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def #{level}?
|
|
80
|
+
level_index <= #{index}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def measure_#{level}(message, params = {}, &block)
|
|
84
|
+
if level_index <= #{index}
|
|
85
|
+
measure_internal(:#{level}, #{index}, message, params, &block)
|
|
86
|
+
else
|
|
87
|
+
block.call(params) if block
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def benchmark_#{level}(message, params = {}, &block)
|
|
92
|
+
if level_index <= #{index}
|
|
93
|
+
measure_internal(:#{level}, #{index}, message, params, &block)
|
|
94
|
+
else
|
|
95
|
+
block.call(params) if block
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
EOT
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Dynamically supply the log level with every measurement call
|
|
102
|
+
def measure(level, message, params = {}, &block)
|
|
103
|
+
index = Sapience.config.level_to_index(level)
|
|
104
|
+
if level_index <= index
|
|
105
|
+
measure_internal(level, index, message, params, &block)
|
|
106
|
+
else
|
|
107
|
+
block.call(params) if block
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
alias_method :benchmark, :measure
|
|
112
|
+
|
|
113
|
+
# :nodoc:
|
|
114
|
+
def tagged(*tags, &block)
|
|
115
|
+
Sapience.tagged(*tags, &block)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# :nodoc:
|
|
119
|
+
alias_method :with_tags, :tagged
|
|
120
|
+
|
|
121
|
+
# :nodoc:
|
|
122
|
+
def tags
|
|
123
|
+
Sapience.tags
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# :nodoc:
|
|
127
|
+
def push_tags(*tags)
|
|
128
|
+
Sapience.push_tags(*tags)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# :nodoc:
|
|
132
|
+
def pop_tags(quantity = 1)
|
|
133
|
+
Sapience.pop_tags(quantity)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# :nodoc:
|
|
137
|
+
def silence(new_level = :error, &block)
|
|
138
|
+
Sapience.silence(new_level, &block)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# :nodoc:
|
|
142
|
+
def fast_tag(tag, &block)
|
|
143
|
+
Sapience.fast_tag(tag, &block)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Thread specific context information to be logged with every log entry
|
|
147
|
+
#
|
|
148
|
+
# Add a payload to all log calls on This Thread within the supplied block
|
|
149
|
+
#
|
|
150
|
+
# logger.with_payload(tracking_number: 12345) do
|
|
151
|
+
# logger.debug('Hello World')
|
|
152
|
+
# end
|
|
153
|
+
#
|
|
154
|
+
# If a log call already includes a pyload, this payload will be merged with
|
|
155
|
+
# the supplied payload, with the supplied payload taking precedence
|
|
156
|
+
#
|
|
157
|
+
# logger.with_payload(tracking_number: 12345) do
|
|
158
|
+
# logger.debug('Hello World', result: 'blah')
|
|
159
|
+
# end
|
|
160
|
+
def with_payload(payload)
|
|
161
|
+
current_payload = self.payload
|
|
162
|
+
Thread.current[:sapience_payload] = current_payload ? current_payload.merge(payload) : payload
|
|
163
|
+
yield
|
|
164
|
+
ensure
|
|
165
|
+
Thread.current[:sapience_payload] = current_payload
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns [Hash] payload to be added to every log entry in the current scope
|
|
169
|
+
# on this thread.
|
|
170
|
+
# Returns nil if no payload is currently set
|
|
171
|
+
def payload
|
|
172
|
+
Thread.current[:sapience_payload]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
protected
|
|
176
|
+
|
|
177
|
+
# Write log data to underlying data storage
|
|
178
|
+
def log(_log_)
|
|
179
|
+
raise NotImplementedError.new('Logging Appender must implement #log(log)')
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private
|
|
183
|
+
|
|
184
|
+
# Initializer for Abstract Class Sapience::Base
|
|
185
|
+
#
|
|
186
|
+
# Parameters
|
|
187
|
+
# klass [String]
|
|
188
|
+
# Name of the class, module, or other identifier for which the log messages
|
|
189
|
+
# are being logged
|
|
190
|
+
#
|
|
191
|
+
# level [Symbol]
|
|
192
|
+
# Only allow log entries of this level or higher to be written to this appender
|
|
193
|
+
# For example if set to :warn, this appender would only log :warn and :fatal
|
|
194
|
+
# log messages when other appenders could be logging :info and lower
|
|
195
|
+
#
|
|
196
|
+
# filter [Regexp|Proc]
|
|
197
|
+
# RegExp: Only include log messages where the class name matches the supplied
|
|
198
|
+
# regular expression. All other messages will be ignored
|
|
199
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
|
200
|
+
# The Proc must return true or false
|
|
201
|
+
def initialize(klass, level = nil, filter = nil) # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
|
202
|
+
# Support filtering all messages to this logger using a Regular Expression
|
|
203
|
+
# or Proc
|
|
204
|
+
raise ':filter must be a Regexp or Proc' unless filter.nil? || filter.is_a?(Regexp) || filter.is_a?(Proc)
|
|
205
|
+
|
|
206
|
+
@filter = filter.is_a?(Regexp) ? filter.freeze : filter
|
|
207
|
+
@name = klass.is_a?(String) ? klass : klass.name
|
|
208
|
+
if level.nil?
|
|
209
|
+
# Allow the global default level to determine this loggers log level
|
|
210
|
+
@level_index = nil
|
|
211
|
+
@level = nil
|
|
212
|
+
else
|
|
213
|
+
self.level = level
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Return the level index for fast comparisons
|
|
218
|
+
# Returns the global default level index if the level has not been explicitly
|
|
219
|
+
# set for this instance
|
|
220
|
+
def level_index
|
|
221
|
+
@level_index || Sapience.config.default_level_index
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Whether to log the supplied message based on the current filter if any
|
|
225
|
+
def include_message?(log)
|
|
226
|
+
return true if @filter.nil?
|
|
227
|
+
|
|
228
|
+
if @filter.is_a?(Regexp)
|
|
229
|
+
(@filter =~ log.name) != nil
|
|
230
|
+
elsif @filter.is_a?(Proc)
|
|
231
|
+
@filter.call(log) == true
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Whether the log message should be logged for the current logger or appender
|
|
236
|
+
def should_log?(log)
|
|
237
|
+
# Ensure minimum log level is met, and check filter
|
|
238
|
+
(level_index <= (log.level_index || 0)) && include_message?(log)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Log message at the specified level
|
|
242
|
+
def log_internal(level, index, message = nil, payload = nil, exception = nil) # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
|
243
|
+
# Exception being logged?
|
|
244
|
+
if exception.nil? && payload.nil? && message.respond_to?(:backtrace) && message.respond_to?(:message)
|
|
245
|
+
exception = message
|
|
246
|
+
message = nil
|
|
247
|
+
elsif exception.nil? && payload && payload.respond_to?(:backtrace) && payload.respond_to?(:message)
|
|
248
|
+
exception = payload
|
|
249
|
+
payload = nil
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Add result of block as message or payload if not nil
|
|
253
|
+
if block_given? && (result = yield)
|
|
254
|
+
if result.is_a?(String)
|
|
255
|
+
message = message.nil? ? result : "#{message} -- #{result}"
|
|
256
|
+
elsif message.nil? && result.is_a?(Hash)
|
|
257
|
+
message = result
|
|
258
|
+
elsif payload && payload.respond_to?(:merge)
|
|
259
|
+
payload.merge(result)
|
|
260
|
+
else
|
|
261
|
+
payload = result
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Add scoped payload
|
|
266
|
+
if self.payload
|
|
267
|
+
payload = payload.nil? ? self.payload : self.payload.merge(payload)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Add caller stack trace
|
|
271
|
+
backtrace = extract_backtrace if index >= Sapience.config.backtrace_level_index
|
|
272
|
+
|
|
273
|
+
log = Log.new(level, Thread.current.name, name, message, payload, Time.now, nil, tags, index, exception, nil, backtrace)
|
|
274
|
+
|
|
275
|
+
# Logging Hash only?
|
|
276
|
+
# logger.info(name: 'value')
|
|
277
|
+
if payload.nil? && exception.nil? && message.is_a?(Hash)
|
|
278
|
+
payload = message.dup
|
|
279
|
+
min_duration = payload.delete(:min_duration) || 0.0
|
|
280
|
+
log.exception = payload.delete(:exception)
|
|
281
|
+
log.message = payload.delete(:message)
|
|
282
|
+
log.metric = payload.delete(:metric)
|
|
283
|
+
log.metric_amount = payload.delete(:metric_amount) || 1
|
|
284
|
+
if duration = payload.delete(:duration)
|
|
285
|
+
return false if duration <= min_duration
|
|
286
|
+
log.duration = duration
|
|
287
|
+
end
|
|
288
|
+
log.payload = payload if payload.size > 0
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
self.log(log) if include_message?(log)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
SELF_PATTERN = File.join('lib', 'sapience')
|
|
295
|
+
|
|
296
|
+
# Extract the callers backtrace leaving out Sapience
|
|
297
|
+
def extract_backtrace
|
|
298
|
+
stack = caller
|
|
299
|
+
while (first = stack.first) && first.include?(SELF_PATTERN)
|
|
300
|
+
stack.shift
|
|
301
|
+
end
|
|
302
|
+
stack
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Measure the supplied block and log the message
|
|
306
|
+
def measure_internal(level, index, message, params) # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
|
307
|
+
start = Time.now
|
|
308
|
+
exception = nil
|
|
309
|
+
begin
|
|
310
|
+
if block_given?
|
|
311
|
+
result =
|
|
312
|
+
if silence_level = params[:silence]
|
|
313
|
+
# In case someone accidentally sets `silence: true` instead of `silence: :error`
|
|
314
|
+
silence_level = :error if silence_level == true
|
|
315
|
+
silence(silence_level) { yield(params) }
|
|
316
|
+
else
|
|
317
|
+
yield(params)
|
|
318
|
+
end
|
|
319
|
+
exception = params[:exception]
|
|
320
|
+
result
|
|
321
|
+
end
|
|
322
|
+
rescue Exception => exc # rubocop:disable RescueException
|
|
323
|
+
exception = exc
|
|
324
|
+
ensure
|
|
325
|
+
end_time = Time.now
|
|
326
|
+
# Extract options after block completes so that block can modify any of the options
|
|
327
|
+
log_exception = params[:log_exception] || :partial
|
|
328
|
+
on_exception_level = params[:on_exception_level]
|
|
329
|
+
min_duration = params[:min_duration] || 0.0
|
|
330
|
+
payload = params[:payload]
|
|
331
|
+
metric = params[:metric]
|
|
332
|
+
duration =
|
|
333
|
+
if block_given?
|
|
334
|
+
1000.0 * (end_time - start)
|
|
335
|
+
else
|
|
336
|
+
params[:duration] || fail('Mandatory block missing when :duration option is not supplied')
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Add scoped payload
|
|
340
|
+
if self.payload
|
|
341
|
+
payload = payload.nil? ? self.payload : self.payload.merge(payload)
|
|
342
|
+
end
|
|
343
|
+
if exception
|
|
344
|
+
logged_exception = exception
|
|
345
|
+
backtrace = nil
|
|
346
|
+
case log_exception
|
|
347
|
+
when :full
|
|
348
|
+
# On exception change the log level
|
|
349
|
+
if on_exception_level
|
|
350
|
+
level = on_exception_level
|
|
351
|
+
index = Sapience.config.level_to_index(level)
|
|
352
|
+
end
|
|
353
|
+
when :partial
|
|
354
|
+
# On exception change the log level
|
|
355
|
+
if on_exception_level
|
|
356
|
+
level = on_exception_level
|
|
357
|
+
index = Sapience.config.level_to_index(level)
|
|
358
|
+
end
|
|
359
|
+
message = "#{message} -- Exception: #{exception.class}: #{exception.message}"
|
|
360
|
+
logged_exception = nil
|
|
361
|
+
backtrace = exception.backtrace
|
|
362
|
+
else
|
|
363
|
+
# Log the message with its duration but leave out the exception that was raised
|
|
364
|
+
logged_exception = nil
|
|
365
|
+
backtrace = exception.backtrace
|
|
366
|
+
end
|
|
367
|
+
log = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, logged_exception, metric, backtrace)
|
|
368
|
+
self.log(log) if include_message?(log)
|
|
369
|
+
raise exception
|
|
370
|
+
elsif duration >= min_duration
|
|
371
|
+
# Only log if the block took longer than 'min_duration' to complete
|
|
372
|
+
# Add caller stack trace
|
|
373
|
+
backtrace = extract_backtrace if index >= Sapience.config.backtrace_level_index
|
|
374
|
+
|
|
375
|
+
log = Log.new(level, Thread.current.name, name, message, payload, end_time, duration, tags, index, nil, metric, backtrace)
|
|
376
|
+
self.log(log) if include_message?(log)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# :nodoc:
|
|
2
|
+
module Sapience
|
|
3
|
+
# :nodoc:
|
|
4
|
+
module Concerns
|
|
5
|
+
# :nodoc:
|
|
6
|
+
module Compatibility
|
|
7
|
+
#
|
|
8
|
+
# For compatibility with Ruby Logger only.
|
|
9
|
+
#
|
|
10
|
+
def self.included(base)
|
|
11
|
+
base.class_eval do
|
|
12
|
+
# Map :unknown to :error
|
|
13
|
+
alias_method :unknown, :error # :nodoc:
|
|
14
|
+
alias_method :unknown?, :error? # :nodoc:
|
|
15
|
+
|
|
16
|
+
alias_method :<<, :info # :nodoc:
|
|
17
|
+
# Active Record's Session Store calls silence_logger
|
|
18
|
+
alias_method :silence_logger, :silence # :nodoc:
|
|
19
|
+
|
|
20
|
+
alias_method :progname, :name # :nodoc:
|
|
21
|
+
alias_method :progname=, :name= # :nodoc:
|
|
22
|
+
|
|
23
|
+
alias_method :sev_threshold, :level # :nodoc:
|
|
24
|
+
alias_method :sev_threshold=, :level= # :nodoc:
|
|
25
|
+
|
|
26
|
+
attr_accessor :formatter # :nodoc:
|
|
27
|
+
attr_accessor :datetime_format # :nodoc:
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# :nodoc:
|
|
32
|
+
def close
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# :nodoc:
|
|
36
|
+
def reopen(_logdev = nil)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# :nodoc:
|
|
40
|
+
def add(severity, message = nil, progname = nil, &block)
|
|
41
|
+
index = Sapience.config.level_to_index(severity)
|
|
42
|
+
if level_index <= index
|
|
43
|
+
level = Sapience.config.index_to_level(index)
|
|
44
|
+
log_internal(level, index, message, progname, &block)
|
|
45
|
+
true
|
|
46
|
+
else
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require "ostruct"
|
|
2
|
+
|
|
3
|
+
module Sapience
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_reader :default_level, :backtrace_level, :backtrace_level_index
|
|
6
|
+
attr_writer :host
|
|
7
|
+
attr_accessor :application, :ap_options, :appenders
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
# Initial default Level for all new instances of Sapience::Logger
|
|
11
|
+
self.default_level = :info
|
|
12
|
+
self.backtrace_level = :info
|
|
13
|
+
self.application = "Sapience"
|
|
14
|
+
self.host = nil
|
|
15
|
+
self.ap_options = { multiline: false }
|
|
16
|
+
self.appenders = [ { file: {io: STDOUT, formatter: :color } } ]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Sets the global default log level
|
|
20
|
+
def default_level=(level)
|
|
21
|
+
@default_level = level
|
|
22
|
+
# For performance reasons pre-calculate the level index
|
|
23
|
+
@default_level_index = level_to_index(level)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the symbolic level for the supplied level index
|
|
27
|
+
def index_to_level(level_index)
|
|
28
|
+
LEVELS[level_index]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Internal method to return the log level as an internal index
|
|
32
|
+
# Also supports mapping the ::Logger levels to Sapience levels
|
|
33
|
+
def level_to_index(level) # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
|
34
|
+
return if level.nil?
|
|
35
|
+
|
|
36
|
+
index =
|
|
37
|
+
if level.is_a?(Symbol)
|
|
38
|
+
LEVELS.index(level)
|
|
39
|
+
elsif level.is_a?(String)
|
|
40
|
+
level = level.downcase.to_sym
|
|
41
|
+
LEVELS.index(level)
|
|
42
|
+
elsif level.is_a?(Integer) && defined?(::Logger::Severity)
|
|
43
|
+
# Mapping of Rails and Ruby Logger levels to Sapience levels
|
|
44
|
+
@@map_levels ||= begin
|
|
45
|
+
levels = []
|
|
46
|
+
::Logger::Severity.constants.each do |constant|
|
|
47
|
+
levels[::Logger::Severity.const_get(constant)] = LEVELS.find_index(constant.downcase.to_sym) || LEVELS.find_index(:error)
|
|
48
|
+
end
|
|
49
|
+
levels
|
|
50
|
+
end
|
|
51
|
+
@@map_levels[level]
|
|
52
|
+
end
|
|
53
|
+
fail "Invalid level:#{level.inspect} being requested. Must be one of #{LEVELS.inspect}" unless index
|
|
54
|
+
index
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def default_level_index
|
|
58
|
+
Thread.current[:sapience_silence] || @default_level_index
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Sets the level at which backtraces should be captured
|
|
63
|
+
# for every log message.
|
|
64
|
+
#
|
|
65
|
+
# By enabling backtrace capture the filename and line number of where
|
|
66
|
+
# message was logged can be written to the log file. Additionally, the backtrace
|
|
67
|
+
# can be forwarded to error management services such as Bugsnag.
|
|
68
|
+
#
|
|
69
|
+
# Warning:
|
|
70
|
+
# Capturing backtraces is very expensive and should not be done all
|
|
71
|
+
# the time. It is recommended to run it at :error level in production.
|
|
72
|
+
def backtrace_level=(level)
|
|
73
|
+
@backtrace_level = level
|
|
74
|
+
# For performance reasons pre-calculate the level index
|
|
75
|
+
@backtrace_level_index = level.nil? ? 65_535 : level_to_index(level)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns [String] name of this host for logging purposes
|
|
79
|
+
# Note: Not all appenders use `host`
|
|
80
|
+
def host
|
|
81
|
+
@host ||= Socket.gethostname
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require "thread"
|
|
2
|
+
class Thread
|
|
3
|
+
# Returns the name of the current thread
|
|
4
|
+
# Default:
|
|
5
|
+
# String representation of this thread's object_id
|
|
6
|
+
def name
|
|
7
|
+
@name ||= object_id.to_s
|
|
8
|
+
end unless defined?(:name)
|
|
9
|
+
|
|
10
|
+
# Set the name of this thread
|
|
11
|
+
def name=(name)
|
|
12
|
+
@name = name.to_s
|
|
13
|
+
end unless defined?(:name=)
|
|
14
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Sapience
|
|
2
|
+
module Formatters
|
|
3
|
+
class Base
|
|
4
|
+
attr_accessor :time_format, :precision, :log_host, :log_application
|
|
5
|
+
|
|
6
|
+
# Parameters
|
|
7
|
+
# time_format: [String|Symbol|nil]
|
|
8
|
+
# See Time#strftime for the format of this string
|
|
9
|
+
# :iso_8601 Outputs an ISO8601 Formatted timestamp
|
|
10
|
+
# nil: Returns Empty string for time ( no time is output ).
|
|
11
|
+
# Default: '%Y-%m-%d %H:%M:%S.%6N'
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
options = options.dup
|
|
14
|
+
@precision = 6
|
|
15
|
+
default_format = "%Y-%m-%d %H:%M:%S.%#{precision}N"
|
|
16
|
+
@time_format = options.key?(:time_format) ? options.delete(:time_format) : default_format
|
|
17
|
+
@log_host = options.key?(:log_host) ? options.delete(:log_host) : true
|
|
18
|
+
@log_application = options.key?(:log_application) ? options.delete(:log_application) : true
|
|
19
|
+
fail(ArgumentError, "Unknown options: #{options.inspect}") if options.size > 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Return the Time as a formatted string
|
|
23
|
+
def format_time(time)
|
|
24
|
+
case time_format
|
|
25
|
+
when :iso_8601
|
|
26
|
+
time.utc.iso8601(precision)
|
|
27
|
+
when nil
|
|
28
|
+
""
|
|
29
|
+
else
|
|
30
|
+
time.strftime(time_format)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Load AwesomePrint if available
|
|
2
|
+
begin
|
|
3
|
+
require "awesome_print"
|
|
4
|
+
rescue LoadError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module Sapience
|
|
8
|
+
module Formatters
|
|
9
|
+
class Color < Base
|
|
10
|
+
# Parameters:
|
|
11
|
+
# ap: Any valid AwesomePrint option for rendering data.
|
|
12
|
+
# These options can also be changed be creating a `~/.aprc` file.
|
|
13
|
+
# See: https://github.com/michaeldv/awesome_print
|
|
14
|
+
#
|
|
15
|
+
# Note: The option :multiline is set to false if not supplied.
|
|
16
|
+
# Note: Has no effect if Awesome Print is not installed.
|
|
17
|
+
def initialize(options = {})
|
|
18
|
+
options = options.dup
|
|
19
|
+
@ai_options = options.delete(:ap) || { multiline: false }
|
|
20
|
+
super(options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Adds color to the default log formatter
|
|
24
|
+
# Example:
|
|
25
|
+
# Sapience.add_appender(:file, io: $stdout, formatter: :color)
|
|
26
|
+
def call(log, _logger) # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
|
27
|
+
colors = Sapience::AnsiColors
|
|
28
|
+
level_color = colors::LEVEL_MAP[log.level]
|
|
29
|
+
|
|
30
|
+
message = time_format.nil? ? "" : "#{format_time(log.time)} "
|
|
31
|
+
|
|
32
|
+
# Header with date, time, log level and process info
|
|
33
|
+
message << "#{level_color}#{log.level_to_s}#{colors::CLEAR} [#{log.process_info}]"
|
|
34
|
+
|
|
35
|
+
# Tags
|
|
36
|
+
message << " " << log.tags.collect { |tag| "[#{level_color}#{tag}#{colors::CLEAR}]" }.join(" ") if log.tags && (log.tags.size > 0)
|
|
37
|
+
|
|
38
|
+
# Duration
|
|
39
|
+
message << " (#{colors::BOLD}#{log.duration_human}#{colors::CLEAR})" if log.duration
|
|
40
|
+
|
|
41
|
+
# Class / app name
|
|
42
|
+
message << " #{level_color}#{log.name}#{colors::CLEAR}"
|
|
43
|
+
|
|
44
|
+
# Log message
|
|
45
|
+
message << " -- #{log.message}" if log.message
|
|
46
|
+
|
|
47
|
+
# Payload: Colorize the payload if the AwesomePrint gem is loaded
|
|
48
|
+
if log.payload?
|
|
49
|
+
payload = log.payload
|
|
50
|
+
message << " -- " <<
|
|
51
|
+
if defined?(AwesomePrint) && payload.respond_to?(:ai)
|
|
52
|
+
payload.ai(@ai_options) rescue payload.inspect
|
|
53
|
+
else
|
|
54
|
+
payload.inspect
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Exceptions
|
|
59
|
+
if log.exception
|
|
60
|
+
message << " -- Exception: #{colors::BOLD}#{log.exception.class}: #{log.exception.message}#{colors::CLEAR}\n"
|
|
61
|
+
message << log.backtrace_to_s
|
|
62
|
+
end
|
|
63
|
+
message
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Sapience
|
|
2
|
+
module Formatters
|
|
3
|
+
class Default < Base
|
|
4
|
+
# Default text log format
|
|
5
|
+
# Generates logs of the form:
|
|
6
|
+
# 2011-07-19 14:36:15.660235 D [1149:ScriptThreadProcess] Rails -- Hello World
|
|
7
|
+
def call(log, _logger) # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
|
|
8
|
+
# Date & time
|
|
9
|
+
message = time_format.nil? ? "" : "#{format_time(log.time)} "
|
|
10
|
+
|
|
11
|
+
# Log level and process info
|
|
12
|
+
message << "#{log.level_to_s} [#{log.process_info}]"
|
|
13
|
+
|
|
14
|
+
# Tags
|
|
15
|
+
message << " " << log.tags.collect { |tag| "[#{tag}]" }.join(" ") if log.tags && (log.tags.size > 0)
|
|
16
|
+
|
|
17
|
+
# Duration
|
|
18
|
+
message << " (#{log.duration_human})" if log.duration
|
|
19
|
+
|
|
20
|
+
# Class / app name
|
|
21
|
+
message << " #{log.name}"
|
|
22
|
+
|
|
23
|
+
# Log message
|
|
24
|
+
message << " -- #{log.message}" if log.message
|
|
25
|
+
|
|
26
|
+
# Payload
|
|
27
|
+
if payload = log.payload_to_s
|
|
28
|
+
message << " -- " << payload
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Exceptions
|
|
32
|
+
if log.exception
|
|
33
|
+
message << " -- Exception: #{log.exception.class}: #{log.exception.message}\n"
|
|
34
|
+
message << log.backtrace_to_s
|
|
35
|
+
end
|
|
36
|
+
message
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|