chalk-log 0.1.2
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.
- 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
|