tty-logger 0.0.0 → 0.5.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 +51 -1
- data/README.md +769 -4
- data/lib/tty/logger.rb +332 -0
- data/lib/tty/logger/config.rb +128 -0
- data/lib/tty/logger/data_filter.rb +118 -0
- data/lib/tty/logger/event.rb +40 -0
- data/lib/tty/logger/formatters/json.rb +63 -0
- data/lib/tty/logger/formatters/text.rb +130 -0
- data/lib/tty/logger/handlers/base.rb +73 -0
- data/lib/tty/logger/handlers/console.rb +178 -0
- data/lib/tty/logger/handlers/null.rb +16 -0
- data/lib/tty/logger/handlers/stream.rb +63 -0
- data/lib/tty/logger/levels.rb +68 -0
- data/lib/tty/logger/version.rb +3 -3
- metadata +28 -22
- data/Rakefile +0 -8
- data/spec/spec_helper.rb +0 -14
- data/spec/unit/log_spec.rb +0 -7
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-logger.gemspec +0 -33
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Logger
|
5
|
+
class DataFilter
|
6
|
+
FILTERED = "[FILTERED]"
|
7
|
+
DOT = "."
|
8
|
+
|
9
|
+
attr_reader :filters, :compiled_filters, :mask
|
10
|
+
|
11
|
+
# Create a data filter instance with filters.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# TTY::Logger::DataFilter.new(%w[foo], mask: "<SECRET>")
|
15
|
+
#
|
16
|
+
# @param [String] mask
|
17
|
+
# the mask to replace object with. Defaults to `"[FILTERED]"`
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def initialize(filters = [], mask: nil)
|
21
|
+
@mask = mask || FILTERED
|
22
|
+
@filters = filters
|
23
|
+
@compiled_filters = compile(filters)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Filter object for keys matching provided filters.
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# data_filter = TTY::Logger::DataFilter.new(%w[foo])
|
30
|
+
# data_filter.filter({"foo" => "bar"})
|
31
|
+
# # => {"foo" => "[FILTERED]"}
|
32
|
+
#
|
33
|
+
# @param [Object] obj
|
34
|
+
# the object to filter
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def filter(obj)
|
38
|
+
return obj if filters.empty?
|
39
|
+
|
40
|
+
obj.each_with_object({}) do |(k, v), acc|
|
41
|
+
acc[k] = filter_val(k, v)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def compile(filters)
|
48
|
+
compiled = {
|
49
|
+
regexps: [],
|
50
|
+
nested_regexps: [],
|
51
|
+
blocks: []
|
52
|
+
}
|
53
|
+
strings = []
|
54
|
+
nested_strings = []
|
55
|
+
|
56
|
+
filters.each do |filter|
|
57
|
+
case filter
|
58
|
+
when Proc
|
59
|
+
compiled[:blocks] << filter
|
60
|
+
when Regexp
|
61
|
+
if filter.to_s.include?(DOT)
|
62
|
+
compiled[:nested_regexps] << filter
|
63
|
+
else
|
64
|
+
compiled[:regexps] << filter
|
65
|
+
end
|
66
|
+
else
|
67
|
+
exp = Regexp.escape(filter)
|
68
|
+
if exp.include?(DOT)
|
69
|
+
nested_strings << exp
|
70
|
+
else
|
71
|
+
strings << exp
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if !strings.empty?
|
77
|
+
compiled[:regexps] << /^(#{strings.join("|")})$/
|
78
|
+
end
|
79
|
+
|
80
|
+
if !nested_strings.empty?
|
81
|
+
compiled[:nested_regexps] << /^(#{nested_strings.join("|")})$/
|
82
|
+
end
|
83
|
+
|
84
|
+
compiled
|
85
|
+
end
|
86
|
+
|
87
|
+
def filter_val(key, val, composite = [])
|
88
|
+
return mask if filtered?(key, composite)
|
89
|
+
|
90
|
+
case val
|
91
|
+
when Hash then filter_obj(key, val, composite << key)
|
92
|
+
when Array then filter_arr(key, val, composite)
|
93
|
+
else val
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def filtered?(key, composite)
|
98
|
+
composite_key = composite + [key]
|
99
|
+
joined_key = composite_key.join(DOT)
|
100
|
+
@compiled_filters[:regexps].any? { |reg| !!reg.match(key.to_s) } ||
|
101
|
+
@compiled_filters[:nested_regexps].any? { |reg| !!reg.match(joined_key) } ||
|
102
|
+
@compiled_filters[:blocks].any? { |block| block.(composite_key.dup) }
|
103
|
+
end
|
104
|
+
|
105
|
+
def filter_obj(_key, obj, composite)
|
106
|
+
obj.each_with_object({}) do |(k, v), acc|
|
107
|
+
acc[k] = filter_val(k, v, composite)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def filter_arr(key, obj, composite)
|
112
|
+
obj.reduce([]) do |acc, v|
|
113
|
+
acc << filter_val(key, v, composite)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end # DataFilter
|
117
|
+
end # Logger
|
118
|
+
end # TTY
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Logger
|
5
|
+
class Event
|
6
|
+
attr_reader :message
|
7
|
+
|
8
|
+
attr_reader :fields
|
9
|
+
|
10
|
+
attr_reader :metadata
|
11
|
+
|
12
|
+
attr_reader :backtrace
|
13
|
+
|
14
|
+
def initialize(message, fields = {}, metadata = {})
|
15
|
+
@message = message
|
16
|
+
@fields = fields
|
17
|
+
@metadata = metadata
|
18
|
+
@backtrace = []
|
19
|
+
|
20
|
+
evaluate_message
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Extract backtrace information if message contains exception
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
def evaluate_message
|
29
|
+
@message.each do |msg|
|
30
|
+
case msg
|
31
|
+
when Exception
|
32
|
+
@backtrace = msg.backtrace if msg.backtrace
|
33
|
+
else
|
34
|
+
msg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end # Event
|
39
|
+
end # Logger
|
40
|
+
end # TTY
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Logger
|
7
|
+
module Formatters
|
8
|
+
# Format data suitable for data exchange
|
9
|
+
class JSON
|
10
|
+
ELLIPSIS = "..."
|
11
|
+
|
12
|
+
# Dump data into a JSON formatted string
|
13
|
+
#
|
14
|
+
# @param [Hash] obj
|
15
|
+
# the object to serialize as JSON
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def dump(obj, max_bytes: 2**12, max_depth: 3)
|
21
|
+
bytesize = 0
|
22
|
+
|
23
|
+
hash = obj.each_with_object({}) do |(k, v), acc|
|
24
|
+
str = (k.to_json + v.to_json)
|
25
|
+
items = acc.keys.size - 1
|
26
|
+
|
27
|
+
if bytesize + str.bytesize + items + ELLIPSIS.bytesize > max_bytes
|
28
|
+
acc[k] = ELLIPSIS
|
29
|
+
break acc
|
30
|
+
else
|
31
|
+
bytesize += str.bytesize
|
32
|
+
acc[k] = dump_val(v, max_depth)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
::JSON.generate(hash)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def dump_val(val, depth)
|
41
|
+
case val
|
42
|
+
when Hash then enc_obj(val, depth - 1)
|
43
|
+
when Array then enc_arr(val, depth - 1)
|
44
|
+
else
|
45
|
+
val
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def enc_obj(obj, depth)
|
50
|
+
return ELLIPSIS if depth.zero?
|
51
|
+
|
52
|
+
obj.each_with_object({}) { |(k, v), acc| acc[k] = dump_val(v, depth) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def enc_arr(obj, depth)
|
56
|
+
return ELLIPSIS if depth.zero?
|
57
|
+
|
58
|
+
obj.each_with_object([]) { |v, acc| acc << dump_val(v, depth) }
|
59
|
+
end
|
60
|
+
end # JSON
|
61
|
+
end # Formatters
|
62
|
+
end # Logger
|
63
|
+
end # TTY
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Logger
|
5
|
+
module Formatters
|
6
|
+
# Format data suitable for text reading
|
7
|
+
class Text
|
8
|
+
SPACE = " "
|
9
|
+
LPAREN = "("
|
10
|
+
RPAREN = ")"
|
11
|
+
LBRACE = "{"
|
12
|
+
RBRACE = "}"
|
13
|
+
LBRACKET = "["
|
14
|
+
RBRACKET = "]"
|
15
|
+
ELLIPSIS = "..."
|
16
|
+
LITERAL_TRUE = "true"
|
17
|
+
LITERAL_FALSE = "false"
|
18
|
+
LITERAL_NIL = "nil"
|
19
|
+
SINGLE_QUOTE_REGEX = /'/.freeze
|
20
|
+
ESCAPE_DOUBLE_QUOTE = "\""
|
21
|
+
ESCAPE_STR_REGEX = /[ ="|{}()\[\]^$+*?.-]/.freeze
|
22
|
+
NUM_REGEX = /^-?\d*(?:\.\d+)?\d+$/.freeze
|
23
|
+
|
24
|
+
# Dump data in a single formatted line
|
25
|
+
#
|
26
|
+
# @param [Hash] obj
|
27
|
+
# the object to serialize as text
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
def dump(obj, max_bytes: 2**12, max_depth: 3)
|
33
|
+
bytesize = 0
|
34
|
+
|
35
|
+
line = obj.each_with_object([]) do |(k, v), acc|
|
36
|
+
str = "#{dump_key(k)}=#{dump_val(v, max_depth)}"
|
37
|
+
items = acc.size - 1
|
38
|
+
|
39
|
+
if bytesize + str.bytesize + items > max_bytes
|
40
|
+
if bytesize + items +
|
41
|
+
(acc[-1].bytesize - ELLIPSIS.bytesize) > max_bytes
|
42
|
+
acc.pop
|
43
|
+
end
|
44
|
+
acc << ELLIPSIS
|
45
|
+
break acc
|
46
|
+
else
|
47
|
+
bytesize += str.bytesize
|
48
|
+
acc << str
|
49
|
+
end
|
50
|
+
end
|
51
|
+
line.join(SPACE)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def dump_key(key)
|
57
|
+
key = key.to_s
|
58
|
+
case key
|
59
|
+
when SINGLE_QUOTE_REGEX
|
60
|
+
key.inspect
|
61
|
+
when ESCAPE_STR_REGEX
|
62
|
+
ESCAPE_DOUBLE_QUOTE + key.inspect[1..-2] + ESCAPE_DOUBLE_QUOTE
|
63
|
+
else
|
64
|
+
key
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def dump_val(val, depth)
|
69
|
+
case val
|
70
|
+
when Hash then enc_obj(val, depth - 1)
|
71
|
+
when Array then enc_arr(val, depth - 1)
|
72
|
+
when String, Symbol then enc_str(val)
|
73
|
+
when Complex then enc_cpx(val)
|
74
|
+
when Float then enc_float(val)
|
75
|
+
when Numeric then enc_num(val)
|
76
|
+
when Time then enc_time(val)
|
77
|
+
when TrueClass then LITERAL_TRUE
|
78
|
+
when FalseClass then LITERAL_FALSE
|
79
|
+
when NilClass then LITERAL_NIL
|
80
|
+
else
|
81
|
+
val
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def enc_obj(obj, depth)
|
86
|
+
return LBRACE + ELLIPSIS + RBRACE if depth.zero?
|
87
|
+
|
88
|
+
LBRACE +
|
89
|
+
obj.map { |k, v| "#{dump_key(k)}=#{dump_val(v, depth)}" }
|
90
|
+
.join(SPACE) + RBRACE
|
91
|
+
end
|
92
|
+
|
93
|
+
def enc_arr(array, depth)
|
94
|
+
return LBRACKET + ELLIPSIS + RBRACKET if depth.zero?
|
95
|
+
|
96
|
+
LBRACKET + array.map { |v| dump_val(v, depth) }.join(SPACE) + RBRACKET
|
97
|
+
end
|
98
|
+
|
99
|
+
def enc_cpx(val)
|
100
|
+
LPAREN + val.to_s + RPAREN
|
101
|
+
end
|
102
|
+
|
103
|
+
def enc_float(val)
|
104
|
+
("%f" % val).sub(/0*?$/, "")
|
105
|
+
end
|
106
|
+
|
107
|
+
def enc_num(val)
|
108
|
+
val
|
109
|
+
end
|
110
|
+
|
111
|
+
def enc_str(str)
|
112
|
+
str = str.to_s
|
113
|
+
case str
|
114
|
+
when SINGLE_QUOTE_REGEX
|
115
|
+
str.inspect
|
116
|
+
when ESCAPE_STR_REGEX, LITERAL_TRUE, LITERAL_FALSE,
|
117
|
+
LITERAL_NIL, NUM_REGEX
|
118
|
+
ESCAPE_DOUBLE_QUOTE + str.inspect[1..-2] + ESCAPE_DOUBLE_QUOTE
|
119
|
+
else
|
120
|
+
str
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def enc_time(time)
|
125
|
+
time.strftime("%FT%T%:z")
|
126
|
+
end
|
127
|
+
end # Text
|
128
|
+
end # Formatters
|
129
|
+
end # Logger
|
130
|
+
end # TTY
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Logger
|
5
|
+
module Handlers
|
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
|
+
|
20
|
+
# Coerce formatter name into constant
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def coerce_formatter(name)
|
24
|
+
case name
|
25
|
+
when String, Symbol
|
26
|
+
const_name = if Formatters.const_defined?(name.upcase)
|
27
|
+
name.upcase
|
28
|
+
else
|
29
|
+
name.capitalize
|
30
|
+
end
|
31
|
+
Formatters.const_get(const_name)
|
32
|
+
when Class
|
33
|
+
name
|
34
|
+
else
|
35
|
+
raise_formatter_error(name)
|
36
|
+
end
|
37
|
+
rescue NameError
|
38
|
+
raise_formatter_error(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Raise error when unknown formatter name
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
def raise_formatter_error(name)
|
45
|
+
raise Error, "Unrecognized formatter name '#{name.inspect}'"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Metadata for the log event
|
49
|
+
#
|
50
|
+
# @return [Array[Symbol]]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
def metadata
|
54
|
+
if config.metadata.include?(:all)
|
55
|
+
[:pid, :date, :time, :file]
|
56
|
+
else
|
57
|
+
config.metadata
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Format path from event metadata
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
def format_filepath(event)
|
67
|
+
"%s:%d:in`%s`" % [event.metadata[:path], event.metadata[:lineno],
|
68
|
+
event.metadata[:method]]
|
69
|
+
end
|
70
|
+
end # Base
|
71
|
+
end # Handlers
|
72
|
+
end # Logger
|
73
|
+
end # TTY
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pastel"
|
4
|
+
|
5
|
+
require_relative "base"
|
6
|
+
|
7
|
+
module TTY
|
8
|
+
class Logger
|
9
|
+
module Handlers
|
10
|
+
class Console
|
11
|
+
include Base
|
12
|
+
|
13
|
+
ARROW = "›"
|
14
|
+
|
15
|
+
STYLES = {
|
16
|
+
debug: {
|
17
|
+
label: "debug",
|
18
|
+
symbol: "•",
|
19
|
+
color: :cyan,
|
20
|
+
levelpad: 2
|
21
|
+
},
|
22
|
+
info: {
|
23
|
+
label: "info",
|
24
|
+
symbol: "ℹ",
|
25
|
+
color: :green,
|
26
|
+
levelpad: 3
|
27
|
+
},
|
28
|
+
warn: {
|
29
|
+
label: "warning",
|
30
|
+
symbol: "⚠",
|
31
|
+
color: :yellow,
|
32
|
+
levelpad: 0
|
33
|
+
},
|
34
|
+
error: {
|
35
|
+
label: "error",
|
36
|
+
symbol: "⨯",
|
37
|
+
color: :red,
|
38
|
+
levelpad: 2
|
39
|
+
},
|
40
|
+
fatal: {
|
41
|
+
label: "fatal",
|
42
|
+
symbol: "!",
|
43
|
+
color: :red,
|
44
|
+
levelpad: 2
|
45
|
+
},
|
46
|
+
success: {
|
47
|
+
label: "success",
|
48
|
+
symbol: "✔",
|
49
|
+
color: :green,
|
50
|
+
levelpad: 0
|
51
|
+
},
|
52
|
+
wait: {
|
53
|
+
label: "waiting",
|
54
|
+
symbol: "…",
|
55
|
+
color: :cyan,
|
56
|
+
levelpad: 0
|
57
|
+
}
|
58
|
+
}.freeze
|
59
|
+
|
60
|
+
TEXT_REGEXP = /([{}()\[\]])?(["']?)(\S+?)(["']?=)/.freeze
|
61
|
+
JSON_REGEXP = /\"([^,]+?)\"(?=:)/.freeze
|
62
|
+
|
63
|
+
COLOR_PATTERNS = {
|
64
|
+
text: [TEXT_REGEXP, ->(c) { "\\1\\2" + c.("\\3") + "\\4" }],
|
65
|
+
json: [JSON_REGEXP, ->(c) { "\"" + c.("\\1") + "\"" }]
|
66
|
+
}.freeze
|
67
|
+
|
68
|
+
# The output stream
|
69
|
+
# @api private
|
70
|
+
attr_reader :output
|
71
|
+
|
72
|
+
# The configuration options
|
73
|
+
# @api private
|
74
|
+
attr_reader :config
|
75
|
+
|
76
|
+
# The logging level
|
77
|
+
# @api private
|
78
|
+
attr_reader :level
|
79
|
+
|
80
|
+
# The format for the message
|
81
|
+
# @api private
|
82
|
+
attr_reader :message_format
|
83
|
+
|
84
|
+
def initialize(output: $stderr, formatter: nil, config: nil, level: nil,
|
85
|
+
styles: {}, message_format: "%-25s")
|
86
|
+
@output = Array[output].flatten
|
87
|
+
@formatter = coerce_formatter(formatter || config.formatter).new
|
88
|
+
@formatter_name = @formatter.class.name.split("::").last.downcase
|
89
|
+
@color_pattern = COLOR_PATTERNS[@formatter_name.to_sym]
|
90
|
+
@config = config
|
91
|
+
@styles = styles
|
92
|
+
@level = level || @config.level
|
93
|
+
@mutex = Mutex.new
|
94
|
+
@pastel = Pastel.new
|
95
|
+
@message_format = message_format
|
96
|
+
end
|
97
|
+
|
98
|
+
# Handle log event output in format
|
99
|
+
#
|
100
|
+
# @param [Event] event
|
101
|
+
# the current event logged
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def call(event)
|
105
|
+
@mutex.lock
|
106
|
+
|
107
|
+
style = configure_styles(event)
|
108
|
+
color = configure_color(style)
|
109
|
+
|
110
|
+
fmt = []
|
111
|
+
metadata.each do |meta|
|
112
|
+
case meta
|
113
|
+
when :date
|
114
|
+
fmt << @pastel.white("[" + event.metadata[:time].
|
115
|
+
strftime(config.date_format) + "]")
|
116
|
+
when :time
|
117
|
+
fmt << @pastel.white("[" + event.metadata[:time].
|
118
|
+
strftime(config.time_format) + "]")
|
119
|
+
when :file
|
120
|
+
fmt << @pastel.white("[#{format_filepath(event)}]")
|
121
|
+
when :pid
|
122
|
+
fmt << @pastel.white("[%d]" % event.metadata[:pid])
|
123
|
+
else
|
124
|
+
raise "Unknown metadata `#{meta}`"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
fmt << ARROW unless config.metadata.empty?
|
128
|
+
unless style.empty?
|
129
|
+
fmt << color.(style[:symbol])
|
130
|
+
fmt << color.(style[:label]) + (" " * style[:levelpad])
|
131
|
+
end
|
132
|
+
fmt << message_format % event.message.join(" ")
|
133
|
+
unless event.fields.empty?
|
134
|
+
pattern, replacement = *@color_pattern
|
135
|
+
fmt << @formatter.dump(event.fields, max_bytes: config.max_bytes,
|
136
|
+
max_depth: config.max_depth)
|
137
|
+
.gsub(pattern, replacement.(color))
|
138
|
+
end
|
139
|
+
unless event.backtrace.empty?
|
140
|
+
fmt << "\n" + format_backtrace(event)
|
141
|
+
end
|
142
|
+
|
143
|
+
output.each { |out| out.puts fmt.join(" ") }
|
144
|
+
ensure
|
145
|
+
@mutex.unlock
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def format_backtrace(event)
|
151
|
+
indent = " " * 4
|
152
|
+
event.backtrace.map do |bktrace|
|
153
|
+
indent + bktrace.to_s
|
154
|
+
end.join("\n")
|
155
|
+
end
|
156
|
+
|
157
|
+
# Merge default styles with custom style overrides
|
158
|
+
#
|
159
|
+
# @return [Hash[String]]
|
160
|
+
# the style matching log type
|
161
|
+
#
|
162
|
+
# @api private
|
163
|
+
def configure_styles(event)
|
164
|
+
return {} if event.metadata[:name].nil?
|
165
|
+
|
166
|
+
STYLES.fetch(event.metadata[:name].to_sym, {})
|
167
|
+
.dup
|
168
|
+
.merge!(@styles[event.metadata[:name].to_sym] || {})
|
169
|
+
end
|
170
|
+
|
171
|
+
def configure_color(style)
|
172
|
+
color = style.fetch(:color) { :cyan }
|
173
|
+
@pastel.send(color).detach
|
174
|
+
end
|
175
|
+
end # Console
|
176
|
+
end # Handlers
|
177
|
+
end # Logger
|
178
|
+
end # TTY
|