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
data/lib/tty/logger.rb
CHANGED
@@ -1,10 +1,216 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "logger/config"
|
4
|
+
require_relative "logger/event"
|
5
|
+
require_relative "logger/formatters/json"
|
6
|
+
require_relative "logger/formatters/text"
|
7
|
+
require_relative "logger/handlers/console"
|
8
|
+
require_relative "logger/handlers/null"
|
9
|
+
require_relative "logger/handlers/stream"
|
10
|
+
require_relative "logger/levels"
|
3
11
|
require_relative "logger/version"
|
4
12
|
|
5
13
|
module TTY
|
6
14
|
class Logger
|
15
|
+
include Levels
|
16
|
+
|
17
|
+
# Error raised by this logger
|
7
18
|
class Error < StandardError; end
|
8
19
|
|
20
|
+
# Logger configuration instance
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
def self.config
|
24
|
+
@config ||= Config.new
|
25
|
+
end
|
26
|
+
|
27
|
+
# Global logger configuration
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def self.configure
|
31
|
+
yield config
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create a logger instance
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# logger = TTY::Logger.new(output: $stdout)
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def initialize(output: nil, fields: {})
|
41
|
+
@fields = fields
|
42
|
+
@config = if block_given?
|
43
|
+
conf = Config.new
|
44
|
+
yield(conf)
|
45
|
+
conf
|
46
|
+
else
|
47
|
+
self.class.config
|
48
|
+
end
|
49
|
+
@level = @config.level
|
50
|
+
@handlers = @config.handlers
|
51
|
+
@output = output || @config.output
|
52
|
+
@ready_handlers = []
|
53
|
+
|
54
|
+
@handlers.each do |handler|
|
55
|
+
add_handler(handler)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Add handler for logging messages
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# add_handler(:console)
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def add_handler(handler)
|
66
|
+
h, options = *(handler.is_a?(Array) ? handler : [handler, {}])
|
67
|
+
name = coerce_handler(h)
|
68
|
+
global_opts = { output: @output, config: @config }
|
69
|
+
opts = global_opts.merge(options)
|
70
|
+
ready_handler = name.new(opts)
|
71
|
+
@ready_handlers << ready_handler
|
72
|
+
end
|
73
|
+
|
74
|
+
# Remove log events handler
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# remove_handler(:console)
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def remove_handler(handler)
|
81
|
+
@ready_handlers.delete(handler)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Coerce handler name into object
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# coerce_handler(:console)
|
88
|
+
# # => TTY::Logger::Handlers::Console
|
89
|
+
#
|
90
|
+
# @raise [Error] when class cannot be coerced
|
91
|
+
#
|
92
|
+
# @return [Class]
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def coerce_handler(name)
|
96
|
+
case name
|
97
|
+
when String, Symbol
|
98
|
+
Handlers.const_get(name.capitalize)
|
99
|
+
when Class
|
100
|
+
name
|
101
|
+
else
|
102
|
+
raise_handler_error
|
103
|
+
end
|
104
|
+
rescue NameError
|
105
|
+
raise_handler_error
|
106
|
+
end
|
107
|
+
|
108
|
+
# Raise error when unknown handler name
|
109
|
+
#
|
110
|
+
# @api private
|
111
|
+
def raise_handler_error
|
112
|
+
raise Error, "Handler needs to be a class name or a symbol name"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Add structured data
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# logger = TTY::Logger.new
|
119
|
+
# logger.with(app: "myenv", env: "prod").debug("Deplying")
|
120
|
+
#
|
121
|
+
# @return [TTY::Logger]
|
122
|
+
# a new copy of this logger
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
def with(new_fields)
|
126
|
+
self.class.new(fields: @fields.merge(new_fields), output: @output)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check current level against another
|
130
|
+
#
|
131
|
+
# @return [Symbol]
|
132
|
+
#
|
133
|
+
# @api public
|
134
|
+
def log?(level, other_level)
|
135
|
+
compare_levels(level, other_level) != :gt
|
136
|
+
end
|
137
|
+
|
138
|
+
# Log a message given the severtiy level
|
139
|
+
#
|
140
|
+
# @api public
|
141
|
+
def log(current_level, *msg, **scoped_fields)
|
142
|
+
if msg.empty? && block_given?
|
143
|
+
msg = [yield]
|
144
|
+
end
|
145
|
+
loc = caller_locations(2,1)[0]
|
146
|
+
metadata = {
|
147
|
+
level: current_level,
|
148
|
+
time: Time.now,
|
149
|
+
pid: Process.pid,
|
150
|
+
name: caller_locations(1,1)[0].label,
|
151
|
+
path: loc.path,
|
152
|
+
lineno: loc.lineno,
|
153
|
+
method: loc.base_label
|
154
|
+
}
|
155
|
+
event = Event.new(msg, @fields.merge(scoped_fields), metadata)
|
156
|
+
@ready_handlers.each do |handler|
|
157
|
+
level = handler.respond_to?(:level) ? handler.level : @config.level
|
158
|
+
handler.(event) if log?(level, current_level)
|
159
|
+
end
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
# Log a message at :debug level
|
164
|
+
#
|
165
|
+
# @api public
|
166
|
+
def debug(*msg, &block)
|
167
|
+
log(:debug, *msg, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Log a message at :info level
|
171
|
+
#
|
172
|
+
# @examples
|
173
|
+
# logger.info "Successfully deployed"
|
174
|
+
# logger.info { "Dynamically generated info" }
|
175
|
+
#
|
176
|
+
# @api public
|
177
|
+
def info(*msg, &block)
|
178
|
+
log(:info, *msg, &block)
|
179
|
+
end
|
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)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Log a message at :fatal level
|
196
|
+
#
|
197
|
+
# @api public
|
198
|
+
def fatal(*msg, &block)
|
199
|
+
log(:fatal, *msg, &block)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Log a message with a success label
|
203
|
+
#
|
204
|
+
# @api public
|
205
|
+
def success(*msg, &block)
|
206
|
+
log(:info, *msg, &block)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Log a message with a wait label
|
210
|
+
#
|
211
|
+
# @api public
|
212
|
+
def wait(*msg, &block)
|
213
|
+
log(:info, *msg, &block)
|
214
|
+
end
|
9
215
|
end # Logger
|
10
216
|
end # TTY
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if ENV['COVERAGE'] || ENV['TRAVIS']
|
4
|
+
require 'simplecov'
|
5
|
+
require 'coveralls'
|
6
|
+
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
8
|
+
SimpleCov::Formatter::HTMLFormatter,
|
9
|
+
Coveralls::SimpleCov::Formatter
|
10
|
+
])
|
11
|
+
|
12
|
+
SimpleCov.start do
|
13
|
+
command_name 'spec'
|
14
|
+
add_filter 'spec'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
1
18
|
require "bundler/setup"
|
2
19
|
require "tty-logger"
|
3
20
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe TTY::Logger, "#add_handler" do
|
4
|
+
let(:output) { StringIO.new }
|
5
|
+
let(:styles) { TTY::Logger::Handlers::Console::STYLES }
|
6
|
+
|
7
|
+
it "dynamically adds and removes a handler object" do
|
8
|
+
logger = TTY::Logger.new(output: output) do |config|
|
9
|
+
config.handlers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
logger.info("No handler")
|
13
|
+
|
14
|
+
logger.add_handler :console
|
15
|
+
|
16
|
+
logger.info("Console handler")
|
17
|
+
|
18
|
+
logger.remove_handler :console
|
19
|
+
|
20
|
+
expect(output.string).to eq([
|
21
|
+
"\e[32m#{styles[:info][:symbol]}\e[0m ",
|
22
|
+
"\e[32minfo\e[0m ",
|
23
|
+
"Console handler \n"].join)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe TTY::Logger::Config do
|
4
|
+
let(:output) { StringIO.new }
|
5
|
+
let(:styles) { TTY::Logger::Handlers::Console::STYLES }
|
6
|
+
|
7
|
+
it "defaults :max_bytes to 8192" do
|
8
|
+
config = described_class.new
|
9
|
+
expect(config.max_bytes).to eq(8192)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "defaults :max_depth to 3" do
|
13
|
+
config = described_class.new
|
14
|
+
expect(config.max_depth).to eq(3)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "defaults :level to :info" do
|
18
|
+
config = described_class.new
|
19
|
+
expect(config.level).to eq(:info)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "sets :max_bytes" do
|
23
|
+
config = described_class.new
|
24
|
+
config.max_bytes = 2**8
|
25
|
+
expect(config.max_bytes).to eq(256)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "defaults metadata to empty array" do
|
29
|
+
config = described_class.new
|
30
|
+
expect(config.metadata).to eq([])
|
31
|
+
end
|
32
|
+
|
33
|
+
it "defaults handlers to console" do
|
34
|
+
config = described_class.new
|
35
|
+
expect(config.handlers).to eq([:console])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "defaults formatter to text" do
|
39
|
+
config = described_class.new
|
40
|
+
expect(config.formatter).to eq(:text)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "defaults date format to %F" do
|
44
|
+
config = described_class.new
|
45
|
+
expect(config.date_format).to eq("%F")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "defaults output to stderr" do
|
49
|
+
config = described_class.new
|
50
|
+
expect(config.output).to eq($stderr)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "serializes data into hash" do
|
54
|
+
config = described_class.new
|
55
|
+
expect(config.to_h).to eq({
|
56
|
+
date_format: "%F",
|
57
|
+
formatter: :text,
|
58
|
+
handlers: [:console],
|
59
|
+
level: :info,
|
60
|
+
max_bytes: 8192,
|
61
|
+
max_depth: 3,
|
62
|
+
metadata: [],
|
63
|
+
output: $stderr,
|
64
|
+
time_format: "%T.%3N"
|
65
|
+
})
|
66
|
+
end
|
67
|
+
|
68
|
+
it "yields configuration instance" do
|
69
|
+
config = double(:config)
|
70
|
+
allow(TTY::Logger).to receive(:config).and_return(config)
|
71
|
+
expect { |block|
|
72
|
+
TTY::Logger.configure(&block)
|
73
|
+
}.to yield_with_args(config)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "configures output size" do
|
77
|
+
logger = TTY::Logger.new(output: output) do |config|
|
78
|
+
config.max_bytes = 2**4
|
79
|
+
config.level = :debug
|
80
|
+
end
|
81
|
+
|
82
|
+
logger.debug("Deploying", app: "myapp", env: "prod")
|
83
|
+
|
84
|
+
expect(output.string).to eq([
|
85
|
+
"\e[36m#{styles[:debug][:symbol]}\e[0m ",
|
86
|
+
"\e[36mdebug\e[0m ",
|
87
|
+
"Deploying ",
|
88
|
+
"\e[36mapp\e[0m=myapp ...\n"
|
89
|
+
].join)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "configures maximum depth of structured data" do
|
93
|
+
logger = TTY::Logger.new(output: output) do |config|
|
94
|
+
config.max_depth = 1
|
95
|
+
config.level = :debug
|
96
|
+
end
|
97
|
+
|
98
|
+
logger.debug("Deploying", app: "myapp", env: { name: "prod" })
|
99
|
+
|
100
|
+
expect(output.string).to eq([
|
101
|
+
"\e[36m#{styles[:debug][:symbol]}\e[0m ",
|
102
|
+
"\e[36mdebug\e[0m ",
|
103
|
+
"Deploying ",
|
104
|
+
"\e[36mapp\e[0m=myapp \e[36menv\e[0m={...}\n"
|
105
|
+
].join)
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe TTY::Logger::Event, "event" do
|
4
|
+
it "defaults backtrace to an empty array" do
|
5
|
+
event = described_class.new(["message"], {}, {})
|
6
|
+
expect(event.backtrace).to eq([])
|
7
|
+
end
|
8
|
+
|
9
|
+
it "extracts backtrace if message contains exception" do
|
10
|
+
event = nil
|
11
|
+
error = nil
|
12
|
+
|
13
|
+
begin
|
14
|
+
raise ArgumentError, "Wrong data"
|
15
|
+
rescue => ex
|
16
|
+
error = ex
|
17
|
+
event = described_class.new(["Error", ex], {}, {})
|
18
|
+
end
|
19
|
+
|
20
|
+
expect(event.backtrace.join).to eq(error.backtrace.join)
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe TTY::Logger, "exception logging" do
|
4
|
+
let(:output) { StringIO.new }
|
5
|
+
let(:styles) { TTY::Logger::Handlers::Console::STYLES }
|
6
|
+
|
7
|
+
it "handles exception type message when console" do
|
8
|
+
logger = TTY::Logger.new(output: output)
|
9
|
+
error = nil
|
10
|
+
|
11
|
+
begin
|
12
|
+
raise ArgumentError, "Wrong data"
|
13
|
+
rescue => ex
|
14
|
+
error = ex
|
15
|
+
logger.fatal("Error:", error)
|
16
|
+
end
|
17
|
+
|
18
|
+
expect(output.string).to eq([
|
19
|
+
"\e[31m#{styles[:fatal][:symbol]}\e[0m ",
|
20
|
+
"\e[31mfatal\e[0m ",
|
21
|
+
"Error: Wrong data \n",
|
22
|
+
"#{error.backtrace.map {|bktrace| bktrace.to_s.insert(0, " " * 4) }.join("\n")}\n"
|
23
|
+
].join)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "handles exception type message when stream" do
|
27
|
+
logger = TTY::Logger.new(output: output) do |config|
|
28
|
+
config.handlers = [:stream]
|
29
|
+
end
|
30
|
+
|
31
|
+
error = nil
|
32
|
+
|
33
|
+
begin
|
34
|
+
raise ArgumentError, "Wrong data"
|
35
|
+
rescue => ex
|
36
|
+
error = ex
|
37
|
+
logger.fatal("Error:", error)
|
38
|
+
end
|
39
|
+
|
40
|
+
expect(output.string).to eq([
|
41
|
+
"level=fatal message=\"Error: Wrong data\" backtrace=\"",
|
42
|
+
"#{error.backtrace.map {|bktrace| bktrace }.join(",")}\"\n"
|
43
|
+
].join)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe TTY::Logger, "formatter" do
|
4
|
+
let(:output) { StringIO.new }
|
5
|
+
let(:styles) { TTY::Logger::Handlers::Console::STYLES }
|
6
|
+
|
7
|
+
it "changes default formatter to JSON as class name" do
|
8
|
+
logger = TTY::Logger.new(output: output) do |config|
|
9
|
+
config.formatter = TTY::Logger::Formatters::JSON
|
10
|
+
end
|
11
|
+
|
12
|
+
logger.info("Logging", app: "myapp", env: "prod")
|
13
|
+
|
14
|
+
expect(output.string).to eq([
|
15
|
+
"\e[32m#{styles[:info][:symbol]}\e[0m ",
|
16
|
+
"\e[32minfo\e[0m ",
|
17
|
+
"Logging ",
|
18
|
+
"{\"\e[32mapp\e[0m\":\"myapp\",\"\e[32menv\e[0m\":\"prod\"}\n"].join)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "changes default formatter to JSON as name" do
|
22
|
+
logger = TTY::Logger.new(output: output) do |config|
|
23
|
+
config.formatter = :json
|
24
|
+
end
|
25
|
+
|
26
|
+
logger.info("Logging", app: "myapp", env: "prod")
|
27
|
+
|
28
|
+
expect(output.string).to eq([
|
29
|
+
"\e[32m#{styles[:info][:symbol]}\e[0m ",
|
30
|
+
"\e[32minfo\e[0m ",
|
31
|
+
"Logging ",
|
32
|
+
"{\"\e[32mapp\e[0m\":\"myapp\",\"\e[32menv\e[0m\":\"prod\"}\n"].join)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "changes default formatter for only one handler" do
|
36
|
+
logger = TTY::Logger.new(output: output) do |config|
|
37
|
+
config.handlers = [:console,
|
38
|
+
[:console, {formatter: :JSON}]]
|
39
|
+
end
|
40
|
+
|
41
|
+
logger.info("Logging", app: "myapp", env: "prod")
|
42
|
+
|
43
|
+
expect(output.string).to eq([
|
44
|
+
"\e[32m#{styles[:info][:symbol]}\e[0m ",
|
45
|
+
"\e[32minfo\e[0m ",
|
46
|
+
"Logging ",
|
47
|
+
"\e[32mapp\e[0m=myapp \e[32menv\e[0m=prod\n",
|
48
|
+
"\e[32m#{styles[:info][:symbol]}\e[0m ",
|
49
|
+
"\e[32minfo\e[0m ",
|
50
|
+
"Logging ",
|
51
|
+
"{\"\e[32mapp\e[0m\":\"myapp\",\"\e[32menv\e[0m\":\"prod\"}\n"].join)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "fails to recognize formatter object type" do
|
55
|
+
expect {
|
56
|
+
TTY::Logger.new(output: output) do |config|
|
57
|
+
config.formatter = true
|
58
|
+
end
|
59
|
+
}.to raise_error(TTY::Logger::Error, "Unrecognized formatter name 'true'")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "fails to recognize formatter name" do
|
63
|
+
|
64
|
+
expect {
|
65
|
+
TTY::Logger.new(output: output) do |config|
|
66
|
+
config.formatter = :unknown
|
67
|
+
end
|
68
|
+
}.to raise_error(TTY::Logger::Error, "Unrecognized formatter name ':unknown'")
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe TTY::Logger::Formatters::JSON, "#dump" do
|
4
|
+
it "dumps a log line" do
|
5
|
+
formatter = described_class.new
|
6
|
+
data = {
|
7
|
+
app: "myapp",
|
8
|
+
env: "prod",
|
9
|
+
sql: "SELECT * FROM admins",
|
10
|
+
at: Time.at(123456).utc
|
11
|
+
}
|
12
|
+
|
13
|
+
expect(formatter.dump(data)).to eq("{\"app\":\"myapp\",\"env\":\"prod\",\"sql\":\"SELECT * FROM admins\",\"at\":\"1970-01-02 10:17:36 UTC\"}")
|
14
|
+
end
|
15
|
+
|
16
|
+
[
|
17
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 3*12+2, want: "{\"a\":\"aaaaa\",\"b\":\"bbbbb\",\"c\":\"ccccc\"}"},
|
18
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 2*12+4, want: "{\"a\":\"aaaaa\",\"b\":\"bbbbb\",\"c\":\"...\"}"},
|
19
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 12+4, want: "{\"a\":\"aaaaa\",\"b\":\"...\"}"},
|
20
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 11, want: "{\"a\":\"...\"}"},
|
21
|
+
].each do |data|
|
22
|
+
it "truncates #{data[:obj].inspect} to #{data[:want].inspect} of #{data[:bytes]} bytes" do
|
23
|
+
formatter = described_class.new
|
24
|
+
expect(formatter.dump(data[:obj], max_bytes: data[:bytes])).to eq(data[:want])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
[
|
29
|
+
{obj: {a: {b: {c: "ccccc"}}}, depth: 1, want: "{\"a\":\"...\"}"},
|
30
|
+
{obj: {a: {b: {c: "ccccc"}}}, depth: 2, want: "{\"a\":{\"b\":\"...\"}}"},
|
31
|
+
{obj: {a: {b: {c: "ccccc"}}}, depth: 3, want: "{\"a\":{\"b\":{\"c\":\"ccccc\"}}}"},
|
32
|
+
{obj: {a: ["b", {c: "ccccc"}]}, depth: 1, want: "{\"a\":\"...\"}"},
|
33
|
+
{obj: {a: ["b", {c: "ccccc"}]}, depth: 2, want: "{\"a\":[\"b\",\"...\"]}"},
|
34
|
+
{obj: {a: ["b", {c: "ccccc"}]}, depth: 3, want: "{\"a\":[\"b\",{\"c\":\"ccccc\"}]}"},
|
35
|
+
].each do |data|
|
36
|
+
it "truncates nested object #{data[:obj].inspect} to #{data[:want].inspect}" do
|
37
|
+
formatter = described_class.new
|
38
|
+
expect(formatter.dump(data[:obj], max_depth: data[:depth])).to eq(data[:want])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe TTY::Logger::Formatters::Text, "#dump" do
|
4
|
+
|
5
|
+
[
|
6
|
+
{key: "k", value: "v", want: "k=v"},
|
7
|
+
{key: "k", value: '\n', want: "k=\\n"},
|
8
|
+
{key: "k", value: '\r', want: "k=\\r"},
|
9
|
+
{key: "k", value: '\t', want: "k=\\t"},
|
10
|
+
{key: "k", value: nil, want: "k=nil"},
|
11
|
+
{key: "k", value: "nil", want: "k=\"nil\""},
|
12
|
+
{key: "k", value: "", want: "k="},
|
13
|
+
{key: "k", value: true, want: "k=true"},
|
14
|
+
{key: "k", value: "true", want: "k=\"true\""},
|
15
|
+
{key: "k", value: "false", want: "k=\"false\""},
|
16
|
+
{key: "k", value: 1, want: "k=1"},
|
17
|
+
{key: "k", value: 1.035, want: "k=1.035"},
|
18
|
+
{key: "k", value: 1e-5, want: "k=0.00001"},
|
19
|
+
{key: "k", value: Complex(2,1), want: "k=(2+1i)"},
|
20
|
+
{key: "k", value: "1", want: "k=\"1\""},
|
21
|
+
{key: "k", value: "1.035", want: "k=\"1.035\""},
|
22
|
+
{key: "k", value: "1e-5", want: "k=\"1e-5\""},
|
23
|
+
{key: "k", value: "v v", want: "k=\"v v\""},
|
24
|
+
{key: "k", value: " ", want: 'k=" "'},
|
25
|
+
{key: "k", value: '"', want: 'k="\""'},
|
26
|
+
{key: "k", value: '=', want: 'k="="'},
|
27
|
+
{key: "k", value: "\\", want: "k=\\"},
|
28
|
+
{key: "k", value: "=\\", want: "k=\"=\\\\\""},
|
29
|
+
{key: "k", value: "\\\"", want: "k=\"\\\\\\\"\""},
|
30
|
+
{key: "", value: "", want: "="},
|
31
|
+
{key: '"', value: "v", want: '"\""=v'},
|
32
|
+
{key: "k", value: Time.new(2019, 7, 7, 12, 21, 35, "+02:00"), want: "k=2019-07-07T12:21:35+02:00"},
|
33
|
+
{key: "k", value: {a: 1}, want: "k={a=1}"},
|
34
|
+
{key: "k", value: {a: 1, b: 2}, want: "k={a=1 b=2}"},
|
35
|
+
{key: "k", value: {a: {b: 2}}, want: "k={a={b=2}}"},
|
36
|
+
{key: "k", value: ["a", 1], want: "k=[a 1]"},
|
37
|
+
{key: "k", value: ["a", ["b", 2], 1], want: "k=[a [b 2] 1]"},
|
38
|
+
].each do |data|
|
39
|
+
it "dumps {#{data[:key].inspect} => #{data[:value].inspect}} as #{data[:want].inspect}" do
|
40
|
+
formatter = described_class.new
|
41
|
+
expect(formatter.dump({data[:key] => data[:value]})).to eq(data[:want])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
[
|
46
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 24, want: "a=aaaaa b=bbbbb c=ccccc"},
|
47
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 20, want: "a=aaaaa b=bbbbb ..."},
|
48
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 15, want: "a=aaaaa ..."},
|
49
|
+
{obj: {a: "aaaaa", b: "bbbbb", c: "ccccc"}, bytes: 7, want: "..."},
|
50
|
+
].each do |data|
|
51
|
+
it "truncates #{data[:obj].inspect} to #{data[:want].inspect} of #{data[:bytes]} bytes" do
|
52
|
+
formatter = described_class.new
|
53
|
+
expect(formatter.dump(data[:obj], max_bytes: data[:bytes])).to eq(data[:want])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
[
|
58
|
+
{obj: {a: {b: {c: "ccccc"}}}, depth: 1, want: "a={...}"},
|
59
|
+
{obj: {a: {b: {c: "ccccc"}}}, depth: 2, want: "a={b={...}}"},
|
60
|
+
{obj: {a: {b: {c: "ccccc"}}}, depth: 3, want: "a={b={c=ccccc}}"},
|
61
|
+
{obj: {a: ["b", {c: "ccccc"}]}, depth: 1, want: "a=[...]"},
|
62
|
+
{obj: {a: ["b", {c: "ccccc"}]}, depth: 2, want: "a=[b {...}]"},
|
63
|
+
{obj: {a: ["b", {c: "ccccc"}]}, depth: 3, want: "a=[b {c=ccccc}]"},
|
64
|
+
].each do |data|
|
65
|
+
it "truncates nested object #{data[:obj].inspect} to #{data[:want].inspect}" do
|
66
|
+
formatter = described_class.new
|
67
|
+
expect(formatter.dump(data[:obj], max_depth: data[:depth])).to eq(data[:want])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "dumps a log line" do
|
72
|
+
formatter = described_class.new
|
73
|
+
data = {
|
74
|
+
app: "myapp",
|
75
|
+
env: "prod",
|
76
|
+
sql: "SELECT * FROM admins",
|
77
|
+
at: Time.at(123456).utc
|
78
|
+
}
|
79
|
+
|
80
|
+
expect(formatter.dump(data)).to eq("app=myapp env=prod sql=\"SELECT * FROM admins\" at=1970-01-02T10:17:36+00:00")
|
81
|
+
end
|
82
|
+
end
|