hanami-utils 2.0.0.alpha1 → 2.0.0

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.
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "logger"
4
-
5
- module Hanami
6
- class Logger < ::Logger
7
- # Filtering logic
8
- #
9
- # @since 1.1.0
10
- # @api private
11
- class Filter
12
- # @since 1.1.0
13
- # @api private
14
- def initialize(filters = [])
15
- @filters = filters
16
- end
17
-
18
- # @since 1.1.0
19
- # @api private
20
- def call(hash)
21
- _filtered_keys(hash).each do |key|
22
- *keys, last = _actual_keys(hash, key.split("."))
23
- keys.inject(hash, :fetch)[last] = "[FILTERED]"
24
- end
25
-
26
- hash
27
- end
28
-
29
- private
30
-
31
- # @since 1.1.0
32
- # @api private
33
- attr_reader :filters
34
-
35
- # @since 1.1.0
36
- # @api private
37
- def _filtered_keys(hash)
38
- _key_paths(hash).select { |key| filters.any? { |filter| key =~ %r{(\.|\A)#{filter}(\.|\z)} } }
39
- end
40
-
41
- # @since 1.1.0
42
- # @api private
43
- def _key_paths(hash, base = nil)
44
- hash.inject([]) do |results, (k, v)|
45
- results + (v.respond_to?(:each) ? _key_paths(v, _build_path(base, k)) : [_build_path(base, k)])
46
- end
47
- end
48
-
49
- # @since 1.1.0
50
- # @api private
51
- def _build_path(base, key)
52
- [base, key.to_s].compact.join(".")
53
- end
54
-
55
- # @since 1.1.0
56
- # @api private
57
- def _actual_keys(hash, keys)
58
- search_in = hash
59
-
60
- keys.inject([]) do |res, key|
61
- correct_key = search_in.key?(key.to_sym) ? key.to_sym : key
62
- search_in = search_in[correct_key]
63
- res + [correct_key]
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,183 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
- require "json"
5
- require "logger"
6
- require "hanami/utils/json"
7
- require "hanami/utils/class_attribute"
8
- require "hanami/utils/query_string"
9
-
10
- module Hanami
11
- class Logger < ::Logger
12
- # Hanami::Logger default formatter.
13
- # This formatter returns string in key=value format.
14
- #
15
- # @since 0.5.0
16
- # @api private
17
- #
18
- # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Formatter.html
19
- class Formatter < ::Logger::Formatter
20
- require "hanami/logger/filter"
21
- require "hanami/logger/colorizer"
22
-
23
- # @since 0.8.0
24
- # @api private
25
- SEPARATOR = " "
26
-
27
- # @since 0.8.0
28
- # @api private
29
- NEW_LINE = $/
30
-
31
- # @since 1.0.0
32
- # @api private
33
- RESERVED_KEYS = %i[app severity time].freeze
34
-
35
- include Utils::ClassAttribute
36
-
37
- class_attribute :subclasses
38
- self.subclasses = Set.new
39
-
40
- def self.fabricate(formatter, application_name, filters, colorizer)
41
- fabricated_formatter = _formatter_instance(formatter)
42
-
43
- fabricated_formatter.application_name = application_name
44
- fabricated_formatter.filter = Filter.new(filters)
45
- fabricated_formatter.colorizer = colorizer
46
-
47
- fabricated_formatter
48
- end
49
-
50
- # @api private
51
- def self.inherited(subclass)
52
- super
53
- subclasses << subclass
54
- end
55
-
56
- # @api private
57
- def self.eligible?(name)
58
- name == :default
59
- end
60
-
61
- # @api private
62
- # @since 1.1.0
63
- def self._formatter_instance(formatter)
64
- case formatter
65
- when Symbol
66
- (subclasses.find { |s| s.eligible?(formatter) } || self).new
67
- when nil
68
- new
69
- else
70
- formatter
71
- end
72
- end
73
- private_class_method :_formatter_instance
74
-
75
- # @since 0.5.0
76
- # @api private
77
- attr_writer :application_name
78
-
79
- # @since 1.0.0
80
- # @api private
81
- attr_reader :application_name
82
-
83
- # @since 1.2.0
84
- # @api private
85
- attr_writer :filter
86
-
87
- # @since 1.2.0
88
- # @api private
89
- attr_writer :colorizer
90
-
91
- # @since 0.5.0
92
- # @api private
93
- #
94
- # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Formatter.html#method-i-call
95
- def call(severity, time, progname, msg)
96
- colorized = @colorizer.call(application_name, severity, time, progname)
97
- colorized.merge!(_message_hash(msg))
98
-
99
- _format(colorized)
100
- end
101
-
102
- private
103
-
104
- # @since 0.8.0
105
- # @api private
106
- def _message_hash(message) # rubocop:disable Metrics/MethodLength
107
- case message
108
- when ::Hash
109
- @filter.call(message)
110
- when Exception
111
- ::Hash[
112
- message: message.message,
113
- backtrace: message.backtrace || [],
114
- error: message.class
115
- ]
116
- else
117
- ::Hash[message: message]
118
- end
119
- end
120
-
121
- # @since 0.8.0
122
- # @api private
123
- def _format(hash)
124
- "#{_line_front_matter(hash.delete(:app), hash.delete(:severity), hash.delete(:time))}#{SEPARATOR}#{_format_message(hash)}"
125
- end
126
-
127
- # @since 1.2.0
128
- # @api private
129
- def _line_front_matter(*args)
130
- args.map { |string| "[#{string}]" }.join(SEPARATOR)
131
- end
132
-
133
- # @since 1.2.0
134
- # @api private
135
- def _format_message(hash)
136
- if hash.key?(:error)
137
- _format_error(hash)
138
- elsif hash.key?(:params)
139
- "#{hash.values.join(SEPARATOR)}#{NEW_LINE}"
140
- else
141
- "#{Utils::QueryString.call(hash[:message] || hash)}#{NEW_LINE}"
142
- end
143
- end
144
-
145
- # @since 1.2.0
146
- # @api private
147
- def _format_error(hash)
148
- result = [hash[:error], hash[:message]].compact.join(": ").concat(NEW_LINE)
149
- hash[:backtrace].each do |line|
150
- result << "from #{line}#{NEW_LINE}"
151
- end
152
-
153
- result
154
- end
155
- end
156
-
157
- # Hanami::Logger JSON formatter.
158
- # This formatter returns string in JSON format.
159
- #
160
- # @since 0.5.0
161
- # @api private
162
- class JSONFormatter < Formatter
163
- # @api private
164
- def self.eligible?(name)
165
- name == :json
166
- end
167
-
168
- # @api private
169
- def colorizer=(*)
170
- @colorizer = NullColorizer.new
171
- end
172
-
173
- private
174
-
175
- # @since 0.8.0
176
- # @api private
177
- def _format(hash)
178
- hash[:time] = hash[:time].utc.iso8601
179
- Hanami::Utils::Json.generate(hash) + NEW_LINE
180
- end
181
- end
182
- end
183
- end
data/lib/hanami/logger.rb DELETED
@@ -1,360 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "logger"
4
- require "hanami/utils/string"
5
- require "hanami/utils/files"
6
-
7
- module Hanami
8
- # Hanami logger
9
- #
10
- # Implement with the same interface of Ruby std lib `Logger`.
11
- # It uses `STDOUT`, `STDERR`, file name or open file as output stream.
12
- #
13
- #
14
- # When a Hanami application is initialized, it creates a logger for that specific application.
15
- # For instance for a `Bookshelf::Application` a `Bookshelf::Logger` will be available.
16
- #
17
- # This is useful for auto-tagging the output. Eg (`app=Booshelf`).
18
- #
19
- # When used stand alone (eg. `Hanami::Logger.info`), it tags lines with `app=Shared`.
20
- #
21
- #
22
- # The available severity levels are the same of `Logger`:
23
- #
24
- # * DEBUG
25
- # * INFO
26
- # * WARN
27
- # * ERROR
28
- # * FATAL
29
- # * UNKNOWN
30
- #
31
- # Those levels are available both as class and instance methods.
32
- #
33
- # Also Hanami::Logger support different formatters. Now available only two:
34
- #
35
- # * Formatter (default)
36
- # * JSONFormatter
37
- #
38
- # And if you want to use custom formatter you need create new class inherited from
39
- # `Formatter` class and define `_format` private method like this:
40
- #
41
- # class CustomFormatter < Formatter
42
- # private
43
- # def _format(hash)
44
- # # ...
45
- # end
46
- # end
47
- #
48
- # @since 0.5.0
49
- #
50
- # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html
51
- # @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Severity.html
52
- #
53
- # @example Basic usage
54
- # require 'hanami'
55
- #
56
- # module Bookshelf
57
- # class Application < Hanami::Application
58
- # end
59
- # end
60
- #
61
- # # Initialize the application with the following code:
62
- # Bookshelf::Application.load!
63
- # # or
64
- # Bookshelf::Application.new
65
- #
66
- # Bookshelf::Logger.new.info('Hello')
67
- # # => app=Bookshelf severity=INFO time=1988-09-01 00:00:00 UTC message=Hello
68
- #
69
- # @example Standalone usage
70
- # require 'hanami/logger'
71
- #
72
- # Hanami::Logger.new.info('Hello')
73
- # # => app=Hanami severity=INFO time=2016-05-27 10:14:42 UTC message=Hello
74
- #
75
- # @example Custom tagging
76
- # require 'hanami/logger'
77
- #
78
- # Hanami::Logger.new('FOO').info('Hello')
79
- # # => app=FOO severity=INFO time=2016-05-27 10:14:42 UTC message=Hello
80
- #
81
- # @example Write to file
82
- # require 'hanami/logger'
83
- #
84
- # Hanami::Logger.new(stream: 'logfile.log').info('Hello')
85
- # # in logfile.log
86
- # # => app=FOO severity=INFO time=2016-05-27 10:14:42 UTC message=Hello
87
- #
88
- # @example Use JSON formatter
89
- # require 'hanami/logger'
90
- #
91
- # Hanami::Logger.new(formatter: Hanami::Logger::JSONFormatter).info('Hello')
92
- # # => "{\"app\":\"Hanami\",\"severity\":\"INFO\",\"time\":\"1988-09-01 00:00:00 UTC\",\"message\":\"Hello\"}"
93
- #
94
- # @example Disable colorization
95
- # require 'hanami/logger'
96
- #
97
- # Hanami::Logger.new(colorizer: false)
98
- #
99
- # @example Use custom colors
100
- # require 'hanami/logger'
101
- #
102
- # Hanami::Logger.new(colorizer: Hanami::Logger::Colorizer.new(colors: { app: :red }))
103
- #
104
- # @example Use custom colorizer
105
- # require "hanami/logger"
106
- # require "paint" # gem install paint
107
- #
108
- # class LogColorizer < Hanami::Logger::Colorizer
109
- # def initialize(colors: { app: [:red, :bright], severity: [:red, :blue], datetime: [:italic, :yellow] })
110
- # super
111
- # end
112
- #
113
- # private
114
- #
115
- # def colorize(message, color:)
116
- # Paint[message, *color]
117
- # end
118
- # end
119
- #
120
- # Hanami::Logger.new(colorizer: LogColorizer.new)
121
- class Logger < ::Logger
122
- require "hanami/logger/formatter"
123
- require "hanami/logger/colorizer"
124
-
125
- # Default application name.
126
- # This is used as a fallback for tagging purposes.
127
- #
128
- # @since 0.5.0
129
- # @api private
130
- DEFAULT_APPLICATION_NAME = "hanami"
131
-
132
- # @since 0.8.0
133
- # @api private
134
- LEVELS = ::Hash[
135
- "debug" => DEBUG,
136
- "info" => INFO,
137
- "warn" => WARN,
138
- "error" => ERROR,
139
- "fatal" => FATAL,
140
- "unknown" => UNKNOWN
141
- ].freeze
142
-
143
- # @since 1.2.0
144
- # @api private
145
- def self.level(level)
146
- case level
147
- when DEBUG..UNKNOWN
148
- level
149
- else
150
- LEVELS.fetch(level.to_s.downcase, DEBUG)
151
- end
152
- end
153
-
154
- # @since 0.5.0
155
- # @api private
156
- attr_writer :application_name
157
-
158
- # Initialize a logger
159
- #
160
- # @param application_name [String] an optional application name used for
161
- # tagging purposes
162
- #
163
- # @param args [Array<Object>] an optional set of arguments to honor Ruby's
164
- # `Logger#initialize` arguments. See Ruby documentation for details.
165
- #
166
- # @param stream [String, IO, StringIO, Pathname] an optional log stream.
167
- # This is a filename (`String`) or `IO` object (typically `$stdout`,
168
- # `$stderr`, or an open file). It defaults to `$stderr`.
169
- #
170
- # @param level [Integer,String] logging level. It can be expressed as an
171
- # integer, according to Ruby's `Logger` from standard library or as a
172
- # string with the name of the level
173
- #
174
- # @param formatter [Symbol,#_format] a formatter - We support `:json` as
175
- # JSON formatter or an object that respond to `#_format(data)`
176
- #
177
- # @since 0.5.0
178
- #
179
- # @see https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html#class-Logger-label-How+to+create+a+logger
180
- #
181
- # @example Basic usage
182
- # require 'hanami/logger'
183
- #
184
- # logger = Hanami::Logger.new
185
- # logger.info "Hello World"
186
- #
187
- # # => [Hanami] [DEBUG] [2017-03-30 15:41:01 +0200] Hello World
188
- #
189
- # @example Custom application name
190
- # require 'hanami/logger'
191
- #
192
- # logger = Hanami::Logger.new('bookshelf')
193
- # logger.info "Hello World"
194
- #
195
- # # => [bookshelf] [DEBUG] [2017-03-30 15:44:23 +0200] Hello World
196
- #
197
- # @example Logger level (Integer)
198
- # require 'hanami/logger'
199
- #
200
- # logger = Hanami::Logger.new(level: 2) # WARN
201
- # logger.info "Hello World"
202
- # # => true
203
- #
204
- # logger.info "Hello World"
205
- # # => true
206
- #
207
- # logger.warn "Hello World"
208
- # # => [Hanami] [WARN] [2017-03-30 16:00:48 +0200] Hello World
209
- #
210
- # @example Logger level (Constant)
211
- # require 'hanami/logger'
212
- #
213
- # logger = Hanami::Logger.new(level: Hanami::Logger::WARN)
214
- # logger.info "Hello World"
215
- # # => true
216
- #
217
- # logger.info "Hello World"
218
- # # => true
219
- #
220
- # logger.warn "Hello World"
221
- # # => [Hanami] [WARN] [2017-03-30 16:00:48 +0200] Hello World
222
- #
223
- # @example Logger level (String)
224
- # require 'hanami/logger'
225
- #
226
- # logger = Hanami::Logger.new(level: 'warn')
227
- # logger.info "Hello World"
228
- # # => true
229
- #
230
- # logger.info "Hello World"
231
- # # => true
232
- #
233
- # logger.warn "Hello World"
234
- # # => [Hanami] [WARN] [2017-03-30 16:00:48 +0200] Hello World
235
- #
236
- # @example Use a file
237
- # require 'hanami/logger'
238
- #
239
- # logger = Hanami::Logger.new(stream: "development.log")
240
- # logger.info "Hello World"
241
- #
242
- # # => true
243
- #
244
- # File.read("development.log")
245
- # # =>
246
- # # # Logfile created on 2017-03-30 15:52:48 +0200 by logger.rb/56815
247
- # # [Hanami] [DEBUG] [2017-03-30 15:52:54 +0200] Hello World
248
- #
249
- # @example Period rotation
250
- # require 'hanami/logger'
251
- #
252
- # # Rotate daily
253
- # logger = Hanami::Logger.new('bookshelf', 'daily', stream: 'development.log')
254
- #
255
- # @example File size rotation
256
- # require 'hanami/logger'
257
- #
258
- # # leave 10 old log files where the size is about 1,024,000 bytes
259
- # logger = Hanami::Logger.new('bookshelf', 10, 1024000, stream: 'development.log')
260
- #
261
- # @example Use a StringIO
262
- # require 'hanami/logger'
263
- #
264
- # stream = StringIO.new
265
- # logger = Hanami::Logger.new(stream: stream)
266
- # logger.info "Hello World"
267
- #
268
- # # => true
269
- #
270
- # stream.rewind
271
- # stream.read
272
- #
273
- # # => "[Hanami] [DEBUG] [2017-03-30 15:55:22 +0200] Hello World\n"
274
- #
275
- # @example JSON formatter
276
- # require 'hanami/logger'
277
- #
278
- # logger = Hanami::Logger.new(formatter: :json)
279
- # logger.info "Hello World"
280
- #
281
- # # => {"app":"Hanami","severity":"DEBUG","time":"2017-03-30T13:57:59Z","message":"Hello World"}
282
- # rubocop:disable Lint/HandleExceptions
283
- # rubocop:disable Metrics/ParameterLists
284
- def initialize(application_name = nil, *args, stream: $stdout, level: DEBUG, formatter: nil, filter: [], colorizer: nil)
285
- begin
286
- Utils::Files.mkdir_p(stream)
287
- rescue TypeError
288
- end
289
-
290
- super(stream, *args)
291
-
292
- @level = _level(level)
293
- @stream = stream
294
- @application_name = application_name
295
- @formatter = Formatter.fabricate(formatter, self.application_name, filter, lookup_colorizer(colorizer))
296
- end
297
-
298
- # rubocop:enable Metrics/ParameterLists
299
- # rubocop:enable Lint/HandleExceptions
300
-
301
- # Returns the current application name, this is used for tagging purposes
302
- #
303
- # @return [String] the application name
304
- #
305
- # @since 0.5.0
306
- def application_name
307
- @application_name || _application_name_from_namespace || _default_application_name
308
- end
309
-
310
- # @since 0.8.0
311
- # @api private
312
- def level=(value)
313
- super _level(value)
314
- end
315
-
316
- # Close the logging stream if this stream isn't an STDOUT
317
- #
318
- # @since 0.8.0
319
- def close
320
- super unless [STDOUT, $stdout].include?(@stream)
321
- end
322
-
323
- private
324
-
325
- # @since 0.5.0
326
- # @api private
327
- def _application_name_from_namespace
328
- class_name = self.class.name
329
- namespace = Utils::String.namespace(class_name)
330
-
331
- class_name != namespace and return namespace
332
- end
333
-
334
- # @since 0.5.0
335
- # @api private
336
- def _default_application_name
337
- DEFAULT_APPLICATION_NAME
338
- end
339
-
340
- # @since 0.8.0
341
- # @api private
342
- def _level(level)
343
- self.class.level(level)
344
- end
345
-
346
- # @since 1.2.0
347
- # @api private
348
- def lookup_colorizer(colorizer)
349
- return NullColorizer.new if colorizer == false
350
-
351
- colorizer || (tty? ? Colorizer : NullColorizer).new
352
- end
353
-
354
- # @since 1.2.0
355
- # @api private
356
- def tty?
357
- @logdev.dev.tty?
358
- end
359
- end
360
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Hanami
4
- module Utils
5
- # BasicObject
6
- #
7
- # @since 0.3.5
8
- class BasicObject < ::BasicObject
9
- # Return the class for debugging purposes.
10
- #
11
- # @since 0.3.5
12
- #
13
- # @see http://ruby-doc.org/core/Object.html#method-i-class
14
- def class
15
- (class << self; self; end).superclass
16
- end
17
-
18
- # Bare minimum inspect for debugging purposes.
19
- #
20
- # @return [String] the inspect string
21
- #
22
- # @since 0.3.5
23
- #
24
- # @see http://ruby-doc.org/core/Object.html#method-i-inspect
25
- #
26
- # rubocop:disable Style/FormatString
27
- # rubocop:disable Style/FormatStringToken
28
- def inspect
29
- "#<#{self.class}:#{'0x0000%x' % (__id__ << 1)}#{__inspect}>"
30
- end
31
- # rubocop:enable Style/FormatStringToken
32
- # rubocop:enable Style/FormatString
33
-
34
- # Alias for __id__
35
- #
36
- # @return [Fixnum] the object id
37
- #
38
- # @since 0.9.0
39
- #
40
- # @see http://ruby-doc.org/core/Object.html#method-i-object_id
41
- def object_id
42
- __id__
43
- end
44
-
45
- # Interface for pp
46
- #
47
- # @param printer [PP] the Pretty Printable printer
48
- # @return [String] the pretty-printable inspection of the object
49
- #
50
- # @since 0.9.0
51
- #
52
- # @see https://ruby-doc.org/stdlib/libdoc/pp/rdoc/PP.html
53
- def pretty_print(printer)
54
- printer.text(inspect)
55
- end
56
-
57
- # Returns true if responds to the given method.
58
- #
59
- # @return [TrueClass,FalseClass] the result of the check
60
- #
61
- # @since 0.3.5
62
- #
63
- # @see http://ruby-doc.org/core-2.2.1/Object.html#method-i-respond_to-3F
64
- def respond_to?(method_name, include_all = false)
65
- respond_to_missing?(method_name, include_all)
66
- end
67
-
68
- private
69
-
70
- # Must be overridden by descendants
71
- #
72
- # @since 0.3.5
73
- # @api private
74
- def respond_to_missing?(_method_name, _include_all)
75
- ::Kernel.raise ::NotImplementedError
76
- end
77
-
78
- # @since 0.3.5
79
- # @api private
80
- def __inspect
81
- end
82
- end
83
- end
84
- end