sapience 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|