tty-logger 0.1.0 → 0.6.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 +62 -0
- data/README.md +300 -48
- data/lib/tty/logger.rb +187 -51
- data/lib/tty/logger/config.rb +63 -5
- data/lib/tty/logger/data_filter.rb +118 -0
- data/lib/tty/logger/event.rb +2 -2
- data/lib/tty/logger/formatters/json.rb +3 -4
- data/lib/tty/logger/formatters/text.rb +7 -6
- data/lib/tty/logger/handlers/base.rb +13 -0
- data/lib/tty/logger/handlers/console.rb +37 -14
- data/lib/tty/logger/levels.rb +34 -18
- data/lib/tty/logger/version.rb +3 -3
- metadata +15 -52
- data/Rakefile +0 -8
- data/examples/console.rb +0 -22
- data/examples/error.rb +0 -11
- data/examples/handler.rb +0 -19
- data/examples/output.rb +0 -15
- data/examples/override.rb +0 -29
- data/examples/stream.rb +0 -22
- data/spec/spec_helper.rb +0 -31
- data/spec/unit/add_handler_spec.rb +0 -25
- data/spec/unit/config_spec.rb +0 -107
- data/spec/unit/event_spec.rb +0 -22
- data/spec/unit/exception_spec.rb +0 -45
- data/spec/unit/formatter_spec.rb +0 -70
- data/spec/unit/formatters/json_spec.rb +0 -41
- data/spec/unit/formatters/text_spec.rb +0 -82
- data/spec/unit/handler_spec.rb +0 -83
- data/spec/unit/handlers/custom_spec.rb +0 -26
- data/spec/unit/handlers/null_spec.rb +0 -15
- data/spec/unit/handlers/stream_spec.rb +0 -72
- data/spec/unit/levels_spec.rb +0 -40
- data/spec/unit/log_metadata_spec.rb +0 -55
- data/spec/unit/log_spec.rb +0 -144
- data/spec/unit/output_spec.rb +0 -40
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-logger.gemspec +0 -35
data/lib/tty/logger.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "logger/config"
|
4
|
+
require_relative "logger/data_filter"
|
4
5
|
require_relative "logger/event"
|
5
6
|
require_relative "logger/formatters/json"
|
6
7
|
require_relative "logger/formatters/text"
|
@@ -17,6 +18,43 @@ module TTY
|
|
17
18
|
# Error raised by this logger
|
18
19
|
class Error < StandardError; end
|
19
20
|
|
21
|
+
LOG_TYPES = {
|
22
|
+
debug: { level: :debug },
|
23
|
+
info: { level: :info },
|
24
|
+
warn: { level: :warn },
|
25
|
+
error: { level: :error },
|
26
|
+
fatal: { level: :fatal },
|
27
|
+
success: { level: :info },
|
28
|
+
wait: { level: :info }
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
# Macro to dynamically define log types
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
def self.define_level(name, log_level = nil)
|
35
|
+
const_level = (LOG_TYPES[name.to_sym] || log_level)[:level]
|
36
|
+
|
37
|
+
loc = caller_locations(0, 1)[0]
|
38
|
+
if loc
|
39
|
+
file, line = loc.path, loc.lineno + 7
|
40
|
+
else
|
41
|
+
file, line = __FILE__, __LINE__ + 3
|
42
|
+
end
|
43
|
+
class_eval(<<-EOL, file, line)
|
44
|
+
def #{name}(*msg, &block)
|
45
|
+
log(:#{const_level}, *msg, &block)
|
46
|
+
end
|
47
|
+
EOL
|
48
|
+
end
|
49
|
+
|
50
|
+
define_level :debug
|
51
|
+
define_level :info
|
52
|
+
define_level :warn
|
53
|
+
define_level :error
|
54
|
+
define_level :fatal
|
55
|
+
define_level :success
|
56
|
+
define_level :wait
|
57
|
+
|
20
58
|
# Logger configuration instance
|
21
59
|
#
|
22
60
|
# @api public
|
@@ -31,11 +69,24 @@ module TTY
|
|
31
69
|
yield config
|
32
70
|
end
|
33
71
|
|
72
|
+
# Instance logger configuration
|
73
|
+
#
|
74
|
+
# @api public
|
75
|
+
def configure
|
76
|
+
yield @config
|
77
|
+
end
|
78
|
+
|
34
79
|
# Create a logger instance
|
35
80
|
#
|
36
81
|
# @example
|
37
82
|
# logger = TTY::Logger.new(output: $stdout)
|
38
83
|
#
|
84
|
+
# @param [IO] output
|
85
|
+
# the output object, can be stream
|
86
|
+
#
|
87
|
+
# @param [Hash] fields
|
88
|
+
# the data fields for each log message
|
89
|
+
#
|
39
90
|
# @api public
|
40
91
|
def initialize(output: nil, fields: {})
|
41
92
|
@fields = fields
|
@@ -50,24 +101,55 @@ module TTY
|
|
50
101
|
@handlers = @config.handlers
|
51
102
|
@output = output || @config.output
|
52
103
|
@ready_handlers = []
|
104
|
+
@data_filter = DataFilter.new(@config.filters.data,
|
105
|
+
mask: @config.filters.mask)
|
106
|
+
|
107
|
+
@types = LOG_TYPES.dup
|
108
|
+
@config.types.each do |name, log_level|
|
109
|
+
add_type(name, log_level)
|
110
|
+
end
|
53
111
|
|
54
112
|
@handlers.each do |handler|
|
55
113
|
add_handler(handler)
|
56
114
|
end
|
57
115
|
end
|
58
116
|
|
117
|
+
# Add new log type
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# add_type(:thanks, {level: :info})
|
121
|
+
#
|
122
|
+
# @api private
|
123
|
+
def add_type(name, log_level)
|
124
|
+
if @types.include?(name)
|
125
|
+
raise Error, "Already defined log type #{name.inspect}"
|
126
|
+
end
|
127
|
+
|
128
|
+
@types[name.to_sym] = log_level
|
129
|
+
self.class.define_level(name, log_level)
|
130
|
+
end
|
131
|
+
|
59
132
|
# Add handler for logging messages
|
60
133
|
#
|
61
134
|
# @example
|
62
135
|
# add_handler(:console)
|
63
136
|
#
|
137
|
+
# @example
|
138
|
+
# add_handler(:console, styles: { info: { color: :yellow } })
|
139
|
+
#
|
140
|
+
# @example
|
141
|
+
# add_handler([:console, message_format: "%-10s"])
|
142
|
+
#
|
143
|
+
# @param [Symbol, Handlers] handler
|
144
|
+
# the handler name or class
|
145
|
+
#
|
64
146
|
# @api public
|
65
|
-
def add_handler(handler)
|
66
|
-
h,
|
67
|
-
|
147
|
+
def add_handler(handler, **options)
|
148
|
+
h, h_opts = *(handler.is_a?(Array) ? handler : [handler, options])
|
149
|
+
handler_type = coerce_handler(h)
|
68
150
|
global_opts = { output: @output, config: @config }
|
69
|
-
opts = global_opts.merge(
|
70
|
-
ready_handler =
|
151
|
+
opts = global_opts.merge(h_opts)
|
152
|
+
ready_handler = handler_type.new(**opts)
|
71
153
|
@ready_handlers << ready_handler
|
72
154
|
end
|
73
155
|
|
@@ -78,7 +160,8 @@ module TTY
|
|
78
160
|
#
|
79
161
|
# @api public
|
80
162
|
def remove_handler(handler)
|
81
|
-
|
163
|
+
handler_type = coerce_handler(handler)
|
164
|
+
@ready_handlers.delete_if { |_handler| _handler.is_a?(handler_type) }
|
82
165
|
end
|
83
166
|
|
84
167
|
# Coerce handler name into object
|
@@ -112,18 +195,24 @@ module TTY
|
|
112
195
|
raise Error, "Handler needs to be a class name or a symbol name"
|
113
196
|
end
|
114
197
|
|
115
|
-
#
|
198
|
+
# Copy this logger
|
116
199
|
#
|
117
200
|
# @example
|
118
201
|
# logger = TTY::Logger.new
|
119
|
-
# logger.
|
202
|
+
# child_logger = logger.copy(app: "myenv", env: "prod")
|
203
|
+
# child_logger.info("Deploying")
|
120
204
|
#
|
121
205
|
# @return [TTY::Logger]
|
122
206
|
# a new copy of this logger
|
123
207
|
#
|
124
208
|
# @api public
|
125
|
-
def
|
126
|
-
|
209
|
+
def copy(new_fields)
|
210
|
+
new_config = @config.to_proc.call(Config.new)
|
211
|
+
if block_given?
|
212
|
+
yield(new_config)
|
213
|
+
end
|
214
|
+
self.class.new(fields: @fields.merge(new_fields),
|
215
|
+
output: @output, &new_config)
|
127
216
|
end
|
128
217
|
|
129
218
|
# Check current level against another
|
@@ -135,24 +224,57 @@ module TTY
|
|
135
224
|
compare_levels(level, other_level) != :gt
|
136
225
|
end
|
137
226
|
|
227
|
+
# Logs streaming output.
|
228
|
+
#
|
229
|
+
# @example
|
230
|
+
# logger << "Example output"
|
231
|
+
#
|
232
|
+
# @api public
|
233
|
+
def write(*msg)
|
234
|
+
event = Event.new(filter(*msg))
|
235
|
+
|
236
|
+
@ready_handlers.each do |handler|
|
237
|
+
handler.(event)
|
238
|
+
end
|
239
|
+
|
240
|
+
self
|
241
|
+
end
|
242
|
+
alias << write
|
243
|
+
|
138
244
|
# Log a message given the severtiy level
|
139
245
|
#
|
246
|
+
# @example
|
247
|
+
# logger.log(:info, "Deployed successfully")
|
248
|
+
#
|
249
|
+
# @example
|
250
|
+
# logger.log(:info) { "Deployed successfully" }
|
251
|
+
#
|
140
252
|
# @api public
|
141
|
-
def log(current_level, *msg
|
253
|
+
def log(current_level, *msg)
|
254
|
+
scoped_fields = msg.last.is_a?(::Hash) ? msg.pop : {}
|
255
|
+
fields_copy = scoped_fields.dup
|
142
256
|
if msg.empty? && block_given?
|
143
|
-
msg = [
|
257
|
+
msg = []
|
258
|
+
Array[yield].flatten(1).each do |el|
|
259
|
+
el.is_a?(::Hash) ? fields_copy.merge!(el) : msg << el
|
260
|
+
end
|
144
261
|
end
|
145
|
-
|
262
|
+
top_caller = caller_locations(1, 1)[0]
|
263
|
+
loc = caller_locations(2, 1)[0] || top_caller
|
264
|
+
label = top_caller.label
|
146
265
|
metadata = {
|
147
266
|
level: current_level,
|
148
267
|
time: Time.now,
|
149
268
|
pid: Process.pid,
|
150
|
-
name:
|
269
|
+
name: @types.include?(label.to_sym) ? label : current_level,
|
151
270
|
path: loc.path,
|
152
271
|
lineno: loc.lineno,
|
153
272
|
method: loc.base_label
|
154
273
|
}
|
155
|
-
event = Event.new(msg
|
274
|
+
event = Event.new(filter(*msg),
|
275
|
+
@data_filter.filter(@fields.merge(fields_copy)),
|
276
|
+
metadata)
|
277
|
+
|
156
278
|
@ready_handlers.each do |handler|
|
157
279
|
level = handler.respond_to?(:level) ? handler.level : @config.level
|
158
280
|
handler.(event) if log?(level, current_level)
|
@@ -160,57 +282,71 @@ module TTY
|
|
160
282
|
self
|
161
283
|
end
|
162
284
|
|
163
|
-
#
|
285
|
+
# Change current log level for the duration of the block
|
164
286
|
#
|
165
|
-
# @
|
166
|
-
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
# Log a message at :info level
|
287
|
+
# @example
|
288
|
+
# logger.log_at :debug do
|
289
|
+
# logger.debug("logged")
|
290
|
+
# end
|
171
291
|
#
|
172
|
-
# @
|
173
|
-
#
|
174
|
-
# logger.info { "Dynamically generated info" }
|
292
|
+
# @param [String] tmp_level
|
293
|
+
# the temporary log level
|
175
294
|
#
|
176
295
|
# @api public
|
177
|
-
def
|
178
|
-
|
296
|
+
def log_at(tmp_level, &block)
|
297
|
+
@ready_handlers.each do |handler|
|
298
|
+
handler.log_at(tmp_level, &block)
|
299
|
+
end
|
179
300
|
end
|
180
301
|
|
181
|
-
#
|
302
|
+
# Filter message parts for any sensitive information and
|
303
|
+
# replace with placeholder.
|
182
304
|
#
|
183
|
-
# @
|
184
|
-
|
185
|
-
log(:warn, *msg, &block)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Log a message at :error level
|
305
|
+
# @param [Array[Object]] objects
|
306
|
+
# the messages to filter
|
189
307
|
#
|
190
|
-
# @
|
191
|
-
|
192
|
-
log(:error, *msg, &block)
|
193
|
-
end
|
194
|
-
|
195
|
-
# Log a message at :fatal level
|
308
|
+
# @return [Array[String]]
|
309
|
+
# the filtered message
|
196
310
|
#
|
197
|
-
# @api
|
198
|
-
def
|
199
|
-
|
311
|
+
# @api private
|
312
|
+
def filter(*objects)
|
313
|
+
objects.map do |obj|
|
314
|
+
case obj
|
315
|
+
when Exception
|
316
|
+
backtrace = Array(obj.backtrace).map { |line| swap_filtered(line) }
|
317
|
+
copy_error(obj, swap_filtered(obj.message), backtrace)
|
318
|
+
else
|
319
|
+
swap_filtered(obj.to_s)
|
320
|
+
end
|
321
|
+
end
|
200
322
|
end
|
201
323
|
|
202
|
-
#
|
324
|
+
# Create a new error instance copy
|
203
325
|
#
|
204
|
-
# @
|
205
|
-
|
206
|
-
|
326
|
+
# @param [Exception] error
|
327
|
+
# @param [String] message
|
328
|
+
# @param [Array,nil] backtrace
|
329
|
+
#
|
330
|
+
# @return [Exception]
|
331
|
+
#
|
332
|
+
# @api private
|
333
|
+
def copy_error(error, message, backtrace = nil)
|
334
|
+
new_error = error.exception(message)
|
335
|
+
new_error.set_backtrace(backtrace)
|
336
|
+
new_error
|
207
337
|
end
|
208
338
|
|
209
|
-
#
|
339
|
+
# Swap string content for filtered content
|
210
340
|
#
|
211
|
-
# @
|
212
|
-
|
213
|
-
|
341
|
+
# @param [String] obj
|
342
|
+
#
|
343
|
+
# @api private
|
344
|
+
def swap_filtered(obj)
|
345
|
+
obj.dup.tap do |obj_copy|
|
346
|
+
@config.filters.message.each do |text|
|
347
|
+
obj_copy.gsub!(text, @config.filters.mask)
|
348
|
+
end
|
349
|
+
end
|
214
350
|
end
|
215
351
|
end # Logger
|
216
352
|
end # TTY
|
data/lib/tty/logger/config.rb
CHANGED
@@ -5,10 +5,12 @@ require_relative "handlers/console"
|
|
5
5
|
module TTY
|
6
6
|
class Logger
|
7
7
|
class Config
|
8
|
-
|
8
|
+
FILTERED = "[FILTERED]"
|
9
|
+
|
10
|
+
# The format used for date display. uses strftime format
|
9
11
|
attr_accessor :date_format
|
10
12
|
|
11
|
-
# The format used for time display
|
13
|
+
# The format used for time display. uses strftime format
|
12
14
|
attr_accessor :time_format
|
13
15
|
|
14
16
|
# The format used for displaying structured data
|
@@ -23,15 +25,18 @@ module TTY
|
|
23
25
|
# The maximum message size to be logged in bytes. Defaults to 8192
|
24
26
|
attr_accessor :max_bytes
|
25
27
|
|
26
|
-
# The maximum depth for
|
28
|
+
# The maximum depth for formatting array and hash objects. Defaults to 3
|
27
29
|
attr_accessor :max_depth
|
28
30
|
|
29
31
|
# The meta info to display, can be :date, :time, :file, :pid. Defaults to []
|
30
32
|
attr_accessor :metadata
|
31
33
|
|
32
|
-
# The output for the log messages.
|
34
|
+
# The output for the log messages. Defaults to `stderr`
|
33
35
|
attr_accessor :output
|
34
36
|
|
37
|
+
# The new custom log types. Defaults to `{}`
|
38
|
+
attr_accessor :types
|
39
|
+
|
35
40
|
# Create a configuration instance
|
36
41
|
#
|
37
42
|
# @api private
|
@@ -45,6 +50,57 @@ module TTY
|
|
45
50
|
@date_format = options.fetch(:date_format) { "%F" }
|
46
51
|
@time_format = options.fetch(:time_format) { "%T.%3N" }
|
47
52
|
@output = options.fetch(:output) { $stderr }
|
53
|
+
@types = options.fetch(:types) { {} }
|
54
|
+
end
|
55
|
+
|
56
|
+
class FiltersProvider
|
57
|
+
attr_accessor :message, :data, :mask
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@message = []
|
61
|
+
@data = []
|
62
|
+
@mask = FILTERED
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_h
|
66
|
+
{ message: @message, data: @data, mask: @mask }
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_s
|
70
|
+
to_h.inspect
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# The filters to hide sensitive data from the message(s) and data.
|
75
|
+
#
|
76
|
+
# @return [FiltersProvider]
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def filters
|
80
|
+
@filters ||= FiltersProvider.new
|
81
|
+
end
|
82
|
+
|
83
|
+
# Allow to overwirte filters
|
84
|
+
attr_writer :filters
|
85
|
+
|
86
|
+
# Clone settings
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
def to_proc
|
90
|
+
-> (config) {
|
91
|
+
config.date_format = @date_format.dup
|
92
|
+
config.time_format = @time_format.dup
|
93
|
+
config.filters = @filters.dup
|
94
|
+
config.formatter = @formatter
|
95
|
+
config.handlers = @handlers.dup
|
96
|
+
config.level = @level
|
97
|
+
config.max_bytes = @max_bytes
|
98
|
+
config.max_depth = @max_depth
|
99
|
+
config.metadata = @metadata.dup
|
100
|
+
config.output = @output.dup
|
101
|
+
config.types = @types.dup
|
102
|
+
config
|
103
|
+
}
|
48
104
|
end
|
49
105
|
|
50
106
|
# Hash representation of this config
|
@@ -55,6 +111,7 @@ module TTY
|
|
55
111
|
def to_h
|
56
112
|
{
|
57
113
|
date_format: date_format,
|
114
|
+
filters: filters.to_h,
|
58
115
|
formatter: formatter,
|
59
116
|
handlers: handlers,
|
60
117
|
level: level,
|
@@ -62,7 +119,8 @@ module TTY
|
|
62
119
|
max_depth: max_depth,
|
63
120
|
metadata: metadata,
|
64
121
|
output: output,
|
65
|
-
time_format: time_format
|
122
|
+
time_format: time_format,
|
123
|
+
types: types
|
66
124
|
}
|
67
125
|
end
|
68
126
|
end # Config
|