New Upstream Release - ruby-logging

Ready changes

Summary

Merged new upstream version: 2.3.1 (was: 2.2.2).

Resulting package

Built on 2022-12-29T20:35 (took 5m5s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-logging

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
index 538924b..4b48f00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ tmp/
 vendor/
 .rbx
 .rvmrc
+.tool-versions
diff --git a/.travis.yml b/.travis.yml
index 6329578..574f46a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,6 +9,8 @@ rvm:
   - 2.0.0-p648
   - 2.1.10
   - 2.2.7
-  - 2.3.4
-  - 2.4.1
-  - jruby-9.1.8.0
+  - 2.3.8
+  - 2.4.9
+  - 2.5.7
+  - 2.6.5
+  - jruby-9.2.9.0
diff --git a/History.txt b/History.txt
index 73b007a..5032a70 100644
--- a/History.txt
+++ b/History.txt
@@ -1,3 +1,24 @@
+== 2.3.1 / 2022-05-24
+
+Bug Fixes
+- logging hangs on JRuby when the stdout appender is closed [PR #237]
+- initialize the Logging framework when a Filter is created [PR #238]
+
+== 2.3.0 / 2020-07-04
+
+Enhancements
+- all appender output is now synchronized [PR #219]
+- renamed the `LogEvent#method` to no longer conflict with `Kernel#method` [PR #218]
+- @bhuga (not the Fortnite star) added a `raise_errors` method for debugging [PR #203]
+- thanks to @olleolleolle for keeping on top of Travis and Ruby versions
+
+Bug Fixes
+- conosle appenders can be reopened [PR #220]
+- fixed a race condition in the rolling file appender [PR #216]
+- fixed a race condition when opening log file destinations [PR #208 #217]
+- @MikaelSmith fixed a race condition in Logger creation [PR #201]
+- documentation bug fixes [PR #184 #185 #188 #194 #209]
+
 == 2.2.2 / 2017-04-11
 
 Enhancements
diff --git a/Rakefile b/Rakefile
index 6d673b0..bd16f85 100644
--- a/Rakefile
+++ b/Rakefile
@@ -26,9 +26,9 @@ Bones {
   use_gmail
 
   depend_on 'little-plugger', '~> 1.1'
-  depend_on 'multi_json',     '~> 1.10'
+  depend_on 'multi_json',     '~> 1.14'
 
-  depend_on 'test-unit', '~> 3.1', :development => true
+  depend_on 'test-unit', '~> 3.3', :development => true
   depend_on 'bones-git', '~> 1.3', :development => true
   #depend_on 'bones-rcov',   :development => true
 }
diff --git a/debian/changelog b/debian/changelog
index 89baeed..c9e3f22 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-ruby-logging (2.2.2-2) UNRELEASED; urgency=medium
+ruby-logging (2.3.1-1) UNRELEASED; urgency=medium
 
   [ Hleb Valoshka ]
   * Remove myself from uploaders
@@ -22,8 +22,9 @@ ruby-logging (2.2.2-2) UNRELEASED; urgency=medium
   * Bump debhelper from old 12 to 13.
   * Update Vcs-* headers from URL redirect.
   * Use canonical URL in Vcs-Git.
+  * New upstream release.
 
- -- Utkarsh Gupta <guptautkarsh2102@gmail.com>  Tue, 13 Aug 2019 05:55:48 +0530
+ -- Utkarsh Gupta <guptautkarsh2102@gmail.com>  Thu, 29 Dec 2022 20:31:03 -0000
 
 ruby-logging (2.2.2-1) unstable; urgency=medium
 
diff --git a/debian/patches/0002-Disable-assertions-not-working-when-run-by-root.patch b/debian/patches/0002-Disable-assertions-not-working-when-run-by-root.patch
index 25b2685..35fb5e0 100644
--- a/debian/patches/0002-Disable-assertions-not-working-when-run-by-root.patch
+++ b/debian/patches/0002-Disable-assertions-not-working-when-run-by-root.patch
@@ -7,23 +7,25 @@ Subject: Disable assertions not working when run by root
  test/appenders/test_file.rb | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
 
---- a/test/appenders/test_file.rb
-+++ b/test/appenders/test_file.rb
-@@ -30,7 +30,7 @@
-       log = File.join(TMP, 'uw_dir', 'file.log')
+Index: ruby-logging.git/test/appenders/test_file.rb
+===================================================================
+--- ruby-logging.git.orig/test/appenders/test_file.rb
++++ ruby-logging.git/test/appenders/test_file.rb
+@@ -30,7 +30,7 @@ module TestAppenders
+       log = File.join(@tmpdir, 'uw_dir', 'file.log')
        assert_raise(ArgumentError) do
          Logging.appenders.file(log).class.assert_valid_logfile(log)
 -      end
 +      end if Process.euid != 0
  
-       log = File.join(TMP, 'dir')
+       log = File.join(@tmpdir, 'dir')
        assert_raise(ArgumentError) do
-@@ -40,7 +40,7 @@
-       log = File.join(TMP, 'uw_file')
+@@ -40,7 +40,7 @@ module TestAppenders
+       log = File.join(@tmpdir, 'uw_file')
        assert_raise(ArgumentError) do
          Logging.appenders.file(log).class.assert_valid_logfile(log)
 -      end
 +      end if Process.euid != 0
  
-       log = File.join(TMP, 'file.log')
+       log = File.join(@tmpdir, 'file.log')
        assert Logging.appenders.file(log).class.assert_valid_logfile(log)
diff --git a/debian/patches/0003-remove-relative-path.patch b/debian/patches/0003-remove-relative-path.patch
index c9bc9ba..9722946 100644
--- a/debian/patches/0003-remove-relative-path.patch
+++ b/debian/patches/0003-remove-relative-path.patch
@@ -1,8 +1,10 @@
 autopkgtest should take installed lib
 
---- a/test/setup.rb
-+++ b/test/setup.rb
-@@ -10,7 +10,7 @@
+Index: ruby-logging.git/test/setup.rb
+===================================================================
+--- ruby-logging.git.orig/test/setup.rb
++++ ruby-logging.git/test/setup.rb
+@@ -16,7 +16,7 @@ if Test::Unit::TestCase.respond_to? :tes
    Test::Unit::TestCase.test_order = :random
  end
  
diff --git a/examples/appenders.rb b/examples/appenders.rb
index 2bbf80e..12f7bf8 100644
--- a/examples/appenders.rb
+++ b/examples/appenders.rb
@@ -1,7 +1,7 @@
 # :stopdoc:
 #
 # Appenders are used to output log events to some logging destination. The
-# same log event can be sent to multiple desitnations by associating
+# same log event can be sent to multiple destinations by associating
 # multiple appenders with the logger.
 #
 # The following is a list of all the available appenders and a brief
diff --git a/examples/mdc.rb b/examples/mdc.rb
index b58ef1d..2f3dd87 100644
--- a/examples/mdc.rb
+++ b/examples/mdc.rb
@@ -29,9 +29,9 @@
   Logging.mdc['first'] = 'John'
   Logging.mdc['last']  = 'Doe'
 
-  # in this first thread we will log some quotes by Allan Rickman
+  # in this first thread we will log some quotes by Alan Rickman
   t1 = Thread.new {
-    Logging.mdc['first'] = 'Allan'
+    Logging.mdc['first'] = 'Alan'
     Logging.mdc['last']  = 'Rickman'
 
     [ %q{I've never been able to plan my life. I just lurch from indecision to indecision.},
diff --git a/lib/logging.rb b/lib/logging.rb
index 78bcce1..eb4668a 100644
--- a/lib/logging.rb
+++ b/lib/logging.rb
@@ -258,6 +258,7 @@ module Logging
       module_eval "MAX_LEVEL_LENGTH = #{longest.length}", __FILE__, __LINE__
 
       self.cause_depth = nil unless defined? @cause_depth
+      self.raise_errors = false unless defined? @raise_errors
 
       initialize_plugins
       levels.keys
@@ -268,8 +269,8 @@ module Logging
     #
     # Defines the default _obj_format_ method to use when converting objects
     # into string representations for logging. _obj_format_ can be one of
-    # <tt>:string</tt>, <tt>:inspect</tt>, or <tt>:yaml</tt>. These
-    # formatting commands map to the following object methods
+    # <tt>:string</tt>, <tt>:inspect</tt>, <tt>:json</tt> or <tt>:yaml</tt>.
+    # These formatting commands map to the following object methods
     #
     # * :string  => to_s
     # * :inspect => inspect
@@ -277,7 +278,7 @@ module Logging
     # * :json    => MultiJson.encode(obj)
     #
     # An +ArgumentError+ is raised if anything other than +:string+,
-    # +:inspect+, +:yaml+ is passed to this method.
+    # +:inspect+, +:json+ or +:yaml+ is passed to this method.
     #
     def format_as( f )
       f = f.intern if f.instance_of? String
@@ -490,6 +491,21 @@ module Logging
       io
     end
 
+    # Raise an exception when an error is encountered while logging, be it with
+    # a backing store, formatter, or anything else. You probably wouldn't want
+    # to enable this outside of test.
+    #
+    # Not that only one error will ever be raised per logging backend, as
+    # backends that raise errors on write will be set to :off.
+    def raise_errors=(boolean)
+      @raise_errors = boolean
+    end
+
+    # Whether or not we should raise errors when writing logs.
+    def raise_errors?
+      @raise_errors
+    end
+
     # :stopdoc:
     # Convert the given level into a canonical form - a lowercase string.
     def levelify( level )
@@ -518,7 +534,7 @@ module Logging
     # exception will be raised again.
     def log_internal_error( err )
       log_internal(-2) { err }
-      raise err if Thread.abort_on_exception
+      raise err if ::Logging.raise_errors?
     end
 
     # Close all appenders
@@ -569,8 +585,7 @@ module Logging
   require libpath('logging/diagnostic_context')
 
   require libpath('logging/rails_compat')
-end  # module Logging
-
+end
 
 # This finalizer will close all the appenders that exist in the system.
 # This is needed for closing IO streams and connections to the syslog server
diff --git a/lib/logging/appender.rb b/lib/logging/appender.rb
index 8724065..8d7abd1 100644
--- a/lib/logging/appender.rb
+++ b/lib/logging/appender.rb
@@ -9,11 +9,6 @@ module Logging
 # Each subclass should provide a +write+ method that will write log
 # messages to the logging destination.
 #
-# A private +sync+ method is provided for use by subclasses. It is used to
-# synchronize writes to the logging destination, and can be used by
-# subclasses to synchronize the closing or flushing of the logging
-# destination.
-#
 class Appender
 
   attr_reader :name, :layout, :level, :filters
@@ -263,13 +258,9 @@ class Appender
     "<%s name=\"%s\">" % [self.class.name.sub(%r/^Logging::/, ''), self.name]
   end
 
-  # Returns the current Encoding for the appender or nil if an encoding has
-  # not been set.
-  #
-  def encoding
-    return @encoding if defined? @encoding
-    @encoding = Object.const_defined?(:Encoding) ? Encoding.default_external : nil
-  end
+  # Returns the current Encoding for the appender. The default external econding
+  # will be used if none is explicitly set.
+  attr_reader :encoding
 
   # Set the appender encoding to the given value. The value can either be an
   # Encoding instance or a String or Symbol referring to a valid encoding.
@@ -282,9 +273,9 @@ class Appender
   # Raises ArgumentError if the value is not a valid encoding.
   def encoding=( value )
     if value.nil?
-      @encoding = nil
+      @encoding = Encoding.default_external
     else
-      @encoding = Object.const_defined?(:Encoding) ? Encoding.find(value.to_s) : nil
+      @encoding = Encoding.find(value.to_s)
     end
   end
 
@@ -328,17 +319,6 @@ private
     nil
   end
 
-  # call-seq:
-  #    sync { block }
-  #
-  # Obtains an exclusive lock, runs the block, and releases the lock when
-  # the block completes. This method is re-entrant so that a single thread
-  # can call +sync+ multiple times without hanging the thread.
-  #
-  def sync( &block )
-    @mutex.synchronize(&block)
-  end
-
 end  # class Appender
 end  # module Logging
 
diff --git a/lib/logging/appenders/buffering.rb b/lib/logging/appenders/buffering.rb
index afbeedf..6a6de49 100644
--- a/lib/logging/appenders/buffering.rb
+++ b/lib/logging/appenders/buffering.rb
@@ -79,7 +79,7 @@ module Logging::Appenders
       return self if @buffer.empty?
 
       ary = nil
-      sync {
+      @mutex.synchronize {
         ary = @buffer.dup
         @buffer.clear
       }
@@ -100,7 +100,7 @@ module Logging::Appenders
     # Clear the underlying buffer of all log events. These events will not be
     # appended to the logging destination; they will be lost.
     def clear!
-      sync { @buffer.clear }
+      @mutex.synchronize { @buffer.clear }
     end
 
     # Configure the levels that will trigger an immediate flush of the
@@ -285,7 +285,7 @@ module Logging::Appenders
         canonical_write(str)
       else
         str = str.force_encoding(encoding) if encoding && str.encoding != encoding
-        sync {
+        @mutex.synchronize {
           @buffer << str
         }
         flush_now = @buffer.length >= @auto_flushing || immediate?(event)
diff --git a/lib/logging/appenders/console.rb b/lib/logging/appenders/console.rb
index 0b08f70..fe4871c 100644
--- a/lib/logging/appenders/console.rb
+++ b/lib/logging/appenders/console.rb
@@ -22,17 +22,39 @@ module Logging::Appenders
     #
     def initialize( *args )
       name = self.class.name.split("::").last.downcase
-      io   = Object.const_get(name.upcase)
 
       opts = args.last.is_a?(Hash) ? args.pop : {}
       name = args.shift unless args.empty?
 
-      opts[:encoding] = io.external_encoding if io.respond_to? :external_encoding
+      io = open_fd
+      opts[:encoding] = io.external_encoding
 
       super(name, io, opts)
-    rescue NameError
-      raise RuntimeError, "Please do not use the `Logging::Appenders::Console` class directly - " +
-                          "use `Logging::Appenders::Stdout` and `Logging::Appenders::Stderr` instead"
+    end
+
+    # Reopen the connection to the underlying logging destination. If the
+    # connection is currently closed then it will be opened. If the connection
+    # is currently open then it will be closed and immediately reopened.
+    def reopen
+      @mutex.synchronize {
+        flush if defined? @io && @io
+        @io = open_fd
+      }
+      super
+      self
+    end
+
+  private
+
+    def open_fd
+      case self.class.name
+      when "Logging::Appenders::Stdout"; STDOUT
+      when "Logging::Appenders::Stderr"; STDERR
+      else
+        raise RuntimeError, "Please do not use the `Logging::Appenders::Console` class directly - " +
+                            "use `Logging::Appenders::Stdout` and `Logging::Appenders::Stderr` instead" +
+                            " [class #{self.class.name}]"
+      end
     end
   end
 
@@ -43,7 +65,6 @@ module Logging::Appenders
   Stderr = Class.new(Console)
 
   # Accessor / Factory for the Stdout appender.
-  #
   def self.stdout( *args )
     if args.empty?
       return self['stdout'] || ::Logging::Appenders::Stdout.new
@@ -52,7 +73,6 @@ module Logging::Appenders
   end
 
   # Accessor / Factory for the Stderr appender.
-  #
   def self.stderr( *args )
     if args.empty?
       return self['stderr'] || ::Logging::Appenders::Stderr.new
diff --git a/lib/logging/appenders/file.rb b/lib/logging/appenders/file.rb
index 8b3fea6..d2ac2a2 100644
--- a/lib/logging/appenders/file.rb
+++ b/lib/logging/appenders/file.rb
@@ -2,14 +2,12 @@
 module Logging::Appenders
 
   # Accessor / Factory for the File appender.
-  #
   def self.file( *args )
     fail ArgumentError, '::Logging::Appenders::File needs a name as first argument.' if args.empty?
     ::Logging::Appenders::File.new(*args)
   end
 
   # This class provides an Appender that can write to a File.
-  #
   class File < ::Logging::Appenders::IO
 
     # call-seq:
@@ -21,15 +19,14 @@ module Logging::Appenders
     # writable.
     #
     # An +ArgumentError+ is raised if any of these assertions fail.
-    #
     def self.assert_valid_logfile( fn )
       if ::File.exist?(fn)
-        if not ::File.file?(fn)
+        if !::File.file?(fn)
           raise ArgumentError, "#{fn} is not a regular file"
-        elsif not ::File.writable?(fn)
+        elsif !::File.writable?(fn)
           raise ArgumentError, "#{fn} is not writeable"
         end
-      elsif not ::File.writable?(::File.dirname(fn))
+      elsif !::File.writable?(::File.dirname(fn))
         raise ArgumentError, "#{::File.dirname(fn)} is not writable"
       end
       true
@@ -45,41 +42,65 @@ module Logging::Appenders
     # created. If the :truncate option is set to +true+ then the file will
     # be truncated before writing begins; otherwise, log messages will be
     # appended to the file.
-    #
     def initialize( name, opts = {} )
-      @fn = opts.fetch(:filename, name)
-      raise ArgumentError, 'no filename was given' if @fn.nil?
+      @filename = opts.fetch(:filename, name)
+      raise ArgumentError, 'no filename was given' if @filename.nil?
 
-      @fn = ::File.expand_path(@fn)
-      self.class.assert_valid_logfile(@fn)
-      @mode = opts.fetch(:truncate, false) ? 'w' : 'a'
+      @filename = ::File.expand_path(@filename).freeze
+      self.class.assert_valid_logfile(@filename)
 
       self.encoding = opts.fetch(:encoding, self.encoding)
-      @mode = "#{@mode}:#{self.encoding}" if self.encoding
 
-      super(name, ::File.new(@fn, @mode), opts)
+      io = open_file
+      super(name, io, opts)
+
+      truncate if opts.fetch(:truncate, false)
     end
 
     # Returns the path to the logfile.
-    #
-    def filename() @fn.dup end
+    attr_reader :filename
 
     # Reopen the connection to the underlying logging destination. If the
     # connection is currently closed then it will be opened. If the connection
     # is currently open then it will be closed and immediately opened.
-    #
     def reopen
       @mutex.synchronize {
-        if defined? @io and @io
+        if defined? @io && @io
           flush
           @io.close rescue nil
         end
-        @io = ::File.new(@fn, @mode)
+        @io = open_file
       }
       super
       self
     end
 
-  end  # FileAppender
-end  # Logging::Appenders
 
+  protected
+
+    def truncate
+      @mutex.synchronize {
+        begin
+          @io.flock(::File::LOCK_EX)
+          @io.truncate(0)
+        ensure
+          @io.flock(::File::LOCK_UN)
+        end
+      }
+    end
+
+    def open_file
+      mode = ::File::WRONLY | ::File::APPEND
+      ::File.open(filename, mode: mode, external_encoding: encoding)
+    rescue Errno::ENOENT
+      create_file
+    end
+
+    def create_file
+      mode = ::File::WRONLY | ::File::APPEND | ::File::CREAT | ::File::EXCL
+      ::File.open(filename, mode: mode, external_encoding: encoding)
+    rescue Errno::EEXIST
+      open_file
+    end
+  end
+end
diff --git a/lib/logging/appenders/io.rb b/lib/logging/appenders/io.rb
index 4c6fcee..4b12011 100644
--- a/lib/logging/appenders/io.rb
+++ b/lib/logging/appenders/io.rb
@@ -2,7 +2,6 @@
 module Logging::Appenders
 
   # Accessor / Factory for the IO appender.
-  #
   def self.io( *args )
     return ::Logging::Appenders::IO if args.empty?
     ::Logging::Appenders::IO.new(*args)
@@ -10,14 +9,12 @@ module Logging::Appenders
 
   # This class provides an Appender that can write to any IO stream
   # configured for writing.
-  #
   class IO < ::Logging::Appender
     include Buffering
 
     # The method that will be used to close the IO stream. Defaults to :close
     # but can be :close_read, :close_write or nil. When nil, the IO stream
     # will not be closed when the appender's close method is called.
-    #
     attr_accessor :close_method
 
     # call-seq:
@@ -26,7 +23,6 @@ module Logging::Appenders
     #
     # Creates a new IO Appender using the given name that will use the _io_
     # stream as the logging destination.
-    #
     def initialize( name, io, opts = {} )
       unless io.respond_to? :write
         raise TypeError, "expecting an IO object but got '#{io.class.name}'"
@@ -47,13 +43,12 @@ module Logging::Appenders
     # destination if the _footer_ flag is set to +true+. Log events will
     # no longer be written to the logging destination after the appender
     # is closed.
-    #
     def close( *args )
       return self if @io.nil?
       super
 
       io, @io = @io, nil
-      unless [STDIN, STDERR, STDOUT].include?(io)
+      if ![STDIN, STDERR, STDOUT].include?(io)
         io.send(@close_method) if @close_method && io.respond_to?(@close_method)
       end
     rescue IOError
@@ -61,16 +56,25 @@ module Logging::Appenders
       return self
     end
 
+    # Reopen the connection to the underlying logging destination. If the
+    # connection is currently closed then it will be opened. If the connection
+    # is currently open then it will be closed and immediately opened. If
+    # supported, the IO will have its sync mode set to `true` so that all writes
+    # are immediately flushed to the underlying operating system.
+    def reopen
+      super
+      @io.sync = true if @io.respond_to? :sync=
+      self
+    end
 
   private
 
     # This method is called by the buffering code when messages need to be
     # written to the logging destination.
-    #
     def canonical_write( str )
       return self if @io.nil?
       str = str.force_encoding(encoding) if encoding && str.encoding != encoding
-      @io.write str
+      @mutex.synchronize { @io.write str }
       self
     rescue StandardError => err
       handle_internal_error(err)
@@ -82,6 +86,5 @@ module Logging::Appenders
       ::Logging.log_internal {"appender #{name.inspect} has been disabled"}
       ::Logging.log_internal_error(err)
     end
-
-  end  # IO
-end  # Logging::Appenders
+  end
+end
diff --git a/lib/logging/appenders/rolling_file.rb b/lib/logging/appenders/rolling_file.rb
index 61f3e64..f795600 100644
--- a/lib/logging/appenders/rolling_file.rb
+++ b/lib/logging/appenders/rolling_file.rb
@@ -84,24 +84,32 @@ module Logging::Appenders
     #               'date'.
     #
     def initialize( name, opts = {} )
-      @roller = Roller.new name, opts
+      @roller = Roller.new(
+        opts.fetch(:filename, name),
+        age:     opts.fetch(:age, nil),
+        size:    opts.fetch(:size, nil),
+        roll_by: opts.fetch(:roll_by, nil),
+        keep:    opts.fetch(:keep, nil)
+      )
 
       # grab our options
       @size = opts.fetch(:size, nil)
       @size = Integer(@size) unless @size.nil?
 
-      @age_fn = filename + '.age'
+      @age_fn = self.filename + '.age'
       @age_fn_mtime = nil
       @age = opts.fetch(:age, nil)
 
       # create our `sufficiently_aged?` method
       build_singleton_methods
-      FileUtils.touch(@age_fn) if @age && !test(?f, @age_fn)
+      FileUtils.touch(@age_fn) if @age && !::File.file?(@age_fn)
 
       # we are opening the file in read/write mode so that a shared lock can
       # be used on the file descriptor => http://pubs.opengroup.org/onlinepubs/009695399/functions/fcntl.html
-      @mode = encoding ? "a+:#{encoding}" : 'a+'
-      super(name, ::File.new(filename, @mode), opts)
+      self.encoding = opts.fetch(:encoding, self.encoding)
+
+      io = open_file
+      super(name, io, opts)
 
       # if the truncate flag was set to true, then roll
       roll_now = opts.fetch(:truncate, false)
@@ -121,11 +129,11 @@ module Logging::Appenders
     # is currently open then it will be closed and immediately opened.
     def reopen
       @mutex.synchronize {
-        if defined?(@io) && @io
+        if defined? @io && @io
           flush
           @io.close rescue nil
         end
-        @io = ::File.new(filename, @mode)
+        @io = open_file
       }
       super
       self
@@ -134,6 +142,20 @@ module Logging::Appenders
 
   private
 
+    def open_file
+      mode = ::File::RDWR | ::File::APPEND
+      ::File.open(filename, mode: mode, external_encoding: encoding)
+    rescue Errno::ENOENT
+      create_file
+    end
+
+    def create_file
+      mode = ::File::RDWR | ::File::APPEND | ::File::CREAT | ::File::EXCL
+      ::File.open(filename, mode: mode, external_encoding: encoding)
+    rescue Errno::EEXIST
+      open_file
+    end
+
     # Returns the file name to use as the temporary copy location. We are
     # using copy-and-truncate semantics for rolling files so that the IO
     # file descriptor remains valid during rolling.
@@ -157,14 +179,18 @@ module Logging::Appenders
       return self if @io.nil?
 
       str = str.force_encoding(encoding) if encoding && str.encoding != encoding
-      @io.flock_sh { @io.write str }
+      @mutex.synchronize {
+        @io.flock_sh { @io.write str }
+      }
 
       if roll_required?
-        @io.flock? {
-          @age_fn_mtime = nil
-          copy_truncate if roll_required?
+        @mutex.synchronize {
+          @io.flock? {
+            @age_fn_mtime = nil
+            copy_truncate if roll_required?
+          }
+          @roller.roll_files
         }
-        @roller.roll_files
       end
       self
     rescue StandardError => err
@@ -193,7 +219,7 @@ module Logging::Appenders
     def copy_truncate
       return unless ::File.exist?(filename)
       FileUtils.concat filename, copy_file
-      @io.truncate 0
+      @io.truncate(0)
 
       # touch the age file if needed
       if @age
@@ -255,22 +281,22 @@ module Logging::Appenders
       # Create a new roller. See the RollingFile#initialize documentation for
       # the list of options.
       #
-      # name - The appender name as a String
-      # opts - The options Hash
+      # filename - the name of the file to roll
+      # age      - the age of the file at which it should be rolled
+      # size     - the size of the file in bytes at which it should be rolled
+      # roll_by  - roll either by 'number' or 'date'
+      # keep     - the number of log files to keep when rolling
       #
-      def initialize( name, opts )
+      def initialize( filename, age: nil, size: nil, roll_by: nil, keep: nil )
         # raise an error if a filename was not given
-        @fn = opts.fetch(:filename, name)
+        @fn = filename
         raise ArgumentError, 'no filename was given' if @fn.nil?
 
         if (m = RGXP.match @fn)
           @roll_by = ("#{m[2]}%d" == m[1]) ? :number : :date
         else
-          age = opts.fetch(:age, nil)
-          size = opts.fetch(:size, nil)
-
           @roll_by =
-              case opts.fetch(:roll_by, nil)
+              case roll_by
               when 'number'; :number
               when 'date'; :date
               else
@@ -293,8 +319,7 @@ module Logging::Appenders
         ::Logging::Appenders::File.assert_valid_logfile(filename)
 
         @roll = false
-        @keep = opts.fetch(:keep, nil)
-        @keep = Integer(keep) unless keep.nil?
+        @keep = keep.nil? ? nil : Integer(keep)
       end
 
       attr_reader :keep, :roll_by
@@ -347,7 +372,6 @@ module Logging::Appenders
         files.delete copy_file
 
         self.send "roll_by_#{roll_by}", files
-
         nil
       ensure
         self.roll = false
diff --git a/lib/logging/appenders/string_io.rb b/lib/logging/appenders/string_io.rb
index 4e9faec..7651740 100644
--- a/lib/logging/appenders/string_io.rb
+++ b/lib/logging/appenders/string_io.rb
@@ -62,7 +62,7 @@ module Logging::Appenders
     %w[read readline readlines].each do|m|
       class_eval <<-CODE, __FILE__, __LINE__+1
         def #{m}( *args )
-          sync {
+          @mutex.synchronize {
             begin
               @sio.seek @pos
               rv = @sio.#{m}(*args)
diff --git a/lib/logging/appenders/syslog.rb b/lib/logging/appenders/syslog.rb
index 9850ee6..818e0b4 100644
--- a/lib/logging/appenders/syslog.rb
+++ b/lib/logging/appenders/syslog.rb
@@ -188,7 +188,7 @@ module Logging::Appenders
         end
       return if message.empty?
 
-      @syslog.log(pri, '%s', message)
+      @mutex.synchronize { @syslog.log(pri, '%s', message) }
       self
     end
 
@@ -205,11 +205,10 @@ module Logging::Appenders
         level = level.to_s.upcase
         self.class.const_get level
       else
-        raise ArgumentError, "unkonwn level '#{level}'"
+        raise ArgumentError, "unknown level '#{level}'"
       end
     end
 
   end  # Syslog
 end  # Logging::Appenders
 end  # HAVE_SYSLOG
-
diff --git a/lib/logging/filter.rb b/lib/logging/filter.rb
index f23eee5..33f935f 100644
--- a/lib/logging/filter.rb
+++ b/lib/logging/filter.rb
@@ -8,10 +8,17 @@ module Logging
   # Otherwise the `allow` method should return `nil`.
   class Filter
 
-    # Returns the event if it should be allowed into the log. Returns `nil` if
-    # the event should _not_ be allowed into the log. Subclasses should override
-    # this method and provide their own filtering semantics.
-    def allow( event )
+    # Creates a new level filter that will pass all log events. Create a
+    # subclass and override the `allow` method to filter log events.
+    def initialize
+      ::Logging.init unless ::Logging.initialized?
+    end
+
+    # Returns the event if it should be forwarded to the logging appender.
+    # Returns `nil` if the event should _not_ be forwarded to the logging
+    # appender. Subclasses should override this method and provide their own
+    # filtering semantics.
+    def allow(event)
       event
     end
   end
diff --git a/lib/logging/filters.rb b/lib/logging/filters.rb
index e3e6f3c..3152b87 100644
--- a/lib/logging/filters.rb
+++ b/lib/logging/filters.rb
@@ -1,4 +1,4 @@
 module Logging
   module Filters ; end
   require libpath('logging/filters/level')
-end
\ No newline at end of file
+end
diff --git a/lib/logging/filters/level.rb b/lib/logging/filters/level.rb
index f5dc4f1..09138f6 100644
--- a/lib/logging/filters/level.rb
+++ b/lib/logging/filters/level.rb
@@ -4,8 +4,7 @@ module Logging
   module Filters
 
     # The `Level` filter class provides a simple level-based filtering mechanism
-    # that filters messages to only include those from an enumerated list of
-    # levels to log.
+    # that allows events whose log level matches a preconfigured list of values.
     class Level < ::Logging::Filter
 
       # Creates a new level filter that will only allow the given _levels_ to
@@ -15,15 +14,19 @@ module Logging
       # Examples
       #     Logging::Filters::Level.new(:debug, :info)
       #
-      def initialize( *levels )
-        levels  = levels.map { |level| ::Logging::level_num(level) }
-        @levels = Set.new levels
+      def initialize(*levels)
+        super()
+        levels  = levels.flatten.map {|level| ::Logging::level_num(level)}
+        @levels = Set.new(levels)
       end
 
-      def allow( event )
+      # Returns the event if it should be forwarded to the logging appender.
+      # Otherwise, `nil` is returned. The log event is allowed if the
+      # `event.level` matches one of the levels provided to the filter when it
+      # was constructred.
+      def allow(event)
         @levels.include?(event.level) ? event : nil
       end
-
     end
   end
 end
diff --git a/lib/logging/layouts/parseable.rb b/lib/logging/layouts/parseable.rb
index f6df0f8..5046606 100644
--- a/lib/logging/layouts/parseable.rb
+++ b/lib/logging/layouts/parseable.rb
@@ -103,7 +103,7 @@ module Logging::Layouts
       'message'   => 'format_obj(event.data)'.freeze,
       'file'      => 'event.file'.freeze,
       'line'      => 'event.line'.freeze,
-      'method'    => 'event.method'.freeze,
+      'method'    => 'event.method_name'.freeze,
       'hostname'  => "'#{Socket.gethostname}'".freeze,
       'pid'       => 'Process.pid'.freeze,
       'millis'    => 'Integer((event.time-@created_at)*1000)'.freeze,
diff --git a/lib/logging/layouts/pattern.rb b/lib/logging/layouts/pattern.rb
index 301e432..45bb39e 100644
--- a/lib/logging/layouts/pattern.rb
+++ b/lib/logging/layouts/pattern.rb
@@ -307,7 +307,7 @@ module Logging::Layouts
         'l' => '::Logging::LNAMES[event.level]'.freeze,
         'L' => 'event.line'.freeze,
         'm' => 'format_obj(event.data)'.freeze,
-        'M' => 'event.method'.freeze,
+        'M' => 'event.method_name'.freeze,
         'h' => "'#{Socket.gethostname}'".freeze,
         'p' => 'Process.pid'.freeze,
         'r' => 'Integer((event.time-@created_at)*1000).to_s'.freeze,
diff --git a/lib/logging/log_event.rb b/lib/logging/log_event.rb
index b66fe63..35ce66f 100644
--- a/lib/logging/log_event.rb
+++ b/lib/logging/log_event.rb
@@ -12,10 +12,11 @@ module Logging
     # * $3 == method name (might be nil)
     CALLER_RGXP = %r/([-\.\/\(\)\w]+):(\d+)(?::in `([^']+)')?/o
     #CALLER_INDEX = 2
-    CALLER_INDEX = ((defined? JRUBY_VERSION and JRUBY_VERSION > '1.6') or (defined? RUBY_ENGINE and RUBY_ENGINE[%r/^rbx/i])) ? 1 : 2
+    CALLER_INDEX = ((defined?(JRUBY_VERSION) && JRUBY_VERSION > '1.6' && JRUBY_VERSION < '9.0') ||
+                    (defined?(RUBY_ENGINE) && RUBY_ENGINE[%r/^rbx/i])) ? 1 : 2
     # :startdoc:
 
-    attr_accessor :logger, :level, :data, :time, :file, :line, :method
+    attr_accessor :logger, :level, :data, :time, :file, :line, :method_name
 
     # call-seq:
     #    LogEvent.new( logger, level, [data], caller_tracing )
@@ -36,15 +37,15 @@ module Logging
         return if stack.nil?
 
         match = CALLER_RGXP.match(stack)
-        self.file   = match[1]
-        self.line   = Integer(match[2])
-        self.method = match[3] unless match[3].nil?
+        self.file = match[1]
+        self.line = Integer(match[2])
+        self.method_name = match[3] unless match[3].nil?
 
         if (bp = ::Logging.basepath) && !bp.empty? && file.index(bp) == 0
           self.file = file.slice(bp.length + 1, file.length - bp.length)
         end
       else
-        self.file = self.line = self.method = ''
+        self.file = self.line = self.method_name = ''
       end
     end
   end
diff --git a/lib/logging/logger.rb b/lib/logging/logger.rb
index a6a2e85..9fcec72 100644
--- a/lib/logging/logger.rb
+++ b/lib/logging/logger.rb
@@ -24,8 +24,6 @@ module Logging
   #
   class Logger
 
-    @mutex = Mutex.new  # :nodoc:
-
     # Returns the root logger.
     def self.root
       ::Logging::Repository.instance[:root]
@@ -48,7 +46,10 @@ module Logging
       logger = repo[name]
       return logger unless logger.nil?
 
-      @mutex.synchronize do
+      # Share the same mutex that's used by 'define_log_methods' because
+      # it iterates over the hash of loggers, and adding a new key to a hash
+      # while iterating over it produces an error.
+      ::Logging::Logger.mutex.synchronize do
         logger = repo[name]
         return logger unless logger.nil? # thread-safe double checking
 
@@ -118,6 +119,14 @@ module Logging
       code.join("\n")
     end
 
+    @mutex = ReentrantMutex.new
+
+    # Returns a global ReentrantMutex for use when creating Logger instances
+    # and/or updating log levels.
+    def self.mutex
+      @mutex
+    end
+
     attr_reader :name, :parent, :additive, :caller_tracing
 
     # call-seq:
@@ -411,7 +420,7 @@ module Logging
     def define_log_methods( force = false, code = nil )
       return if has_own_level? and !force
 
-      ::Logging::Logger._reentrant_mutex.synchronize do
+      ::Logging::Logger.mutex.synchronize do
         ::Logging::Logger.define_log_methods(self)
         ::Logging::Repository.instance.children(name).each do |child|
           child.define_log_methods
@@ -423,12 +432,6 @@ module Logging
     # :stopdoc:
   public
 
-    @reentrant_mutex = ReentrantMutex.new
-
-    def self._reentrant_mutex
-      @reentrant_mutex
-    end
-
     # call-seq:
     #    _meta_eval( code )
     #
@@ -511,6 +514,5 @@ module Logging
     end
     # :startdoc:
 
-  end  # Logger
-end  # Logging
-
+  end
+end
diff --git a/lib/logging/proxy.rb b/lib/logging/proxy.rb
index 798e3b3..a9f6719 100644
--- a/lib/logging/proxy.rb
+++ b/lib/logging/proxy.rb
@@ -47,7 +47,7 @@ module Logging
 
     # All hail the magic of method missing. Here is where we are going to log
     # the method call and then forward to the proxied object. The return value
-    # from the proxied objet method call is passed back.
+    # from the proxied object method call is passed back.
     #
     def method_missing( name, *args, &block )
       @logger << "#@leader#{name}(#{args.inspect[1..-2]})\n"
diff --git a/lib/logging/version.rb b/lib/logging/version.rb
index cd0c0e9..8a3964c 100644
--- a/lib/logging/version.rb
+++ b/lib/logging/version.rb
@@ -1,5 +1,5 @@
 module Logging
-  VERSION = "2.2.2".freeze
+  VERSION = "2.3.1".freeze
 
   # Returns the version string for the library.
   def self.version
diff --git a/logging.gemspec b/logging.gemspec
index a5ee2c8..e1e7c5d 100644
--- a/logging.gemspec
+++ b/logging.gemspec
@@ -1,45 +1,44 @@
 # -*- encoding: utf-8 -*-
-# stub: logging 2.1.0.48 ruby lib
+# stub: logging 2.3.0 ruby lib
 
 Gem::Specification.new do |s|
   s.name = "logging".freeze
-  s.version = "2.1.0.48"
+  s.version = "2.3.0"
 
   s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
   s.require_paths = ["lib".freeze]
   s.authors = ["Tim Pease".freeze]
-  s.date = "2017-01-08"
+  s.date = "2020-07-04"
   s.description = "**Logging** is a flexible logging library for use in Ruby programs based on the\ndesign of Java's log4j library. It features a hierarchical logging system,\ncustom level names, multiple output destinations per log event, custom\nformatting, and more.".freeze
   s.email = "tim.pease@gmail.com".freeze
   s.extra_rdoc_files = ["History.txt".freeze]
-  s.files = [".gitignore".freeze, ".travis.yml".freeze, "History.txt".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "examples/appenders.rb".freeze, "examples/classes.rb".freeze, "examples/colorization.rb".freeze, "examples/custom_log_levels.rb".freeze, "examples/fork.rb".freeze, "examples/formatting.rb".freeze, "examples/hierarchies.rb".freeze, "examples/layouts.rb".freeze, "examples/lazy.rb".freeze, "examples/loggers.rb".freeze, "examples/mdc.rb".freeze, "examples/names.rb".freeze, "examples/rails4.rb".freeze, "examples/reusing_layouts.rb".freeze, "examples/rspec_integration.rb".freeze, "examples/simple.rb".freeze, "lib/logging.rb".freeze, "lib/logging/appender.rb".freeze, "lib/logging/appenders.rb".freeze, "lib/logging/appenders/buffering.rb".freeze, "lib/logging/appenders/console.rb".freeze, "lib/logging/appenders/file.rb".freeze, "lib/logging/appenders/io.rb".freeze, "lib/logging/appenders/rolling_file.rb".freeze, "lib/logging/appenders/string_io.rb".freeze, "lib/logging/appenders/syslog.rb".freeze, "lib/logging/color_scheme.rb".freeze, "lib/logging/diagnostic_context.rb".freeze, "lib/logging/filter.rb".freeze, "lib/logging/filters.rb".freeze, "lib/logging/filters/level.rb".freeze, "lib/logging/layout.rb".freeze, "lib/logging/layouts.rb".freeze, "lib/logging/layouts/basic.rb".freeze, "lib/logging/layouts/parseable.rb".freeze, "lib/logging/layouts/pattern.rb".freeze, "lib/logging/log_event.rb".freeze, "lib/logging/logger.rb".freeze, "lib/logging/proxy.rb".freeze, "lib/logging/rails_compat.rb".freeze, "lib/logging/repository.rb".freeze, "lib/logging/root_logger.rb".freeze, "lib/logging/utils.rb".freeze, "lib/logging/version.rb".freeze, "lib/rspec/logging_helper.rb".freeze, "lib/spec/logging_helper.rb".freeze, "logging.gemspec".freeze, "script/bootstrap".freeze, "script/console".freeze, "test/appenders/test_async_flushing.rb".freeze, "test/appenders/test_buffered_io.rb".freeze, "test/appenders/test_console.rb".freeze, "test/appenders/test_file.rb".freeze, "test/appenders/test_io.rb".freeze, "test/appenders/test_rolling_file.rb".freeze, "test/appenders/test_string_io.rb".freeze, "test/appenders/test_syslog.rb".freeze, "test/benchmark.rb".freeze, "test/layouts/test_basic.rb".freeze, "test/layouts/test_color_pattern.rb".freeze, "test/layouts/test_json.rb".freeze, "test/layouts/test_pattern.rb".freeze, "test/layouts/test_yaml.rb".freeze, "test/performance.rb".freeze, "test/setup.rb".freeze, "test/test_appender.rb".freeze, "test/test_color_scheme.rb".freeze, "test/test_filter.rb".freeze, "test/test_layout.rb".freeze, "test/test_log_event.rb".freeze, "test/test_logger.rb".freeze, "test/test_logging.rb".freeze, "test/test_mapped_diagnostic_context.rb".freeze, "test/test_nested_diagnostic_context.rb".freeze, "test/test_proxy.rb".freeze, "test/test_repository.rb".freeze, "test/test_root_logger.rb".freeze, "test/test_utils.rb".freeze]
+  s.files = [".gitignore".freeze, ".travis.yml".freeze, "History.txt".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "examples/appenders.rb".freeze, "examples/classes.rb".freeze, "examples/colorization.rb".freeze, "examples/custom_log_levels.rb".freeze, "examples/fork.rb".freeze, "examples/formatting.rb".freeze, "examples/hierarchies.rb".freeze, "examples/layouts.rb".freeze, "examples/lazy.rb".freeze, "examples/loggers.rb".freeze, "examples/mdc.rb".freeze, "examples/names.rb".freeze, "examples/rails4.rb".freeze, "examples/reusing_layouts.rb".freeze, "examples/rspec_integration.rb".freeze, "examples/simple.rb".freeze, "lib/logging.rb".freeze, "lib/logging/appender.rb".freeze, "lib/logging/appenders.rb".freeze, "lib/logging/appenders/buffering.rb".freeze, "lib/logging/appenders/console.rb".freeze, "lib/logging/appenders/file.rb".freeze, "lib/logging/appenders/io.rb".freeze, "lib/logging/appenders/rolling_file.rb".freeze, "lib/logging/appenders/string_io.rb".freeze, "lib/logging/appenders/syslog.rb".freeze, "lib/logging/color_scheme.rb".freeze, "lib/logging/diagnostic_context.rb".freeze, "lib/logging/filter.rb".freeze, "lib/logging/filters.rb".freeze, "lib/logging/filters/level.rb".freeze, "lib/logging/layout.rb".freeze, "lib/logging/layouts.rb".freeze, "lib/logging/layouts/basic.rb".freeze, "lib/logging/layouts/parseable.rb".freeze, "lib/logging/layouts/pattern.rb".freeze, "lib/logging/log_event.rb".freeze, "lib/logging/logger.rb".freeze, "lib/logging/proxy.rb".freeze, "lib/logging/rails_compat.rb".freeze, "lib/logging/repository.rb".freeze, "lib/logging/root_logger.rb".freeze, "lib/logging/utils.rb".freeze, "lib/logging/version.rb".freeze, "lib/rspec/logging_helper.rb".freeze, "lib/spec/logging_helper.rb".freeze, "logging.gemspec".freeze, "script/bootstrap".freeze, "script/console".freeze, "test/appenders/test_async_flushing.rb".freeze, "test/appenders/test_buffered_io.rb".freeze, "test/appenders/test_console.rb".freeze, "test/appenders/test_file.rb".freeze, "test/appenders/test_io.rb".freeze, "test/appenders/test_rolling_file.rb".freeze, "test/appenders/test_string_io.rb".freeze, "test/appenders/test_syslog.rb".freeze, "test/benchmark.rb".freeze, "test/layouts/test_basic.rb".freeze, "test/layouts/test_color_pattern.rb".freeze, "test/layouts/test_json.rb".freeze, "test/layouts/test_nested_exceptions.rb".freeze, "test/layouts/test_pattern.rb".freeze, "test/layouts/test_yaml.rb".freeze, "test/performance.rb".freeze, "test/setup.rb".freeze, "test/test_appender.rb".freeze, "test/test_color_scheme.rb".freeze, "test/test_filter.rb".freeze, "test/test_layout.rb".freeze, "test/test_log_event.rb".freeze, "test/test_logger.rb".freeze, "test/test_logging.rb".freeze, "test/test_mapped_diagnostic_context.rb".freeze, "test/test_nested_diagnostic_context.rb".freeze, "test/test_proxy.rb".freeze, "test/test_repository.rb".freeze, "test/test_root_logger.rb".freeze, "test/test_utils.rb".freeze]
   s.homepage = "http://rubygems.org/gems/logging".freeze
   s.rdoc_options = ["--main".freeze, "README.md".freeze]
-  s.rubyforge_project = "logging".freeze
-  s.rubygems_version = "2.5.2".freeze
+  s.rubygems_version = "3.0.1".freeze
   s.summary = "A flexible and extendable logging library for Ruby".freeze
-  s.test_files = ["test/appenders/test_async_flushing.rb".freeze, "test/appenders/test_buffered_io.rb".freeze, "test/appenders/test_console.rb".freeze, "test/appenders/test_file.rb".freeze, "test/appenders/test_io.rb".freeze, "test/appenders/test_rolling_file.rb".freeze, "test/appenders/test_string_io.rb".freeze, "test/appenders/test_syslog.rb".freeze, "test/layouts/test_basic.rb".freeze, "test/layouts/test_color_pattern.rb".freeze, "test/layouts/test_json.rb".freeze, "test/layouts/test_pattern.rb".freeze, "test/layouts/test_yaml.rb".freeze, "test/test_appender.rb".freeze, "test/test_color_scheme.rb".freeze, "test/test_filter.rb".freeze, "test/test_layout.rb".freeze, "test/test_log_event.rb".freeze, "test/test_logger.rb".freeze, "test/test_logging.rb".freeze, "test/test_mapped_diagnostic_context.rb".freeze, "test/test_nested_diagnostic_context.rb".freeze, "test/test_proxy.rb".freeze, "test/test_repository.rb".freeze, "test/test_root_logger.rb".freeze, "test/test_utils.rb".freeze]
+  s.test_files = ["test/appenders/test_async_flushing.rb".freeze, "test/appenders/test_buffered_io.rb".freeze, "test/appenders/test_console.rb".freeze, "test/appenders/test_file.rb".freeze, "test/appenders/test_io.rb".freeze, "test/appenders/test_rolling_file.rb".freeze, "test/appenders/test_string_io.rb".freeze, "test/appenders/test_syslog.rb".freeze, "test/layouts/test_basic.rb".freeze, "test/layouts/test_color_pattern.rb".freeze, "test/layouts/test_json.rb".freeze, "test/layouts/test_nested_exceptions.rb".freeze, "test/layouts/test_pattern.rb".freeze, "test/layouts/test_yaml.rb".freeze, "test/test_appender.rb".freeze, "test/test_color_scheme.rb".freeze, "test/test_filter.rb".freeze, "test/test_layout.rb".freeze, "test/test_log_event.rb".freeze, "test/test_logger.rb".freeze, "test/test_logging.rb".freeze, "test/test_mapped_diagnostic_context.rb".freeze, "test/test_nested_diagnostic_context.rb".freeze, "test/test_proxy.rb".freeze, "test/test_repository.rb".freeze, "test/test_root_logger.rb".freeze, "test/test_utils.rb".freeze]
 
   if s.respond_to? :specification_version then
     s.specification_version = 4
 
     if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
       s.add_runtime_dependency(%q<little-plugger>.freeze, ["~> 1.1"])
-      s.add_runtime_dependency(%q<multi_json>.freeze, ["~> 1.10"])
-      s.add_development_dependency(%q<test-unit>.freeze, ["~> 3.1"])
+      s.add_runtime_dependency(%q<multi_json>.freeze, ["~> 1.14"])
+      s.add_development_dependency(%q<test-unit>.freeze, ["~> 3.3"])
       s.add_development_dependency(%q<bones-git>.freeze, ["~> 1.3"])
       s.add_development_dependency(%q<bones>.freeze, [">= 3.8.4"])
     else
       s.add_dependency(%q<little-plugger>.freeze, ["~> 1.1"])
-      s.add_dependency(%q<multi_json>.freeze, ["~> 1.10"])
-      s.add_dependency(%q<test-unit>.freeze, ["~> 3.1"])
+      s.add_dependency(%q<multi_json>.freeze, ["~> 1.14"])
+      s.add_dependency(%q<test-unit>.freeze, ["~> 3.3"])
       s.add_dependency(%q<bones-git>.freeze, ["~> 1.3"])
       s.add_dependency(%q<bones>.freeze, [">= 3.8.4"])
     end
   else
     s.add_dependency(%q<little-plugger>.freeze, ["~> 1.1"])
-    s.add_dependency(%q<multi_json>.freeze, ["~> 1.10"])
-    s.add_dependency(%q<test-unit>.freeze, ["~> 3.1"])
+    s.add_dependency(%q<multi_json>.freeze, ["~> 1.14"])
+    s.add_dependency(%q<test-unit>.freeze, ["~> 3.3"])
     s.add_dependency(%q<bones-git>.freeze, ["~> 1.3"])
     s.add_dependency(%q<bones>.freeze, [">= 3.8.4"])
   end
diff --git a/test/appenders/test_buffered_io.rb b/test/appenders/test_buffered_io.rb
index 62642b4..3a62c25 100644
--- a/test/appenders/test_buffered_io.rb
+++ b/test/appenders/test_buffered_io.rb
@@ -194,24 +194,22 @@ module TestAppenders
       assert_nil(readline)
     end
 
-    if Object.const_defined?(:Encoding)
-      def test_force_encoding
-        a = 'ümlaut'
-        b = 'hello ümlaut'.force_encoding('BINARY')
-
-        event_a = Logging::LogEvent.new('TestLogger', @levels['info'], a, false)
-        event_b = Logging::LogEvent.new('TestLogger', @levels['info'], b, false)
-
-        @appender.append event_a
-        @appender.append event_b
-        assert_nil(readline)
-
-        @appender.append event_a
-        assert_equal " INFO  TestLogger : #{a}\n", readline
-        assert_equal " INFO  TestLogger : #{b.force_encoding('UTF-8')}\n", readline
-        assert_equal " INFO  TestLogger : #{a}\n", readline
-        assert_nil(readline)
-      end
+    def test_force_encoding
+      a = 'ümlaut'
+      b = 'hello ümlaut'.force_encoding('BINARY')
+
+      event_a = Logging::LogEvent.new('TestLogger', @levels['info'], a, false)
+      event_b = Logging::LogEvent.new('TestLogger', @levels['info'], b, false)
+
+      @appender.append event_a
+      @appender.append event_b
+      assert_nil(readline)
+
+      @appender.append event_a
+      assert_equal " INFO  TestLogger : #{a}\n", readline
+      assert_equal " INFO  TestLogger : #{b.force_encoding('UTF-8')}\n", readline
+      assert_equal " INFO  TestLogger : #{a}\n", readline
+      assert_nil(readline)
     end
 
   private
diff --git a/test/appenders/test_console.rb b/test/appenders/test_console.rb
index 7bac4f2..175a6d6 100644
--- a/test/appenders/test_console.rb
+++ b/test/appenders/test_console.rb
@@ -20,11 +20,10 @@ module TestAppenders
 
       appender = Logging.appenders.stdout
       assert_equal 'stdout', appender.name
-      assert_same STDOUT, appender.instance_variable_get(:@io)
 
-      appender.close
-      assert_equal true, appender.closed?
-      assert_equal false, STDOUT.closed?
+      io = appender.instance_variable_get(:@io)
+      assert_same STDOUT, io
+      assert_equal STDOUT.fileno, io.fileno
 
       appender = Logging.appenders.stdout('foo')
       assert_equal 'foo', appender.name
@@ -38,7 +37,26 @@ module TestAppenders
       assert_equal 3, appender.level
     end
 
-  end  # class TestStdout
+    def test_reopen
+      Logging::Repository.instance
+
+      appender = Logging.appenders.stdout
+      io = appender.instance_variable_get(:@io)
+
+      appender.close
+      assert appender.closed?
+      refute io.closed?
+      refute STDOUT.closed?
+
+      appender.reopen
+      refute appender.closed?
+
+      new_io = appender.instance_variable_get(:@io)
+      assert_same io, new_io
+      refute new_io.closed?
+      refute io.closed?
+    end
+  end
 
   class TestStderr < Test::Unit::TestCase
     include LoggingTestCase
@@ -48,11 +66,10 @@ module TestAppenders
 
       appender = Logging.appenders.stderr
       assert_equal 'stderr', appender.name
-      assert_same STDERR, appender.instance_variable_get(:@io)
 
-      appender.close
-      assert_equal true, appender.closed?
-      assert_equal false, STDERR.closed?
+      io = appender.instance_variable_get(:@io)
+      assert_same STDERR, io
+      assert_equal STDERR.fileno, io.fileno
 
       appender = Logging.appenders.stderr('foo')
       assert_equal 'foo', appender.name
@@ -66,8 +83,26 @@ module TestAppenders
       assert_equal 3, appender.level
     end
 
-  end  # class TestStderr
+    def test_reopen
+      Logging::Repository.instance
 
-end  # module TestAppenders
-end  # module TestLogging
+      appender = Logging.appenders.stderr
+      io = appender.instance_variable_get(:@io)
+
+      appender.close
+      assert appender.closed?
+      refute io.closed?
+      refute STDERR.closed?
+
+      appender.reopen
+      refute appender.closed?
+
+      new_io = appender.instance_variable_get(:@io)
+      assert_same io, new_io
+      refute new_io.closed?
+      refute io.closed?
+    end
+  end
+end
+end
 
diff --git a/test/appenders/test_file.rb b/test/appenders/test_file.rb
index b084057..3d7e12a 100644
--- a/test/appenders/test_file.rb
+++ b/test/appenders/test_file.rb
@@ -14,10 +14,10 @@ module TestAppenders
       super
       Logging.init
 
-      FileUtils.mkdir [File.join(TMP, 'dir'), File.join(TMP, 'uw_dir')]
-      FileUtils.chmod 0555, File.join(TMP, 'uw_dir')
-      FileUtils.touch File.join(TMP, 'uw_file')
-      FileUtils.chmod 0444, File.join(TMP, 'uw_file')
+      FileUtils.mkdir [File.join(@tmpdir, 'dir'), File.join(@tmpdir, 'uw_dir')]
+      FileUtils.chmod 0555, File.join(@tmpdir, 'uw_dir')
+      FileUtils.touch File.join(@tmpdir, 'uw_file')
+      FileUtils.chmod 0444, File.join(@tmpdir, 'uw_file')
     end
 
     def test_factory_method_validates_input
@@ -27,27 +27,27 @@ module TestAppenders
     end
 
     def test_class_assert_valid_logfile
-      log = File.join(TMP, 'uw_dir', 'file.log')
+      log = File.join(@tmpdir, 'uw_dir', 'file.log')
       assert_raise(ArgumentError) do
         Logging.appenders.file(log).class.assert_valid_logfile(log)
       end
 
-      log = File.join(TMP, 'dir')
+      log = File.join(@tmpdir, 'dir')
       assert_raise(ArgumentError) do
         Logging.appenders.file(log).class.assert_valid_logfile(log)
       end
 
-      log = File.join(TMP, 'uw_file')
+      log = File.join(@tmpdir, 'uw_file')
       assert_raise(ArgumentError) do
         Logging.appenders.file(log).class.assert_valid_logfile(log)
       end
 
-      log = File.join(TMP, 'file.log')
+      log = File.join(@tmpdir, 'file.log')
       assert Logging.appenders.file(log).class.assert_valid_logfile(log)
     end
 
     def test_initialize
-      log = File.join(TMP, 'file.log')
+      log = File.join(@tmpdir, 'file.log')
       appender = Logging.appenders.file(NAME, :filename => log)
       assert_equal 'logfile', appender.name
       assert_equal ::File.expand_path(log), appender.filename
@@ -87,7 +87,7 @@ module TestAppenders
     end
 
     def test_changing_directories
-      log = File.join(TMP, 'file.log')
+      log = File.join(@tmpdir, 'file.log')
       appender = Logging.appenders.file(NAME, :filename => log)
 
       assert_equal 'logfile', appender.name
@@ -95,29 +95,45 @@ module TestAppenders
 
       begin
         pwd = Dir.pwd
-        Dir.chdir TMP
+        Dir.chdir @tmpdir
         assert_nothing_raised { appender.reopen }
       ensure
         Dir.chdir pwd
       end
     end
 
-    if Object.const_defined? :Encoding
+    def test_encoding
+      log = File.join(@tmpdir, 'file-encoding.log')
+      appender = Logging.appenders.file(NAME, :filename => log, :encoding => 'ASCII')
 
-      def test_encoding
-        log = File.join(TMP, 'file-encoding.log')
-        appender = Logging.appenders.file(NAME, :filename => log, :encoding => 'ASCII')
+      appender << "A normal line of text\n"
+      appender << "ümlaut\n"
+      appender.close
 
-        appender << "A normal line of text\n"
-        appender << "ümlaut\n"
-        appender.close
+      lines = File.readlines(log, :encoding => 'UTF-8')
+      assert_equal "A normal line of text\n", lines[0]
+      assert_equal "ümlaut\n", lines[1]
 
-        lines = File.readlines(log, :encoding => 'UTF-8')
-        assert_equal "A normal line of text\n", lines[0]
-        assert_equal "ümlaut\n", lines[1]
+      cleanup
+    end
+
+    def test_reopening_should_not_truncate_the_file
+      log = File.join(@tmpdir, 'truncate.log')
+      appender = Logging.appenders.file(NAME, filename: log, truncate: true)
+
+      appender << "This will be the first line\n"
+      appender << "This will be the second line\n"
+      appender << "This will be the third line\n"
+      appender.reopen
 
-        cleanup
+      File.open(log, 'r') do |file|
+        assert_equal "This will be the first line\n", file.readline
+        assert_equal "This will be the second line\n", file.readline
+        assert_equal "This will be the third line\n", file.readline
+        assert_raise(EOFError) {file.readline}
       end
+
+      cleanup
     end
 
   private
@@ -127,7 +143,6 @@ module TestAppenders
         Logging.appenders[NAME] = nil
       end
     end
-
   end  # TestFile
 
 end  # TestAppenders
diff --git a/test/appenders/test_rolling_file.rb b/test/appenders/test_rolling_file.rb
index f9f5043..6d0d90e 100644
--- a/test/appenders/test_rolling_file.rb
+++ b/test/appenders/test_rolling_file.rb
@@ -13,9 +13,9 @@ module TestAppenders
       super
       Logging.init
 
-      @fn = File.expand_path('test.log', TMP)
-      @fn_fmt = File.expand_path('test.%d.log', TMP)
-      @glob = File.expand_path('*.log', TMP)
+      @fn = File.expand_path('test.log', @tmpdir)
+      @fn_fmt = File.expand_path('test.%d.log', @tmpdir)
+      @glob = File.expand_path('*.log', @tmpdir)
     end
 
     def test_factory_method_validates_input
@@ -93,8 +93,8 @@ module TestAppenders
     end
 
     def test_age
-      d_glob = File.join(TMP, 'test.*.log')
-      dt_glob = File.join(TMP, 'test.*-*.log')
+      d_glob = File.join(@tmpdir, 'test.*.log')
+      dt_glob = File.join(@tmpdir, 'test.*-*.log')
       age_fn = @fn + '.age'
 
       assert_equal [], Dir.glob(@glob)
@@ -205,7 +205,7 @@ module TestAppenders
 
       begin
         pwd = Dir.pwd
-        Dir.chdir TMP
+        Dir.chdir @tmpdir
 
         ap << 'X' * 100; ap.flush
         assert_equal 1, Dir.glob(@glob).length
@@ -249,9 +249,9 @@ module TestAppenders
     end
 
     def test_custom_numberd_filename
-      fn = File.expand_path('test.log{{.%d}}', TMP)
-      filename = File.expand_path('test.log', TMP)
-      glob = File.expand_path('test.log.*', TMP)
+      fn = File.expand_path('test.log{{.%d}}', @tmpdir)
+      filename = File.expand_path('test.log', @tmpdir)
+      glob = File.expand_path('test.log.*', @tmpdir)
 
       assert_equal [], Dir.glob(glob)
       ap = Logging.appenders.rolling_file(NAME, :filename => fn, :size => 100, :keep => 2)
@@ -285,10 +285,10 @@ module TestAppenders
     end
 
     def test_custom_timestamp_filename
-      fn = File.expand_path('test{{.%S:%M}}.log', TMP)
-      filename = File.expand_path('test.log', TMP)
+      fn = File.expand_path('test{{.%S:%M}}.log', @tmpdir)
+      filename = File.expand_path('test.log', @tmpdir)
       age_file = filename + '.age'
-      glob = File.expand_path('test.*.log', TMP)
+      glob = File.expand_path('test.*.log', @tmpdir)
 
       assert_equal [], Dir.glob(glob)
       ap = Logging.appenders.rolling_file(NAME, :filename => fn, :age => 1, :keep => 2)
diff --git a/test/layouts/test_json.rb b/test/layouts/test_json.rb
index 9e4c548..d750108 100644
--- a/test/layouts/test_json.rb
+++ b/test/layouts/test_json.rb
@@ -78,7 +78,7 @@ module TestLayouts
                                     'log message', false)
       event.file = 'test_file.rb'
       event.line = 123
-      event.method = 'method_name'
+      event.method_name = 'method_name'
 
       @layout.items = %w[logger]
       assert_equal %Q[{"logger":"TestLogger"}\n], @layout.format(event)
diff --git a/test/layouts/test_nested_exceptions.rb b/test/layouts/test_nested_exceptions.rb
index ef66f96..e784d95 100644
--- a/test/layouts/test_nested_exceptions.rb
+++ b/test/layouts/test_nested_exceptions.rb
@@ -19,7 +19,7 @@ module TestLogging
         end
 
         layout = Logging.layouts.basic({})
-        log = layout.format_obj(e)
+        log = layout.format_obj(err)
         assert_not_nil log.index('<StandardError> root exception')
 
         if err.respond_to?(:cause)
@@ -45,7 +45,7 @@ module TestLogging
         end
 
         layout = Logging.layouts.basic(cause_depth: 1)
-        log = layout.format_obj(e)
+        log = layout.format_obj(err)
         assert_not_nil log.index('<StandardError> root exception')
 
         if err.respond_to?(:cause)
@@ -68,12 +68,12 @@ module TestLogging
         end
 
         layout = Logging.layouts.parseable.new
-        log = layout.format_obj(e)
+        log = layout.format_obj(err)
         assert_equal 'StandardError', log[:class]
         assert_equal 'root exception', log[:message]
         assert log[:backtrace].size > 0
 
-        if e.respond_to?(:cause)
+        if err.respond_to?(:cause)
           assert_not_nil log[:cause]
 
           log = log[:cause]
@@ -101,13 +101,13 @@ module TestLogging
         end
 
         layout = Logging.layouts.parseable.new(cause_depth: 1)
-        log = layout.format_obj(e)
+        log = layout.format_obj(err)
 
         assert_equal 'StandardError', log[:class]
         assert_equal 'root exception', log[:message]
         assert log[:backtrace].size > 0
 
-        if e.respond_to?(:cause)
+        if err.respond_to?(:cause)
           assert_not_nil log[:cause]
 
           log = log[:cause]
diff --git a/test/layouts/test_pattern.rb b/test/layouts/test_pattern.rb
index 78fe9e2..1e22cfd 100644
--- a/test/layouts/test_pattern.rb
+++ b/test/layouts/test_pattern.rb
@@ -105,7 +105,7 @@ module TestLayouts
                                     'log message', false)
       event.file = 'test_file.rb'
       event.line = '123'
-      event.method = 'method_name'
+      event.method_name = 'method_name'
 
       @layout.pattern = '%c'
       assert_equal 'TestLogger', @layout.format(event)
diff --git a/test/layouts/test_yaml.rb b/test/layouts/test_yaml.rb
index 8df420d..7471893 100644
--- a/test/layouts/test_yaml.rb
+++ b/test/layouts/test_yaml.rb
@@ -68,7 +68,7 @@ module TestLayouts
                                     'log message', false)
       event.file = 'test_file.rb'
       event.line = 123
-      event.method = 'method_name'
+      event.method_name = 'method_name'
 
       @layout.items = %w[logger]
       assert_match %r/\A--- ?\nlogger: TestLogger\n/, @layout.format(event)
diff --git a/test/setup.rb b/test/setup.rb
index ef293d9..b8b8ca4 100644
--- a/test/setup.rb
+++ b/test/setup.rb
@@ -5,6 +5,12 @@ LOGGING_TEST_SETUP = true
 
 require "rubygems"
 require "test/unit"
+require "tmpdir"
+
+LOGGING_TEST_TMPDIR = Dir.mktmpdir("logging")
+Test::Unit.at_exit do
+  FileUtils.remove_entry(LOGGING_TEST_TMPDIR)
+end
 
 if Test::Unit::TestCase.respond_to? :test_order=
   Test::Unit::TestCase.test_order = :random
@@ -15,18 +21,16 @@ require File.expand_path("../../lib/logging", __FILE__)
 module TestLogging
   module LoggingTestCase
 
-    TMP = 'tmp'
-
     def setup
       super
       Logging.reset
-      FileUtils.rm_rf TMP
-      FileUtils.mkdir TMP
+      @tmpdir = LOGGING_TEST_TMPDIR
+      FileUtils.rm_rf(Dir.glob(File.join(@tmpdir, "*")))
     end
 
     def teardown
       super
-      FileUtils.rm_rf TMP
+      FileUtils.rm_rf(Dir.glob(File.join(@tmpdir, "*")))
     end
   end
 end
diff --git a/test/test_log_event.rb b/test/test_log_event.rb
index eb6523e..53c6dcb 100644
--- a/test/test_log_event.rb
+++ b/test/test_log_event.rb
@@ -68,12 +68,12 @@ module TestLogging
       assert_equal 'MyLogger', @event.logger
     end
 
-    def test_method
+    def test_method_name
       assert_equal '', @event.file
 
       @logger.caller_tracing = true
       @logger.debug 'debug message'
-      assert_equal 'test_method', @appender.event.method
+      assert_equal 'test_method_name', @appender.event.method_name
     end
 
   end  # class TestLogEvent
diff --git a/test/test_logging.rb b/test/test_logging.rb
index f8548ba..e0ca6c3 100644
--- a/test/test_logging.rb
+++ b/test/test_logging.rb
@@ -11,8 +11,8 @@ module TestLogging
       @levels = ::Logging::LEVELS
       @lnames = ::Logging::LNAMES
 
-      @fn = File.join(TMP, 'test.log')
-      @glob = File.join(TMP, '*.log')
+      @fn = File.join(@tmpdir, 'test.log')
+      @glob = File.join(@tmpdir, '*.log')
     end
 
     def test_backtrace
@@ -253,6 +253,31 @@ module TestLogging
       assert_match %r/\d+\.\d+\.\d+/, ::Logging.version
     end
 
-  end  # class TestLogging
-end  # module TestLogging
+    class Failer
+      class WriteError < StandardError ; end
+      def self.write(*args)
+        raise WriteError.new("Oh noooooo")
+      end
+    end
 
+    def test_error_handling
+      logger = ::Logging.logger Failer, 2, 100
+      logger.appenders.first.level = :debug
+
+      # No errors are raised by default
+      logger.fatal 'this is a debug message'
+      # Always reset the level; we disable appenders that raise by setting them
+      # to :off
+      logger.appenders.first.level = :debug
+
+      begin
+        Logging.raise_errors = true
+        assert_raises Failer::WriteError do
+          logger.fatal 'this fails because the file descriptor is closed'
+        end
+      ensure
+        Logging.raise_errors = false
+      end
+    end
+  end
+end

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/logging-2.3.0.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/logging-2.1.0.48.gemspec

No differences were encountered in the control files

More details

Full run details