tty-logger 0.1.0 → 0.2.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 +13 -0
- data/README.md +200 -40
- data/examples/child.rb +9 -0
- data/examples/console.rb +4 -4
- data/examples/custom_type.rb +27 -0
- data/examples/log.rb +9 -0
- data/examples/stream.rb +2 -2
- data/lib/tty/logger.rb +116 -50
- data/lib/tty/logger/config.rb +32 -2
- data/lib/tty/logger/handlers/base.rb +13 -0
- data/lib/tty/logger/handlers/console.rb +1 -1
- data/lib/tty/logger/levels.rb +32 -17
- data/lib/tty/logger/version.rb +1 -1
- data/spec/perf/json_formatter_spec.rb +29 -0
- data/spec/perf/log_spec.rb +21 -0
- data/spec/perf/text_formatter_spec.rb +29 -0
- data/spec/unit/config_spec.rb +3 -1
- data/spec/unit/copy_spec.rb +27 -0
- data/spec/unit/filter_spec.rb +32 -0
- data/spec/unit/log_at_spec.rb +34 -0
- data/spec/unit/log_spec.rb +71 -12
- data/tasks/spec.rake +12 -7
- data/tty-logger.gemspec +3 -0
- metadata +11 -2
data/examples/child.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require_relative "../lib/tty/logger"
|
2
|
+
|
3
|
+
logger = TTY::Logger.new(fields: {app: "parent", env: "prod"})
|
4
|
+
child_logger = logger.copy(app: "child") do |config|
|
5
|
+
config.filters = ["logging"]
|
6
|
+
end
|
7
|
+
|
8
|
+
logger.info("Parent logging")
|
9
|
+
child_logger.warn("Child logging")
|
data/examples/console.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
require_relative "../lib/tty/logger"
|
2
2
|
|
3
3
|
TTY::Logger.configure do |config|
|
4
|
-
config.max_bytes = 2**5
|
5
|
-
config.metadata = [:all]
|
6
|
-
config.handlers = [[:console, formatter: :text]]
|
4
|
+
# config.max_bytes = 2**5
|
5
|
+
# config.metadata = [:all]
|
6
|
+
# config.handlers = [[:console, formatter: :text]]
|
7
7
|
config.level = :debug
|
8
8
|
end
|
9
9
|
|
10
10
|
logger = TTY::Logger.new(fields: {app: "myapp", env: "prod"})
|
11
11
|
|
12
|
-
logger.
|
12
|
+
logger.copy(path: "/var/www/example.com").info("Deploying", "code")
|
13
13
|
|
14
14
|
puts "Levels:"
|
15
15
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "../lib/tty/logger"
|
2
|
+
|
3
|
+
logger = TTY::Logger.new do |config|
|
4
|
+
config.types = {thanks: {level: :info}, done: {level: :info}}
|
5
|
+
config.handlers = [
|
6
|
+
[:console, {
|
7
|
+
styles: {
|
8
|
+
thanks: {
|
9
|
+
symbol: "❤️ ",
|
10
|
+
label: "thanks",
|
11
|
+
color: :magenta,
|
12
|
+
levelpad: 0
|
13
|
+
},
|
14
|
+
done: {
|
15
|
+
symbol: "!!",
|
16
|
+
label: "done",
|
17
|
+
color: :green,
|
18
|
+
levelpad: 2
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
logger.info("This is info!")
|
26
|
+
logger.thanks("Great work!")
|
27
|
+
logger.done("Work done!")
|
data/examples/log.rb
ADDED
data/examples/stream.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require_relative "../lib/tty/logger"
|
2
2
|
|
3
3
|
TTY::Logger.configure do |config|
|
4
|
-
config.max_bytes = 2**
|
4
|
+
config.max_bytes = 2**7
|
5
5
|
config.metadata = [:all]
|
6
6
|
config.handlers = [[:stream, formatter: :text]]
|
7
7
|
config.level = :debug
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
|
10
10
|
logger = TTY::Logger.new(fields: {app: "myapp", env: "prod"})
|
11
11
|
|
12
|
-
logger.
|
12
|
+
logger.copy(path: "/var/www/example.com").info("Deploying", "code")
|
13
13
|
|
14
14
|
puts "Levels:"
|
15
15
|
|
data/lib/tty/logger.rb
CHANGED
@@ -17,6 +17,45 @@ module TTY
|
|
17
17
|
# Error raised by this logger
|
18
18
|
class Error < StandardError; end
|
19
19
|
|
20
|
+
FILTERED = "[FILTERED]"
|
21
|
+
|
22
|
+
LOG_TYPES = {
|
23
|
+
debug: { level: :debug },
|
24
|
+
info: { level: :info },
|
25
|
+
warn: { level: :warn },
|
26
|
+
error: { level: :error },
|
27
|
+
fatal: { level: :fatal },
|
28
|
+
success: { level: :info },
|
29
|
+
wait: { level: :info }
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# Macro to dynamically define log types
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def self.define_level(name, log_level = nil)
|
36
|
+
const_level = (LOG_TYPES[name.to_sym] || log_level)[:level]
|
37
|
+
|
38
|
+
loc = caller_locations(0,1)[0]
|
39
|
+
if loc
|
40
|
+
file, line = loc.path, loc.lineno + 7
|
41
|
+
else
|
42
|
+
file, line = __FILE__, __LINE__ + 3
|
43
|
+
end
|
44
|
+
class_eval(<<-EOL, file, line)
|
45
|
+
def #{name}(*msg, &block)
|
46
|
+
log(:#{const_level}, *msg, &block)
|
47
|
+
end
|
48
|
+
EOL
|
49
|
+
end
|
50
|
+
|
51
|
+
define_level :debug
|
52
|
+
define_level :info
|
53
|
+
define_level :warn
|
54
|
+
define_level :error
|
55
|
+
define_level :fatal
|
56
|
+
define_level :success
|
57
|
+
define_level :wait
|
58
|
+
|
20
59
|
# Logger configuration instance
|
21
60
|
#
|
22
61
|
# @api public
|
@@ -36,6 +75,12 @@ module TTY
|
|
36
75
|
# @example
|
37
76
|
# logger = TTY::Logger.new(output: $stdout)
|
38
77
|
#
|
78
|
+
# @param [IO] output
|
79
|
+
# the output object, can be stream
|
80
|
+
#
|
81
|
+
# @param [Hash] fields
|
82
|
+
# the data fields for each log message
|
83
|
+
#
|
39
84
|
# @api public
|
40
85
|
def initialize(output: nil, fields: {})
|
41
86
|
@fields = fields
|
@@ -51,11 +96,28 @@ module TTY
|
|
51
96
|
@output = output || @config.output
|
52
97
|
@ready_handlers = []
|
53
98
|
|
99
|
+
@config.types.each do |name, log_level|
|
100
|
+
add_type(name, log_level)
|
101
|
+
end
|
102
|
+
|
54
103
|
@handlers.each do |handler|
|
55
104
|
add_handler(handler)
|
56
105
|
end
|
57
106
|
end
|
58
107
|
|
108
|
+
# Add new log type
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# add_type(:thanks, {level: :info})
|
112
|
+
#
|
113
|
+
# @api private
|
114
|
+
def add_type(name, log_level)
|
115
|
+
if respond_to?(name)
|
116
|
+
raise Error, "Already defined log type #{name.inspect}"
|
117
|
+
end
|
118
|
+
self.class.define_level(name, log_level)
|
119
|
+
end
|
120
|
+
|
59
121
|
# Add handler for logging messages
|
60
122
|
#
|
61
123
|
# @example
|
@@ -112,18 +174,24 @@ module TTY
|
|
112
174
|
raise Error, "Handler needs to be a class name or a symbol name"
|
113
175
|
end
|
114
176
|
|
115
|
-
#
|
177
|
+
# Copy this logger
|
116
178
|
#
|
117
179
|
# @example
|
118
180
|
# logger = TTY::Logger.new
|
119
|
-
# logger.
|
181
|
+
# child_logger = logger.copy(app: "myenv", env: "prod")
|
182
|
+
# child_logger.info("Deploying")
|
120
183
|
#
|
121
184
|
# @return [TTY::Logger]
|
122
185
|
# a new copy of this logger
|
123
186
|
#
|
124
187
|
# @api public
|
125
|
-
def
|
126
|
-
|
188
|
+
def copy(new_fields)
|
189
|
+
new_config = @config.to_proc.call(Config.new)
|
190
|
+
if block_given?
|
191
|
+
yield(new_config)
|
192
|
+
end
|
193
|
+
self.class.new(fields: @fields.merge(new_fields),
|
194
|
+
output: @output, &new_config)
|
127
195
|
end
|
128
196
|
|
129
197
|
# Check current level against another
|
@@ -137,22 +205,35 @@ module TTY
|
|
137
205
|
|
138
206
|
# Log a message given the severtiy level
|
139
207
|
#
|
208
|
+
# @example
|
209
|
+
# logger.log(:info, "Deployed successfully")
|
210
|
+
#
|
211
|
+
# @example
|
212
|
+
# logger.log(:info) { "Deployed successfully" }
|
213
|
+
#
|
140
214
|
# @api public
|
141
215
|
def log(current_level, *msg, **scoped_fields)
|
216
|
+
fields_copy = scoped_fields.dup
|
142
217
|
if msg.empty? && block_given?
|
143
|
-
msg = [
|
218
|
+
msg = []
|
219
|
+
Array[yield].flatten(1).each do |el|
|
220
|
+
el.is_a?(::Hash) ? fields_copy.merge!(el) : msg << el
|
221
|
+
end
|
144
222
|
end
|
145
|
-
|
223
|
+
top_caller = caller_locations(1,1)[0]
|
224
|
+
loc = caller_locations(2,1)[0] || top_caller
|
225
|
+
label = top_caller.label
|
146
226
|
metadata = {
|
147
227
|
level: current_level,
|
148
228
|
time: Time.now,
|
149
229
|
pid: Process.pid,
|
150
|
-
name:
|
230
|
+
name: /<top\s+\(required\)>|<main>/ =~ label ? current_level : label,
|
151
231
|
path: loc.path,
|
152
232
|
lineno: loc.lineno,
|
153
233
|
method: loc.base_label
|
154
234
|
}
|
155
|
-
event = Event.new(msg, @fields.merge(
|
235
|
+
event = Event.new(filter(*msg), @fields.merge(fields_copy), metadata)
|
236
|
+
|
156
237
|
@ready_handlers.each do |handler|
|
157
238
|
level = handler.respond_to?(:level) ? handler.level : @config.level
|
158
239
|
handler.(event) if log?(level, current_level)
|
@@ -160,57 +241,42 @@ module TTY
|
|
160
241
|
self
|
161
242
|
end
|
162
243
|
|
163
|
-
#
|
244
|
+
# Change current log level for the duration of the block
|
164
245
|
#
|
165
|
-
# @
|
166
|
-
|
167
|
-
|
168
|
-
end
|
169
|
-
|
170
|
-
# Log a message at :info level
|
246
|
+
# @example
|
247
|
+
# logger.log_at :debug do
|
248
|
+
# logger.debug("logged")
|
249
|
+
# end
|
171
250
|
#
|
172
|
-
# @
|
173
|
-
#
|
174
|
-
# logger.info { "Dynamically generated info" }
|
251
|
+
# @param [String] tmp_level
|
252
|
+
# the temporary log level
|
175
253
|
#
|
176
254
|
# @api public
|
177
|
-
def
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
# Log a message at :warn level
|
182
|
-
#
|
183
|
-
# @api public
|
184
|
-
def warn(*msg, &block)
|
185
|
-
log(:warn, *msg, &block)
|
186
|
-
end
|
187
|
-
|
188
|
-
# Log a message at :error level
|
189
|
-
#
|
190
|
-
# @api public
|
191
|
-
def error(*msg, &block)
|
192
|
-
log(:error, *msg, &block)
|
255
|
+
def log_at(tmp_level, &block)
|
256
|
+
@ready_handlers.each do |handler|
|
257
|
+
handler.log_at(tmp_level, &block)
|
258
|
+
end
|
193
259
|
end
|
194
260
|
|
195
|
-
#
|
261
|
+
# Filter message parts for any sensitive information and
|
262
|
+
# replace with placeholder.
|
196
263
|
#
|
197
|
-
# @
|
198
|
-
|
199
|
-
log(:fatal, *msg, &block)
|
200
|
-
end
|
201
|
-
|
202
|
-
# Log a message with a success label
|
264
|
+
# @param [Array[String]] messages
|
265
|
+
# the messages to filter
|
203
266
|
#
|
204
|
-
# @
|
205
|
-
|
206
|
-
log(:info, *msg, &block)
|
207
|
-
end
|
208
|
-
|
209
|
-
# Log a message with a wait label
|
267
|
+
# @return [Array[String]]
|
268
|
+
# the filtered message
|
210
269
|
#
|
211
|
-
# @api
|
212
|
-
def
|
213
|
-
|
270
|
+
# @api private
|
271
|
+
def filter(*messages)
|
272
|
+
messages.reduce([]) do |acc, msg|
|
273
|
+
acc << msg.dup.tap do |msg_copy|
|
274
|
+
@config.filters.each do |text, placeholder|
|
275
|
+
msg_copy.gsub!(text, placeholder || FILTERED)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
acc
|
279
|
+
end
|
214
280
|
end
|
215
281
|
end # Logger
|
216
282
|
end # TTY
|
data/lib/tty/logger/config.rb
CHANGED
@@ -11,6 +11,9 @@ module TTY
|
|
11
11
|
# The format used for time display
|
12
12
|
attr_accessor :time_format
|
13
13
|
|
14
|
+
# The storage of placholders to filter sensitive data out from the logs. Defaults to {}
|
15
|
+
attr_accessor :filters
|
16
|
+
|
14
17
|
# The format used for displaying structured data
|
15
18
|
attr_accessor :formatter
|
16
19
|
|
@@ -29,9 +32,12 @@ module TTY
|
|
29
32
|
# The meta info to display, can be :date, :time, :file, :pid. Defaults to []
|
30
33
|
attr_accessor :metadata
|
31
34
|
|
32
|
-
# The output for the log messages.
|
35
|
+
# The output for the log messages. Defaults to `stderr`
|
33
36
|
attr_accessor :output
|
34
37
|
|
38
|
+
# The new custom log types. Defaults to `{}`
|
39
|
+
attr_accessor :types
|
40
|
+
|
35
41
|
# Create a configuration instance
|
36
42
|
#
|
37
43
|
# @api private
|
@@ -40,11 +46,33 @@ module TTY
|
|
40
46
|
@max_depth = options.fetch(:max_depth) { 3 }
|
41
47
|
@level = options.fetch(:level) { :info }
|
42
48
|
@metadata = options.fetch(:metadata) { [] }
|
49
|
+
@filters = options.fetch(:filters) { {} }
|
43
50
|
@handlers = options.fetch(:handlers) { [:console] }
|
44
51
|
@formatter = options.fetch(:formatter) { :text }
|
45
52
|
@date_format = options.fetch(:date_format) { "%F" }
|
46
53
|
@time_format = options.fetch(:time_format) { "%T.%3N" }
|
47
54
|
@output = options.fetch(:output) { $stderr }
|
55
|
+
@types = options.fetch(:types) { {} }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Clone settings
|
59
|
+
#
|
60
|
+
# @api public
|
61
|
+
def to_proc
|
62
|
+
-> (config) {
|
63
|
+
config.date_format = @date_format.dup
|
64
|
+
config.filters = @filters.dup
|
65
|
+
config.formatter = @formatter
|
66
|
+
config.handlers = @handlers.dup
|
67
|
+
config.level = @level
|
68
|
+
config.max_bytes = @max_bytes
|
69
|
+
config.max_depth = @max_depth
|
70
|
+
config.metadata = @metadata.dup
|
71
|
+
config.output = @output.dup
|
72
|
+
config.time_format = @time_format.dup
|
73
|
+
config.types = @types.dup
|
74
|
+
config
|
75
|
+
}
|
48
76
|
end
|
49
77
|
|
50
78
|
# Hash representation of this config
|
@@ -55,6 +83,7 @@ module TTY
|
|
55
83
|
def to_h
|
56
84
|
{
|
57
85
|
date_format: date_format,
|
86
|
+
filters: filters,
|
58
87
|
formatter: formatter,
|
59
88
|
handlers: handlers,
|
60
89
|
level: level,
|
@@ -62,7 +91,8 @@ module TTY
|
|
62
91
|
max_depth: max_depth,
|
63
92
|
metadata: metadata,
|
64
93
|
output: output,
|
65
|
-
time_format: time_format
|
94
|
+
time_format: time_format,
|
95
|
+
types: types
|
66
96
|
}
|
67
97
|
end
|
68
98
|
end # Config
|
@@ -4,6 +4,19 @@ module TTY
|
|
4
4
|
class Logger
|
5
5
|
module Handlers
|
6
6
|
module Base
|
7
|
+
# Change current log level for the duration of the block
|
8
|
+
#
|
9
|
+
# @param [String] tmp_level
|
10
|
+
# the temporary log level
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
def log_at(tmp_level)
|
14
|
+
old_level, @level = level, tmp_level
|
15
|
+
yield
|
16
|
+
ensure
|
17
|
+
@level = old_level
|
18
|
+
end
|
19
|
+
|
7
20
|
# Coerce formatter name into constant
|
8
21
|
#
|
9
22
|
# @api private
|
@@ -138,7 +138,7 @@ module TTY
|
|
138
138
|
#
|
139
139
|
# @api private
|
140
140
|
def configure_styles(event)
|
141
|
-
style = STYLES
|
141
|
+
style = STYLES.fetch(event.metadata[:name].to_sym, {}).dup
|
142
142
|
(@styles[event.metadata[:name].to_sym] || {}).each do |k, v|
|
143
143
|
style[k] = v
|
144
144
|
end
|
data/lib/tty/logger/levels.rb
CHANGED
@@ -10,35 +10,50 @@ module TTY
|
|
10
10
|
FATAL_LEVEL = 4
|
11
11
|
|
12
12
|
LEVEL_NAMES = {
|
13
|
-
DEBUG_LEVEL
|
14
|
-
INFO_LEVEL
|
15
|
-
WARN_LEVEL
|
16
|
-
ERROR_LEVEL
|
17
|
-
FATAL_LEVEL
|
13
|
+
debug: DEBUG_LEVEL,
|
14
|
+
info: INFO_LEVEL,
|
15
|
+
warn: WARN_LEVEL,
|
16
|
+
error: ERROR_LEVEL,
|
17
|
+
fatal: FATAL_LEVEL
|
18
18
|
}
|
19
19
|
|
20
|
+
# All the default level names
|
21
|
+
#
|
22
|
+
# @return [Array[Symbol]]
|
23
|
+
#
|
24
|
+
# @api private
|
20
25
|
def level_names
|
21
|
-
|
26
|
+
LEVEL_NAMES.keys
|
22
27
|
end
|
23
28
|
|
29
|
+
# Convert level name to level number
|
30
|
+
#
|
31
|
+
# @param [Symbol] level
|
32
|
+
#
|
33
|
+
# @return [Integer]
|
34
|
+
#
|
24
35
|
# @api private
|
25
36
|
def level_to_number(level)
|
26
|
-
|
27
|
-
|
28
|
-
when "info" then INFO_LEVEL
|
29
|
-
when "warn" then WARN_LEVEL
|
30
|
-
when "error" then ERROR_LEVEL
|
31
|
-
when "fatal" then FATAL_LEVEL
|
32
|
-
else
|
33
|
-
raise ArgumentError, "Invalid level #{level.inspect}"
|
34
|
-
end
|
37
|
+
LEVEL_NAMES[level.to_s.downcase.to_sym] ||
|
38
|
+
raise(ArgumentError, "Invalid level #{level.inspect}")
|
35
39
|
end
|
36
40
|
|
41
|
+
# Convert level number to level name
|
42
|
+
#
|
43
|
+
# @param [Integer] number
|
44
|
+
#
|
45
|
+
# @return [Symbol]
|
46
|
+
#
|
37
47
|
# @api private
|
38
|
-
def number_to_level(
|
39
|
-
LEVEL_NAMES
|
48
|
+
def number_to_level(number)
|
49
|
+
LEVEL_NAMES.key(number)
|
40
50
|
end
|
41
51
|
|
52
|
+
# Compares two levels by name or number
|
53
|
+
#
|
54
|
+
# @return [Symbol]
|
55
|
+
# either :lt, :gt or :eq
|
56
|
+
#
|
42
57
|
# @api private
|
43
58
|
def compare_levels(left, right)
|
44
59
|
left = left.is_a?(Integer) ? left : level_to_number(left)
|