tty-logger 0.0.0 → 0.5.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 +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
|