hanami-utils 2.0.0.beta1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1c3da2c837516bf458d208681a9183b4ce2255d4015d1ef436344b951f72127
4
- data.tar.gz: 1dd912dfe51da2149599514b70c1d1893651d09e2a2c6f65ffdce4b5142c7132
3
+ metadata.gz: 2575ff2a640a9ffcddf8a9930a887d536ec9da4b64bc4a2883849ed261f57b43
4
+ data.tar.gz: 70fbfe55f826d940c0f5decad39840990925e061de975e53d2b672fdae9873df
5
5
  SHA512:
6
- metadata.gz: d1fb0f77574208374688089528fc4e9479f17fe514d795c0407fd6cb65c3c14cd64aa5956bf90f9e29692042c784845ea3f27c0c6c0fc5fbcf52fb439f6ba557
7
- data.tar.gz: 5e5dd4627115e3d92f8fa5c19a323e645b5bc245e72a6156c40b449af68eed08b42148639f25a5cee98e5d2739504d9cb0bfb6dd7781f54e1ae554b77955bbd9
6
+ metadata.gz: 73d5a9fade1cb961ac4be22b8967507fceebde1a9d8edb33d71634b06ef6c06538c8d0ef2b1a32a33e8442c7609b675ae99bd63780c353c6db02485ddbfd3432
7
+ data.tar.gz: 368ffe941852e67432d9574b79220b1807b1529cbdf4289fb2fd2db92299888617bc3779bd6e0c3f03b81d5fbcb9725b8b36364733d262a3c5fa2cb257db91b1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  Ruby core extensions and class utilities for Hanami
4
4
 
5
+ ## v2.0.0 - 2022-11-22
6
+
7
+ ## v2.0.0.rc1 - 2022-11-08
8
+
9
+ ### Fixed
10
+
11
+ - [Benjamin Klotz] Ensure `Hanami::Utils::String.underscore` to replace `"."` (dot character) into underscore
12
+
13
+ ### Changed
14
+
15
+ - [Luca Guidi] Removed `Hanami::Logger` in favor of `Dry::Logger`
16
+
5
17
  ## v2.0.0.beta1 - 2022-07-20
6
18
 
7
19
  ### Changed
data/hanami-utils.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.metadata["rubygems_mfa_required"] = "true"
21
21
  spec.required_ruby_version = ">= 3.0"
22
22
 
23
- spec.add_dependency "dry-transformer", "~> 0.1"
23
+ spec.add_dependency "dry-transformer", "~> 1.0", "< 2"
24
24
  spec.add_dependency "concurrent-ruby", "~> 1.0"
25
25
 
26
26
  spec.add_development_dependency "bundler", ">= 1.6", "< 3"
@@ -1,11 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hanami
4
+ # Shared Rack middleware for Hanami apps.
5
+ #
6
+ # This module is defined in hanami-utils so that any gem providing middleware can use its
7
+ # resources.
8
+ #
9
+ # @since 2.0.0
4
10
  module Middleware
5
- # Hanami middleware utils
6
-
11
+ # Base class for all errors raised during middleware loading.
12
+ #
7
13
  # @since 2.0.0
8
- class Error < ::StandardError
14
+ # @api public
15
+ class Error < StandardError
9
16
  end
10
17
  end
11
18
  end
@@ -71,33 +71,6 @@ module Hanami
71
71
  def self.load(name, namespace = Object)
72
72
  load!(name, namespace) if namespace.const_defined?(name.to_s, false)
73
73
  end
74
-
75
- def self.tokenize(pattern)
76
- if match = TOKENIZE_REGEXP.match(pattern)
77
- pre = match.pre_match
78
- post = match.post_match
79
- tokens = match[1].split(TOKENIZE_SEPARATOR)
80
- tokens.each do |token|
81
- yield("#{pre}#{token}#{post}")
82
- end
83
- else
84
- yield(pattern)
85
- end
86
-
87
- nil
88
- end
89
-
90
- # Regexp for .tokenize
91
- #
92
- # @since 1.3.0
93
- # @api private
94
- TOKENIZE_REGEXP = /\((.*)\)/
95
-
96
- # Separator for .tokenize
97
- #
98
- # @since 1.3.0
99
- # @api private
100
- TOKENIZE_SEPARATOR = "|"
101
74
  end
102
75
  end
103
76
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry-transformer"
3
+ require "dry/transformer"
4
4
 
5
5
  module Hanami
6
6
  module Utils
@@ -35,10 +35,20 @@ module Hanami
35
35
 
36
36
  # rubocop:disable Style/ClassVars
37
37
  if defined?(MultiJson)
38
+ # @since 0.8.0
39
+ # @api private
38
40
  @@engine = MultiJsonAdapter.new
41
+
42
+ # @since 0.8.0
43
+ # @api private
39
44
  ParserError = MultiJson::ParseError
40
45
  else
46
+ # @since 0.8.0
47
+ # @api private
41
48
  @@engine = ::JSON
49
+
50
+ # @since 0.8.0
51
+ # @api private
42
52
  ParserError = ::JSON::ParserError
43
53
  end
44
54
  # rubocop:enable Style/ClassVars
@@ -5,6 +5,7 @@ require "date"
5
5
  require "time"
6
6
  require "pathname"
7
7
  require "bigdecimal"
8
+ require "bigdecimal/util"
8
9
  require "hanami/utils"
9
10
  require "hanami/utils/string"
10
11
 
@@ -7,12 +7,16 @@ module Hanami
7
7
  # It doesn't check if you're writing to a file or anything, so you have to
8
8
  # check that yourself before using this module.
9
9
  #
10
+ # @api public
10
11
  # @since 1.2.0
11
12
  module ShellColor
12
- # Unknown color code error
13
+ # Error raised for unknown color codes.
13
14
  #
14
15
  # @since 1.2.0
16
+ # @api public
15
17
  class UnknownColorCodeError < ::StandardError
18
+ # @since 1.2.0
19
+ # @api private
16
20
  def initialize(code)
17
21
  super("unknown color code: `#{code.inspect}'")
18
22
  end
@@ -231,7 +231,7 @@ module Hanami
231
231
  string.gsub!(NAMESPACE_SEPARATOR, UNDERSCORE_SEPARATOR)
232
232
  string.gsub!(/([A-Z\d]+)([A-Z][a-z])/, UNDERSCORE_DIVISION_TARGET)
233
233
  string.gsub!(/([a-z\d])([A-Z])/, UNDERSCORE_DIVISION_TARGET)
234
- string.gsub!(/[[:space:]]|-/, UNDERSCORE_DIVISION_TARGET)
234
+ string.gsub!(/[[:space:]]|-|\./, UNDERSCORE_DIVISION_TARGET)
235
235
  string.downcase
236
236
  end
237
237
 
@@ -2,9 +2,10 @@
2
2
 
3
3
  module Hanami
4
4
  module Utils
5
- # Defines the version
5
+ # The current hanami-utils version.
6
6
  #
7
7
  # @since 0.1.0
8
- VERSION = "2.0.0.beta1"
8
+ # @api public
9
+ VERSION = "2.0.0"
9
10
  end
10
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-20 00:00:00.000000000 Z
11
+ date: 2022-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-transformer
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.1'
19
+ version: '1.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - "~>"
25
28
  - !ruby/object:Gem::Version
26
- version: '0.1'
29
+ version: '1.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: concurrent-ruby
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -112,10 +118,6 @@ files:
112
118
  - README.md
113
119
  - hanami-utils.gemspec
114
120
  - lib/hanami-utils.rb
115
- - lib/hanami/logger.rb
116
- - lib/hanami/logger/colorizer.rb
117
- - lib/hanami/logger/filter.rb
118
- - lib/hanami/logger/formatter.rb
119
121
  - lib/hanami/middleware.rb
120
122
  - lib/hanami/utils.rb
121
123
  - lib/hanami/utils/blank.rb
@@ -153,11 +155,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
155
  version: '3.0'
154
156
  required_rubygems_version: !ruby/object:Gem::Requirement
155
157
  requirements:
156
- - - ">"
158
+ - - ">="
157
159
  - !ruby/object:Gem::Version
158
- version: 1.3.1
160
+ version: '0'
159
161
  requirements: []
160
- rubygems_version: 3.3.3
162
+ rubygems_version: 3.3.7
161
163
  signing_key:
162
164
  specification_version: 4
163
165
  summary: Ruby core extentions and Hanami utilities
@@ -1,105 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "logger"
4
- require "hanami/utils/shell_color"
5
-
6
- module Hanami
7
- class Logger < ::Logger
8
- # Null colorizer for logger streams that aren't a TTY (eg. files)
9
- #
10
- # @since 1.2.0
11
- # @api private
12
- class NullColorizer
13
- # @since 1.2.0
14
- # @api private
15
- def call(app, severity, datetime, _progname)
16
- ::Hash[
17
- app: app,
18
- severity: severity,
19
- time: datetime,
20
- ]
21
- end
22
- end
23
-
24
- # Hanami::Logger Default Colorizer
25
- #
26
- # This colorizer takes in parts of the log message and returns them with
27
- # proper shellcode to colorize when displayed to a tty.
28
- #
29
- # @since 1.2.0
30
- # @api private
31
- class Colorizer < NullColorizer
32
- def initialize(colors: COLORS)
33
- @colors = colors
34
- end
35
-
36
- # Colorize the inputs
37
- #
38
- # @param app [#to_s] the app name
39
- # @param severity [#to_s] log severity
40
- # @param datetime [#to_s] timestamp
41
- # @param _progname [#to_s] program name - ignored, accepted for
42
- # compatibility with Ruby's Logger
43
- #
44
- # @return [::Hash] an Hash containing the keys `:app`, `:severity`, and `:time`
45
- def call(app, severity, datetime, _progname)
46
- ::Hash[
47
- app: app(app),
48
- severity: severity(severity),
49
- time: datetime(datetime),
50
- ]
51
- end
52
-
53
- private
54
-
55
- # The colors defined for the three parts of the log message
56
- #
57
- # @since 1.2.0
58
- # @api private
59
- COLORS = ::Hash[
60
- app: :blue,
61
- datetime: :cyan,
62
- ].freeze
63
-
64
- # @since 1.2.0
65
- # @api private
66
- LEVELS = ::Hash[
67
- Hanami::Logger::DEBUG => :cyan,
68
- Hanami::Logger::INFO => :magenta,
69
- Hanami::Logger::WARN => :yellow,
70
- Hanami::Logger::ERROR => :red,
71
- Hanami::Logger::FATAL => :red,
72
- Hanami::Logger::UNKNOWN => :blue,
73
- ].freeze
74
-
75
- attr_reader :colors
76
-
77
- # @since 1.2.0
78
- # @api private
79
- def app(input)
80
- colorize(input, color: colors.fetch(:app, nil))
81
- end
82
-
83
- # @since 1.2.0
84
- # @api private
85
- def severity(input)
86
- color = LEVELS.fetch(Hanami::Logger.level(input), :gray)
87
- colorize(input, color: color)
88
- end
89
-
90
- # @since 1.2.0
91
- # @api private
92
- def datetime(input)
93
- colorize(input, color: colors.fetch(:datetime, nil))
94
- end
95
-
96
- # @since 1.2.0
97
- # @api private
98
- def colorize(message, color:)
99
- return message if color.nil?
100
-
101
- Hanami::Utils::ShellColor.call(message, color: color)
102
- end
103
- end
104
- end
105
- end
@@ -1,144 +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.3.7
13
- # @api private
14
- FILTERED_VALUE = "[FILTERED]"
15
-
16
- def initialize(filters = [], mask: FILTERED_VALUE)
17
- @filters = filters
18
- @mask = mask
19
- end
20
-
21
- # @since 1.1.0
22
- # @api private
23
- def call(params)
24
- _filter(_copy_params(params))
25
- end
26
-
27
- private
28
-
29
- # @since 1.1.0
30
- # @api private
31
- attr_reader :filters
32
-
33
- # @since 1.3.7
34
- # @api private
35
- attr_reader :mask
36
-
37
- # This is a simple deep merge to merge the original input
38
- # with the filtered hash which contains '[FILTERED]' string.
39
- #
40
- # It only deep-merges if the conflict values are both hashes.
41
- #
42
- # @since 1.3.7
43
- # @api private
44
- def _deep_merge(original_hash, filtered_hash)
45
- original_hash.merge(filtered_hash) do |_key, original_item, filtered_item|
46
- if original_item.is_a?(Hash) && filtered_item.is_a?(Hash)
47
- _deep_merge(original_item, filtered_item)
48
- elsif filtered_item == FILTERED_VALUE
49
- filtered_item
50
- else
51
- original_item
52
- end
53
- end
54
- end
55
-
56
- # @since 1.1.0
57
- # @api private
58
- def _filtered_keys(hash)
59
- _key_paths(hash).select { |key| filters.any? { |filter| key =~ %r{(\.|\A)#{filter}(\.|\z)} } }
60
- end
61
-
62
- # @since 1.1.0
63
- # @api private
64
- def _key_paths(hash, base = nil)
65
- hash.inject([]) do |results, (k, v)|
66
- results + (_key_paths?(v) ? _key_paths(v, _build_path(base, k)) : [_build_path(base, k)])
67
- end
68
- end
69
-
70
- # @since 1.1.0
71
- # @api private
72
- def _build_path(base, key)
73
- [base, key.to_s].compact.join(".")
74
- end
75
-
76
- # @since 1.1.0
77
- # @api private
78
- def _actual_keys(hash, keys)
79
- search_in = hash
80
-
81
- keys.inject([]) do |res, key|
82
- correct_key = search_in.key?(key.to_sym) ? key.to_sym : key
83
- search_in = search_in[correct_key]
84
- res + [correct_key]
85
- end
86
- end
87
-
88
- # Check if the given value can be iterated (`Enumerable`) and that isn't a `File`.
89
- # This is useful to detect closed `Tempfiles`.
90
- #
91
- # @since 1.3.5
92
- # @api private
93
- #
94
- # @see https://github.com/hanami/utils/pull/342
95
- def _key_paths?(value)
96
- value.is_a?(Enumerable) && !value.is_a?(File)
97
- end
98
-
99
- # @since 1.3.7
100
- # @api private
101
- def _deep_dup(hash)
102
- hash.transform_values do |value|
103
- if value.is_a?(Hash)
104
- _deep_dup(value)
105
- else
106
- _key_paths?(value) ? value.dup : value
107
- end
108
- end
109
- end
110
-
111
- # @since 1.3.7
112
- # @api private
113
- def _copy_params(params)
114
- case params
115
- when Hash
116
- _deep_dup(params)
117
- when Array
118
- params.map { |hash| _deep_dup(hash) }
119
- end
120
- end
121
-
122
- # @since 1.3.7
123
- # @api private
124
- def _filter_hash(hash)
125
- _filtered_keys(hash).each do |key|
126
- *keys, last = _actual_keys(hash, key.split("."))
127
- keys.inject(hash, :fetch)[last] = mask
128
- end
129
- hash
130
- end
131
-
132
- # @since 1.3.7
133
- # @api private
134
- def _filter(params)
135
- case params
136
- when Hash
137
- _filter_hash(params)
138
- when Array
139
- params.map { |hash| _filter_hash(hash) }
140
- end
141
- end
142
- end
143
- end
144
- 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)
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)}" # rubocop:disable Layout/LineLength
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,362 +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
- # Implementation 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 standalone (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 supports 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/SuppressedException
283
- # rubocop:disable Metrics/ParameterLists
284
- def initialize(application_name = nil, *args, stream: $stdout, level: DEBUG, formatter: nil, filter: [], colorizer: nil) # rubocop:disable Layout/LineLength
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/SuppressedException
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
- # Closes 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) # rubocop:disable Style/GlobalStdStream
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
- return false if @logdev.nil?
358
-
359
- @logdev.dev.tty?
360
- end
361
- end
362
- end