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/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",

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.
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):

pip install -e .[test]
parse_log_pytest
pytest
coverage flake8 zope.testrunner pytest
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
coverage==6.2
docutils==0.18.1
flake8==5.0.4
importlib-metadata==4.2.0
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
manuel==1.13.0
mccabe==0.7.0
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pycodestyle==2.9.1
pyflakes==2.5.0
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
-e git+https://github.com/zopefoundation/ZConfig.git@ce8ad23032399376a972df8a69591bc717258ff5#egg=ZConfig
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work
zope.exceptions==4.6
zope.interface==5.5.2
zope.testrunner==5.6

name: ZConfig
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==6.2
      - docutils==0.18.1
      - flake8==5.0.4
      - importlib-metadata==4.2.0
      - manuel==1.13.0
      - mccabe==0.7.0
      - pycodestyle==2.9.1
      - pyflakes==2.5.0
      - six==1.17.0
      - zope-exceptions==4.6
      - zope-interface==5.5.2
      - zope-testrunner==5.6
prefix: /opt/conda/envs/ZConfig

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::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_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::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/components/logger/loghandler.py setup.py ZConfig/components/logger/handlers.xml CHANGES.rst ZConfig/components/logger/handlers.py
ZConfig/components/logger/loghandler.py setup.py ZConfig/components/logger/handlers.xml CHANGES.rst ZConfig/components/logger/handlers.py