threadedlogger 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,5 +1,10 @@
1
1
  # History for ThreadedLogger
2
2
 
3
+ ## v1.2.0
4
+
5
+ * Allow multiple loggers to be instantiated as subclasses
6
+ * add ::clear and ::clear_all class methods
7
+
3
8
  ## v1.1.0
4
9
 
5
10
  * Dynamically generate logging methods
data/Manifest.txt CHANGED
@@ -4,8 +4,13 @@ README.txt
4
4
  LICENSE.txt
5
5
  Rakefile
6
6
  lib/threadedlogger.rb
7
+ lib/threadedlogger/core.rb
8
+ lib/threadedlogger/version.rb
9
+ lib/threadedlogger/logger.rb
7
10
  test/minitest_helper.rb
8
11
  test/test_logging.rb
9
12
  test/test_methods.rb
10
13
  test/test_threadedlogger.rb
14
+ test/test_shutdown.rb
15
+ test/test_clear.rb
11
16
  threadedlogger.gemspec
data/README.txt CHANGED
@@ -13,24 +13,46 @@ to ensure that multiple threads don't step on each other's toes.
13
13
 
14
14
  ## SYNOPSIS:
15
15
 
16
- ```ruby
17
- require 'threadedlogger'
18
-
19
- log = ThreadedLogger.instance(logfname, 'daily', 'debug')
20
- log.info('START')
21
- ...
22
- log.debug('super important stuff')
23
- ...
24
- log.info("STOP")
25
- log.shutdown
26
-
27
- log2 = ThreadedLogger.instance(logfname, 'daily', 'debug', proc { |l| "prefix: #{l}" })
28
- ```
29
-
30
- ThreadedLogger has one and only one instance, accessed via the ::instance
16
+ require 'threadedlogger'
17
+
18
+ log = ThreadedLogger.instance(logfname, 'daily', 'debug')
19
+ log.info('START')
20
+ ...
21
+ log.debug('super important stuff')
22
+ ...
23
+ log.info("STOP")
24
+ log.shutdown
25
+
26
+ or if you have multiple loggers
27
+
28
+ require 'threadedlogger'
29
+
30
+ class Log1 < ThreadedLogger
31
+ end
32
+
33
+ class Log2 < ThreadedLogger
34
+ end
35
+
36
+ log1 = Log1.instance(logfname1, 'daily', 'debug')
37
+ log2 = Log2.instance(logfname2, 'daily', 'debug')
38
+ log1.info('START')
39
+ ...
40
+ log1.debug('super important stuff')
41
+ log2.info('something that only goes to the second log')
42
+ ...
43
+ log1.info("STOP")
44
+ log2.shutdown
45
+ log1.shutdown
46
+
47
+ ThreadedLogger can be subclassed if you need to have multiple logs in a
48
+ program. Each subclass has one logger instance, accessed via the ::instance
31
49
  class method. You can only provide arguments the first time - trying to
32
50
  're-construct' the object will throw an ArgumentError.
33
51
 
52
+ If you only need one logger, you can just use ThreadedLogger without
53
+ subclassing it. Instances are stored keyed on class name, and the base
54
+ class is a valid key.
55
+
34
56
  Under the covers, the dedicated thread uses the standard Ruby Logger
35
57
  library. Refer to [Logger](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html)
36
58
  for further detail.
@@ -46,9 +68,21 @@ instance method before your program exits. On a clean shutdown this should
46
68
  happen automatically, but if you exit in a funky way it might not capture
47
69
  the last message.
48
70
 
71
+ The catalog of instances can be cleared using the ::clear and ::clear_all
72
+ class methods. Each takes an optional boolean argument indicating whether
73
+ ::shutdown should be called on any active loggers before clearing them.
74
+
49
75
  This library also overrides Logger.LogDevice.add_log_header to prevent it
50
76
  from putting a header line at the top of a logfile when it is first opened.
51
77
 
78
+ ## CONSTRUCTION THREAD SAFETY
79
+
80
+ ThreadedLogger does not mutex construction, as the typical use case is for
81
+ the logger to be initialized for the first time outside of threaded code.
82
+ If you call the constructor for the first time from threaded code, you will
83
+ need to protect ::instance with some kind of synchronization to avoid a race
84
+ condition.
85
+
52
86
  ## LICENSE:
53
87
 
54
88
  The MIT License (MIT)
data/Rakefile CHANGED
@@ -14,5 +14,5 @@ Rake::TestTask.new("unit_tests") { |t|
14
14
  t.libs.push 'test'
15
15
  t.pattern = 'test/test_*.rb'
16
16
  t.verbose = true
17
- t.warning = true
17
+ t.warning = false
18
18
  }
@@ -1,133 +1,3 @@
1
- require 'thread'
2
- require 'logger'
3
-
4
- class ThreadedLogger
5
-
6
- VERSION = '1.1.0'
7
-
8
- @@instance = nil
9
-
10
- LOGLEVELS = {
11
- 'debug' => Logger::DEBUG,
12
- 'info' => Logger::INFO,
13
- 'warn' => Logger::WARN,
14
- 'error' => Logger::ERROR,
15
- 'fatal' => Logger::FATAL,
16
- }
17
-
18
- private_class_method :new
19
-
20
- # create the logging methods
21
- LOGLEVELS.each do |k,v|
22
- log_method = k.to_sym
23
- test_method = "#{k}?".to_sym
24
- define_method(log_method) { |msg=nil|
25
- enqueue(v, msg)
26
- }
27
- define_method(test_method) {
28
- @log.send(test_method)
29
- }
30
- end
31
-
32
- def ThreadedLogger.instance(*args)
33
-
34
- if @@instance.nil?
35
- @@instance = new(*args)
36
- else
37
- if ! args.empty?
38
- raise ArgumentError, "instance already constructed"
39
- end
40
- end
41
-
42
- return @@instance
43
-
44
- end
45
-
46
- def initialize(file, rotation = 'daily', level = 'info', formatter = nil)
47
-
48
- if file.nil?
49
- raise ArgumentError, "log file name is required"
50
- end
51
-
52
- # create a logger
53
- @log = Logger.new(file, rotation)
54
-
55
- # set the min threshold
56
- send(:level=, level)
57
-
58
- # apply a formatter if one was given
59
- if ! formatter.nil?
60
- @log.formatter = formatter
61
- end
62
-
63
- # set up a queue and spawn a thread to do the logging
64
- @queue = Queue.new
65
- @shutdown = nil
66
- @t = Thread.new { runlogger }
67
-
68
- end
69
-
70
- def level=(level)
71
- if LOGLEVELS.has_key?(level)
72
- @log.level = LOGLEVELS[level]
73
- else
74
- raise ArgumentError, "invalid log level #{level}"
75
- end
76
- end
77
-
78
- def shutdown
79
-
80
- # stops new messages from being enqueued and tells thread
81
- # to drain what's in the queue
82
- @shutdown = true
83
- @t.join
84
-
85
- end
86
-
87
- def enqueue(severity, msg=nil)
88
-
89
- # don't enqueue if we're in shutdown
90
- if( @shutdown )
91
- return
92
- end
93
-
94
- # put the message on the queue
95
- @queue.push( [severity, msg] )
96
-
97
- end
98
-
99
- private
100
-
101
- def runlogger
102
-
103
- # do blocking pops off the queue until the shutdown flag is set
104
- while @shutdown.nil?
105
- msg = @queue.pop(false)
106
- @log.add(msg[0], msg[1])
107
- end
108
-
109
- # pop anything left on the queue off
110
- while ! @queue.empty?
111
- msg = @queue.pop(true)
112
- @log.add(msg[0], msg[1])
113
- end
114
-
115
- end
116
-
117
- end
118
-
119
- # supress the "Logfile created at" header line that logger.rb provides
120
- class Logger
121
-
122
- private
123
-
124
- class LogDevice
125
-
126
- private
127
-
128
- def add_log_header(file)
129
- end
130
-
131
- end
132
-
133
- end
1
+ require 'threadedlogger/version'
2
+ require 'threadedlogger/core'
3
+ require 'threadedlogger/logger'
@@ -0,0 +1,138 @@
1
+ require 'thread'
2
+ require 'logger'
3
+
4
+ class ThreadedLogger
5
+
6
+ LOGLEVELS = {
7
+ 'debug' => Logger::DEBUG,
8
+ 'info' => Logger::INFO,
9
+ 'warn' => Logger::WARN,
10
+ 'error' => Logger::ERROR,
11
+ 'fatal' => Logger::FATAL,
12
+ }
13
+
14
+ @@instances = nil
15
+
16
+ private_class_method :new
17
+
18
+ # create the logging methods
19
+ LOGLEVELS.each do |k,v|
20
+ log_method = k.to_sym
21
+ test_method = "#{k}?".to_sym
22
+ define_method(log_method) { |msg=nil|
23
+ enqueue(v, msg)
24
+ }
25
+ define_method(test_method) {
26
+ @log.send(test_method)
27
+ }
28
+ end
29
+
30
+ def self.instance(*args)
31
+ if @@instances[self].nil?
32
+ @@instances[self] = new(*args)
33
+ else
34
+ if ! args.empty?
35
+ raise ArgumentError, "instance for #{self} already constructed"
36
+ end
37
+ end
38
+ return @@instances[self]
39
+ end
40
+
41
+ def self.clear(shutdown = false)
42
+ if shutdown and ! @@instances[self].nil?
43
+ @@instances[self].shutdown
44
+ end
45
+ @@instances[self] = nil
46
+ end
47
+
48
+ def self.clear_all(shutdown = false)
49
+ if shutdown and ! @@instances.nil?
50
+ @@instances.each_value do |obj|
51
+ if ! obj.nil?
52
+ obj.shutdown
53
+ end
54
+ end
55
+ end
56
+ @@instances = Hash.new
57
+ end
58
+
59
+ def initialize(file, rotation = 'daily', level = 'info', formatter = nil)
60
+ if file.nil?
61
+ raise ArgumentError, "log file name is required"
62
+ end
63
+
64
+ # create a logger
65
+ @log = Logger.new(file, rotation)
66
+
67
+ # set the min threshold
68
+ send(:level=, level)
69
+
70
+ # apply a formatter if one was given
71
+ if ! formatter.nil?
72
+ @log.formatter = formatter
73
+ end
74
+
75
+ # set up a queue and spawn a thread to do the logging
76
+ @queue = Queue.new
77
+ @shutdown = false
78
+ @t = Thread.new {
79
+ runlogger
80
+ }
81
+ end
82
+
83
+ def level=(level)
84
+ if LOGLEVELS.has_key?(level)
85
+ @log.level = LOGLEVELS[level]
86
+ else
87
+ raise ArgumentError, "invalid log level #{level}"
88
+ end
89
+ end
90
+
91
+ def shutdown
92
+
93
+ # stops new messages from being enqueued and tells thread
94
+ # to drain what's in the queue
95
+ if ! @shutdown
96
+ @shutdown = true
97
+ @queue.push(nil)
98
+ @t.join
99
+ @t = nil
100
+ end
101
+
102
+ end
103
+
104
+ def enqueue(severity, msg=nil)
105
+ # don't enqueue if we're in shutdown
106
+ if( @shutdown )
107
+ return
108
+ end
109
+
110
+ # put the message on the queue
111
+ @queue.push( [severity, msg] )
112
+ end
113
+
114
+ private
115
+
116
+ def runlogger
117
+ # do blocking pops off the queue until the shutdown flag is set
118
+ while ! @shutdown
119
+ msg = @queue.pop(false)
120
+ if ! msg.nil?
121
+ @log.add(msg[0], msg[1])
122
+ end
123
+ end
124
+
125
+ # pop anything left on the queue off
126
+ while ! @queue.empty?
127
+ msg = @queue.pop(true)
128
+ if ! msg.nil?
129
+ @log.add(msg[0], msg[1])
130
+ end
131
+ end
132
+ end
133
+
134
+ # create catalog of per-subclass instances
135
+ self.clear_all
136
+
137
+ end
138
+
@@ -0,0 +1,15 @@
1
+ # Monkey patch Logger to supress the "Logfile created at" header line
2
+ class Logger
3
+
4
+ private
5
+
6
+ class LogDevice
7
+
8
+ private
9
+
10
+ def add_log_header(file)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,5 @@
1
+ class ThreadedLogger
2
+
3
+ VERSION = '1.2.0'
4
+
5
+ end
@@ -1,5 +1,21 @@
1
1
  require 'simplecov'
2
+ require 'simplecov-console'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ SimpleCov::Formatter::Console,
7
+ ]
2
8
  SimpleCov.start do
3
9
  add_filter '/vendor/'
4
10
  add_filter '/test/'
5
11
  end
12
+
13
+ require 'minitest/autorun'
14
+ require 'minitest/debugger' if ENV['DEBUG']
15
+ require 'threadedlogger'
16
+
17
+ class ThreadedLoggerTest < Minitest::Test
18
+ def teardown
19
+ ThreadedLogger.clear_all(true)
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ require 'minitest_helper'
2
+ require 'threadedlogger'
3
+
4
+ class TestClear < ThreadedLoggerTest
5
+ def test_clear_without_shutdown
6
+ logger = ThreadedLogger.instance('test/foo.log', 'daily')
7
+ ThreadedLogger.clear
8
+ logger = ThreadedLogger.instance('test/foo.log', 'daily')
9
+ ThreadedLogger.clear(true)
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ require 'minitest_helper'
2
+ require 'threadedlogger'
3
+
4
+ class OurLog1 < ThreadedLogger
5
+ end
6
+
7
+ class OurLog2 < ThreadedLogger
8
+ end
9
+
10
+ class TestInheritable < ThreadedLoggerTest
11
+ def test_construct_subclass
12
+ logger = OurLog1.instance('test/foo.log', 'daily')
13
+ assert_instance_of(OurLog1, logger)
14
+ logger.info 'foo'
15
+ File.exists?('test/foo.log')
16
+ logger.shutdown
17
+ end
18
+ def test_construct_two_subclasses
19
+ logger1 = OurLog1.instance('test/foo.log', 'daily')
20
+ logger2 = OurLog2.instance('test/bar.log', 'daily')
21
+ assert_instance_of(OurLog1, logger1)
22
+ assert_instance_of(OurLog2, logger2)
23
+ logger1.info 'foo'
24
+ logger2.info 'bar'
25
+ File.exists?('test/foo.log')
26
+ File.exists?('test/bar.log')
27
+ logger1.shutdown
28
+ logger2.shutdown
29
+ end
30
+ def test_reconstruct_subclass
31
+ logger1 = OurLog1.instance('test/foo.log', 'daily')
32
+ logger2 = OurLog2.instance('test/bar.log', 'daily')
33
+ assert_instance_of(OurLog1, logger1)
34
+ assert_instance_of(OurLog2, logger2)
35
+ assert_raises(ArgumentError) {
36
+ OurLog1.instance('test/foo.log', 'daily')
37
+ }
38
+ assert_raises(ArgumentError) {
39
+ OurLog2.instance('test/bar.log', 'daily')
40
+ }
41
+ end
42
+ def test_clear
43
+ logger1 = OurLog1.instance('test/foo.log', 'daily')
44
+ logger2 = OurLog2.instance('test/bar.log', 'daily')
45
+ assert_instance_of(OurLog1, logger1)
46
+ assert_instance_of(OurLog2, logger2)
47
+ assert_raises(ArgumentError) {
48
+ OurLog1.instance('test/foo.log', 'daily')
49
+ }
50
+ OurLog1.clear
51
+ logger2b = OurLog2.instance
52
+ assert_instance_of(OurLog2, logger2b)
53
+ assert_equal logger2b, logger2
54
+ logger1 = OurLog1.instance('test/foo.log', 'daily')
55
+ assert_instance_of(OurLog1, logger1)
56
+ end
57
+ def test_clear_all
58
+ logger1 = OurLog1.instance('test/foo.log', 'daily')
59
+ assert_instance_of(OurLog1, logger1)
60
+ assert_raises(ArgumentError) {
61
+ OurLog1.instance('test/foo.log', 'daily')
62
+ }
63
+ ThreadedLogger.clear_all
64
+ logger1 = OurLog1.instance('test/foo.log', 'daily')
65
+ assert_instance_of(OurLog1, logger1)
66
+ end
67
+ end
data/test/test_logging.rb CHANGED
@@ -1,25 +1,11 @@
1
1
  require 'minitest_helper'
2
- require 'minitest/autorun'
3
2
  require 'threadedlogger'
4
- require 'fakefs'
5
3
 
6
- class ThreadedLogger
7
- def ThreadedLogger.clear
8
- if ! @@instance.nil?
9
- @@instance.shutdown
10
- @@instance = nil
11
- end
12
- end
13
- end
14
-
15
- class TestLogging < Minitest::Test
16
- def setup
17
- ThreadedLogger.clear
18
- end
4
+ class TestLogging < ThreadedLoggerTest
19
5
  def test_logging
20
- logger = ThreadedLogger.instance('foo.log', 'daily')
6
+ logger = ThreadedLogger.instance('test/foo.log', 'daily')
21
7
  logger.info 'foo'
22
- File.exists?('foo.log')
8
+ File.exists?('test/foo.log')
23
9
  logger.shutdown
24
10
  end
25
11
  end
data/test/test_methods.rb CHANGED
@@ -1,23 +1,8 @@
1
1
  require 'minitest_helper'
2
- require 'minitest/autorun'
3
- require 'threadedlogger'
4
- require 'fakefs'
5
2
 
6
- class ThreadedLogger
7
- def ThreadedLogger.clear
8
- if ! @@instance.nil?
9
- @@instance.shutdown
10
- @@instance = nil
11
- end
12
- end
13
- end
14
-
15
- class TestMethods < Minitest::Test
16
- def setup
17
- ThreadedLogger.clear
18
- end
3
+ class TestMethods < ThreadedLoggerTest
19
4
  def test_levels
20
- logger = ThreadedLogger.instance('test/example.log', 'daily')
5
+ logger = ThreadedLogger.instance('test/foo.log', 'daily')
21
6
  assert_instance_of ThreadedLogger, logger, "constructor works with 2 args"
22
7
  %w(debug info warn error fatal).each do |level|
23
8
  assert_respond_to(logger, level.to_sym, "logger responds to level #{level}")
@@ -27,14 +12,14 @@ class TestMethods < Minitest::Test
27
12
  }
28
13
  end
29
14
  def test_level_predicates
30
- logger = ThreadedLogger.instance('test/example.log', 'daily', 'info')
15
+ logger = ThreadedLogger.instance('test/foo.log', 'daily', 'info')
31
16
  assert_instance_of ThreadedLogger, logger, "constructor works with 3 args"
32
17
  %w(debug info warn error fatal).each do |level|
33
18
  assert_respond_to(logger, "#{level}?".to_sym, "logger responds to predicate #{level}?")
34
19
  end
35
20
  end
36
21
  def test_setlevel
37
- logger = ThreadedLogger.instance('test/example.log', 'daily', 'info')
22
+ logger = ThreadedLogger.instance('test/foo.log', 'daily', 'info')
38
23
  assert_instance_of ThreadedLogger, logger, "constructor works with 3 args"
39
24
  assert_equal(false, logger.debug?, 'debug is not on when contstructed at level info')
40
25
  logger.level = 'debug'
@@ -0,0 +1,10 @@
1
+ require 'minitest_helper'
2
+ require 'threadedlogger'
3
+
4
+ class TestShutdown < ThreadedLoggerTest
5
+ def test_enqueue_after_shutdown
6
+ logger = ThreadedLogger.instance('test/foo.log', 'daily')
7
+ logger.shutdown
8
+ logger.info('foo')
9
+ end
10
+ end
@@ -1,31 +1,16 @@
1
1
  require 'minitest_helper'
2
- require 'minitest/autorun'
3
- require 'threadedlogger'
4
- require 'fakefs'
5
2
 
6
- class ThreadedLogger
7
- def ThreadedLogger.clear
8
- if ! @@instance.nil?
9
- @@instance.shutdown
10
- @@instance = nil
11
- end
12
- end
13
- end
14
-
15
- class TestThreadedLogger < Minitest::Test
16
- def setup
17
- ThreadedLogger.clear
18
- end
3
+ class TestThreadedLogger < ThreadedLoggerTest
19
4
  def test_constructor
20
- logger = ThreadedLogger.instance('test/example.log', 'daily')
5
+ logger = ThreadedLogger.instance('test/foo.log', 'daily')
21
6
  assert_instance_of ThreadedLogger, logger, "constructor works with 2 args"
22
7
  end
23
8
  def test_constructor_with_level
24
- logger = ThreadedLogger.instance('test/example.log', 'daily', 'info')
9
+ logger = ThreadedLogger.instance('test/foo.log', 'daily', 'info')
25
10
  assert_instance_of ThreadedLogger, logger, "constructor works with 3 args"
26
11
  end
27
12
  def test_constructor_with_level_and_formatter
28
- logger = ThreadedLogger.instance('test/example.log', 'daily', 'info', proc { |l| l } )
13
+ logger = ThreadedLogger.instance('test/foo.log', 'daily', 'info', proc { |l| l } )
29
14
  assert_instance_of ThreadedLogger, logger, "constructor works with 4 args"
30
15
  end
31
16
  def test_constructor_noargs
@@ -34,14 +19,14 @@ class TestThreadedLogger < Minitest::Test
34
19
  end
35
20
  end
36
21
  def test_singleton
37
- loggera = ThreadedLogger.instance('test/example.log')
22
+ loggera = ThreadedLogger.instance('test/foo.log')
38
23
  loggerb = ThreadedLogger.instance
39
24
  assert_same loggera, loggerb, "two instances are the same object"
40
25
  end
41
26
  def test_construct_twice
42
27
  assert_raises ArgumentError, "cannot call instance a second time with args" do
43
- ThreadedLogger.instance('test/example.log')
44
- ThreadedLogger.instance('test/example2.log')
28
+ ThreadedLogger.instance('test/foo.log')
29
+ ThreadedLogger.instance('test/bar.log')
45
30
  end
46
31
  end
47
32
  end
@@ -4,7 +4,6 @@ require 'threadedlogger'
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'threadedlogger'
6
6
  s.version = ThreadedLogger::VERSION
7
- s.date = '2013-03-23'
8
7
  s.summary = 'Simple logging library with a dedicated logging thread'
9
8
  s.homepage = 'https://github.com/jf647/ThreadedLogger'
10
9
  s.authors = ['James FitzGibbon']
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: threadedlogger
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -63,11 +63,17 @@ files:
63
63
  - LICENSE.txt
64
64
  - Rakefile
65
65
  - lib/threadedlogger.rb
66
+ - lib/threadedlogger/core.rb
67
+ - lib/threadedlogger/version.rb
68
+ - lib/threadedlogger/logger.rb
66
69
  - test/minitest_helper.rb
67
70
  - test/test_logging.rb
68
71
  - test/test_methods.rb
69
72
  - test/test_threadedlogger.rb
73
+ - test/test_shutdown.rb
74
+ - test/test_clear.rb
70
75
  - threadedlogger.gemspec
76
+ - test/test_inheritable.rb
71
77
  - .gemtest
72
78
  homepage: https://github.com/jf647/ThreadedLogger
73
79
  licenses:
@@ -86,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
92
  version: '0'
87
93
  segments:
88
94
  - 0
89
- hash: -1670757235924190297
95
+ hash: -484491560591714406
90
96
  required_rubygems_version: !ruby/object:Gem::Requirement
91
97
  none: false
92
98
  requirements:
@@ -95,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
101
  version: '0'
96
102
  segments:
97
103
  - 0
98
- hash: -1670757235924190297
104
+ hash: -484491560591714406
99
105
  requirements: []
100
106
  rubyforge_project: threadedlogger
101
107
  rubygems_version: 1.8.23
@@ -104,6 +110,9 @@ specification_version: 3
104
110
  summary: ThreadedLogger runs a dedicated logging thread around Ruby's Logger library
105
111
  to ensure that multiple threads don't step on each other's toes.
106
112
  test_files:
113
+ - test/test_clear.rb
114
+ - test/test_inheritable.rb
107
115
  - test/test_logging.rb
108
116
  - test/test_methods.rb
117
+ - test/test_shutdown.rb
109
118
  - test/test_threadedlogger.rb