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