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.
Files changed (125) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +2 -0
  5. data/.ruby-version +1 -0
  6. data/.simplecov +15 -0
  7. data/.travis.yml +27 -0
  8. data/CODE_OF_CONDUCT.md +49 -0
  9. data/Gemfile +12 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +43 -0
  12. data/Rakefile +13 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/lib/sapience/ansi_colors.rb +27 -0
  16. data/lib/sapience/appender/file.rb +138 -0
  17. data/lib/sapience/appender/sentry.rb +72 -0
  18. data/lib/sapience/appender/statsd.rb +68 -0
  19. data/lib/sapience/appender/wrapper.rb +74 -0
  20. data/lib/sapience/base.rb +381 -0
  21. data/lib/sapience/concerns/compatibility.rb +53 -0
  22. data/lib/sapience/configuration.rb +86 -0
  23. data/lib/sapience/core_ext/thread.rb +14 -0
  24. data/lib/sapience/formatters/base.rb +36 -0
  25. data/lib/sapience/formatters/color.rb +68 -0
  26. data/lib/sapience/formatters/default.rb +41 -0
  27. data/lib/sapience/formatters/json.rb +22 -0
  28. data/lib/sapience/formatters/raw.rb +12 -0
  29. data/lib/sapience/log.rb +221 -0
  30. data/lib/sapience/loggable.rb +29 -0
  31. data/lib/sapience/logger.rb +236 -0
  32. data/lib/sapience/rails.rb +15 -0
  33. data/lib/sapience/sapience.rb +357 -0
  34. data/lib/sapience/subscriber.rb +127 -0
  35. data/lib/sapience/version.rb +3 -0
  36. data/lib/sapience.rb +35 -0
  37. data/sapience.gemspec +39 -0
  38. data/test_app/.gitignore +42 -0
  39. data/test_app/.rspec +2 -0
  40. data/test_app/.ruby-version +1 -0
  41. data/test_app/Gemfile +36 -0
  42. data/test_app/README.md +24 -0
  43. data/test_app/Rakefile +6 -0
  44. data/test_app/app/assets/config/manifest.js +2 -0
  45. data/test_app/app/assets/images/.keep +0 -0
  46. data/test_app/app/assets/javascripts/posts.js +2 -0
  47. data/test_app/app/assets/stylesheets/application.css +15 -0
  48. data/test_app/app/assets/stylesheets/posts.css +4 -0
  49. data/test_app/app/assets/stylesheets/scaffold.css +84 -0
  50. data/test_app/app/channels/application_cable/channel.rb +4 -0
  51. data/test_app/app/channels/application_cable/connection.rb +4 -0
  52. data/test_app/app/controllers/application_controller.rb +3 -0
  53. data/test_app/app/controllers/concerns/.keep +0 -0
  54. data/test_app/app/controllers/posts_controller.rb +58 -0
  55. data/test_app/app/helpers/application_helper.rb +2 -0
  56. data/test_app/app/helpers/posts_helper.rb +2 -0
  57. data/test_app/app/jobs/application_job.rb +2 -0
  58. data/test_app/app/mailers/application_mailer.rb +4 -0
  59. data/test_app/app/models/application_record.rb +3 -0
  60. data/test_app/app/models/concerns/.keep +0 -0
  61. data/test_app/app/models/post.rb +3 -0
  62. data/test_app/app/models/user.rb +2 -0
  63. data/test_app/app/views/layouts/application.html.erb +13 -0
  64. data/test_app/app/views/layouts/mailer.html.erb +13 -0
  65. data/test_app/app/views/layouts/mailer.text.erb +1 -0
  66. data/test_app/app/views/posts/_form.html.erb +32 -0
  67. data/test_app/app/views/posts/create.html.erb +2 -0
  68. data/test_app/app/views/posts/destroy.html.erb +2 -0
  69. data/test_app/app/views/posts/edit.html.erb +6 -0
  70. data/test_app/app/views/posts/index.html.erb +31 -0
  71. data/test_app/app/views/posts/new.html.erb +5 -0
  72. data/test_app/app/views/posts/show.html.erb +19 -0
  73. data/test_app/app/views/posts/update.html.erb +2 -0
  74. data/test_app/bin/bundle +3 -0
  75. data/test_app/bin/rails +4 -0
  76. data/test_app/bin/rake +4 -0
  77. data/test_app/bin/setup +34 -0
  78. data/test_app/bin/update +29 -0
  79. data/test_app/config/application.rb +26 -0
  80. data/test_app/config/boot.rb +3 -0
  81. data/test_app/config/cable.yml +9 -0
  82. data/test_app/config/database.yml +25 -0
  83. data/test_app/config/environment.rb +5 -0
  84. data/test_app/config/environments/development.rb +49 -0
  85. data/test_app/config/environments/production.rb +78 -0
  86. data/test_app/config/environments/test.rb +42 -0
  87. data/test_app/config/initializers/application_controller_renderer.rb +6 -0
  88. data/test_app/config/initializers/backtrace_silencers.rb +7 -0
  89. data/test_app/config/initializers/cookies_serializer.rb +5 -0
  90. data/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  91. data/test_app/config/initializers/inflections.rb +16 -0
  92. data/test_app/config/initializers/mime_types.rb +4 -0
  93. data/test_app/config/initializers/new_framework_defaults.rb +24 -0
  94. data/test_app/config/initializers/session_store.rb +3 -0
  95. data/test_app/config/initializers/wrap_parameters.rb +14 -0
  96. data/test_app/config/locales/en.yml +23 -0
  97. data/test_app/config/puma.rb +45 -0
  98. data/test_app/config/routes.rb +3 -0
  99. data/test_app/config/secrets.yml +22 -0
  100. data/test_app/config.ru +5 -0
  101. data/test_app/db/migrate/20160812092236_create_users.rb +13 -0
  102. data/test_app/db/migrate/20160812093621_create_posts.rb +11 -0
  103. data/test_app/db/schema.rb +33 -0
  104. data/test_app/db/seeds.rb +7 -0
  105. data/test_app/lib/assets/.keep +0 -0
  106. data/test_app/lib/tasks/.keep +0 -0
  107. data/test_app/log/.keep +0 -0
  108. data/test_app/public/404.html +67 -0
  109. data/test_app/public/422.html +67 -0
  110. data/test_app/public/500.html +66 -0
  111. data/test_app/public/apple-touch-icon-precomposed.png +0 -0
  112. data/test_app/public/apple-touch-icon.png +0 -0
  113. data/test_app/public/favicon.ico +0 -0
  114. data/test_app/public/robots.txt +5 -0
  115. data/test_app/spec/controllers/posts_controller_spec.rb +7 -0
  116. data/test_app/spec/helpers/posts_helper_spec.rb +15 -0
  117. data/test_app/spec/models/post_spec.rb +5 -0
  118. data/test_app/spec/models/user_spec.rb +5 -0
  119. data/test_app/spec/rails_helper.rb +23 -0
  120. data/test_app/spec/requests/posts_spec.rb +10 -0
  121. data/test_app/spec/routing/posts_routing_spec.rb +39 -0
  122. data/test_app/spec/spec_helper.rb +60 -0
  123. data/test_app/tmp/.keep +0 -0
  124. data/test_app/vendor/assets/stylesheets/.keep +0 -0
  125. 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