sapience 0.1.1

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