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