logging 2.0.0 → 2.3.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 +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +8 -5
- data/History.txt +59 -0
- data/LICENSE +22 -0
- data/README.md +20 -41
- data/Rakefile +2 -2
- data/examples/appenders.rb +1 -1
- data/examples/layouts.rb +1 -1
- data/examples/lazy.rb +1 -1
- data/examples/mdc.rb +2 -2
- data/examples/rails4.rb +21 -0
- data/examples/reusing_layouts.rb +51 -0
- data/lib/logging.rb +99 -9
- data/lib/logging/appender.rb +13 -34
- data/lib/logging/appenders/buffering.rb +130 -59
- data/lib/logging/appenders/console.rb +68 -57
- data/lib/logging/appenders/file.rb +43 -22
- data/lib/logging/appenders/io.rb +22 -16
- data/lib/logging/appenders/rolling_file.rb +60 -26
- data/lib/logging/appenders/string_io.rb +1 -1
- data/lib/logging/appenders/syslog.rb +3 -4
- data/lib/logging/color_scheme.rb +1 -1
- data/lib/logging/diagnostic_context.rb +100 -73
- data/lib/logging/layout.rb +144 -16
- data/lib/logging/layouts/parseable.rb +50 -12
- data/lib/logging/layouts/pattern.rb +8 -9
- data/lib/logging/log_event.rb +19 -12
- data/lib/logging/logger.rb +117 -95
- data/lib/logging/proxy.rb +1 -1
- data/lib/logging/rails_compat.rb +4 -13
- data/lib/logging/version.rb +1 -1
- data/logging.gemspec +31 -32
- data/script/console +8 -0
- data/test/appenders/{test_periodic_flushing.rb → test_async_flushing.rb} +67 -14
- data/test/appenders/test_buffered_io.rb +19 -18
- data/test/appenders/test_console.rb +55 -12
- data/test/appenders/test_file.rb +48 -28
- data/test/appenders/test_rolling_file.rb +18 -12
- data/test/appenders/test_syslog.rb +6 -0
- data/test/benchmark.rb +42 -18
- data/test/layouts/test_json.rb +14 -1
- data/test/layouts/test_nested_exceptions.rb +124 -0
- data/test/layouts/test_pattern.rb +16 -3
- data/test/layouts/test_yaml.rb +15 -1
- data/test/performance.rb +66 -0
- data/test/setup.rb +26 -30
- data/test/test_appender.rb +2 -4
- data/test/test_layout.rb +49 -0
- data/test/test_log_event.rb +10 -2
- data/test/test_logger.rb +20 -3
- data/test/test_logging.rb +75 -4
- data/test/test_mapped_diagnostic_context.rb +15 -6
- data/test/test_nested_diagnostic_context.rb +6 -1
- metadata +23 -17
@@ -13,9 +13,15 @@ module TestAppenders
|
|
13
13
|
super
|
14
14
|
Logging.init
|
15
15
|
|
16
|
-
@fn = File.expand_path('test.log',
|
17
|
-
@fn_fmt = File.expand_path('test.%d.log',
|
18
|
-
@glob = File.expand_path('*.log',
|
16
|
+
@fn = File.expand_path('test.log', @tmpdir)
|
17
|
+
@fn_fmt = File.expand_path('test.%d.log', @tmpdir)
|
18
|
+
@glob = File.expand_path('*.log', @tmpdir)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_factory_method_validates_input
|
22
|
+
assert_raise(ArgumentError) do
|
23
|
+
Logging.appenders.rolling_file
|
24
|
+
end
|
19
25
|
end
|
20
26
|
|
21
27
|
def test_initialize
|
@@ -87,8 +93,8 @@ module TestAppenders
|
|
87
93
|
end
|
88
94
|
|
89
95
|
def test_age
|
90
|
-
d_glob = File.join(
|
91
|
-
dt_glob = File.join(
|
96
|
+
d_glob = File.join(@tmpdir, 'test.*.log')
|
97
|
+
dt_glob = File.join(@tmpdir, 'test.*-*.log')
|
92
98
|
age_fn = @fn + '.age'
|
93
99
|
|
94
100
|
assert_equal [], Dir.glob(@glob)
|
@@ -199,7 +205,7 @@ module TestAppenders
|
|
199
205
|
|
200
206
|
begin
|
201
207
|
pwd = Dir.pwd
|
202
|
-
Dir.chdir
|
208
|
+
Dir.chdir @tmpdir
|
203
209
|
|
204
210
|
ap << 'X' * 100; ap.flush
|
205
211
|
assert_equal 1, Dir.glob(@glob).length
|
@@ -243,9 +249,9 @@ module TestAppenders
|
|
243
249
|
end
|
244
250
|
|
245
251
|
def test_custom_numberd_filename
|
246
|
-
fn = File.expand_path('test.log{{.%d}}',
|
247
|
-
filename = File.expand_path('test.log',
|
248
|
-
glob = File.expand_path('test.log.*',
|
252
|
+
fn = File.expand_path('test.log{{.%d}}', @tmpdir)
|
253
|
+
filename = File.expand_path('test.log', @tmpdir)
|
254
|
+
glob = File.expand_path('test.log.*', @tmpdir)
|
249
255
|
|
250
256
|
assert_equal [], Dir.glob(glob)
|
251
257
|
ap = Logging.appenders.rolling_file(NAME, :filename => fn, :size => 100, :keep => 2)
|
@@ -279,10 +285,10 @@ module TestAppenders
|
|
279
285
|
end
|
280
286
|
|
281
287
|
def test_custom_timestamp_filename
|
282
|
-
fn = File.expand_path('test{{.%S:%M}}.log',
|
283
|
-
filename = File.expand_path('test.log',
|
288
|
+
fn = File.expand_path('test{{.%S:%M}}.log', @tmpdir)
|
289
|
+
filename = File.expand_path('test.log', @tmpdir)
|
284
290
|
age_file = filename + '.age'
|
285
|
-
glob = File.expand_path('test.*.log',
|
291
|
+
glob = File.expand_path('test.*.log', @tmpdir)
|
286
292
|
|
287
293
|
assert_equal [], Dir.glob(glob)
|
288
294
|
ap = Logging.appenders.rolling_file(NAME, :filename => fn, :age => 1, :keep => 2)
|
@@ -19,6 +19,12 @@ module TestAppenders
|
|
19
19
|
@logopt |= ::Syslog::LOG_PERROR if defined?(::Syslog::LOG_PERROR)
|
20
20
|
end
|
21
21
|
|
22
|
+
def test_factory_method_validates_input
|
23
|
+
assert_raise(ArgumentError) do
|
24
|
+
Logging.appenders.syslog
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
22
28
|
def test_append
|
23
29
|
return if RUBY_PLATFORM =~ %r/cygwin|java/i
|
24
30
|
|
data/test/benchmark.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
require 'rubygems'
|
3
2
|
|
4
3
|
libpath = File.expand_path('../../lib', __FILE__)
|
@@ -22,16 +21,14 @@ module Logging
|
|
22
21
|
def run
|
23
22
|
this_many = 300_000
|
24
23
|
|
25
|
-
Logging.
|
26
|
-
'
|
27
|
-
:
|
28
|
-
|
29
|
-
|
30
|
-
)
|
31
|
-
)
|
24
|
+
pattern = Logging.layouts.pattern \
|
25
|
+
:pattern => '%.1l, [%d #%p] %5l -- %c: %m\n',
|
26
|
+
:date_pattern => "%Y-%m-%dT%H:%M:%S.%s"
|
27
|
+
|
28
|
+
Logging.appenders.string_io('sio', :layout => pattern)
|
32
29
|
sio = Logging.appenders['sio'].sio
|
33
30
|
|
34
|
-
logging = ::Logging.logger
|
31
|
+
logging = ::Logging.logger['benchmark']
|
35
32
|
logging.level = :warn
|
36
33
|
logging.appenders = 'sio'
|
37
34
|
|
@@ -39,16 +36,16 @@ module Logging
|
|
39
36
|
logger.level = ::Logger::WARN
|
40
37
|
|
41
38
|
log4r = if $log4r
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
l4r = ::Log4r::Logger.new('benchmark')
|
40
|
+
l4r.level = ::Log4r::WARN
|
41
|
+
l4r.add ::Log4r::IOOutputter.new(
|
45
42
|
'benchmark', sio,
|
46
43
|
:formatter => ::Log4r::PatternFormatter.new(
|
47
44
|
:pattern => "%.1l, [%d #\#{Process.pid}] %5l : %M\n",
|
48
|
-
:date_pattern => "%Y-%m-%dT%H:%M:%S
|
45
|
+
:date_pattern => "%Y-%m-%dT%H:%M:%S.%6N"
|
49
46
|
)
|
50
47
|
)
|
51
|
-
|
48
|
+
l4r
|
52
49
|
end
|
53
50
|
|
54
51
|
puts "== Debug (not logged) ==\n"
|
@@ -76,14 +73,41 @@ module Logging
|
|
76
73
|
bm.report('Logger:') {this_many.times {logger << 'logged'}}
|
77
74
|
puts "Log4r: not supported" if log4r
|
78
75
|
end
|
79
|
-
end
|
80
76
|
|
81
|
-
|
82
|
-
|
77
|
+
write_size = 250
|
78
|
+
auto_flushing_size = 500
|
79
|
+
|
80
|
+
logging_async = ::Logging.logger['AsyncFile']
|
81
|
+
logging_async.level = :info
|
82
|
+
logging_async.appenders = Logging.appenders.file \
|
83
|
+
'benchmark_async.log',
|
84
|
+
:layout => pattern,
|
85
|
+
:write_size => write_size,
|
86
|
+
:auto_flushing => auto_flushing_size,
|
87
|
+
:async => true
|
88
|
+
|
89
|
+
logging_sync = ::Logging.logger['SyncFile']
|
90
|
+
logging_sync.level = :info
|
91
|
+
logging_sync.appenders = Logging.appenders.file \
|
92
|
+
'benchmark_sync.log',
|
93
|
+
:layout => pattern,
|
94
|
+
:write_size => write_size,
|
95
|
+
:auto_flushing => auto_flushing_size,
|
96
|
+
:async => false
|
97
|
+
|
98
|
+
puts "\n== File ==\n"
|
99
|
+
::Benchmark.bm(20) do |bm|
|
100
|
+
bm.report('Logging (Async):') {this_many.times { |n| logging_async.info "Iteration #{n}"}}
|
101
|
+
bm.report('Logging (Sync):') {this_many.times { |n| logging_sync.info "Iteration #{n}"}}
|
102
|
+
end
|
83
103
|
|
104
|
+
File.delete('benchmark_async.log')
|
105
|
+
File.delete('benchmark_sync.log')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
84
109
|
|
85
110
|
if __FILE__ == $0
|
86
111
|
bm = ::Logging::Benchmark.new
|
87
112
|
bm.run
|
88
113
|
end
|
89
|
-
|
data/test/layouts/test_json.rb
CHANGED
@@ -78,7 +78,7 @@ module TestLayouts
|
|
78
78
|
'log message', false)
|
79
79
|
event.file = 'test_file.rb'
|
80
80
|
event.line = 123
|
81
|
-
event.
|
81
|
+
event.method_name = 'method_name'
|
82
82
|
|
83
83
|
@layout.items = %w[logger]
|
84
84
|
assert_equal %Q[{"logger":"TestLogger"}\n], @layout.format(event)
|
@@ -166,6 +166,19 @@ module TestLayouts
|
|
166
166
|
assert_match %r/"ndc":\[\]/, format
|
167
167
|
end
|
168
168
|
|
169
|
+
def test_utc_offset
|
170
|
+
layout = Logging.layouts.json(:items => %w[timestamp])
|
171
|
+
event = Logging::LogEvent.new('TimestampLogger', @levels['info'], 'log message', false)
|
172
|
+
event.time = Time.utc(2016, 12, 1, 12, 0, 0).freeze
|
173
|
+
|
174
|
+
assert_equal %Q/{"timestamp":"2016-12-01T12:00:00.000000Z"}\n/, layout.format(event)
|
175
|
+
|
176
|
+
layout.utc_offset = "-06:00"
|
177
|
+
assert_equal %Q/{"timestamp":"2016-12-01T06:00:00.000000-06:00"}\n/, layout.format(event)
|
178
|
+
|
179
|
+
layout.utc_offset = "+01:00"
|
180
|
+
assert_equal %Q/{"timestamp":"2016-12-01T13:00:00.000000+01:00"}\n/, layout.format(event)
|
181
|
+
end
|
169
182
|
end # class TestJson
|
170
183
|
end # module TestLayouts
|
171
184
|
end # module TestLogging
|
@@ -0,0 +1,124 @@
|
|
1
|
+
|
2
|
+
require_relative '../setup'
|
3
|
+
|
4
|
+
module TestLogging
|
5
|
+
module TestLayouts
|
6
|
+
class TestNestedExceptions < Test::Unit::TestCase
|
7
|
+
include LoggingTestCase
|
8
|
+
|
9
|
+
def test_basic_format_obj
|
10
|
+
err = nil
|
11
|
+
begin
|
12
|
+
begin
|
13
|
+
raise ArgumentError, 'nested exception'
|
14
|
+
rescue
|
15
|
+
raise StandardError, 'root exception'
|
16
|
+
end
|
17
|
+
rescue => e
|
18
|
+
err = e
|
19
|
+
end
|
20
|
+
|
21
|
+
layout = Logging.layouts.basic({})
|
22
|
+
log = layout.format_obj(err)
|
23
|
+
assert_not_nil log.index('<StandardError> root exception')
|
24
|
+
|
25
|
+
if err.respond_to?(:cause)
|
26
|
+
assert_not_nil log.index('<ArgumentError> nested exception')
|
27
|
+
assert(log.index('<StandardError> root exception') < log.index('<ArgumentError> nested exception'))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_cause_depth_limiting
|
32
|
+
err = nil
|
33
|
+
begin
|
34
|
+
begin
|
35
|
+
begin
|
36
|
+
raise TypeError, 'nested exception 2'
|
37
|
+
rescue
|
38
|
+
raise ArgumentError, 'nested exception 1'
|
39
|
+
end
|
40
|
+
rescue
|
41
|
+
raise StandardError, 'root exception'
|
42
|
+
end
|
43
|
+
rescue => e
|
44
|
+
err = e
|
45
|
+
end
|
46
|
+
|
47
|
+
layout = Logging.layouts.basic(cause_depth: 1)
|
48
|
+
log = layout.format_obj(err)
|
49
|
+
assert_not_nil log.index('<StandardError> root exception')
|
50
|
+
|
51
|
+
if err.respond_to?(:cause)
|
52
|
+
assert_not_nil log.index('<ArgumentError> nested exception 1')
|
53
|
+
assert_nil log.index('<TypeError> nested exception 2')
|
54
|
+
assert_equal '--- Further #cause backtraces were omitted ---', log.split("\n\t").last
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_parseable_format_obj
|
59
|
+
err = nil
|
60
|
+
begin
|
61
|
+
begin
|
62
|
+
raise ArgumentError, 'nested exception'
|
63
|
+
rescue
|
64
|
+
raise StandardError, 'root exception'
|
65
|
+
end
|
66
|
+
rescue => e
|
67
|
+
err = e
|
68
|
+
end
|
69
|
+
|
70
|
+
layout = Logging.layouts.parseable.new
|
71
|
+
log = layout.format_obj(err)
|
72
|
+
assert_equal 'StandardError', log[:class]
|
73
|
+
assert_equal 'root exception', log[:message]
|
74
|
+
assert log[:backtrace].size > 0
|
75
|
+
|
76
|
+
if err.respond_to?(:cause)
|
77
|
+
assert_not_nil log[:cause]
|
78
|
+
|
79
|
+
log = log[:cause]
|
80
|
+
assert_equal 'ArgumentError', log[:class]
|
81
|
+
assert_equal 'nested exception', log[:message]
|
82
|
+
assert_nil log[:cause]
|
83
|
+
assert log[:backtrace].size > 0
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_parseable_cause_depth_limiting
|
88
|
+
err = nil
|
89
|
+
begin
|
90
|
+
begin
|
91
|
+
begin
|
92
|
+
raise TypeError, 'nested exception 2'
|
93
|
+
rescue
|
94
|
+
raise ArgumentError, 'nested exception 1'
|
95
|
+
end
|
96
|
+
rescue
|
97
|
+
raise StandardError, 'root exception'
|
98
|
+
end
|
99
|
+
rescue => e
|
100
|
+
err = e
|
101
|
+
end
|
102
|
+
|
103
|
+
layout = Logging.layouts.parseable.new(cause_depth: 1)
|
104
|
+
log = layout.format_obj(err)
|
105
|
+
|
106
|
+
assert_equal 'StandardError', log[:class]
|
107
|
+
assert_equal 'root exception', log[:message]
|
108
|
+
assert log[:backtrace].size > 0
|
109
|
+
|
110
|
+
if err.respond_to?(:cause)
|
111
|
+
assert_not_nil log[:cause]
|
112
|
+
|
113
|
+
log = log[:cause]
|
114
|
+
assert_equal 'ArgumentError', log[:class]
|
115
|
+
assert_equal 'nested exception 1', log[:message]
|
116
|
+
assert_equal({message: "Further #cause backtraces were omitted"}, log[:cause])
|
117
|
+
assert log[:backtrace].size > 0
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
require 'pp'
|
@@ -26,7 +26,7 @@ module TestLayouts
|
|
26
26
|
|
27
27
|
@layout.date_method = 'usec'
|
28
28
|
assert_equal 'usec', @layout.date_method
|
29
|
-
|
29
|
+
assert_kind_of Integer, @layout.format_date(Time.now)
|
30
30
|
|
31
31
|
@layout.date_method = :to_s
|
32
32
|
assert_equal :to_s, @layout.date_method
|
@@ -38,7 +38,7 @@ module TestLayouts
|
|
38
38
|
|
39
39
|
@layout.date_method = 'usec'
|
40
40
|
assert_equal 'usec', @layout.date_method
|
41
|
-
|
41
|
+
assert_kind_of Integer, @layout.format_date(Time.now)
|
42
42
|
end
|
43
43
|
|
44
44
|
def test_date_pattern
|
@@ -105,7 +105,7 @@ module TestLayouts
|
|
105
105
|
'log message', false)
|
106
106
|
event.file = 'test_file.rb'
|
107
107
|
event.line = '123'
|
108
|
-
event.
|
108
|
+
event.method_name = 'method_name'
|
109
109
|
|
110
110
|
@layout.pattern = '%c'
|
111
111
|
assert_equal 'TestLogger', @layout.format(event)
|
@@ -230,6 +230,19 @@ module TestLayouts
|
|
230
230
|
assert_equal 'context a', @layout.format(event)
|
231
231
|
end
|
232
232
|
|
233
|
+
def test_utc_offset
|
234
|
+
layout = Logging.layouts.pattern(:pattern => "%d", :utc_offset => "UTC")
|
235
|
+
event = Logging::LogEvent.new('DateLogger', @levels['info'], 'log message', false)
|
236
|
+
event.time = Time.utc(2016, 12, 1, 12, 0, 0).freeze
|
237
|
+
|
238
|
+
assert_equal "2016-12-01T12:00:00", layout.format(event)
|
239
|
+
|
240
|
+
layout.utc_offset = "-06:00"
|
241
|
+
assert_equal "2016-12-01T06:00:00", layout.format(event)
|
242
|
+
|
243
|
+
layout.utc_offset = "+01:00"
|
244
|
+
assert_equal "2016-12-01T13:00:00", layout.format(event)
|
245
|
+
end
|
233
246
|
end # TestBasic
|
234
247
|
end # TestLayouts
|
235
248
|
end # TestLogging
|
data/test/layouts/test_yaml.rb
CHANGED
@@ -68,7 +68,7 @@ module TestLayouts
|
|
68
68
|
'log message', false)
|
69
69
|
event.file = 'test_file.rb'
|
70
70
|
event.line = 123
|
71
|
-
event.
|
71
|
+
event.method_name = 'method_name'
|
72
72
|
|
73
73
|
@layout.items = %w[logger]
|
74
74
|
assert_match %r/\A--- ?\nlogger: TestLogger\n/, @layout.format(event)
|
@@ -148,6 +148,20 @@ module TestLayouts
|
|
148
148
|
assert_match %r/\nndc: \[\]\n/, format
|
149
149
|
end
|
150
150
|
|
151
|
+
def test_utc_offset
|
152
|
+
layout = Logging.layouts.yaml(:items => %w[timestamp])
|
153
|
+
event = Logging::LogEvent.new('TimestampLogger', @levels['info'], 'log message', false)
|
154
|
+
event.time = Time.utc(2016, 12, 1, 12, 0, 0).freeze
|
155
|
+
|
156
|
+
assert_equal %Q{---\ntimestamp: '2016-12-01T12:00:00.000000Z'\n}, layout.format(event)
|
157
|
+
|
158
|
+
layout.utc_offset = "-06:00"
|
159
|
+
assert_equal %Q{---\ntimestamp: '2016-12-01T06:00:00.000000-06:00'\n}, layout.format(event)
|
160
|
+
|
161
|
+
layout.utc_offset = "+01:00"
|
162
|
+
assert_equal %Q{---\ntimestamp: '2016-12-01T13:00:00.000000+01:00'\n}, layout.format(event)
|
163
|
+
end
|
164
|
+
|
151
165
|
private
|
152
166
|
|
153
167
|
def assert_yaml_match( expected, actual )
|
data/test/performance.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#
|
2
|
+
# The peformance script is used to output a performance analysis page for the
|
3
|
+
# Logging framework. You can run this script simply:
|
4
|
+
#
|
5
|
+
# ruby test/performance.rb
|
6
|
+
#
|
7
|
+
# This will write a file called "performance.html" that you can open in your web
|
8
|
+
# browser. You will need the `ruby-prof` gem installed in order to run this
|
9
|
+
# script.
|
10
|
+
# ------------------------------------------------------------------------------
|
11
|
+
require 'rubygems'
|
12
|
+
|
13
|
+
libpath = File.expand_path('../../lib', __FILE__)
|
14
|
+
$:.unshift libpath
|
15
|
+
require 'logging'
|
16
|
+
|
17
|
+
begin
|
18
|
+
gem 'log4r'
|
19
|
+
require 'log4r'
|
20
|
+
$log4r = true
|
21
|
+
rescue LoadError
|
22
|
+
$log4r = false
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'logger'
|
26
|
+
require 'ruby-prof'
|
27
|
+
|
28
|
+
module Logging
|
29
|
+
class Performance
|
30
|
+
|
31
|
+
# number of iterations
|
32
|
+
attr_reader :this_many
|
33
|
+
|
34
|
+
# performance output file name
|
35
|
+
attr_reader :output_file
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@this_many = 300_000
|
39
|
+
@output_file = "performance.html"
|
40
|
+
end
|
41
|
+
|
42
|
+
def run
|
43
|
+
pattern = Logging.layouts.pattern \
|
44
|
+
:pattern => '%.1l, [%d#%p] %5l -- %c: %m\n',
|
45
|
+
:date_pattern => "%Y-%m-%dT%H:%M:%S.%s"
|
46
|
+
|
47
|
+
Logging.appenders.string_io("sio", :layout => pattern)
|
48
|
+
|
49
|
+
logger = ::Logging.logger["Performance"]
|
50
|
+
logger.level = :warn
|
51
|
+
logger.appenders = "sio"
|
52
|
+
|
53
|
+
result = RubyProf.profile do
|
54
|
+
this_many.times {logger.warn 'logged'}
|
55
|
+
end
|
56
|
+
|
57
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
58
|
+
File.open(output_file, "w") { |fd| printer.print(fd) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if __FILE__ == $0
|
64
|
+
perf = Logging::Performance.new
|
65
|
+
perf.run
|
66
|
+
end
|