chalk-log 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +8 -0
- data/History.txt +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +142 -0
- data/Rakefile +17 -0
- data/chalk-log.gemspec +26 -0
- data/config.yaml +22 -0
- data/lib/chalk-log.rb +137 -0
- data/lib/chalk-log/errors.rb +7 -0
- data/lib/chalk-log/layout.rb +192 -0
- data/lib/chalk-log/logger.rb +63 -0
- data/lib/chalk-log/utils.rb +84 -0
- data/lib/chalk-log/version.rb +5 -0
- data/tddium.yml +8 -0
- data/test/_lib.rb +16 -0
- data/test/_lib/fake.rb +5 -0
- data/test/_lib/fake/event.rb +9 -0
- data/test/functional/_lib.rb +10 -0
- data/test/functional/formatting.rb +188 -0
- data/test/functional/log.rb +296 -0
- data/test/unit/_lib.rb +10 -0
- data/test/unit/chalk-log/utils.rb +13 -0
- metadata +195 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# Thin wrapper over Logging::Logger. This is the per-class object
|
2
|
+
# instantiated by the `log` method.
|
3
|
+
class Chalk::Log::Logger
|
4
|
+
attr_reader :backend
|
5
|
+
|
6
|
+
# Initialization of the logger backend. It does the actual creation
|
7
|
+
# of the various logger methods. Will be called automatically upon
|
8
|
+
# your first `log` method call.
|
9
|
+
def self.init
|
10
|
+
Chalk::Log::LEVELS.each do |level|
|
11
|
+
define_method(level) do |*data, &blk|
|
12
|
+
return if logging_disabled?
|
13
|
+
@backend.send(level, data, &blk)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# The level this logger is set to.
|
19
|
+
def level
|
20
|
+
@backend.level
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the maximum log level.
|
24
|
+
#
|
25
|
+
# @param level [Fixnum|String|Symbol] A valid Logging::Logger level, e.g. :debug, 0, 'DEBUG', etc.
|
26
|
+
def level=(level)
|
27
|
+
@backend.level = level
|
28
|
+
end
|
29
|
+
|
30
|
+
# Create a new logger, and auto-initialize everything.
|
31
|
+
def initialize(name)
|
32
|
+
# It's generally a bad pattern to auto-init, but we want
|
33
|
+
# Chalk::Log to be usable anytime during the boot process, which
|
34
|
+
# requires being a little bit less explicit than we usually like.
|
35
|
+
Chalk::Log.init
|
36
|
+
@backend = ::Logging::Logger.new(name)
|
37
|
+
if level = configatron.chalk.log.default_level
|
38
|
+
@backend.level = level
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Check whether logging has been globally turned off, either through
|
43
|
+
# configatron or LSpace.
|
44
|
+
def logging_disabled?
|
45
|
+
configatron.chalk.log.disabled || LSpace[:'chalk.log.disabled']
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_contextual_info(contextual_info={}, &blk)
|
49
|
+
unless blk
|
50
|
+
raise ArgumentError.new("Must pass a block to #{__method__}")
|
51
|
+
end
|
52
|
+
unless contextual_info.is_a?(Hash)
|
53
|
+
raise TypeError.new(
|
54
|
+
"contextual_info must be a Hash, but got #{contextual_info.class}"
|
55
|
+
)
|
56
|
+
end
|
57
|
+
existing_context = LSpace[:'chalk.log.contextual_info'] || {}
|
58
|
+
LSpace.with(
|
59
|
+
:'chalk.log.contextual_info' => contextual_info.merge(existing_context),
|
60
|
+
&blk
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Chalk::Log::Utils
|
2
|
+
# Nicely formats a backtrace:
|
3
|
+
#
|
4
|
+
# ```ruby
|
5
|
+
# format_backtrace(['line1', 'line2'])
|
6
|
+
# #=> line1
|
7
|
+
# line2
|
8
|
+
# ```
|
9
|
+
#
|
10
|
+
# (Used internally when `Chalk::Log` is formatting exceptions.)
|
11
|
+
#
|
12
|
+
# TODO: add autotruncating of backtraces.
|
13
|
+
def self.format_backtrace(backtrace)
|
14
|
+
if configatron.chalk.log.compress_backtraces
|
15
|
+
backtrace = compress_backtrace(backtrace)
|
16
|
+
backtrace << '(Disable backtrace compression by setting configatron.chalk.log.compress_backtraces = false.)'
|
17
|
+
end
|
18
|
+
|
19
|
+
" " + backtrace.join("\n ")
|
20
|
+
end
|
21
|
+
|
22
|
+
# Explodes a nested hash to just have top-level keys. This is
|
23
|
+
# generally useful if you have something that knows how to parse
|
24
|
+
# kv-pairs.
|
25
|
+
#
|
26
|
+
# ```ruby
|
27
|
+
# explode_nested_hash(foo: {bar: 'baz', bat: 'zom'})
|
28
|
+
# #=> {'foo_bar' => 'baz', 'foo_bat' => 'zom'}
|
29
|
+
# ```
|
30
|
+
def self.explode_nested_hash(hash, prefix=nil)
|
31
|
+
exploded = {}
|
32
|
+
|
33
|
+
hash.each do |key, value|
|
34
|
+
new_prefix = prefix ? "#{prefix}_#{key}" : key.to_s
|
35
|
+
|
36
|
+
if value.is_a?(Hash)
|
37
|
+
exploded.merge!(self.explode_nested_hash(value, new_prefix))
|
38
|
+
else
|
39
|
+
exploded[new_prefix] = value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
exploded
|
44
|
+
end
|
45
|
+
|
46
|
+
# Compresses a backtrace, omitting gem lines (unless they appear
|
47
|
+
# before any application lines).
|
48
|
+
def self.compress_backtrace(backtrace)
|
49
|
+
compressed = []
|
50
|
+
gemdir = Gem.dir
|
51
|
+
|
52
|
+
hit_application = false
|
53
|
+
# This isn't currently read by anything, but we could easily use
|
54
|
+
# it to limit the number of leading gem lines.
|
55
|
+
leading_lines = 0
|
56
|
+
gemlines = 0
|
57
|
+
backtrace.each do |line|
|
58
|
+
if line.start_with?(gemdir)
|
59
|
+
# If we're in a gem, always increment the counter. Record the
|
60
|
+
# first three lines if we haven't seen any application lines
|
61
|
+
# yet.
|
62
|
+
if !hit_application
|
63
|
+
compressed << line
|
64
|
+
leading_lines += 1
|
65
|
+
else
|
66
|
+
gemlines += 1
|
67
|
+
end
|
68
|
+
elsif gemlines > 0
|
69
|
+
# If we were in a gem and now are not, record the number of
|
70
|
+
# lines skipped.
|
71
|
+
compressed << "<#{gemlines} #{gemlines == 1 ? 'line' : 'lines'} omitted>"
|
72
|
+
compressed << line
|
73
|
+
hit_application = true
|
74
|
+
gemlines = 0
|
75
|
+
else
|
76
|
+
# If we're in the application, always record the line.
|
77
|
+
compressed << line
|
78
|
+
hit_application = true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
compressed
|
83
|
+
end
|
84
|
+
end
|
data/tddium.yml
ADDED
data/test/_lib.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/spec'
|
6
|
+
require 'mocha/setup'
|
7
|
+
|
8
|
+
module Critic
|
9
|
+
require_relative '_lib/fake'
|
10
|
+
|
11
|
+
class Test < ::MiniTest::Spec
|
12
|
+
def setup
|
13
|
+
# Put any stubs here that you want to apply globally
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/test/_lib/fake.rb
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
require File.expand_path('../_lib', __FILE__)
|
2
|
+
|
3
|
+
require 'chalk-log'
|
4
|
+
|
5
|
+
module Critic::Functional
|
6
|
+
class LogTest < Test
|
7
|
+
def enable_timestamp
|
8
|
+
configatron.chalk.log.stubs(:timestamp).returns(true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def disable_timestamp
|
12
|
+
configatron.chalk.log.stubs(:timestamp).returns(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
def disable_pid
|
16
|
+
configatron.chalk.log.stubs(:pid).returns(false)
|
17
|
+
end
|
18
|
+
|
19
|
+
def disable_tagging
|
20
|
+
configatron.chalk.log.stubs(:tagging).returns(false)
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
Chalk::Log.init
|
25
|
+
Process.stubs(:pid).returns(9973)
|
26
|
+
disable_timestamp
|
27
|
+
end
|
28
|
+
|
29
|
+
class MyClass
|
30
|
+
include Chalk::Log
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'when called without arguments' do
|
34
|
+
it 'does not loop infinitely' do
|
35
|
+
MyClass.log.info
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'when called with a message' do
|
40
|
+
it 'does not mutate the input' do
|
41
|
+
canary = "'hello, world!'"
|
42
|
+
baseline = canary.dup
|
43
|
+
MyClass.log.info(canary)
|
44
|
+
assert_equal(baseline, canary)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'layout' do
|
49
|
+
before do
|
50
|
+
@layout = MyClass::Layout.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def layout(opts)
|
54
|
+
event = Critic::Fake::Event.new(opts)
|
55
|
+
formatted = @layout.format(event)
|
56
|
+
|
57
|
+
# Everything should end with a newline, but they're annoying
|
58
|
+
# to have to test elsewhere, so strip it away.
|
59
|
+
assert_equal("\n", formatted[-1], "Layout did not end with a newline: #{formatted.inspect}")
|
60
|
+
formatted.chomp
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'log entry from info' do
|
64
|
+
rendered = layout(data: ["A Message", {:key1 => "ValueOne", :key2 => ["An", "Array"]}])
|
65
|
+
assert_equal('[9973] A Message: key1=ValueOne key2=["An","Array"]', rendered)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'logs the action_id correctly' do
|
69
|
+
LSpace.with(action_id: 'action') do
|
70
|
+
rendered = layout(data: ["A Message", {:key1 => "ValueOne", :key2 => ["An", "Array"]}])
|
71
|
+
assert_equal('[9973|action] A Message: key1=ValueOne key2=["An","Array"]', rendered)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'logs timestamp correctly' do
|
76
|
+
enable_timestamp
|
77
|
+
LSpace.with(action_id: 'action') do
|
78
|
+
rendered = layout(data: ["A Message", {:key1 => "ValueOne", :key2 => ["An", "Array"]}])
|
79
|
+
assert_equal('[1979-04-09 00:00:00.000000] [9973|action] A Message: key1=ValueOne key2=["An","Array"]', rendered)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'logs without pid correctly' do
|
84
|
+
disable_pid
|
85
|
+
LSpace.with(action_id: 'action') do
|
86
|
+
rendered = layout(data: ["A Message", {:key1 => "ValueOne", :key2 => ["An", "Array"]}])
|
87
|
+
assert_equal('[action] A Message: key1=ValueOne key2=["An","Array"]', rendered)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'log from info hash without a message' do
|
92
|
+
rendered = layout(data: [{:key1 => "ValueOne", :key2 => ["An", "Array"]}])
|
93
|
+
assert_equal('[9973] key1=ValueOne key2=["An","Array"]', rendered)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'renders [no backtrace] as appropriate' do
|
97
|
+
rendered = layout(data: ["Another Message", StandardError.new('msg')])
|
98
|
+
assert_equal("[9973] Another Message: error_class=StandardError error=msg\n[9973] [no backtrace]", rendered)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'renders when given error and info hash' do
|
102
|
+
rendered = layout(data: ["Another Message", StandardError.new('msg'), {:key1 => "ValueOne", :key2 => ["An", "Array"]}])
|
103
|
+
assert_equal(%Q{[9973] Another Message: key1=ValueOne key2=["An","Array"] error_class=StandardError error=msg\n[9973] [no backtrace]}, rendered)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'renders an error with a backtrace' do
|
107
|
+
error = StandardError.new('msg')
|
108
|
+
backtrace = ["a fake", "backtrace"]
|
109
|
+
error.set_backtrace(backtrace)
|
110
|
+
|
111
|
+
rendered = layout(data: ["Yet Another Message", error])
|
112
|
+
assert_equal("[9973] Yet Another Message: error_class=StandardError error=msg\n[9973] a fake\n[9973] backtrace", rendered)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'renders an error passed alone' do
|
116
|
+
error = StandardError.new('msg')
|
117
|
+
backtrace = ["a fake", "backtrace"]
|
118
|
+
error.set_backtrace(backtrace)
|
119
|
+
|
120
|
+
rendered = layout(data: [error])
|
121
|
+
assert_equal("[9973] error_class=StandardError error=msg\n[9973] a fake\n[9973] backtrace", rendered)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'handles bad unicode' do
|
125
|
+
rendered = layout(data: [{:key1 => "ValueOne", :key2 => "\xC3"}])
|
126
|
+
assert_equal("[9973] key1=ValueOne key2=\"\\xC3\" [JSON-FAILED]", rendered)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'allows disabling tagging' do
|
130
|
+
enable_timestamp
|
131
|
+
disable_tagging
|
132
|
+
|
133
|
+
LSpace.with(action_id: 'action') do
|
134
|
+
rendered = layout(data: [{:key1 => "ValueOne", :key2 => "Value Two"}])
|
135
|
+
assert_equal(%Q{key1=ValueOne key2="Value Two"}, rendered)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'faults' do
|
140
|
+
it 'shows an appropriate error if the invalid arguments are provided' do
|
141
|
+
rendered = layout(data: ['foo', nil])
|
142
|
+
|
143
|
+
lines = rendered.split("\n")
|
144
|
+
assert_equal('[Chalk::Log fault: Could not format message] error_class="Chalk::Log::InvalidArguments" error="Invalid leftover arguments: [\"foo\", nil]"', lines[0])
|
145
|
+
assert(lines.length > 1)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'handles single faults' do
|
149
|
+
e = StandardError.new('msg')
|
150
|
+
@layout.expects(:do_format).raises(e)
|
151
|
+
rendered = layout(data: ['hi'])
|
152
|
+
|
153
|
+
lines = rendered.split("\n")
|
154
|
+
assert_equal('[Chalk::Log fault: Could not format message] error_class=StandardError error=msg', lines[0])
|
155
|
+
assert(lines.length > 1)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'handles double-faults' do
|
159
|
+
e = StandardError.new('msg')
|
160
|
+
def e.to_s; raise 'Time to double-fault'; end
|
161
|
+
|
162
|
+
@layout.expects(:do_format).raises(e)
|
163
|
+
rendered = layout(data: ['hi'])
|
164
|
+
|
165
|
+
lines = rendered.split("\n")
|
166
|
+
assert_match(/Chalk::Log fault: Double fault while formatting message/, lines[0])
|
167
|
+
assert_equal(1, lines.length, "Lines: #{lines.inspect}")
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'handles triple-faults' do
|
171
|
+
e = StandardError.new('msg')
|
172
|
+
def e.to_s
|
173
|
+
f = StandardError.new('double')
|
174
|
+
def f.to_s; raise 'Time to triple fault'; end
|
175
|
+
raise f
|
176
|
+
end
|
177
|
+
|
178
|
+
@layout.expects(:do_format).raises(e)
|
179
|
+
rendered = layout(data: ['hi'])
|
180
|
+
|
181
|
+
lines = rendered.split("\n")
|
182
|
+
assert_match(/Chalk::Log fault: Triple fault while formatting message/, lines[0])
|
183
|
+
assert_equal(1, lines.length, "Lines: #{lines.inspect}")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
require File.expand_path('../_lib', __FILE__)
|
2
|
+
|
3
|
+
require 'chalk-log'
|
4
|
+
|
5
|
+
module Critic::Functional
|
6
|
+
class LogTest < Test
|
7
|
+
def self.add_appender
|
8
|
+
unless @appender
|
9
|
+
@appender = ::Logging.appenders.string_io(
|
10
|
+
'test-stringio',
|
11
|
+
layout: Chalk::Log.layout
|
12
|
+
)
|
13
|
+
::Logging.logger.root.add_appenders(@appender)
|
14
|
+
end
|
15
|
+
@appender
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
Chalk::Log.init
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'when a class has included Log' do
|
23
|
+
it 'instances are loggable' do
|
24
|
+
class MyClass
|
25
|
+
include Chalk::Log
|
26
|
+
end
|
27
|
+
|
28
|
+
MyClass.new.log.info("Hi!")
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'the class is loggable' do
|
32
|
+
class YourClass
|
33
|
+
include Chalk::Log
|
34
|
+
end
|
35
|
+
|
36
|
+
YourClass.log.info("Hi!")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe 'including a loggable module into another' do
|
41
|
+
describe 'the inclusions are straightline' do
|
42
|
+
it 'make the includee loggable' do
|
43
|
+
module LogTestA
|
44
|
+
include Chalk::Log
|
45
|
+
end
|
46
|
+
|
47
|
+
module LogTestB
|
48
|
+
include LogTestA
|
49
|
+
end
|
50
|
+
|
51
|
+
assert(LogTestB < Chalk::Log)
|
52
|
+
assert(LogTestB.respond_to?(:log))
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'preserves any custom include logic prior to Log inclusion' do
|
56
|
+
module CustomLogTestA
|
57
|
+
def self.dict=(dict)
|
58
|
+
@dict = dict
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.dict
|
62
|
+
@dict
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.included(other)
|
66
|
+
dict['included'] = true
|
67
|
+
end
|
68
|
+
|
69
|
+
include Chalk::Log
|
70
|
+
end
|
71
|
+
|
72
|
+
dict = {}
|
73
|
+
CustomLogTestA.dict = dict
|
74
|
+
|
75
|
+
module CustomLogTestB
|
76
|
+
include CustomLogTestA
|
77
|
+
end
|
78
|
+
|
79
|
+
assert(CustomLogTestB < Chalk::Log)
|
80
|
+
assert(CustomLogTestB.respond_to?(:log))
|
81
|
+
assert_equal(true, dict['included'])
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: it'd be nice if this weren't true, but I'm not sure
|
85
|
+
# how to get a hook when a method is overriden.
|
86
|
+
it 'custom include logic after Log inclusion clobbers the default include logic' do
|
87
|
+
module CustomLogTestC
|
88
|
+
def self.dict=(dict)
|
89
|
+
@dict = dict
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.dict
|
93
|
+
@dict
|
94
|
+
end
|
95
|
+
|
96
|
+
include Chalk::Log
|
97
|
+
|
98
|
+
def self.included(other)
|
99
|
+
dict['included'] = true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
dict = {}
|
104
|
+
CustomLogTestC.dict = dict
|
105
|
+
|
106
|
+
module CustomLogTestD
|
107
|
+
include CustomLogTestC
|
108
|
+
end
|
109
|
+
|
110
|
+
assert(CustomLogTestD < Chalk::Log)
|
111
|
+
assert(!CustomLogTestD.respond_to?(:log))
|
112
|
+
assert_equal(true, dict['included'])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe 'extending a Log module into another' do
|
118
|
+
describe 'the inclusions are straightline' do
|
119
|
+
it 'make the extendee loggable' do
|
120
|
+
module ExtendLogTestA
|
121
|
+
include Chalk::Log
|
122
|
+
end
|
123
|
+
|
124
|
+
module ExtendLogTestB
|
125
|
+
extend ExtendLogTestA
|
126
|
+
end
|
127
|
+
|
128
|
+
assert(ExtendLogTestB < Chalk::Log)
|
129
|
+
assert(ExtendLogTestB.respond_to?(:log))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'when a class is loggable' do
|
135
|
+
class MyLog
|
136
|
+
include Chalk::Log
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'log.warn works' do
|
140
|
+
msg = 'msg'
|
141
|
+
# For some reason this isn't working:
|
142
|
+
MyLog.log.backend.expects(:warn).once
|
143
|
+
MyLog.log.warn(msg)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'log.info works' do
|
147
|
+
msg = 'msg'
|
148
|
+
MyLog.log.backend.expects(:info).once
|
149
|
+
MyLog.log.info(msg)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'accepts blocks' do
|
153
|
+
class LogTestE
|
154
|
+
include Chalk::Log
|
155
|
+
end
|
156
|
+
LogTestE.log.level = "INFO"
|
157
|
+
|
158
|
+
LogTestE.log.debug { assert(false, "DEBUG block called when at INFO level") }
|
159
|
+
called = false
|
160
|
+
LogTestE.log.info { called = true; "" }
|
161
|
+
assert(called, "INFO block not called at INFO level")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class TestLogInstanceMethods < Test
|
166
|
+
include Chalk::Log
|
167
|
+
|
168
|
+
before do
|
169
|
+
TestLogInstanceMethods.log.level = 'INFO'
|
170
|
+
Chalk::Log.init
|
171
|
+
@appender = LogTest.add_appender
|
172
|
+
end
|
173
|
+
|
174
|
+
def assert_logged(expected_string, *args)
|
175
|
+
assert_includes(@appender.sio.string, expected_string, *args)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'accepts blocks on instance methods' do
|
179
|
+
called = false
|
180
|
+
log.debug { assert(false, "DEBUG block called at INFO") }
|
181
|
+
log.info { called = true; "" }
|
182
|
+
assert(called, "INFO block not called at INFO level")
|
183
|
+
end
|
184
|
+
|
185
|
+
describe 'when contextual info is set' do
|
186
|
+
|
187
|
+
it 'adds contextual information to `.info` log lines' do
|
188
|
+
log.with_contextual_info(key: 'value') {log.info("message")}
|
189
|
+
assert_logged("key=value")
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'nests contexts' do
|
193
|
+
log.with_contextual_info(top_key: "top_value") do
|
194
|
+
log.info("top message")
|
195
|
+
log.with_contextual_info(inner_key: "inner_value") do
|
196
|
+
log.info("inner message")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
%w{top_key=top_value inner_key=inner_value}.each do |pair|
|
200
|
+
assert_logged(pair)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'requires a block' do
|
205
|
+
exn = assert_raises(ArgumentError) do
|
206
|
+
log.with_contextual_info(i_am_not: "passing a block")
|
207
|
+
end
|
208
|
+
assert_includes(exn.message, "Must pass a block")
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'requires its argument must be a hash' do
|
212
|
+
exn = assert_raises(TypeError) do
|
213
|
+
log.with_contextual_info('not a hash') {}
|
214
|
+
end
|
215
|
+
assert_includes(exn.message, 'must be a Hash')
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe 'when chaining includes and extends' do
|
221
|
+
it 'correctly make the end class loggable' do
|
222
|
+
module Base1
|
223
|
+
include Chalk::Log
|
224
|
+
end
|
225
|
+
|
226
|
+
class Child1
|
227
|
+
extend Base1
|
228
|
+
end
|
229
|
+
|
230
|
+
Child1.log.info("Hello!")
|
231
|
+
assert(true)
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'correctly make the end class loggable when chaining an include and extend' do
|
235
|
+
module Base2
|
236
|
+
include Chalk::Log
|
237
|
+
end
|
238
|
+
|
239
|
+
module Middle2
|
240
|
+
extend Base2
|
241
|
+
end
|
242
|
+
|
243
|
+
class Child2
|
244
|
+
include Middle2
|
245
|
+
end
|
246
|
+
|
247
|
+
Child2.log.info("Hello!")
|
248
|
+
assert(true)
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'correctly make the end class loggable when chaining an extend and an extend' do
|
252
|
+
module Base3
|
253
|
+
include Chalk::Log
|
254
|
+
end
|
255
|
+
|
256
|
+
module Middle3
|
257
|
+
extend Base3
|
258
|
+
end
|
259
|
+
|
260
|
+
class Child3
|
261
|
+
extend Middle3
|
262
|
+
end
|
263
|
+
|
264
|
+
Child3.log.info("Hello!")
|
265
|
+
assert(true)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'correctly make the end class loggable when it has already included loggable' do
|
269
|
+
module Base4
|
270
|
+
include Chalk::Log
|
271
|
+
end
|
272
|
+
|
273
|
+
module Middle4
|
274
|
+
extend Base4
|
275
|
+
end
|
276
|
+
|
277
|
+
class Child4
|
278
|
+
include Chalk::Log
|
279
|
+
extend Middle4
|
280
|
+
end
|
281
|
+
|
282
|
+
Child4.log.info("Hello!")
|
283
|
+
assert(true)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'correctly makes a module loggable' do
|
288
|
+
module Base5
|
289
|
+
include Chalk::Log
|
290
|
+
end
|
291
|
+
|
292
|
+
Base5.log.info("Hello!")
|
293
|
+
assert(true)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|