diff --git a/CHANGES.rst b/CHANGES.rst
index 0802c08..4fb65de 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,10 +2,13 @@
 Change History for ZConfig
 ==========================
 
-3.3.1 (unreleased)
+3.4.0 (unreleased)
 ------------------
 
-- Nothing changed yet.
+- The ``logfile`` section type defined by the ``ZConfig.components.logger``
+  package supports the optional ``delay`` and ``encoding`` parameters.
+  These can only be used for regular files, not the special ``STDOUT``
+  and ``STDERR`` streams.
 
 
 3.3.0 (2018-10-04)
diff --git a/ZConfig/components/logger/handlers.py b/ZConfig/components/logger/handlers.py
index 4b9c2e8..f436749 100644
--- a/ZConfig/components/logger/handlers.py
+++ b/ZConfig/components/logger/handlers.py
@@ -110,13 +110,22 @@ class FileHandlerFactory(HandlerFactory):
         old_files = self.section.old_files
         when = self.section.when
         interval = self.section.interval
-        if path == "STDERR":
+        encoding = self.section.encoding
+        delay = self.section.delay
+
+        def check_std_stream():
             if max_bytes or old_files:
-                raise ValueError("cannot rotate STDERR")
+                raise ValueError("cannot rotate " + path)
+            if delay:
+                raise ValueError("cannot delay opening " + path)
+            if encoding:
+                raise ValueError("cannot specify encoding for " + path)
+
+        if path == "STDERR":
+            check_std_stream()
             handler = loghandler.StreamHandler(sys.stderr)
         elif path == "STDOUT":
-            if max_bytes or old_files:
-                raise ValueError("cannot rotate STDOUT")
+            check_std_stream()
             handler = loghandler.StreamHandler(sys.stdout)
         elif when or max_bytes or old_files or interval:
             if not old_files:
@@ -128,15 +137,17 @@ class FileHandlerFactory(HandlerFactory):
                     interval = 1
                 handler = loghandler.TimedRotatingFileHandler(
                     path, when=when, interval=interval,
-                    backupCount=old_files)
+                    backupCount=old_files, encoding=encoding, delay=delay)
             elif max_bytes:
                 handler = loghandler.RotatingFileHandler(
-                    path, maxBytes=max_bytes, backupCount=old_files)
+                    path, maxBytes=max_bytes, backupCount=old_files,
+                    encoding=encoding, delay=delay)
             else:
                 raise ValueError(
                     "max-bytes or when must be set for log rotation")
         else:
-            handler = loghandler.FileHandler(path)
+            handler = loghandler.FileHandler(
+                path, encoding=encoding, delay=delay)
         return handler
 
 
diff --git a/ZConfig/components/logger/handlers.xml b/ZConfig/components/logger/handlers.xml
index 39491e1..d0d1425 100644
--- a/ZConfig/components/logger/handlers.xml
+++ b/ZConfig/components/logger/handlers.xml
@@ -44,6 +44,26 @@
     <key name="format"
          default="------\n%(asctime)s %(levelname)s %(name)s %(message)s"
          datatype=".log_format"/>
+    <key name="encoding" required="no" datatype="string">
+      <description>
+        Encoding for the underlying file.
+
+        If not specified, Python's default encoding handling is used.
+
+        This cannot be specified for STDOUT or STDERR destinations, and
+        must be omitted in such cases.
+      </description>
+    </key>
+    <key name="delay" required="no" default="false" datatype="boolean">
+      <description>
+        If true, opening of the log file will be delayed until a message
+        is emitted.  This avoids creating logfiles that may only be
+        written to rarely or under special conditions.
+
+        This cannot be specified for STDOUT or STDERR destinations, and
+        must be omitted in such cases.
+      </description>
+    </key>
   </sectiontype>
 
   <sectiontype name="syslog"
diff --git a/ZConfig/components/logger/loghandler.py b/ZConfig/components/logger/loghandler.py
index 2652b28..ddd042b 100644
--- a/ZConfig/components/logger/loghandler.py
+++ b/ZConfig/components/logger/loghandler.py
@@ -61,29 +61,26 @@ def _remove_from_reopenable(wr):
         pass
 
 
-class FileHandler(logging.StreamHandler):
+class FileHandler(logging.FileHandler):
     """File handler which supports reopening of logs.
 
     Re-opening should be used instead of the 'rollover' feature of
     the FileHandler from the standard library's logging package.
     """
 
-    def __init__(self, filename, mode="a"):
-        filename = os.path.abspath(filename)
-        logging.StreamHandler.__init__(self, open(filename, mode))
-        self.baseFilename = filename
-        self.mode = mode
+    def __init__(self, filename, mode="a", encoding=None, delay=False):
+        logging.FileHandler.__init__(self, filename,
+                                     mode=mode, encoding=encoding, delay=delay)
         self._wr = weakref.ref(self, _remove_from_reopenable)
         _reopenable_handlers.append(self._wr)
 
     def close(self):
-        self.stream.close()
         # This can raise a KeyError if the handler has already been
         # removed, but a later error can be raised if
         # StreamHandler.close() isn't called.  This seems the best
         # compromise.  :-(
         try:
-            logging.StreamHandler.close(self)
+            logging.FileHandler.close(self)
         except KeyError:  # pragma: no cover
             pass
         _remove_from_reopenable(self._wr)
@@ -91,8 +88,12 @@ class FileHandler(logging.StreamHandler):
     def reopen(self):
         self.acquire()
         try:
-            self.stream.close()
-            self.stream = open(self.baseFilename, self.mode)
+            if self.stream is not None:
+                self.stream.close()
+                if self.delay:
+                    self.stream = None
+                else:
+                    self.stream = self._open()
         finally:
             self.release()
 
@@ -113,7 +114,10 @@ class Win32FileHandler(FileHandler):
         except OSError:
             pass
 
-        self.stream = open(self.baseFilename, self.mode)
+        if self.delay:
+            self.stream = None
+        else:
+            self.stream = self._open()
 
 
 if os.name == "nt":
@@ -152,15 +156,9 @@ class TimedRotatingFileHandler(logging.handlers.TimedRotatingFileHandler):
         self.doRollover()
 
 
-class NullHandler(logging.Handler):
+class NullHandler(logging.NullHandler):
     """Handler that does nothing."""
 
-    def emit(self, record):
-        pass
-
-    def handle(self, record):
-        pass
-
 
 class StartupHandler(logging.handlers.BufferingHandler):
     """Handler which stores messages in a buffer until later.
diff --git a/ZConfig/info.py b/ZConfig/info.py
index 525d12b..33f8dfb 100644
--- a/ZConfig/info.py
+++ b/ZConfig/info.py
@@ -35,7 +35,7 @@ class UnboundedThing(object):
     def __eq__(self, other):
         return isinstance(other, self.__class__)
 
-    def __repr__(self):  # pragma: no cover
+    def __repr__(self):
         return "<Unbounded>"
 
 
diff --git a/setup.py b/setup.py
index a837214..35d87ca 100644
--- a/setup.py
+++ b/setup.py
@@ -18,7 +18,7 @@ tests_require = [
 
 options = dict(
     name="ZConfig",
-    version='3.3.1.dev0',
+    version='3.4.0.dev0',
     author="Fred L. Drake, Jr.",
     author_email="fred@fdrake.net",
     maintainer="Zope Foundation and Contributors",
diff --git a/tox.ini b/tox.ini
index fdec36b..7a9860b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -23,3 +23,14 @@ commands =
     coverage combine
     coverage html
     coverage report -m --fail-under 99
+
+[testenv:docs]
+# basepython not specified; we want to be sure it's something available
+# on the current system.
+deps =
+    sphinx
+    sphinxcontrib-programoutput
+commands =
+    make -C doc html
+whitelist_externals =
+    make

diff --git a/ZConfig/components/logger/tests/test_logger.py b/ZConfig/components/logger/tests/test_logger.py
index ced3b66..30b5441 100644
--- a/ZConfig/components/logger/tests/test_logger.py
+++ b/ZConfig/components/logger/tests/test_logger.py
@@ -170,6 +170,45 @@ class TestConfig(LoggingTestHelper, unittest.TestCase):
         logfile = logger.handlers[0]
         self.assertEqual(logfile.level, logging.DEBUG)
         self.assertTrue(isinstance(logfile, loghandler.FileHandler))
+        self.assertFalse(logfile.delay)
+        self.assertIsNotNone(logfile.stream)
+        logger.removeHandler(logfile)
+        logfile.close()
+
+    def test_with_encoded(self):
+        fn = self.mktemp()
+        logger = self.check_simple_logger("<eventlog>\n"
+                                          "  <logfile>\n"
+                                          "    path %s\n"
+                                          "    level debug\n"
+                                          "    encoding shift-jis\n"
+                                          "  </logfile>\n"
+                                          "</eventlog>" % fn)
+        logfile = logger.handlers[0]
+        self.assertEqual(logfile.level, logging.DEBUG)
+        self.assertTrue(isinstance(logfile, loghandler.FileHandler))
+        self.assertFalse(logfile.delay)
+        self.assertIsNotNone(logfile.stream)
+        self.assertEqual(logfile.stream.encoding, "shift-jis")
+        logger.removeHandler(logfile)
+        logfile.close()
+
+    def test_with_logfile_delayed(self):
+        fn = self.mktemp()
+        logger = self.check_simple_logger("<eventlog>\n"
+                                          "  <logfile>\n"
+                                          "    path %s\n"
+                                          "    level debug\n"
+                                          "    delay true\n"
+                                          "  </logfile>\n"
+                                          "</eventlog>" % fn)
+        logfile = logger.handlers[0]
+        self.assertEqual(logfile.level, logging.DEBUG)
+        self.assertTrue(isinstance(logfile, loghandler.FileHandler))
+        self.assertTrue(logfile.delay)
+        self.assertIsNone(logfile.stream)
+        logger.info("this is a test")
+        self.assertIsNotNone(logfile.stream)
         logger.removeHandler(logfile)
         logfile.close()
 
@@ -179,6 +218,18 @@ class TestConfig(LoggingTestHelper, unittest.TestCase):
     def test_with_stdout(self):
         self.check_standard_stream("stdout")
 
+    def test_delayed_stderr(self):
+        self.check_standard_stream_cannot_delay("stderr")
+
+    def test_delayed_stdout(self):
+        self.check_standard_stream_cannot_delay("stdout")
+
+    def test_encoded_stderr(self):
+        self.check_standard_stream_cannot_encode("stderr")
+
+    def test_encoded_stdout(self):
+        self.check_standard_stream_cannot_encode("stdout")
+
     def test_with_rotating_logfile(self):
         fn = self.mktemp()
         logger = self.check_simple_logger("<eventlog>\n"
@@ -296,6 +347,50 @@ class TestConfig(LoggingTestHelper, unittest.TestCase):
         logger.warning("woohoo!")
         self.assertTrue(sio.getvalue().find("woohoo!") >= 0)
 
+    def check_standard_stream_cannot_delay(self, name):
+        old_stream = getattr(sys, name)
+        conf = self.get_config("""
+            <eventlog>
+              <logfile>
+                level info
+                path %s
+                delay true
+              </logfile>
+            </eventlog>
+            """ % name.upper())
+        self.assertTrue(conf.eventlog is not None)
+        sio = StringIO()
+        setattr(sys, name, sio)
+        try:
+            with self.assertRaises(ValueError) as cm:
+                conf.eventlog()
+            self.assertIn("cannot delay opening %s" % name.upper(),
+                          str(cm.exception))
+        finally:
+            setattr(sys, name, old_stream)
+
+    def check_standard_stream_cannot_encode(self, name):
+        old_stream = getattr(sys, name)
+        conf = self.get_config("""
+            <eventlog>
+              <logfile>
+                level info
+                path %s
+                encoding utf-8
+              </logfile>
+            </eventlog>
+            """ % name.upper())
+        self.assertTrue(conf.eventlog is not None)
+        sio = StringIO()
+        setattr(sys, name, sio)
+        try:
+            with self.assertRaises(ValueError) as cm:
+                conf.eventlog()
+            self.assertIn("cannot specify encoding for %s" % name.upper(),
+                          str(cm.exception))
+        finally:
+            setattr(sys, name, old_stream)
+
     def test_custom_formatter(self):
         old_stream = sys.stdout
         conf = self.get_config("""
@@ -680,9 +775,43 @@ class TestReopeningLogfiles(TestReopeningRotatingLogfiles):
         h.release = lambda: calls.append("release")
 
         h.reopen()
+        self.assertEqual(calls, ["acquire", "release"])
+        del calls[:]
+
+        # FileHandler.close() does acquire/release, and invokes
+        # StreamHandler.flush(), which does the same. Since the lock is
+        # recursive, that's ok.
+        #
         h.close()
+        self.assertEqual(calls, ["acquire", "acquire", "release", "release"])
 
-        self.assertEqual(calls, ["acquire", "release"])
+    def test_with_logfile_delayed_reopened(self):
+        fn = self.mktemp()
+        conf = self.get_config("<logger>\n"
+                               "  <logfile>\n"
+                               "    path %s\n"
+                               "    level debug\n"
+                               "    delay true\n"
+                               "    encoding shift-jis\n"
+                               "  </logfile>\n"
+                               "</logger>" % fn)
+        logger = conf.loggers[0]()
+        logfile = logger.handlers[0]
+        self.assertTrue(logfile.delay)
+        self.assertIsNone(logfile.stream)
+        logger.info("this is a test")
+        self.assertIsNotNone(logfile.stream)
+        self.assertEqual(logfile.stream.encoding, "shift-jis")
+
+        # After reopening, we expect the stream to be reset, to be
+        # opened at the next event to be logged:
+        logfile.reopen()
+        self.assertIsNone(logfile.stream)
+        logger.info("this is another test")
+        self.assertIsNotNone(logfile.stream)
+        self.assertEqual(logfile.stream.encoding, "shift-jis")
+        logger.removeHandler(logfile)
+        logfile.close()
 
 
 class TestFunctions(TestHelper, unittest.TestCase):
diff --git a/ZConfig/tests/test_info.py b/ZConfig/tests/test_info.py
index dda2d77..145379e 100644
--- a/ZConfig/tests/test_info.py
+++ b/ZConfig/tests/test_info.py
@@ -36,6 +36,9 @@ class UnboundTestCase(unittest.TestCase):
         self.assertFalse(Unbounded > Unbounded)
         self.assertEqual(Unbounded, Unbounded)
 
+    def test_repr(self):
+        self.assertEqual(repr(Unbounded), '<Unbounded>')
+
 
 class InfoMixin(TestHelper):
 

Support delay parameter for file-based handlers
The *delay* parameter was added for all the file-based handlers from the ``logging`` package in Python 2.6, but ``ZConfig`` was not updated to support that.

We don't need to worry about pre-*delay* versions at this point, and it would be useful to have support for this.
ZConfig/components/logger/tests/test_logger.py::TestConfig::test_delayed_stderr ZConfig/components/logger/tests/test_logger.py::TestConfig::test_delayed_stdout ZConfig/components/logger/tests/test_logger.py::TestConfig::test_encoded_stderr ZConfig/components/logger/tests/test_logger.py::TestConfig::test_encoded_stdout ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_encoded ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_logfile ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_logfile_delayed ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_rotating_logfile ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_rotating_logfile_and_STD_should_fail ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_stderr ZConfig/components/logger/tests/test_logger.py::TestReopeningLogfiles::test_filehandler_reopen_thread_safety ZConfig/components/logger/tests/test_logger.py::TestReopeningLogfiles::test_with_logfile_delayed_reopened
ZConfig/components/logger/tests/test_logger.py::TestConfig::test_config_without_handlers ZConfig/components/logger/tests/test_logger.py::TestConfig::test_config_without_logger ZConfig/components/logger/tests/test_logger.py::TestConfig::test_custom_formatter ZConfig/components/logger/tests/test_logger.py::TestConfig::test_factory_without_stream ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_email_notifier ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_email_notifier_with_credentials ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_email_notifier_with_invalid_credentials ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_http_logger_localhost ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_http_logger_remote_host ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_stdout ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_syslog ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_timed_rotating_logfile ZConfig/components/logger/tests/test_logger.py::TestConfig::test_with_timed_rotating_logfile_and_size_should_fail ZConfig/components/logger/tests/test_logger.py::TestReopeningRotatingLogfiles::test_filehandler_reopen ZConfig/components/logger/tests/test_logger.py::TestReopeningRotatingLogfiles::test_logfile_reopening ZConfig/components/logger/tests/test_logger.py::TestReopeningLogfiles::test_filehandler_reopen ZConfig/components/logger/tests/test_logger.py::TestReopeningLogfiles::test_logfile_reopening ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_close_files ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_http_handler_url ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_http_method ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_log_format_bad ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_logging_level ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_reopen_files_missing_wref ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_resolve_deep ZConfig/components/logger/tests/test_logger.py::TestFunctions::test_syslog_facility ZConfig/components/logger/tests/test_logger.py::TestStartupHandler::test_buffer ZConfig/components/logger/tests/test_logger.py::test_logger_convenience_function_and_ommiting_name_to_get_root_logger ZConfig/components/logger/tests/test_logger.py::test_suite ZConfig/tests/test_info.py::UnboundTestCase::test_order ZConfig/tests/test_info.py::UnboundTestCase::test_repr ZConfig/tests/test_info.py::BaseInfoTestCase::test_constructor_error ZConfig/tests/test_info.py::BaseInfoTestCase::test_repr ZConfig/tests/test_info.py::BaseKeyInfoTestCase::test_adddefaultc ZConfig/tests/test_info.py::BaseKeyInfoTestCase::test_cant_instantiate ZConfig/tests/test_info.py::BaseKeyInfoTestCase::test_finish ZConfig/tests/test_info.py::KeyInfoTestCase::test_add_with_default ZConfig/tests/test_info.py::SectionInfoTestCase::test_constructor_error ZConfig/tests/test_info.py::SectionInfoTestCase::test_misc ZConfig/tests/test_info.py::AbstractTypeTestCase::test_subtypes ZConfig/tests/test_info.py::SectionTypeTestCase::test_getinfo_no_key ZConfig/tests/test_info.py::SectionTypeTestCase::test_getsectioninfo ZConfig/tests/test_info.py::SectionTypeTestCase::test_required_types_with_name ZConfig/tests/test_info.py::SchemaTypeTestCase::test_various