dry-logger 1.0.0.rc2 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -2
- data/lib/dry/logger/backends/core.rb +38 -0
- data/lib/dry/logger/backends/proxy.rb +38 -0
- data/lib/dry/logger/backends/stream.rb +6 -11
- data/lib/dry/logger/clock.rb +47 -0
- data/lib/dry/logger/constants.rb +55 -3
- data/lib/dry/logger/dispatcher.rb +81 -12
- data/lib/dry/logger/entry.rb +27 -33
- data/lib/dry/logger/formatters/colors.rb +84 -0
- data/lib/dry/logger/formatters/json.rb +27 -1
- data/lib/dry/logger/formatters/rack.rb +10 -2
- data/lib/dry/logger/formatters/string.rb +93 -26
- data/lib/dry/logger/formatters/structured.rb +16 -5
- data/lib/dry/logger/formatters/template.rb +92 -0
- data/lib/dry/logger/global.rb +123 -0
- data/lib/dry/logger/version.rb +1 -1
- data/lib/dry/logger.rb +45 -61
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 730cb488952659e0a7499948d6f77e3ed61b7be38e462d7a000479d7e6a8c7f2
|
4
|
+
data.tar.gz: 81ddc5b02fc960cb3650f1305e19bfc33d21488722455c8bfdcb3e411cb07bf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02d8e2eb6e2029a3c29269db001e5e3f50eacf1605efa4cdd62ae01a1e8349b80073e0b591761ed3bc82f7acda5b139a2a03c120c32b3716c27c2d054eee0a64
|
7
|
+
data.tar.gz: 2f11fec53ac321f0157f3c620944d460a189fa4da112a5b2d617a21171533dcacf17370bf1288d2ef1e4574ab4f35518aca2e280a7063df669768d1c3dda73b6
|
data/CHANGELOG.md
CHANGED
@@ -1,13 +1,27 @@
|
|
1
1
|
<!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
|
2
2
|
|
3
|
-
## 1.0.0
|
3
|
+
## 1.0.0
|
4
4
|
|
5
5
|
This is a port of the original Hanami logger from hanami-utils extended with support for logging
|
6
|
-
dispatchers that can log to different destinations.
|
6
|
+
dispatchers that can log to different destinations and plenty more.
|
7
7
|
|
8
8
|
|
9
9
|
### Added
|
10
10
|
|
11
|
+
- Support arbitrary logging backends through proxy (via #12) (@solnic)
|
12
|
+
- Support for conditional logging when using arbitrary logging backends (via #13) (@solnic)
|
13
|
+
- Support for registering templates via `Dry::Logger.register_template` (via #14) (@solnic)
|
14
|
+
- Support for payload keys as template tokens (via #14) (@solnic)
|
15
|
+
- Support for payload value formatter methods, ie if there's `:verb` token your formatter can implement `format_verb(value)` (via #14) (@solnic)
|
16
|
+
- Support block-based setup (via #16) (@solnic)
|
17
|
+
- Support for defining cherry-picked keys from the payload in string templates (via #17) (@solnic)
|
18
|
+
- Support for `%<payload>s` template token. It will be replaced by a formatted payload, excluding any key that you specified explicitly in the template (via #17) (@solnic)
|
19
|
+
- Support for colorized output using color tags in templates (via #18) (@solnic)
|
20
|
+
- Support for `colorize: true` logger option which enables severity coloring in string formatter (via #18) (@solnic)
|
21
|
+
- `:details` template: `"[%<progname>s] [%<severity>s] [%<time>s] %<message>s %<payload>s"` (@solnic)
|
22
|
+
- A new option `on_crash` for setting up a logger-crash handling proc (via #21) (@solnic)
|
23
|
+
- Handle logger crashes by default using a simple `$stdout` logger (via #21) (@solnic)
|
24
|
+
- Support for regular logger backends that don't support `log?` predicate (@solnic)
|
11
25
|
- Support for providing a string template for log entries via `template` option (via #7) (@solnic)
|
12
26
|
- `:rack` string log formatter which inlines request info and displays params at the end (@solnic)
|
13
27
|
- Conditional log dispatch via `#log_if` backend's predicate (via #9) (@solnic)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/logger/constants"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Logger
|
7
|
+
module Backends
|
8
|
+
module Core
|
9
|
+
# Return a proc used by the log? predicate
|
10
|
+
#
|
11
|
+
# @since 1.0.0
|
12
|
+
# @api private
|
13
|
+
attr_reader :log_if
|
14
|
+
|
15
|
+
# Set a predicate proc that checks if an entry should be logged by a given backend
|
16
|
+
#
|
17
|
+
# The predicate will receive {Entry} as its argument and should return true/false
|
18
|
+
#
|
19
|
+
# @param [Proc, #to_proc] spec A proc-like object
|
20
|
+
# @since 1.0.0
|
21
|
+
# @api public
|
22
|
+
def log_if=(spec)
|
23
|
+
@log_if = spec&.to_proc
|
24
|
+
end
|
25
|
+
|
26
|
+
# @since 1.0.0
|
27
|
+
# @api private
|
28
|
+
def log?(entry)
|
29
|
+
if log_if
|
30
|
+
log_if.call(entry)
|
31
|
+
else
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
require "dry/logger/constants"
|
6
|
+
require "dry/logger/backends/core"
|
7
|
+
|
8
|
+
module Dry
|
9
|
+
module Logger
|
10
|
+
module Backends
|
11
|
+
# Logger proxy is used for regular loggers that don't work with log entries
|
12
|
+
#
|
13
|
+
# @since 1.0.0
|
14
|
+
# @api private
|
15
|
+
class Proxy < SimpleDelegator
|
16
|
+
include Core
|
17
|
+
|
18
|
+
# @since 0.1.0
|
19
|
+
# @api public
|
20
|
+
attr_accessor :log_if
|
21
|
+
|
22
|
+
LOG_METHODS.each do |method|
|
23
|
+
define_method(method) { |entry| __getobj__.public_send(method, entry.message) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# @since 1.0.0
|
27
|
+
# @api private
|
28
|
+
def log?(entry)
|
29
|
+
if log_if
|
30
|
+
log_if.call(entry)
|
31
|
+
else
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -3,11 +3,14 @@
|
|
3
3
|
require "logger"
|
4
4
|
|
5
5
|
require "dry/logger/constants"
|
6
|
+
require "dry/logger/backends/core"
|
6
7
|
|
7
8
|
module Dry
|
8
9
|
module Logger
|
9
10
|
module Backends
|
10
11
|
class Stream < ::Logger
|
12
|
+
include Core
|
13
|
+
|
11
14
|
# @since 0.1.0
|
12
15
|
# @api private
|
13
16
|
attr_reader :stream
|
@@ -16,10 +19,6 @@ module Dry
|
|
16
19
|
# @api private
|
17
20
|
attr_reader :level
|
18
21
|
|
19
|
-
# @since 0.1.0
|
20
|
-
# @api public
|
21
|
-
attr_accessor :log_if
|
22
|
-
|
23
22
|
# @since 0.1.0
|
24
23
|
# @api private
|
25
24
|
def initialize(stream:, formatter:, level: DEFAULT_LEVEL, progname: nil, log_if: nil)
|
@@ -33,13 +32,9 @@ module Dry
|
|
33
32
|
end
|
34
33
|
|
35
34
|
# @since 1.0.0
|
36
|
-
# @api
|
37
|
-
def
|
38
|
-
|
39
|
-
log_if.call(entry)
|
40
|
-
else
|
41
|
-
true
|
42
|
-
end
|
35
|
+
# @api public
|
36
|
+
def inspect
|
37
|
+
%(#<#{self.class} stream=#{stream} level=#{level} log_if=#{log_if}>)
|
43
38
|
end
|
44
39
|
end
|
45
40
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Logger
|
5
|
+
# @since 1.0.0
|
6
|
+
# @api private
|
7
|
+
class Clock
|
8
|
+
# @since 1.0.0
|
9
|
+
# @api private
|
10
|
+
attr_reader :unit
|
11
|
+
|
12
|
+
# @since 1.0.0
|
13
|
+
# @api private
|
14
|
+
def initialize(unit: :nanosecond)
|
15
|
+
@unit = unit
|
16
|
+
end
|
17
|
+
|
18
|
+
# @since 1.0.0
|
19
|
+
# @api private
|
20
|
+
def now
|
21
|
+
Time.now
|
22
|
+
end
|
23
|
+
|
24
|
+
# @since 1.0.0
|
25
|
+
# @api private
|
26
|
+
def now_utc
|
27
|
+
now.getutc
|
28
|
+
end
|
29
|
+
|
30
|
+
# @since 1.0.0
|
31
|
+
# @api private
|
32
|
+
def measure
|
33
|
+
start = current
|
34
|
+
result = yield
|
35
|
+
[result, current - start]
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# @since 1.0.0
|
41
|
+
# @api private
|
42
|
+
def current
|
43
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, unit)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/dry/logger/constants.rb
CHANGED
@@ -4,22 +4,67 @@ require "logger"
|
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
module Logger
|
7
|
-
|
7
|
+
# @since 1.0.0
|
8
|
+
# @api private
|
9
|
+
NEW_LINE = $/ # rubocop:disable Style/SpecialGlobalVars
|
10
|
+
|
11
|
+
# @since 1.0.0
|
12
|
+
# @api private
|
13
|
+
SEPARATOR = " "
|
14
|
+
|
15
|
+
# @since 1.0.0
|
16
|
+
# @api private
|
17
|
+
TAB = SEPARATOR * 2
|
18
|
+
|
19
|
+
# @since 1.0.0
|
20
|
+
# @api private
|
21
|
+
EMPTY_ARRAY = [].freeze
|
8
22
|
|
23
|
+
# @since 1.0.0
|
24
|
+
# @api private
|
25
|
+
EMPTY_HASH = {}.freeze
|
26
|
+
|
27
|
+
# @since 1.0.0
|
28
|
+
# @api private
|
29
|
+
LOG_METHODS = %i[debug info warn error fatal unknown].freeze
|
30
|
+
|
31
|
+
# @since 1.0.0
|
32
|
+
# @api private
|
9
33
|
BACKEND_METHODS = %i[close].freeze
|
10
34
|
|
35
|
+
# @since 1.0.0
|
36
|
+
# @api private
|
11
37
|
DEBUG = ::Logger::DEBUG
|
38
|
+
|
39
|
+
# @since 1.0.0
|
40
|
+
# @api private
|
12
41
|
INFO = ::Logger::INFO
|
42
|
+
|
43
|
+
# @since 1.0.0
|
44
|
+
# @api private
|
13
45
|
WARN = ::Logger::WARN
|
46
|
+
|
47
|
+
# @since 1.0.0
|
48
|
+
# @api private
|
14
49
|
ERROR = ::Logger::ERROR
|
50
|
+
|
51
|
+
# @since 1.0.0
|
52
|
+
# @api private
|
15
53
|
FATAL = ::Logger::FATAL
|
54
|
+
|
55
|
+
# @since 1.0.0
|
56
|
+
# @api private
|
16
57
|
UNKNOWN = ::Logger::UNKNOWN
|
17
58
|
|
59
|
+
# @since 1.0.0
|
60
|
+
# @api private
|
18
61
|
LEVEL_RANGE = (DEBUG..UNKNOWN).freeze
|
19
62
|
|
63
|
+
# @since 1.0.0
|
64
|
+
# @api private
|
20
65
|
DEFAULT_LEVEL = INFO
|
21
66
|
|
22
|
-
# @since
|
67
|
+
# @since 1.0.0
|
23
68
|
# @api private
|
24
69
|
LEVELS = Hash
|
25
70
|
.new { |levels, key|
|
@@ -35,9 +80,16 @@ module Dry
|
|
35
80
|
)
|
36
81
|
.freeze
|
37
82
|
|
38
|
-
|
83
|
+
# @since 1.0.0
|
84
|
+
# @api private
|
85
|
+
DEFAULT_OPTS = {level: DEFAULT_LEVEL, formatter: nil, progname: nil, log_if: nil}.freeze
|
39
86
|
|
87
|
+
# @since 1.0.0
|
88
|
+
# @api private
|
40
89
|
BACKEND_OPT_KEYS = DEFAULT_OPTS.keys.freeze
|
90
|
+
|
91
|
+
# @since 1.0.0
|
92
|
+
# @api private
|
41
93
|
FORMATTER_OPT_KEYS = %i[filter].freeze
|
42
94
|
end
|
43
95
|
end
|
@@ -4,6 +4,7 @@ require "logger"
|
|
4
4
|
require "pathname"
|
5
5
|
|
6
6
|
require "dry/logger/constants"
|
7
|
+
require "dry/logger/backends/proxy"
|
7
8
|
require "dry/logger/entry"
|
8
9
|
|
9
10
|
module Dry
|
@@ -37,21 +38,48 @@ module Dry
|
|
37
38
|
# @api private
|
38
39
|
attr_reader :options
|
39
40
|
|
41
|
+
# @since 1.0.0
|
42
|
+
# @api private
|
43
|
+
attr_reader :clock
|
44
|
+
|
45
|
+
# @since 1.0.0
|
46
|
+
# @api private
|
47
|
+
attr_reader :on_crash
|
48
|
+
|
40
49
|
# @since 1.0.0
|
41
50
|
# @api private
|
42
51
|
attr_reader :mutex
|
43
52
|
|
53
|
+
# @since 1.0.0
|
54
|
+
# @api private
|
55
|
+
CRASH_LOGGER = ::Logger.new($stdout).tap { |logger|
|
56
|
+
logger.formatter = -> (_, _, _, message) { "#{message}#{NEW_LINE}" }
|
57
|
+
logger.level = FATAL
|
58
|
+
}.freeze
|
59
|
+
|
60
|
+
# @since 1.0.0
|
61
|
+
# @api private
|
62
|
+
ON_CRASH = -> (progname:, exception:, message:, payload:) {
|
63
|
+
CRASH_LOGGER.fatal(Logger.templates[:crash] % {
|
64
|
+
severity: "FATAL",
|
65
|
+
progname: progname,
|
66
|
+
time: Time.now,
|
67
|
+
log_entry: [message, payload].map(&:to_s).reject(&:empty?).join(SEPARATOR),
|
68
|
+
exception: exception.class,
|
69
|
+
message: exception.message,
|
70
|
+
backtrace: TAB + exception.backtrace.join(NEW_LINE + TAB)
|
71
|
+
})
|
72
|
+
}
|
73
|
+
|
44
74
|
# Set up a dispatcher
|
45
75
|
#
|
46
76
|
# @since 1.0.0
|
47
|
-
#
|
48
|
-
# @param [String, Symbol] id The dispatcher id, can be used as progname in log entries
|
49
|
-
# @param [Hash] options Options that can be used for both the backend and formatter
|
77
|
+
# @api private
|
50
78
|
#
|
51
79
|
# @return [Dispatcher]
|
52
|
-
# @api public
|
53
80
|
def self.setup(id, **options)
|
54
81
|
dispatcher = new(id, **DEFAULT_OPTS, **options)
|
82
|
+
yield(dispatcher) if block_given?
|
55
83
|
dispatcher.add_backend if dispatcher.backends.empty?
|
56
84
|
dispatcher
|
57
85
|
end
|
@@ -64,12 +92,17 @@ module Dry
|
|
64
92
|
|
65
93
|
# @since 1.0.0
|
66
94
|
# @api private
|
67
|
-
def initialize(
|
95
|
+
def initialize(
|
96
|
+
id, backends: [], tags: [], context: self.class.default_context, **options
|
97
|
+
)
|
68
98
|
@id = id
|
69
99
|
@backends = backends
|
70
100
|
@options = {**options, progname: id}
|
71
101
|
@mutex = Mutex.new
|
72
102
|
@context = context
|
103
|
+
@tags = tags
|
104
|
+
@clock = Clock.new(**(options[:clock] || EMPTY_HASH))
|
105
|
+
@on_crash = options[:on_crash] || ON_CRASH
|
73
106
|
end
|
74
107
|
|
75
108
|
# Log an entry with UNKNOWN severity
|
@@ -143,8 +176,25 @@ module Dry
|
|
143
176
|
|
144
177
|
# Pass logging to all configured backends
|
145
178
|
#
|
179
|
+
# @example logging a message
|
180
|
+
# logger.log(:info, "Hello World")
|
181
|
+
#
|
182
|
+
# @example logging payload
|
183
|
+
# logger.log(:info, verb: "GET", path: "/users")
|
184
|
+
#
|
185
|
+
# @example logging message and payload
|
186
|
+
# logger.log(:info, "User index request", verb: "GET", path: "/users")
|
187
|
+
#
|
188
|
+
# @example logging exception
|
189
|
+
# begin
|
190
|
+
# # things that may raise
|
191
|
+
# rescue => e
|
192
|
+
# logger.log(:error, e)
|
193
|
+
# raise e
|
194
|
+
# end
|
195
|
+
#
|
146
196
|
# @param [Symbol] severity The log severity name
|
147
|
-
# @param [String
|
197
|
+
# @param [String] message Optional message
|
148
198
|
# @param [Hash] payload Optional log entry payload
|
149
199
|
#
|
150
200
|
# @since 1.0.0
|
@@ -155,17 +205,24 @@ module Dry
|
|
155
205
|
when Hash then log(severity, nil, **message)
|
156
206
|
else
|
157
207
|
entry = Entry.new(
|
208
|
+
clock: clock,
|
158
209
|
progname: id,
|
159
210
|
severity: severity,
|
211
|
+
tags: @tags,
|
160
212
|
message: message,
|
161
213
|
payload: {**context, **payload}
|
162
214
|
)
|
163
215
|
|
164
216
|
each_backend do |backend|
|
165
|
-
backend.__send__(severity, entry) if
|
217
|
+
backend.__send__(severity, entry) if backend.log?(entry)
|
218
|
+
rescue StandardError => e
|
219
|
+
on_crash.(progname: id, exception: e, message: message, payload: payload)
|
166
220
|
end
|
167
221
|
end
|
168
222
|
|
223
|
+
true
|
224
|
+
rescue StandardError => e
|
225
|
+
on_crash.(progname: id, exception: e, message: message, payload: payload)
|
169
226
|
true
|
170
227
|
end
|
171
228
|
|
@@ -182,11 +239,11 @@ module Dry
|
|
182
239
|
#
|
183
240
|
# @since 1.0.0
|
184
241
|
# @api public
|
185
|
-
def tagged(
|
186
|
-
|
242
|
+
def tagged(*tags)
|
243
|
+
@tags.concat(tags)
|
187
244
|
yield
|
188
245
|
ensure
|
189
|
-
|
246
|
+
@tags = []
|
190
247
|
end
|
191
248
|
|
192
249
|
# Add a new backend to an existing dispatcher
|
@@ -200,15 +257,27 @@ module Dry
|
|
200
257
|
# @return [Dispatcher]
|
201
258
|
# @api public
|
202
259
|
def add_backend(instance = nil, **backend_options)
|
203
|
-
backend =
|
260
|
+
backend =
|
261
|
+
case (instance ||= Dry::Logger.new(**options, **backend_options))
|
262
|
+
when Backends::Stream then instance
|
263
|
+
else Backends::Proxy.new(instance)
|
264
|
+
end
|
265
|
+
|
204
266
|
yield(backend) if block_given?
|
267
|
+
|
205
268
|
backends << backend
|
206
269
|
self
|
207
270
|
end
|
208
271
|
|
272
|
+
# @since 1.0.0
|
273
|
+
# @api public
|
274
|
+
def inspect
|
275
|
+
%(#<#{self.class} id=#{id} options=#{options} backends=#{backends}>)
|
276
|
+
end
|
277
|
+
|
209
278
|
# @since 1.0.0
|
210
279
|
# @api private
|
211
|
-
def each_backend(
|
280
|
+
def each_backend(&block)
|
212
281
|
mutex.synchronize do
|
213
282
|
backends.each(&block)
|
214
283
|
end
|
data/lib/dry/logger/entry.rb
CHANGED
@@ -10,14 +10,6 @@ module Dry
|
|
10
10
|
class Entry
|
11
11
|
include Enumerable
|
12
12
|
|
13
|
-
# @since 1.0.0
|
14
|
-
# @api private
|
15
|
-
EMPTY_PAYLOAD = {}.freeze
|
16
|
-
|
17
|
-
# @since 1.0.0
|
18
|
-
# @api private
|
19
|
-
EMPTY_BACKTRACE = [].freeze
|
20
|
-
|
21
13
|
# @since 1.0.0
|
22
14
|
# @api public
|
23
15
|
attr_reader :progname
|
@@ -28,16 +20,19 @@ module Dry
|
|
28
20
|
|
29
21
|
# @since 1.0.0
|
30
22
|
# @api public
|
31
|
-
attr_reader :
|
23
|
+
attr_reader :tags
|
32
24
|
|
33
25
|
# @since 1.0.0
|
34
26
|
# @api public
|
35
|
-
attr_reader :
|
27
|
+
attr_reader :level
|
36
28
|
|
37
29
|
# @since 1.0.0
|
38
30
|
# @api public
|
39
31
|
attr_reader :message
|
40
|
-
|
32
|
+
|
33
|
+
# @since 1.0.0
|
34
|
+
# @api public
|
35
|
+
attr_reader :exception
|
41
36
|
|
42
37
|
# @since 1.0.0
|
43
38
|
# @api public
|
@@ -45,14 +40,23 @@ module Dry
|
|
45
40
|
|
46
41
|
# @since 1.0.0
|
47
42
|
# @api private
|
48
|
-
|
43
|
+
attr_reader :clock
|
44
|
+
|
45
|
+
# @since 1.0.0
|
46
|
+
# @api private
|
47
|
+
# rubocop:disable Metrics/ParameterLists
|
48
|
+
def initialize(clock:, progname:, severity:, tags: EMPTY_ARRAY, message: nil,
|
49
|
+
payload: EMPTY_HASH)
|
50
|
+
@clock = clock
|
49
51
|
@progname = progname
|
50
|
-
@severity = severity.to_s
|
52
|
+
@severity = severity.to_s
|
53
|
+
@tags = tags
|
51
54
|
@level = LEVELS.fetch(severity.to_s)
|
52
|
-
@
|
53
|
-
@
|
55
|
+
@message = message unless message.is_a?(Exception)
|
56
|
+
@exception = message if message.is_a?(Exception)
|
54
57
|
@payload = build_payload(payload)
|
55
58
|
end
|
59
|
+
# rubocop:enable Metrics/ParameterLists
|
56
60
|
|
57
61
|
# @since 1.0.0
|
58
62
|
# @api public
|
@@ -99,7 +103,7 @@ module Dry
|
|
99
103
|
# @since 1.0.0
|
100
104
|
# @api public
|
101
105
|
def exception?
|
102
|
-
|
106
|
+
!exception.nil?
|
103
107
|
end
|
104
108
|
|
105
109
|
# @since 1.0.0
|
@@ -109,28 +113,21 @@ module Dry
|
|
109
113
|
end
|
110
114
|
|
111
115
|
# @since 1.0.0
|
112
|
-
# @api
|
113
|
-
def
|
114
|
-
|
116
|
+
# @api public
|
117
|
+
def tag?(value)
|
118
|
+
tags.include?(value)
|
115
119
|
end
|
116
120
|
|
117
121
|
# @since 1.0.0
|
118
122
|
# @api private
|
119
123
|
def meta
|
120
|
-
@meta ||= {progname: progname, severity: severity, time:
|
124
|
+
@meta ||= {progname: progname, severity: severity, time: clock.now}
|
121
125
|
end
|
122
126
|
|
123
127
|
# @since 1.0.0
|
124
128
|
# @api private
|
125
|
-
def
|
126
|
-
@
|
127
|
-
end
|
128
|
-
|
129
|
-
# @since 1.0.0
|
130
|
-
# @api private
|
131
|
-
def as_json
|
132
|
-
# TODO: why are we enforcing UTC in JSON but not in String?
|
133
|
-
@as_json ||= to_h.merge(message: message, time: utc_time).compact
|
129
|
+
def to_h
|
130
|
+
@to_h ||= meta.merge(message: message, **payload)
|
134
131
|
end
|
135
132
|
|
136
133
|
# @since 1.0.0
|
@@ -146,10 +143,7 @@ module Dry
|
|
146
143
|
# @api private
|
147
144
|
def build_payload(payload)
|
148
145
|
if exception?
|
149
|
-
{
|
150
|
-
backtrace: exception.backtrace || EMPTY_BACKTRACE,
|
151
|
-
error: exception.class,
|
152
|
-
**payload}
|
146
|
+
{exception: exception, **payload}
|
153
147
|
else
|
154
148
|
payload
|
155
149
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Logger
|
5
|
+
module Formatters
|
6
|
+
# Shell colorizer
|
7
|
+
#
|
8
|
+
# This was ported from hanami-utils
|
9
|
+
#
|
10
|
+
# @since 1.0.0
|
11
|
+
# @api private
|
12
|
+
class Colors
|
13
|
+
# Unknown color code error
|
14
|
+
#
|
15
|
+
# @since 1.0.0
|
16
|
+
class UnknownColorCodeError < StandardError
|
17
|
+
def initialize(code)
|
18
|
+
super("Unknown color code: `#{code.inspect}'")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Escapes codes for terminals to output strings in colors
|
23
|
+
#
|
24
|
+
# @since 1.2.0
|
25
|
+
# @api private
|
26
|
+
COLORS = {black: 30,
|
27
|
+
red: 31,
|
28
|
+
green: 32,
|
29
|
+
yellow: 33,
|
30
|
+
blue: 34,
|
31
|
+
magenta: 35,
|
32
|
+
cyan: 36,
|
33
|
+
gray: 37}.freeze
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def self.evaluate(input)
|
37
|
+
COLORS.keys.reduce(input.dup) { |output, color|
|
38
|
+
output.gsub!("<#{color}>", start(color))
|
39
|
+
output.gsub!("</#{color}>", stop)
|
40
|
+
output
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Colorizes output
|
45
|
+
# 8 colors available: black, red, green, yellow, blue, magenta, cyan, and gray
|
46
|
+
#
|
47
|
+
# @param input [#to_s] the string to colorize
|
48
|
+
# @param color [Symbol] the color
|
49
|
+
#
|
50
|
+
# @raise [UnknownColorError] if the color code is unknown
|
51
|
+
#
|
52
|
+
# @return [String] the colorized string
|
53
|
+
#
|
54
|
+
# @since 1.0.0
|
55
|
+
# @api private
|
56
|
+
def self.call(color, input)
|
57
|
+
"#{start(color)}#{input}#{stop}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# @since 1.0.0
|
61
|
+
# @api private
|
62
|
+
def self.start(color)
|
63
|
+
"\e[#{self[color]}m"
|
64
|
+
end
|
65
|
+
|
66
|
+
# @since 1.0.0
|
67
|
+
# @api private
|
68
|
+
def self.stop
|
69
|
+
"\e[0m"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Helper method to translate between color names and terminal escape codes
|
73
|
+
#
|
74
|
+
# @since 1.0.0
|
75
|
+
# @api private
|
76
|
+
#
|
77
|
+
# @raise [UnknownColorError] if the color code is unknown
|
78
|
+
def self.[](code)
|
79
|
+
COLORS.fetch(code) { raise UnknownColorCodeError, code }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
+
|
5
|
+
require "dry/logger/constants"
|
4
6
|
require "dry/logger/formatters/structured"
|
5
7
|
|
6
8
|
module Dry
|
@@ -16,7 +18,31 @@ module Dry
|
|
16
18
|
# @since 0.1.0
|
17
19
|
# @api private
|
18
20
|
def format(entry)
|
19
|
-
|
21
|
+
hash = format_values(entry).compact
|
22
|
+
hash.update(hash.delete(:exception)) if entry.exception?
|
23
|
+
::JSON.dump(hash)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @since 0.1.0
|
27
|
+
# @api private
|
28
|
+
def format_severity(value)
|
29
|
+
value.upcase
|
30
|
+
end
|
31
|
+
|
32
|
+
# @since 0.1.0
|
33
|
+
# @api private
|
34
|
+
def format_exception(value)
|
35
|
+
{
|
36
|
+
exception: value.class,
|
37
|
+
message: value.message,
|
38
|
+
backtrace: value.backtrace || EMPTY_ARRAY
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# @since 0.1.0
|
43
|
+
# @api private
|
44
|
+
def format_time(value)
|
45
|
+
value.getutc.iso8601
|
20
46
|
end
|
21
47
|
end
|
22
48
|
end
|
@@ -12,10 +12,18 @@ module Dry
|
|
12
12
|
#
|
13
13
|
# @see String
|
14
14
|
class Rack < String
|
15
|
+
# @see String#initialize
|
15
16
|
# @since 1.0.0
|
16
17
|
# @api private
|
17
|
-
def
|
18
|
-
|
18
|
+
def initialize(**options)
|
19
|
+
super
|
20
|
+
@template = Template[Logger.templates[:rack]]
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api 1.0.0
|
24
|
+
# @api private
|
25
|
+
def format_params(value)
|
26
|
+
return value unless value.empty?
|
19
27
|
end
|
20
28
|
end
|
21
29
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
require_relative "template"
|
6
|
+
require_relative "structured"
|
4
7
|
|
5
8
|
module Dry
|
6
9
|
module Logger
|
@@ -12,10 +15,6 @@ module Dry
|
|
12
15
|
# @since 1.0.0
|
13
16
|
# @api public
|
14
17
|
class String < Structured
|
15
|
-
# @since 1.0.0
|
16
|
-
# @api private
|
17
|
-
SEPARATOR = " "
|
18
|
-
|
19
18
|
# @since 1.0.0
|
20
19
|
# @api private
|
21
20
|
HASH_SEPARATOR = ","
|
@@ -24,9 +23,16 @@ module Dry
|
|
24
23
|
# @api private
|
25
24
|
EXCEPTION_SEPARATOR = ": "
|
26
25
|
|
27
|
-
# @since 1.
|
26
|
+
# @since 1.2.0
|
28
27
|
# @api private
|
29
|
-
|
28
|
+
DEFAULT_SEVERITY_COLORS = {
|
29
|
+
DEBUG => :cyan,
|
30
|
+
INFO => :magenta,
|
31
|
+
WARN => :yellow,
|
32
|
+
ERROR => :red,
|
33
|
+
FATAL => :red,
|
34
|
+
UNKNOWN => :blue
|
35
|
+
}.freeze
|
30
36
|
|
31
37
|
# @since 1.0.0
|
32
38
|
# @api private
|
@@ -34,47 +40,108 @@ module Dry
|
|
34
40
|
|
35
41
|
# @since 1.0.0
|
36
42
|
# @api private
|
37
|
-
def initialize(template:
|
43
|
+
def initialize(template: Logger.templates[:default], **options)
|
38
44
|
super(**options)
|
39
|
-
@template = template
|
45
|
+
@template = Template[template]
|
46
|
+
end
|
47
|
+
|
48
|
+
# @since 1.0.0
|
49
|
+
# @api private
|
50
|
+
def colorize?
|
51
|
+
options[:colorize].equal?(true)
|
40
52
|
end
|
41
53
|
|
42
54
|
private
|
43
55
|
|
44
56
|
# @since 1.0.0
|
45
57
|
# @api private
|
46
|
-
def
|
47
|
-
|
58
|
+
def format_severity(value)
|
59
|
+
output = value.upcase
|
60
|
+
|
61
|
+
if colorize?
|
62
|
+
Colors.call(severity_colors[LEVELS[value]], output)
|
63
|
+
else
|
64
|
+
output
|
65
|
+
end
|
48
66
|
end
|
49
67
|
|
50
68
|
# @since 1.0.0
|
51
69
|
# @api private
|
52
|
-
def
|
70
|
+
def format(entry)
|
53
71
|
if entry.exception?
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
else
|
59
|
-
"#{entry.message}#{SEPARATOR}#{format_payload(entry)}"
|
60
|
-
end
|
72
|
+
head = template % template_data(entry, exclude: %i[exception])
|
73
|
+
tail = format_exception(entry.exception)
|
74
|
+
|
75
|
+
"#{head}#{NEW_LINE}#{TAB}#{tail}"
|
61
76
|
else
|
62
|
-
|
77
|
+
template % template_data(entry)
|
63
78
|
end
|
64
79
|
end
|
65
80
|
|
66
81
|
# @since 1.0.0
|
67
82
|
# @api private
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
83
|
+
def format_tags(value)
|
84
|
+
Array(value)
|
85
|
+
.map { |tag|
|
86
|
+
case tag
|
87
|
+
when Hash then format_payload(tag)
|
88
|
+
else
|
89
|
+
tag.to_s
|
90
|
+
end
|
91
|
+
}
|
92
|
+
.join(SEPARATOR)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @since 1.0.0
|
96
|
+
# @api private
|
97
|
+
def format_exception(value)
|
98
|
+
[
|
99
|
+
"#{value.message} (#{value.class})",
|
100
|
+
format_backtrace(value.backtrace || EMPTY_BACKTRACE)
|
101
|
+
].join(NEW_LINE)
|
102
|
+
end
|
103
|
+
|
104
|
+
# @since 1.0.0
|
105
|
+
# @api private
|
106
|
+
def format_payload(payload)
|
107
|
+
payload.map { |key, value| "#{key}=#{value.inspect}" }.join(SEPARATOR)
|
108
|
+
end
|
109
|
+
|
110
|
+
# @since 1.0.0
|
111
|
+
# @api private
|
112
|
+
def format_backtrace(value)
|
113
|
+
value.map { |line| "#{TAB}#{line}" }.join(NEW_LINE)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @since 1.0.0
|
117
|
+
# @api private
|
118
|
+
def template_data(entry, exclude: EMPTY_ARRAY)
|
119
|
+
data = format_values(entry)
|
120
|
+
payload = data.except(:message, *entry.meta.keys, *template.tokens, *exclude)
|
121
|
+
data[:payload] = format_payload(payload)
|
122
|
+
|
123
|
+
if template.include?(:tags)
|
124
|
+
data[:tags] = format_tags(entry.tags)
|
125
|
+
end
|
126
|
+
|
127
|
+
if data[:message]
|
128
|
+
data.except(*payload.keys)
|
129
|
+
elsif template.include?(:message)
|
130
|
+
data[:message] = data.delete(:payload)
|
131
|
+
data[:payload] = nil
|
132
|
+
data
|
133
|
+
else
|
134
|
+
data
|
135
|
+
end
|
72
136
|
end
|
73
137
|
|
74
138
|
# @since 1.0.0
|
75
139
|
# @api private
|
76
|
-
def
|
77
|
-
|
140
|
+
def severity_colors
|
141
|
+
@severity_colors ||= DEFAULT_SEVERITY_COLORS.merge(
|
142
|
+
(options[:severity_colors] || EMPTY_HASH)
|
143
|
+
.to_h { |key, value| [LEVELS[key.to_s], value] }
|
144
|
+
)
|
78
145
|
end
|
79
146
|
end
|
80
147
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "logger"
|
4
|
+
|
5
|
+
require "dry/logger/constants"
|
4
6
|
require "dry/logger/filter"
|
5
7
|
|
6
8
|
module Dry
|
@@ -8,6 +10,8 @@ module Dry
|
|
8
10
|
module Formatters
|
9
11
|
# Default structured formatter which receives {Logger::Entry} from the backends.
|
10
12
|
#
|
13
|
+
# This class can be used as the base class for your custom formatters.
|
14
|
+
#
|
11
15
|
# @see http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Formatter.html
|
12
16
|
#
|
13
17
|
# @since 1.0.0
|
@@ -21,10 +25,6 @@ module Dry
|
|
21
25
|
# @api private
|
22
26
|
NOOP_FILTER = -> message { message }
|
23
27
|
|
24
|
-
# @since 1.0.0
|
25
|
-
# @api private
|
26
|
-
NEW_LINE = $/ # rubocop:disable Style/SpecialGlobalVars
|
27
|
-
|
28
28
|
# @since 1.0.0
|
29
29
|
# @api private
|
30
30
|
attr_reader :filter
|
@@ -52,7 +52,7 @@ module Dry
|
|
52
52
|
# @return [String]
|
53
53
|
# @api public
|
54
54
|
def call(_severity, _time, _progname, entry)
|
55
|
-
format(entry.filter(filter))
|
55
|
+
format(entry.filter(filter)) + NEW_LINE
|
56
56
|
end
|
57
57
|
|
58
58
|
# Format entry into a loggable object
|
@@ -63,7 +63,18 @@ module Dry
|
|
63
63
|
# @return [Entry]
|
64
64
|
# @api public
|
65
65
|
def format(entry)
|
66
|
+
format_values(entry)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @since 1.0.0
|
70
|
+
# @api private
|
71
|
+
def format_values(entry)
|
66
72
|
entry
|
73
|
+
.to_h
|
74
|
+
.map { |key, value|
|
75
|
+
[key, respond_to?(meth = "format_#{key}", true) ? __send__(meth, value) : value]
|
76
|
+
}
|
77
|
+
.to_h
|
67
78
|
end
|
68
79
|
end
|
69
80
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "dry/logger/constants"
|
5
|
+
require_relative "colors"
|
6
|
+
|
7
|
+
module Dry
|
8
|
+
module Logger
|
9
|
+
module Formatters
|
10
|
+
# Basic string formatter.
|
11
|
+
#
|
12
|
+
# This formatter returns log entries in key=value format.
|
13
|
+
#
|
14
|
+
# @since 1.0.0
|
15
|
+
# @api public
|
16
|
+
class Template
|
17
|
+
# @since 1.0.0
|
18
|
+
# @api private
|
19
|
+
TOKEN_REGEXP = /%<(\w*)>s/.freeze
|
20
|
+
|
21
|
+
# @since 1.0.0
|
22
|
+
# @api private
|
23
|
+
MESSAGE_TOKEN = "%<message>s"
|
24
|
+
|
25
|
+
# @since 1.0.0
|
26
|
+
# @api private
|
27
|
+
attr_reader :value
|
28
|
+
|
29
|
+
# @since 1.0.0
|
30
|
+
# @api private
|
31
|
+
attr_reader :tokens
|
32
|
+
|
33
|
+
# @since 1.0.0
|
34
|
+
# @api private
|
35
|
+
def self.[](value)
|
36
|
+
cache.fetch(value) {
|
37
|
+
cache[value] = (colorized?(value) ? Template::Colorized : Template).new(value)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# @since 1.0.0
|
42
|
+
# @api private
|
43
|
+
private_class_method def self.colorized?(value)
|
44
|
+
Colors::COLORS.keys.any? { |color| value.include?("<#{color}>") }
|
45
|
+
end
|
46
|
+
|
47
|
+
# @since 1.0.0
|
48
|
+
# @api private
|
49
|
+
private_class_method def self.cache
|
50
|
+
@cache ||= {}
|
51
|
+
end
|
52
|
+
|
53
|
+
# @since 1.0.0
|
54
|
+
# @api private
|
55
|
+
class Colorized < Template
|
56
|
+
# @since 1.0.0
|
57
|
+
# @api private
|
58
|
+
def initialize(value)
|
59
|
+
super(Colors.evaluate(value))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @since 1.0.0
|
64
|
+
# @api private
|
65
|
+
def initialize(value)
|
66
|
+
@value = value
|
67
|
+
@tokens = value.scan(TOKEN_REGEXP).flatten(1).map(&:to_sym).to_set
|
68
|
+
end
|
69
|
+
|
70
|
+
# @since 1.0.0
|
71
|
+
# @api private
|
72
|
+
def %(tokens)
|
73
|
+
output = value % tokens
|
74
|
+
output.strip!
|
75
|
+
output.split(NEW_LINE).map(&:rstrip).join(NEW_LINE)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @since 1.0.0
|
79
|
+
# @api private
|
80
|
+
def colorize(color, input)
|
81
|
+
"\e[#{Colors[color.to_sym]}m#{input}\e[0m"
|
82
|
+
end
|
83
|
+
|
84
|
+
# @since 1.0.0
|
85
|
+
# @api private
|
86
|
+
def include?(token)
|
87
|
+
tokens.include?(token)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module Logger
|
7
|
+
# Global setup methods
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
module Global
|
11
|
+
# Register a new formatter
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# class MyFormatter < Dry::Logger::Formatters::Structured
|
15
|
+
# def format_message(value)
|
16
|
+
# "WOAH: #{message}"
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def format_time(value)
|
20
|
+
# Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Dry::Logger.register_formatter(:my_formatter, MyFormatter)
|
25
|
+
#
|
26
|
+
# logger = Dry.Logger(:app, formatter: :my_formatter, template: :details)
|
27
|
+
#
|
28
|
+
# logger.info "Hello World"
|
29
|
+
# # [test] [INFO] [2022-11-15 10:06:29] WOAH: Hello World
|
30
|
+
#
|
31
|
+
# @since 1.0.0
|
32
|
+
# @return [Hash]
|
33
|
+
# @api public
|
34
|
+
def register_formatter(name, formatter)
|
35
|
+
formatters[name] = formatter
|
36
|
+
formatters
|
37
|
+
end
|
38
|
+
|
39
|
+
# Register a new template
|
40
|
+
#
|
41
|
+
# @example basic template
|
42
|
+
# Dry::Logger.register_template(:request, "[%<severity>s] %<verb>s %<path>s")
|
43
|
+
#
|
44
|
+
# logger = Dry.Logger(:my_app, template: :request)
|
45
|
+
#
|
46
|
+
# logger.info(verb: "GET", path: "/users")
|
47
|
+
# # [INFO] GET /users
|
48
|
+
#
|
49
|
+
# @example template with colors
|
50
|
+
# Dry::Logger.register_template(
|
51
|
+
# :request, "[%<severity>s] <green>%<verb>s</green> <blue>%<path>s</blue>"
|
52
|
+
# )
|
53
|
+
#
|
54
|
+
# @since 1.0.0
|
55
|
+
# @return [Hash]
|
56
|
+
# @api public
|
57
|
+
def register_template(name, template)
|
58
|
+
templates[name] = template
|
59
|
+
templates
|
60
|
+
end
|
61
|
+
|
62
|
+
# Build a logging backend instance
|
63
|
+
#
|
64
|
+
# @since 1.0.0
|
65
|
+
# @return [Backends::Stream]
|
66
|
+
# @api private
|
67
|
+
def new(stream: $stdout, **options)
|
68
|
+
backend =
|
69
|
+
case stream
|
70
|
+
when IO, StringIO then Backends::IO
|
71
|
+
when String, Pathname then Backends::File
|
72
|
+
else
|
73
|
+
raise ArgumentError, "unsupported stream type #{stream.class}"
|
74
|
+
end
|
75
|
+
|
76
|
+
formatter_spec = options[:formatter]
|
77
|
+
template_spec = options[:template]
|
78
|
+
|
79
|
+
template =
|
80
|
+
case template_spec
|
81
|
+
when Symbol then templates.fetch(template_spec)
|
82
|
+
when String then template_spec
|
83
|
+
when nil then templates[:default]
|
84
|
+
else
|
85
|
+
raise ArgumentError,
|
86
|
+
":template option must be a Symbol or a String (`#{template_spec}` given)"
|
87
|
+
end
|
88
|
+
|
89
|
+
formatter_options = {**options, template: template}
|
90
|
+
|
91
|
+
formatter =
|
92
|
+
case formatter_spec
|
93
|
+
when Symbol then formatters.fetch(formatter_spec).new(**formatter_options)
|
94
|
+
when Class then formatter_spec.new(**formatter_options)
|
95
|
+
when nil then formatters[:string].new(**formatter_options)
|
96
|
+
when ::Logger::Formatter then formatter_spec
|
97
|
+
else
|
98
|
+
raise ArgumentError, "Unsupported formatter option #{formatter_spec.inspect}"
|
99
|
+
end
|
100
|
+
|
101
|
+
backend_options = options.select { |key, _| BACKEND_OPT_KEYS.include?(key) }
|
102
|
+
|
103
|
+
backend.new(stream: stream, **backend_options, formatter: formatter)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Internal formatters registry
|
107
|
+
#
|
108
|
+
# @since 1.0.0
|
109
|
+
# @api private
|
110
|
+
def formatters
|
111
|
+
@formatters ||= {}
|
112
|
+
end
|
113
|
+
|
114
|
+
# Internal templates registry
|
115
|
+
#
|
116
|
+
# @since 1.0.0
|
117
|
+
# @api private
|
118
|
+
def templates
|
119
|
+
@templates ||= {}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/dry/logger/version.rb
CHANGED
data/lib/dry/logger.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
|
3
|
+
require "dry/logger/global"
|
5
4
|
require "dry/logger/constants"
|
5
|
+
require "dry/logger/clock"
|
6
6
|
require "dry/logger/dispatcher"
|
7
7
|
|
8
8
|
require "dry/logger/formatters/string"
|
@@ -42,74 +42,58 @@ module Dry
|
|
42
42
|
# logger.info(Hello: "World!")
|
43
43
|
# # {"progname":"my_app","severity":"INFO","time":"2022-11-06T10:11:29Z","Hello":"World!"}
|
44
44
|
#
|
45
|
+
# @example Setting up multiple backends
|
46
|
+
# logger = Dry.Logger(:my_app)
|
47
|
+
# add_backend(formatter: :string, template: :details)
|
48
|
+
# add_backend(formatter: :string, template: :details)
|
49
|
+
#
|
50
|
+
# @example Setting up conditional logging
|
51
|
+
# logger = Dry.Logger(:my_app) { |setup|
|
52
|
+
# setup.add_backend(formatter: :string, template: :details) { |b| b.log_if = :error?.to_proc }
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# @param [String, Symbol] id The dispatcher id, can be used as progname in log entries
|
56
|
+
# @param [Hash] options Options for backends and formatters
|
57
|
+
# @option options [Symbol] :level (:info) The minimum level that should be logged,
|
58
|
+
# @option options [Symbol] :stream (optional) The output stream, default is $stdout
|
59
|
+
# @option options [Symbol, Class, #call] :formatter (:string) The default formatter or its id,
|
60
|
+
# @option options [String, Symbol] :template (:default) The default template that should be used
|
61
|
+
# @option options [Boolean] :colorize (false) Enable/disable colorized severity
|
62
|
+
# @option options [Hash<Symbol=>Symbol>] :severity_colors ({}) A severity=>color mapping
|
63
|
+
# @option options [#call] :on_crash (Dry::Logger::Dispatcher::ON_CRASH) A crash-handling proc.
|
64
|
+
# This is used whenever logging crashes.
|
65
|
+
#
|
45
66
|
# @since 1.0.0
|
46
|
-
# @return [Dispatcher]
|
47
67
|
# @api public
|
48
|
-
|
49
|
-
|
68
|
+
# @return [Dispatcher]
|
69
|
+
def self.Logger(id, **options, &block)
|
70
|
+
Logger::Dispatcher.setup(id, **options, &block)
|
50
71
|
end
|
51
72
|
|
52
73
|
module Logger
|
53
|
-
|
54
|
-
#
|
55
|
-
# @example
|
56
|
-
# class MyFormatter < Dry::Logger::Formatters::Structured
|
57
|
-
# def format(entry)
|
58
|
-
# "WOAH: #{entry.message}"
|
59
|
-
# end
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
# Dry::Logger.register_formatter(MyFormatter)
|
63
|
-
#
|
64
|
-
# @since 1.0.0
|
65
|
-
# @return [Hash]
|
66
|
-
# @api public
|
67
|
-
def self.register_formatter(name, formatter)
|
68
|
-
formatters[name] = formatter
|
69
|
-
formatters
|
70
|
-
end
|
71
|
-
|
72
|
-
# Build a logging backend instance
|
73
|
-
#
|
74
|
-
# @since 1.0.0
|
75
|
-
# @return [Backends::Stream]
|
76
|
-
# @api private
|
77
|
-
def self.new(stream: $stdout, **opts)
|
78
|
-
backend =
|
79
|
-
case stream
|
80
|
-
when IO, StringIO then Backends::IO
|
81
|
-
when String, Pathname then Backends::File
|
82
|
-
else
|
83
|
-
raise ArgumentError, "unsupported stream type #{stream.class}"
|
84
|
-
end
|
74
|
+
extend Global
|
85
75
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
when Symbol then formatters.fetch(formatter_opt).new(**opts)
|
91
|
-
when Class then formatter_opt.new(**opts)
|
92
|
-
when NilClass then formatters[:string].new(**opts)
|
93
|
-
when ::Logger::Formatter then formatter_opt
|
94
|
-
else
|
95
|
-
raise ArgumentError, "unsupported formatter option #{formatter_opt.inspect}"
|
96
|
-
end
|
76
|
+
# Built-in formatters
|
77
|
+
register_formatter(:string, Formatters::String)
|
78
|
+
register_formatter(:rack, Formatters::Rack)
|
79
|
+
register_formatter(:json, Formatters::JSON)
|
97
80
|
|
98
|
-
|
81
|
+
# Built-in templates
|
82
|
+
register_template(:default, "%<message>s %<payload>s")
|
99
83
|
|
100
|
-
|
101
|
-
end
|
84
|
+
register_template(:details, "[%<progname>s] [%<severity>s] [%<time>s] %<message>s %<payload>s")
|
102
85
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
86
|
+
register_template(:crash, <<~STR)
|
87
|
+
[%<progname>s] [%<severity>s] [%<time>s] Logging crashed
|
88
|
+
%<log_entry>s
|
89
|
+
%<message>s (%<exception>s)
|
90
|
+
%<backtrace>s
|
91
|
+
STR
|
110
92
|
|
111
|
-
|
112
|
-
|
113
|
-
|
93
|
+
register_template(:rack, <<~STR)
|
94
|
+
[%<progname>s] [%<severity>s] [%<time>s] \
|
95
|
+
%<verb>s %<status>s %<elapsed>s %<ip>s %<path>s %<length>s %<payload>s
|
96
|
+
%<params>s
|
97
|
+
STR
|
114
98
|
end
|
115
99
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
4
|
+
version: 1.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-11-
|
11
|
+
date: 2022-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -36,17 +36,23 @@ files:
|
|
36
36
|
- README.md
|
37
37
|
- dry-logger.gemspec
|
38
38
|
- lib/dry/logger.rb
|
39
|
+
- lib/dry/logger/backends/core.rb
|
39
40
|
- lib/dry/logger/backends/file.rb
|
40
41
|
- lib/dry/logger/backends/io.rb
|
42
|
+
- lib/dry/logger/backends/proxy.rb
|
41
43
|
- lib/dry/logger/backends/stream.rb
|
44
|
+
- lib/dry/logger/clock.rb
|
42
45
|
- lib/dry/logger/constants.rb
|
43
46
|
- lib/dry/logger/dispatcher.rb
|
44
47
|
- lib/dry/logger/entry.rb
|
45
48
|
- lib/dry/logger/filter.rb
|
49
|
+
- lib/dry/logger/formatters/colors.rb
|
46
50
|
- lib/dry/logger/formatters/json.rb
|
47
51
|
- lib/dry/logger/formatters/rack.rb
|
48
52
|
- lib/dry/logger/formatters/string.rb
|
49
53
|
- lib/dry/logger/formatters/structured.rb
|
54
|
+
- lib/dry/logger/formatters/template.rb
|
55
|
+
- lib/dry/logger/global.rb
|
50
56
|
- lib/dry/logger/version.rb
|
51
57
|
homepage: https://dry-rb.org/gems/dry-logger
|
52
58
|
licenses:
|
@@ -67,9 +73,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
73
|
version: '3.0'
|
68
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
75
|
requirements:
|
70
|
-
- - "
|
76
|
+
- - ">="
|
71
77
|
- !ruby/object:Gem::Version
|
72
|
-
version:
|
78
|
+
version: '0'
|
73
79
|
requirements: []
|
74
80
|
rubygems_version: 3.1.6
|
75
81
|
signing_key:
|