tty-logger 0.0.0 → 0.1.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 +1 -1
- data/README.md +516 -1
- data/examples/console.rb +22 -0
- data/examples/error.rb +11 -0
- data/examples/handler.rb +19 -0
- data/examples/output.rb +15 -0
- data/examples/override.rb +29 -0
- data/examples/stream.rb +22 -0
- data/lib/tty/logger/config.rb +70 -0
- data/lib/tty/logger/event.rb +40 -0
- data/lib/tty/logger/formatters/json.rb +64 -0
- data/lib/tty/logger/formatters/text.rb +129 -0
- data/lib/tty/logger/handlers/base.rb +60 -0
- data/lib/tty/logger/handlers/console.rb +155 -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 +52 -0
- data/lib/tty/logger/version.rb +1 -1
- data/lib/tty/logger.rb +206 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/unit/add_handler_spec.rb +25 -0
- data/spec/unit/config_spec.rb +107 -0
- data/spec/unit/event_spec.rb +22 -0
- data/spec/unit/exception_spec.rb +45 -0
- data/spec/unit/formatter_spec.rb +70 -0
- data/spec/unit/formatters/json_spec.rb +41 -0
- data/spec/unit/formatters/text_spec.rb +82 -0
- data/spec/unit/handler_spec.rb +83 -0
- data/spec/unit/handlers/custom_spec.rb +26 -0
- data/spec/unit/handlers/null_spec.rb +15 -0
- data/spec/unit/handlers/stream_spec.rb +72 -0
- data/spec/unit/levels_spec.rb +40 -0
- data/spec/unit/log_metadata_spec.rb +55 -0
- data/spec/unit/log_spec.rb +140 -3
- data/spec/unit/output_spec.rb +40 -0
- data/tty-logger.gemspec +2 -0
- metadata +45 -2
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "handlers/console"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Logger
|
7
|
+
class Config
|
8
|
+
# The format used for date display
|
9
|
+
attr_accessor :date_format
|
10
|
+
|
11
|
+
# The format used for time display
|
12
|
+
attr_accessor :time_format
|
13
|
+
|
14
|
+
# The format used for displaying structured data
|
15
|
+
attr_accessor :formatter
|
16
|
+
|
17
|
+
# The handlers used to display logging info. Defaults to [:console]
|
18
|
+
attr_accessor :handlers
|
19
|
+
|
20
|
+
# The level to log messages at. Default to :info
|
21
|
+
attr_accessor :level
|
22
|
+
|
23
|
+
# The maximum message size to be logged in bytes. Defaults to 8192
|
24
|
+
attr_accessor :max_bytes
|
25
|
+
|
26
|
+
# The maximum depth for formattin array and hash objects. Defaults to 3
|
27
|
+
attr_accessor :max_depth
|
28
|
+
|
29
|
+
# The meta info to display, can be :date, :time, :file, :pid. Defaults to []
|
30
|
+
attr_accessor :metadata
|
31
|
+
|
32
|
+
# The output for the log messages. Default to `stderr`
|
33
|
+
attr_accessor :output
|
34
|
+
|
35
|
+
# Create a configuration instance
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def initialize(**options)
|
39
|
+
@max_bytes = options.fetch(:max_bytes) { 2**13 }
|
40
|
+
@max_depth = options.fetch(:max_depth) { 3 }
|
41
|
+
@level = options.fetch(:level) { :info }
|
42
|
+
@metadata = options.fetch(:metadata) { [] }
|
43
|
+
@handlers = options.fetch(:handlers) { [:console] }
|
44
|
+
@formatter = options.fetch(:formatter) { :text }
|
45
|
+
@date_format = options.fetch(:date_format) { "%F" }
|
46
|
+
@time_format = options.fetch(:time_format) { "%T.%3N" }
|
47
|
+
@output = options.fetch(:output) { $stderr }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Hash representation of this config
|
51
|
+
#
|
52
|
+
# @return [Hash[Symbol]]
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def to_h
|
56
|
+
{
|
57
|
+
date_format: date_format,
|
58
|
+
formatter: formatter,
|
59
|
+
handlers: handlers,
|
60
|
+
level: level,
|
61
|
+
max_bytes: max_bytes,
|
62
|
+
max_depth: max_depth,
|
63
|
+
metadata: metadata,
|
64
|
+
output: output,
|
65
|
+
time_format: time_format
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end # Config
|
69
|
+
end # Logger
|
70
|
+
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
|
33
|
+
else
|
34
|
+
msg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end # Event
|
39
|
+
end # Logger
|
40
|
+
end # TTY
|
@@ -0,0 +1,64 @@
|
|
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.reduce({}) do |acc, (k, v)|
|
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
|
+
acc
|
35
|
+
end
|
36
|
+
::JSON.generate(hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def dump_val(val, depth)
|
42
|
+
case val
|
43
|
+
when Hash then enc_obj(val, depth - 1)
|
44
|
+
when Array then enc_arr(val, depth - 1)
|
45
|
+
else
|
46
|
+
val
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def enc_obj(obj, depth)
|
51
|
+
return ELLIPSIS if depth.zero?
|
52
|
+
|
53
|
+
obj.reduce({}) { |acc, (k, v)| acc[k] = dump_val(v, depth); acc }
|
54
|
+
end
|
55
|
+
|
56
|
+
def enc_arr(obj, depth)
|
57
|
+
return ELLIPSIS if depth.zero?
|
58
|
+
|
59
|
+
obj.reduce([]) { |acc, v| acc << dump_val(v, depth); acc }
|
60
|
+
end
|
61
|
+
end # JSON
|
62
|
+
end # Formatters
|
63
|
+
end # Logger
|
64
|
+
end # TTY
|
@@ -0,0 +1,129 @@
|
|
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.reduce([]) do |acc, (k, v)|
|
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 + (acc[-1].bytesize - ELLIPSIS.bytesize) > max_bytes
|
41
|
+
acc.pop
|
42
|
+
end
|
43
|
+
acc << ELLIPSIS
|
44
|
+
break acc
|
45
|
+
else
|
46
|
+
bytesize += str.bytesize
|
47
|
+
acc << str
|
48
|
+
end
|
49
|
+
acc
|
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)}" }.join(SPACE) +
|
90
|
+
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, LITERAL_NIL, NUM_REGEX
|
117
|
+
ESCAPE_DOUBLE_QUOTE + str.inspect[1..-2] + ESCAPE_DOUBLE_QUOTE
|
118
|
+
else
|
119
|
+
str
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def enc_time(time)
|
124
|
+
time.strftime("%FT%T%:z")
|
125
|
+
end
|
126
|
+
end # Text
|
127
|
+
end # Formatters
|
128
|
+
end # Logger
|
129
|
+
end # TTY
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Logger
|
5
|
+
module Handlers
|
6
|
+
module Base
|
7
|
+
# Coerce formatter name into constant
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
def coerce_formatter(name)
|
11
|
+
case name
|
12
|
+
when String, Symbol
|
13
|
+
const_name = if Formatters.const_defined?(name.upcase)
|
14
|
+
name.upcase
|
15
|
+
else
|
16
|
+
name.capitalize
|
17
|
+
end
|
18
|
+
Formatters.const_get(const_name)
|
19
|
+
when Class
|
20
|
+
name
|
21
|
+
else
|
22
|
+
raise_formatter_error(name)
|
23
|
+
end
|
24
|
+
rescue NameError
|
25
|
+
raise_formatter_error(name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Raise error when unknown formatter name
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def raise_formatter_error(name)
|
32
|
+
raise Error, "Unrecognized formatter name '#{name.inspect}'"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Metadata for the log event
|
36
|
+
#
|
37
|
+
# @return [Array[Symbol]]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def metadata
|
41
|
+
if config.metadata.include?(:all)
|
42
|
+
[:pid, :date, :time, :file]
|
43
|
+
else
|
44
|
+
config.metadata
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Format path from event metadata
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
def format_filepath(event)
|
54
|
+
"%s:%d:in`%s`" % [event.metadata[:path], event.metadata[:lineno],
|
55
|
+
event.metadata[:method]]
|
56
|
+
end
|
57
|
+
end # Base
|
58
|
+
end # Handlers
|
59
|
+
end # Logger
|
60
|
+
end # TTY
|
@@ -0,0 +1,155 @@
|
|
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
|
+
}
|
59
|
+
|
60
|
+
attr_reader :output
|
61
|
+
|
62
|
+
attr_reader :config
|
63
|
+
|
64
|
+
attr_reader :level
|
65
|
+
|
66
|
+
def initialize(output: $stderr, formatter: nil, config: nil, level: nil,
|
67
|
+
styles: {})
|
68
|
+
@output = Array[output].flatten
|
69
|
+
@formatter = coerce_formatter(formatter || config.formatter).new
|
70
|
+
@config = config
|
71
|
+
@styles = styles
|
72
|
+
@level = level || @config.level
|
73
|
+
@mutex = Mutex.new
|
74
|
+
@pastel = Pastel.new
|
75
|
+
end
|
76
|
+
|
77
|
+
# Handle log event output in format
|
78
|
+
#
|
79
|
+
# @param [Event] event
|
80
|
+
# the current event logged
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
def call(event)
|
84
|
+
@mutex.lock
|
85
|
+
|
86
|
+
style = configure_styles(event)
|
87
|
+
color = configure_color(style)
|
88
|
+
|
89
|
+
fmt = []
|
90
|
+
metadata.each do |meta|
|
91
|
+
case meta
|
92
|
+
when :date
|
93
|
+
fmt << @pastel.white("[" + event.metadata[:time].
|
94
|
+
strftime(config.date_format) + "]")
|
95
|
+
when :time
|
96
|
+
fmt << @pastel.white("[" + event.metadata[:time].
|
97
|
+
strftime(config.time_format) + "]")
|
98
|
+
when :file
|
99
|
+
fmt << @pastel.white("[#{format_filepath(event)}]")
|
100
|
+
when :pid
|
101
|
+
fmt << @pastel.white("[%d]" % event.metadata[:pid])
|
102
|
+
else
|
103
|
+
raise "Unknown metadata `#{meta}`"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
fmt << ARROW unless config.metadata.empty?
|
107
|
+
fmt << color.(style[:symbol])
|
108
|
+
fmt << color.(style[:label]) + (" " * style[:levelpad])
|
109
|
+
fmt << "%-25s" % event.message.join(" ")
|
110
|
+
unless event.fields.empty?
|
111
|
+
fmt << @formatter.dump(event.fields, max_bytes: config.max_bytes,
|
112
|
+
max_depth: config.max_depth).
|
113
|
+
gsub(/(\S+)(?=\=)/, color.("\\1")).
|
114
|
+
gsub(/\"([^,]+?)\"(?=:)/, "\"" + color.("\\1") + "\"")
|
115
|
+
end
|
116
|
+
unless event.backtrace.empty?
|
117
|
+
fmt << "\n" + format_backtrace(event)
|
118
|
+
end
|
119
|
+
|
120
|
+
output.each { |out| out.puts fmt.join(" ") }
|
121
|
+
ensure
|
122
|
+
@mutex.unlock
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def format_backtrace(event)
|
128
|
+
indent = " " * 4
|
129
|
+
event.backtrace.map do |bktrace|
|
130
|
+
indent + bktrace.to_s
|
131
|
+
end.join("\n")
|
132
|
+
end
|
133
|
+
|
134
|
+
# Merge default styles with custom style overrides
|
135
|
+
#
|
136
|
+
# @return [Hash[String]]
|
137
|
+
# the style matching log type
|
138
|
+
#
|
139
|
+
# @api private
|
140
|
+
def configure_styles(event)
|
141
|
+
style = STYLES[event.metadata[:name].to_sym].dup
|
142
|
+
(@styles[event.metadata[:name].to_sym] || {}).each do |k, v|
|
143
|
+
style[k] = v
|
144
|
+
end
|
145
|
+
style
|
146
|
+
end
|
147
|
+
|
148
|
+
def configure_color(style)
|
149
|
+
color = style.fetch(:color) { :cyan }
|
150
|
+
@pastel.send(color).detach
|
151
|
+
end
|
152
|
+
end # Console
|
153
|
+
end # Handlers
|
154
|
+
end # Logger
|
155
|
+
end # TTY
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
class Logger
|
7
|
+
module Handlers
|
8
|
+
class Stream
|
9
|
+
include Base
|
10
|
+
|
11
|
+
attr_reader :output
|
12
|
+
|
13
|
+
attr_reader :config
|
14
|
+
|
15
|
+
attr_reader :level
|
16
|
+
|
17
|
+
def initialize(output: $stderr, formatter: nil, config: nil, level: nil)
|
18
|
+
@output = Array[output].flatten
|
19
|
+
@formatter = coerce_formatter(formatter || config.formatter).new
|
20
|
+
@config = config
|
21
|
+
@level = level || @config.level
|
22
|
+
@mutex = Mutex.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api public
|
26
|
+
def call(event)
|
27
|
+
@mutex.lock
|
28
|
+
|
29
|
+
data = {}
|
30
|
+
metadata.each do |meta|
|
31
|
+
case meta
|
32
|
+
when :date
|
33
|
+
data["date"] = event.metadata[:time].strftime(config.date_format)
|
34
|
+
when :time
|
35
|
+
data["time"] = event.metadata[:time].strftime(config.time_format)
|
36
|
+
when :file
|
37
|
+
data["path"] = format_filepath(event)
|
38
|
+
when :pid
|
39
|
+
data["pid"] = event.metadata[:pid]
|
40
|
+
else
|
41
|
+
raise "Unknown metadata `#{meta}`"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
data["level"] = event.metadata[:level]
|
45
|
+
data["message"] = event.message.join(' ')
|
46
|
+
unless event.fields.empty?
|
47
|
+
data.merge!(event.fields)
|
48
|
+
end
|
49
|
+
unless event.backtrace.empty?
|
50
|
+
data.merge!("backtrace" => event.backtrace.join(","))
|
51
|
+
end
|
52
|
+
|
53
|
+
output.each do |out|
|
54
|
+
out.puts @formatter.dump(data, max_bytes: config.max_bytes,
|
55
|
+
max_depth: config.max_depth)
|
56
|
+
end
|
57
|
+
ensure
|
58
|
+
@mutex.unlock
|
59
|
+
end
|
60
|
+
end # Stream
|
61
|
+
end # Handlers
|
62
|
+
end # Logger
|
63
|
+
end # TTY
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Logger
|
5
|
+
module Levels
|
6
|
+
DEBUG_LEVEL = 0
|
7
|
+
INFO_LEVEL = 1
|
8
|
+
WARN_LEVEL = 2
|
9
|
+
ERROR_LEVEL = 3
|
10
|
+
FATAL_LEVEL = 4
|
11
|
+
|
12
|
+
LEVEL_NAMES = {
|
13
|
+
DEBUG_LEVEL => :debug,
|
14
|
+
INFO_LEVEL => :info,
|
15
|
+
WARN_LEVEL => :warn,
|
16
|
+
ERROR_LEVEL => :error,
|
17
|
+
FATAL_LEVEL => :fatal
|
18
|
+
}
|
19
|
+
|
20
|
+
def level_names
|
21
|
+
[:debug, :info, :warn, :error, :fatal]
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def level_to_number(level)
|
26
|
+
case level.to_s.downcase
|
27
|
+
when "debug" then DEBUG_LEVEL
|
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
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def number_to_level(level)
|
39
|
+
LEVEL_NAMES[level]
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def compare_levels(left, right)
|
44
|
+
left = left.is_a?(Integer) ? left : level_to_number(left)
|
45
|
+
right = right.is_a?(Integer) ? right : level_to_number(right)
|
46
|
+
|
47
|
+
return :eq if left == right
|
48
|
+
left < right ? :lt : :gt
|
49
|
+
end
|
50
|
+
end # Levels
|
51
|
+
end # Logger
|
52
|
+
end # TTY
|