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,357 @@
|
|
1
|
+
require "concurrent"
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# Sapience.configure do |config|
|
7
|
+
# config.default_level = ENV.fetch('SAPIENCE_DEFAULT_LEVEL') { :info }.to_sym
|
8
|
+
# config.backtrace_level = ENV.fetch('SAPIENCE_BACKTRACE_LEVEL') { :info }.to_sym
|
9
|
+
# config.application = 'TestApplication'
|
10
|
+
# config.host = ENV.fetch('SAPIENCE_HOST', nil)
|
11
|
+
# config.ap_options = { multiline: false }
|
12
|
+
# config.appenders = [
|
13
|
+
# { file: { io: STDOUT, formatter: :color } },
|
14
|
+
# { statsd: { url: 'udp://localhost:2222' } },
|
15
|
+
# { sentry: { dsn: 'ASDFASDF' } },
|
16
|
+
# ]
|
17
|
+
# end
|
18
|
+
|
19
|
+
# rubocop:disable ClassVars
|
20
|
+
module Sapience
|
21
|
+
# 1. Have a default configuration
|
22
|
+
# 2. Configure Sapience (Sapience.configure { |c| c.configuration = {} })
|
23
|
+
# 3. Use configuration for rails
|
24
|
+
# 4. Use configuration for grape
|
25
|
+
|
26
|
+
# Logging levels in order of most detailed to most severe
|
27
|
+
LEVELS = [:trace, :debug, :info, :warn, :error, :fatal]
|
28
|
+
|
29
|
+
def self.config
|
30
|
+
@@config ||= Configuration.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.configure
|
34
|
+
yield @@config
|
35
|
+
|
36
|
+
config.appenders.each do |appender|
|
37
|
+
appender.each do |name, options|
|
38
|
+
add_appender(name, options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return a logger for the supplied class or class_name
|
44
|
+
def self.[](klass)
|
45
|
+
Sapience::Logger.new(klass)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Add a new logging appender as a new destination for all log messages
|
49
|
+
# emitted from Sapience
|
50
|
+
#
|
51
|
+
# Appenders will be written to in the order that they are added
|
52
|
+
#
|
53
|
+
# If a block is supplied then it will be used to customize the format
|
54
|
+
# of the messages sent to that appender. See Sapience::Logger.new for
|
55
|
+
# more information on custom formatters
|
56
|
+
#
|
57
|
+
# Parameters
|
58
|
+
# file_name: [String]
|
59
|
+
# File name to write log messages to.
|
60
|
+
#
|
61
|
+
# Or,
|
62
|
+
# io: [IO]
|
63
|
+
# An IO Stream to log to.
|
64
|
+
# For example STDOUT, STDERR, etc.
|
65
|
+
#
|
66
|
+
# Or,
|
67
|
+
# appender: [Symbol|Sapience::Subscriber]
|
68
|
+
# A symbol identifying the appender to create.
|
69
|
+
# For example:
|
70
|
+
# :bugsnag, :elasticsearch, :graylog, :http, :mongodb, :new_relic, :splunk_http, :syslog, :wrapper
|
71
|
+
# Or,
|
72
|
+
# An instance of an appender derived from Sapience::Subscriber
|
73
|
+
# For example:
|
74
|
+
# Sapience::Appender::Http.new(url: 'http://localhost:8088/path')
|
75
|
+
#
|
76
|
+
# Or,
|
77
|
+
# logger: [Logger|Log4r]
|
78
|
+
# An instance of a Logger or a Log4r logger.
|
79
|
+
#
|
80
|
+
# level: [:trace | :debug | :info | :warn | :error | :fatal]
|
81
|
+
# Override the log level for this appender.
|
82
|
+
# Default: Sapience.config.default_level
|
83
|
+
#
|
84
|
+
# formatter: [Symbol|Object|Proc]
|
85
|
+
# Any of the following symbol values: :default, :color, :json
|
86
|
+
# Or,
|
87
|
+
# An instance of a class that implements #call
|
88
|
+
# Or,
|
89
|
+
# A Proc to be used to format the output from this appender
|
90
|
+
# Default: :default
|
91
|
+
#
|
92
|
+
# filter: [Regexp|Proc]
|
93
|
+
# RegExp: Only include log messages where the class name matches the supplied.
|
94
|
+
# regular expression. All other messages will be ignored.
|
95
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
96
|
+
# The Proc must return true or false.
|
97
|
+
#
|
98
|
+
# Examples:
|
99
|
+
#
|
100
|
+
# # Send all logging output to Standard Out (Screen)
|
101
|
+
# Sapience.add_appender(:file, io: STDOUT)
|
102
|
+
#
|
103
|
+
# # Send all logging output to a file
|
104
|
+
# Sapience.add_appender(:file, file_name: 'logfile.log')
|
105
|
+
#
|
106
|
+
# # Send all logging output to a file and only :info and above to standard output
|
107
|
+
# Sapience.add_appender(:file, file_name: 'logfile.log')
|
108
|
+
# Sapience.add_appender(:file, io: STDOUT, level: :info)
|
109
|
+
#
|
110
|
+
# Log to log4r, Logger, etc.:
|
111
|
+
#
|
112
|
+
# # Send logging output to an existing logger
|
113
|
+
# require 'logger'
|
114
|
+
# require 'sapience'
|
115
|
+
#
|
116
|
+
# # Built-in Ruby logger
|
117
|
+
# log = Logger.new(STDOUT)
|
118
|
+
# log.level = Logger::DEBUG
|
119
|
+
#
|
120
|
+
# Sapience.config.default_level = :debug
|
121
|
+
# Sapience.add_appender(:wrapper, logger: log)
|
122
|
+
#
|
123
|
+
# logger = Sapience['Example']
|
124
|
+
# logger.info "Hello World"
|
125
|
+
# logger.debug("Login time", user: 'Joe', duration: 100, ip_address: '127.0.0.1')
|
126
|
+
def self.add_appender(appender, options, _deprecated_level = nil, &block)
|
127
|
+
fail ArgumentError, "options should be a hash" unless options.is_a?(Hash)
|
128
|
+
appender_class = constantize_symbol(appender)
|
129
|
+
appender = appender_class.new(options)
|
130
|
+
@@appenders << appender
|
131
|
+
|
132
|
+
# Start appender thread if it is not already running
|
133
|
+
Sapience::Logger.start_appender_thread
|
134
|
+
Sapience::Logger.logger = appender if appender.is_a?(Sapience::Appender::File)
|
135
|
+
appender
|
136
|
+
end
|
137
|
+
|
138
|
+
# Remove an existing appender
|
139
|
+
# Currently only supports appender instances
|
140
|
+
def self.remove_appender(appender)
|
141
|
+
@@appenders.delete(appender)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns [Sapience::Subscriber] a copy of the list of active
|
145
|
+
# appenders for debugging etc.
|
146
|
+
# Use Sapience.add_appender and Sapience.remove_appender
|
147
|
+
# to manipulate the active appenders list
|
148
|
+
def self.appenders
|
149
|
+
@@appenders.clone
|
150
|
+
end
|
151
|
+
|
152
|
+
# Wait until all queued log messages have been written and flush all active
|
153
|
+
# appenders
|
154
|
+
def self.flush
|
155
|
+
Sapience::Logger.flush
|
156
|
+
end
|
157
|
+
|
158
|
+
# Close and flush all appenders
|
159
|
+
def self.close
|
160
|
+
Sapience::Logger.close
|
161
|
+
end
|
162
|
+
|
163
|
+
# After forking an active process call Sapience.reopen to re-open
|
164
|
+
# any open file handles etc to resources
|
165
|
+
#
|
166
|
+
# Note: Only appenders that implement the reopen method will be called
|
167
|
+
def self.reopen
|
168
|
+
@@appenders.each { |appender| appender.reopen if appender.respond_to?(:reopen) }
|
169
|
+
# After a fork the appender thread is not running, start it if it is not running
|
170
|
+
Sapience::Logger.start_appender_thread
|
171
|
+
end
|
172
|
+
|
173
|
+
# Add signal handlers for Sapience
|
174
|
+
#
|
175
|
+
# Two signal handlers will be registered by default:
|
176
|
+
#
|
177
|
+
# 1. Changing the log_level:
|
178
|
+
#
|
179
|
+
# The log level can be changed without restarting the process by sending the
|
180
|
+
# log_level_signal, which by default is 'USR2'
|
181
|
+
#
|
182
|
+
# When the log_level_signal is raised on this process, the global default log level
|
183
|
+
# rotates through the following log levels in the following order, starting
|
184
|
+
# from the current global default level:
|
185
|
+
# :warn, :info, :debug, :trace
|
186
|
+
#
|
187
|
+
# If the current level is :trace it wraps around back to :warn
|
188
|
+
#
|
189
|
+
# 2. Logging a Ruby thread dump
|
190
|
+
#
|
191
|
+
# When the signal is raised on this process, Sapience will write the list
|
192
|
+
# of threads to the log file, along with their back-traces when available
|
193
|
+
#
|
194
|
+
# It is recommended to name any threads you create in the application, by
|
195
|
+
# calling the following from within the thread itself:
|
196
|
+
# Thread.current.name = 'My Worker'
|
197
|
+
#
|
198
|
+
#
|
199
|
+
# Note:
|
200
|
+
# To only register one of the signal handlers, set the other to nil
|
201
|
+
# rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
|
202
|
+
def self.add_signal_handler(log_level_signal = "USR2", thread_dump_signal = "TTIN", _gc_log_microseconds = 100_000)
|
203
|
+
Signal.trap(log_level_signal) do
|
204
|
+
index = (default_level == :trace) ? LEVELS.find_index(:error) : LEVELS.find_index(default_level)
|
205
|
+
new_level = LEVELS[index - 1]
|
206
|
+
self["Sapience"].warn "Changed global default log level to #{new_level.inspect}"
|
207
|
+
self.default_level = new_level
|
208
|
+
end if log_level_signal
|
209
|
+
|
210
|
+
Signal.trap(thread_dump_signal) do
|
211
|
+
logger = Sapience["Thread Dump"]
|
212
|
+
Thread.list.each do |thread|
|
213
|
+
next if thread == Thread.current
|
214
|
+
message = thread.name
|
215
|
+
if (backtrace = thread.backtrace)
|
216
|
+
message += "\n"
|
217
|
+
message << backtrace.join("\n")
|
218
|
+
end
|
219
|
+
tags = thread[:sapience_tags]
|
220
|
+
tags = tags.nil? ? [] : tags.clone
|
221
|
+
logger.tagged(tags) { logger.warn(message) }
|
222
|
+
end
|
223
|
+
end if thread_dump_signal
|
224
|
+
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
# rubocop:enable AbcSize, CyclomaticComplexity, PerceivedComplexity
|
229
|
+
|
230
|
+
# If the tag being supplied is definitely a string then this fast
|
231
|
+
# tag api can be used for short lived tags
|
232
|
+
def self.fast_tag(tag)
|
233
|
+
(Thread.current[:sapience_tags] ||= []) << tag
|
234
|
+
yield
|
235
|
+
ensure
|
236
|
+
Thread.current[:sapience_tags].pop
|
237
|
+
end
|
238
|
+
|
239
|
+
# Add the supplied named tags to the list of tags to log for this thread whilst
|
240
|
+
# the supplied block is active.
|
241
|
+
#
|
242
|
+
# Returns result of block
|
243
|
+
#
|
244
|
+
# Example:
|
245
|
+
def self.named_tags(tag)
|
246
|
+
(Thread.current[:sapience_tags] ||= []) << tag
|
247
|
+
yield
|
248
|
+
ensure
|
249
|
+
Thread.current[:sapience_tags].pop
|
250
|
+
end
|
251
|
+
|
252
|
+
# Add the supplied tags to the list of tags to log for this thread whilst
|
253
|
+
# the supplied block is active.
|
254
|
+
# Returns result of block
|
255
|
+
def self.tagged(*tags)
|
256
|
+
new_tags = push_tags(*tags)
|
257
|
+
yield self
|
258
|
+
ensure
|
259
|
+
pop_tags(new_tags.size)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Returns a copy of the [Array] of [String] tags currently active for this thread
|
263
|
+
# Returns nil if no tags are set
|
264
|
+
def self.tags
|
265
|
+
# Since tags are stored on a per thread basis this list is thread-safe
|
266
|
+
t = Thread.current[:sapience_tags]
|
267
|
+
t.nil? ? [] : t.clone
|
268
|
+
end
|
269
|
+
|
270
|
+
# Add tags to the current scope
|
271
|
+
# Returns the list of tags pushed after flattening them out and removing blanks
|
272
|
+
def self.push_tags(*tags)
|
273
|
+
# Need to flatten and reject empties to support calls from Rails 4
|
274
|
+
new_tags = tags.flatten.collect(&:to_s).reject(&:empty?)
|
275
|
+
t = Thread.current[:sapience_tags]
|
276
|
+
Thread.current[:sapience_tags] = t.nil? ? new_tags : t.concat(new_tags)
|
277
|
+
new_tags
|
278
|
+
end
|
279
|
+
|
280
|
+
# Remove specified number of tags from the current tag list
|
281
|
+
def self.pop_tags(quantity = 1)
|
282
|
+
t = Thread.current[:sapience_tags]
|
283
|
+
t.pop(quantity) unless t.nil?
|
284
|
+
end
|
285
|
+
|
286
|
+
# Silence noisy log levels by changing the default_level within the block
|
287
|
+
#
|
288
|
+
# This setting is thread-safe and only applies to the current thread
|
289
|
+
#
|
290
|
+
# Any threads spawned within the block will not be affected by this setting
|
291
|
+
#
|
292
|
+
# #silence can be used to both raise and lower the log level within
|
293
|
+
# the supplied block.
|
294
|
+
#
|
295
|
+
# Example:
|
296
|
+
#
|
297
|
+
# # Perform trace level logging within the block when the default is higher
|
298
|
+
# Sapience.config.default_level = :info
|
299
|
+
#
|
300
|
+
# logger.debug 'this will _not_ be logged'
|
301
|
+
#
|
302
|
+
# Sapience.silence(:trace) do
|
303
|
+
# logger.debug "this will be logged"
|
304
|
+
# end
|
305
|
+
#
|
306
|
+
# Parameters
|
307
|
+
# new_level
|
308
|
+
# The new log level to apply within the block
|
309
|
+
# Default: :error
|
310
|
+
#
|
311
|
+
# Example:
|
312
|
+
# # Silence all logging for this thread below :error level
|
313
|
+
# Sapience.silence do
|
314
|
+
# logger.info "this will _not_ be logged"
|
315
|
+
# logger.warn "this neither"
|
316
|
+
# logger.error "but errors will be logged"
|
317
|
+
# end
|
318
|
+
#
|
319
|
+
# Note:
|
320
|
+
# #silence does not affect any loggers which have had their log level set
|
321
|
+
# explicitly. I.e. That do not rely on the global default level
|
322
|
+
def self.silence(new_level = :error)
|
323
|
+
current_index = Thread.current[:sapience_silence]
|
324
|
+
Thread.current[:sapience_silence] = Sapience.config.level_to_index(new_level)
|
325
|
+
yield
|
326
|
+
ensure
|
327
|
+
Thread.current[:sapience_silence] = current_index
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
@@appenders = Concurrent::Array.new
|
333
|
+
|
334
|
+
def self.constantize_symbol(symbol, namespace = "Sapience::Appender")
|
335
|
+
klass = "#{namespace}::#{camelize(symbol.to_s)}"
|
336
|
+
begin
|
337
|
+
if RUBY_VERSION.to_i >= 2
|
338
|
+
Object.const_get(klass)
|
339
|
+
else
|
340
|
+
klass.split("::").inject(Object) { |o, name| o.const_get(name) } # rubocop:disable SingleLineBlockParams
|
341
|
+
end
|
342
|
+
rescue NameError
|
343
|
+
raise(ArgumentError, "Could not convert symbol: #{symbol} to a class in: #{namespace}. Looking for: #{klass}")
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Borrow from Rails, when not running Rails
|
348
|
+
def self.camelize(term) # rubocop:disable AbcSize
|
349
|
+
string = term.to_s
|
350
|
+
string = string.sub(/^[a-z\d]*/) { |match| match.capitalize }
|
351
|
+
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
|
352
|
+
"#{Regexp.last_match[1]}#{inflections.acronyms[Regexp.last_match[2]] || Regexp.last_match[2].capitalize}"
|
353
|
+
end
|
354
|
+
string.gsub!("/".freeze, "::".freeze)
|
355
|
+
string
|
356
|
+
end
|
357
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# Abstract Subscriber
|
2
|
+
#
|
3
|
+
# Abstract base class for appender and metrics subscribers.
|
4
|
+
module Sapience
|
5
|
+
class Subscriber < Sapience::Base
|
6
|
+
# Every logger has its own formatter
|
7
|
+
attr_accessor :formatter
|
8
|
+
attr_writer :application, :host
|
9
|
+
|
10
|
+
# Returns the current log level if set, otherwise it logs everything it receives
|
11
|
+
def level
|
12
|
+
@level || :trace
|
13
|
+
end
|
14
|
+
|
15
|
+
# A subscriber should implement flush if it can.
|
16
|
+
def flush
|
17
|
+
# NOOP
|
18
|
+
end
|
19
|
+
|
20
|
+
# A subscriber should implement close if it can.
|
21
|
+
def close
|
22
|
+
# NOOP
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns [Sapience::Formatters::Default] formatter default for this subscriber
|
26
|
+
def default_formatter
|
27
|
+
Sapience::Formatters::Default.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# Allow application name to be set globally or per subscriber
|
31
|
+
def application
|
32
|
+
@application || Sapience.config.application
|
33
|
+
end
|
34
|
+
|
35
|
+
# Allow host name to be set globally or per subscriber
|
36
|
+
def host
|
37
|
+
@host || Sapience.config.host
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Initializer for Abstract Class Sapience::Subscriber
|
43
|
+
#
|
44
|
+
# Parameters
|
45
|
+
# level: [:trace | :debug | :info | :warn | :error | :fatal]
|
46
|
+
# Override the log level for this subscriber.
|
47
|
+
# Default: :error
|
48
|
+
#
|
49
|
+
# formatter: [Object|Proc]
|
50
|
+
# An instance of a class that implements #call, or a Proc to be used to format
|
51
|
+
# the output from this subscriber
|
52
|
+
# Default: Use the built-in formatter (See: #call)
|
53
|
+
#
|
54
|
+
# filter: [Regexp|Proc]
|
55
|
+
# RegExp: Only include log messages where the class name matches the supplied.
|
56
|
+
# regular expression. All other messages will be ignored.
|
57
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
58
|
+
# The Proc must return true or false.
|
59
|
+
#
|
60
|
+
# host: [String]
|
61
|
+
# Name of this host to appear in log messages.
|
62
|
+
# Default: Sapience.config.host
|
63
|
+
#
|
64
|
+
# application: [String]
|
65
|
+
# Name of this application to appear in log messages.
|
66
|
+
# Default: Sapience.config.application
|
67
|
+
def initialize(options = {}, &block)
|
68
|
+
# Backward compatibility
|
69
|
+
options = { level: options } unless options.is_a?(Hash)
|
70
|
+
options = options.dup
|
71
|
+
level = options.delete(:level)
|
72
|
+
filter = options.delete(:filter)
|
73
|
+
@formatter = extract_formatter(options.delete(:formatter), &block)
|
74
|
+
@application = options.delete(:application)
|
75
|
+
@host = options.delete(:host)
|
76
|
+
fail(ArgumentError, "Unknown options: #{options.inspect}") if options.size > 0
|
77
|
+
|
78
|
+
# Subscribers don't take a class name, so use this class name if an subscriber
|
79
|
+
# is logged to directly
|
80
|
+
super(self.class, level, filter)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return the level index for fast comparisons
|
84
|
+
# Returns the lowest level index if the level has not been explicitly
|
85
|
+
# set for this instance
|
86
|
+
def level_index
|
87
|
+
@level_index || 0
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return formatter that responds to call
|
91
|
+
# Supports formatter supplied as:
|
92
|
+
# - Symbol
|
93
|
+
# - Hash ( Symbol => { options })
|
94
|
+
# - Instance of any of Sapience::Formatters
|
95
|
+
# - Proc
|
96
|
+
# - Any object that responds to :call
|
97
|
+
# - If none of the above apply, then the supplied block is returned as the formatter.
|
98
|
+
# - Otherwise an instance of the default formatter is returned.
|
99
|
+
def extract_formatter(formatter, &block) # rubocop:disable Metrics/CyclomaticComplexity
|
100
|
+
case
|
101
|
+
when formatter.is_a?(Symbol)
|
102
|
+
Sapience.constantize_symbol(formatter, "Sapience::Formatters").new
|
103
|
+
when formatter.is_a?(Hash) && formatter.size > 0
|
104
|
+
fmt, options = formatter.first
|
105
|
+
Sapience.constantize_symbol(fmt.to_sym, "Sapience::Formatters").new(options)
|
106
|
+
when formatter.respond_to?(:call)
|
107
|
+
formatter
|
108
|
+
when block
|
109
|
+
block
|
110
|
+
when respond_to?(:call)
|
111
|
+
self
|
112
|
+
else
|
113
|
+
default_formatter
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
SUBSCRIBER_OPTIONS = [:level, :formatter, :filter, :application, :host].freeze
|
118
|
+
|
119
|
+
# Returns [Hash] the subscriber common options from the supplied Hash
|
120
|
+
def extract_subscriber_options!(options)
|
121
|
+
subscriber_options = {}
|
122
|
+
SUBSCRIBER_OPTIONS.each { |key| subscriber_options[key] = options.delete(key) if options.key?(key) }
|
123
|
+
subscriber_options
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
data/lib/sapience.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "sapience/version"
|
2
|
+
require "sapience/sapience"
|
3
|
+
|
4
|
+
# @formatter:off
|
5
|
+
|
6
|
+
require "sapience/concerns/compatibility"
|
7
|
+
|
8
|
+
require "sapience/formatters/base"
|
9
|
+
require "sapience/formatters/raw"
|
10
|
+
require "sapience/formatters/default"
|
11
|
+
require "sapience/formatters/color"
|
12
|
+
require "sapience/formatters/json"
|
13
|
+
|
14
|
+
require "sapience/configuration"
|
15
|
+
require "sapience/ansi_colors"
|
16
|
+
require "sapience/core_ext/thread"
|
17
|
+
require "sapience/base"
|
18
|
+
require "sapience/log"
|
19
|
+
require "sapience/logger"
|
20
|
+
require "sapience/loggable"
|
21
|
+
require "sapience/subscriber"
|
22
|
+
|
23
|
+
require "sapience/appender/file"
|
24
|
+
require "sapience/appender/sentry"
|
25
|
+
require "sapience/appender/wrapper"
|
26
|
+
require "sapience/appender/statsd"
|
27
|
+
|
28
|
+
# @formatter:on
|
29
|
+
|
30
|
+
# Close and flush all appenders at exit, waiting for outstanding messages on the queue
|
31
|
+
# to be written first
|
32
|
+
at_exit do
|
33
|
+
# Cannot call #close since test frameworks use at_exit to run loaded tests
|
34
|
+
Sapience.flush
|
35
|
+
end
|
data/sapience.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "sapience/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sapience"
|
8
|
+
spec.version = Sapience::VERSION
|
9
|
+
spec.authors = ["Mikael Henriksson", "Alex Malkov"]
|
10
|
+
spec.email = ["mika@reevoo.com", "alex.malkov@reevoo.com"]
|
11
|
+
|
12
|
+
spec.summary = "Write a short summary, because Rubygems requires one."
|
13
|
+
spec.description = "Write a longer description or delete this line."
|
14
|
+
spec.homepage = "https://sapience.github.io"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
fail "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(/^(test|spec|features)\//) }
|
26
|
+
spec.bindir = "bin"
|
27
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "concurrent-ruby"
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
34
|
+
spec.add_development_dependency "reevoocop"
|
35
|
+
spec.add_development_dependency "fuubar"
|
36
|
+
spec.add_development_dependency "simplecov"
|
37
|
+
spec.add_development_dependency "simplecov-json"
|
38
|
+
spec.add_development_dependency "rspec-its"
|
39
|
+
end
|
data/test_app/.gitignore
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
*.rbc
|
2
|
+
capybara-*.html
|
3
|
+
.rspec
|
4
|
+
/log
|
5
|
+
/tmp
|
6
|
+
/db/*.sqlite3
|
7
|
+
/db/*.sqlite3-journal
|
8
|
+
/public/system
|
9
|
+
/coverage/
|
10
|
+
/spec/tmp
|
11
|
+
**.orig
|
12
|
+
rerun.txt
|
13
|
+
pickle-email-*.html
|
14
|
+
|
15
|
+
# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
|
16
|
+
config/initializers/secret_token.rb
|
17
|
+
config/secrets.yml
|
18
|
+
|
19
|
+
# dotenv
|
20
|
+
# TODO Comment out this rule if environment variables can be committed
|
21
|
+
.env
|
22
|
+
|
23
|
+
## Environment normalization:
|
24
|
+
/.bundle
|
25
|
+
/vendor/bundle
|
26
|
+
|
27
|
+
# these should all be checked in to normalize the environment:
|
28
|
+
# Gemfile.lock, .ruby-version, .ruby-gemset
|
29
|
+
|
30
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
31
|
+
.rvmrc
|
32
|
+
|
33
|
+
# if using bower-rails ignore default bower_components path bower.json files
|
34
|
+
/vendor/assets/bower_components
|
35
|
+
*.bowerrc
|
36
|
+
bower.json
|
37
|
+
|
38
|
+
# Ignore pow environment settings
|
39
|
+
.powenv
|
40
|
+
|
41
|
+
# Ignore Byebug command history file.
|
42
|
+
.byebug_history
|
data/test_app/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/test_app/Gemfile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
|
4
|
+
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
5
|
+
gem "rails", "~> 5.0.0"
|
6
|
+
# Use sqlite3 as the database for Active Record
|
7
|
+
gem "sqlite3"
|
8
|
+
# Use Puma as the app server
|
9
|
+
gem "puma", "~> 3.0"
|
10
|
+
# Use Redis adapter to run Action Cable in production
|
11
|
+
# gem 'redis', '~> 3.0'
|
12
|
+
# Use ActiveModel has_secure_password
|
13
|
+
# gem 'bcrypt', '~> 3.1.7'
|
14
|
+
|
15
|
+
# Use Capistrano for deployment
|
16
|
+
# gem 'capistrano-rails', group: :development
|
17
|
+
gem "sapience", path: "../"
|
18
|
+
gem "sentry-raven"
|
19
|
+
gem "statsd-ruby"
|
20
|
+
|
21
|
+
group :development, :test do
|
22
|
+
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
23
|
+
gem "byebug", platform: :mri
|
24
|
+
# gem "pry-nav"
|
25
|
+
gem "pry-byebug"
|
26
|
+
gem "rspec-rails"
|
27
|
+
end
|
28
|
+
|
29
|
+
group :development do
|
30
|
+
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
|
31
|
+
gem "web-console"
|
32
|
+
gem "listen", "~> 3.0.5"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
36
|
+
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
data/test_app/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
data/test_app/Rakefile
ADDED
File without changes
|