diff --git a/.coveragerc b/.coveragerc
index b468a15..b34de8c 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,13 +1,5 @@
 [run]
 source = zope.server
-# https://github.com/zopefoundation/zope.server/issues/4
-omit =
-    src/zope/server/logger/filelogger.py
-    src/zope/server/logger/m_syslog.py
-    src/zope/server/logger/rotatingfilelogger.py
-    src/zope/server/logger/socketlogger.py
-    src/zope/server/logger/sysloglogger.py
-    src/zope/server/logger/taillogger.py
 
 [report]
 precision = 2
diff --git a/CHANGES.rst b/CHANGES.rst
index 3824c08..90d6e59 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -21,6 +21,14 @@
 
 - Achieve and maintain 100% test coverage.
 
+- Remove all the custom logging implementations in
+  ``zope.server.logging`` and change the ``CommonAccessLogger`` and
+  ``CommonFTPActivityLogger`` to only work with Python standard
+  library loggers. The standard library supports all the logging
+  functions this package used to expose. It can be easily configured
+  with ZConfig. See `issue 4
+  <https://github.com/zopefoundation/zope.server/issues/4>`_.
+
 3.9.0 (2013-03-13)
 ==================
 
diff --git a/src/zope/server/http/commonaccesslogger.py b/src/zope/server/http/commonaccesslogger.py
index 7499302..6f48303 100644
--- a/src/zope/server/http/commonaccesslogger.py
+++ b/src/zope/server/http/commonaccesslogger.py
@@ -19,22 +19,28 @@ import time
 from zope.server.http.http_date import monthname
 from zope.server.logger.pythonlogger import PythonLogger
 from zope.server.logger.resolvinglogger import ResolvingLogger
-from zope.server.logger.unresolvinglogger import UnresolvingLogger
 
 class CommonAccessLogger(object):
     """Outputs accesses in common HTTP log format.
     """
 
-    def __init__(self, logger_object=None, resolver=None):
-        if logger_object is None:
-            # logger_object is an IMessageLogger
-            logger_object = PythonLogger('accesslog')
+    def __init__(self, logger_object='accesslog', resolver=None):
+        """
 
-        # self.output is an IRequestLogger
+        :keyword logger_object: Either a Python :class:`logging.Logger`
+           object, or a string giving the name of a Python logger to find.
+
+        .. versionchanged:: 4.0.0
+           Remove support for arbitrary ``IMessageLogger`` objects in
+           *logger_object*. Logging is now always directed through the
+           Python standard logging library.
+        """
+        self.output = PythonLogger(logger_object)
+
+        # self.output is an IRequestLogger, which PythonLogger implements
+        # as unresolving.
         if resolver is not None:
-            self.output = ResolvingLogger(resolver, logger_object)
-        else:
-            self.output = UnresolvingLogger(logger_object)
+            self.output = ResolvingLogger(resolver, self.output)
 
     @classmethod
     def compute_timezone_for_log(cls, tz):
diff --git a/src/zope/server/logger/filelogger.py b/src/zope/server/logger/filelogger.py
deleted file mode 100644
index 7c76aac..0000000
--- a/src/zope/server/logger/filelogger.py
+++ /dev/null
@@ -1,67 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""File Logger
-"""
-
-from zope.server.interfaces.logger import IMessageLogger
-from zope.interface import implementer
-
-@implementer(IMessageLogger)
-class FileLogger(object):
-    """Simple File Logger
-    """
-
-    def __init__(self, file, flush=1, mode='a'):
-        """pass this either a path or a file object."""
-        if not hasattr(file, 'read'):
-            if (file == '-'):
-                import sys
-                self.file = sys.stdout
-            else:
-                self.file = open(file, mode)
-        else:
-            self.file = file
-        self.do_flush = flush
-
-    def __repr__(self):
-        return '<file logger: %s>' % self.file
-
-    def write(self, data):
-        self.file.write(data)
-        self.maybe_flush()
-
-    def writeline(self, line):
-        self.file.writeline(line)
-        self.maybe_flush()
-
-    def writelines(self, lines):
-        self.file.writelines(lines)
-        self.maybe_flush()
-
-    def maybe_flush(self):
-        if self.do_flush:
-            self.file.flush()
-
-    def flush(self):
-        self.file.flush()
-
-    def softspace(self, *args):
-        pass
-
-    def logMessage(self, message):
-        'See IMessageLogger'
-        if message[-1] not in ('\r', '\n'):
-            self.write(message + '\n')
-        else:
-            self.write(message)
diff --git a/src/zope/server/logger/m_syslog.py b/src/zope/server/logger/m_syslog.py
deleted file mode 100644
index 92c8a36..0000000
--- a/src/zope/server/logger/m_syslog.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# -*- Mode: Python; tab-width: 4 -*-
-
-# ======================================================================
-# Copyright 1997 by Sam Rushing
-#
-#                         All Rights Reserved
-#
-# Permission to use, copy, modify, and distribute this software and
-# its documentation for any purpose and without fee is hereby
-# granted, provided that the above copyright notice appear in all
-# copies and that both that copyright notice and this permission
-# notice appear in supporting documentation, and that the name of Sam
-# Rushing not be used in advertising or publicity pertaining to
-# distribution of the software without specific, written prior
-# permission.
-#
-# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
-# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
-# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-# ======================================================================
-
-"""socket interface to unix syslog.
-On Unix, there are usually two ways of getting to syslog: via a
-local unix-domain socket, or via the TCP service.
-
-Usually "/dev/log" is the unix domain socket.  This may be different
-for other systems.
-
->>> my_client = syslog_client ('/dev/log')
-
-Otherwise, just use the UDP version, port 514.
-
->>> my_client = syslog_client (('my_log_host', 514))
-
-On win32, you will have to use the UDP version.  Note that
-you can use this to log to other hosts (and indeed, multiple
-hosts).
-
-This module is not a drop-in replacement for the python
-<syslog> extension module - the interface is different.
-
-Usage:
-
->>> c = syslog_client()
->>> c = syslog_client ('/strange/non_standard_log_location')
->>> c = syslog_client (('other_host.com', 514))
->>> c.log ('testing', facility='local0', priority='debug')
-
-"""
-
-# TODO: support named-pipe syslog.
-# [see ftp://sunsite.unc.edu/pub/Linux/system/Daemons/syslog-fifo.tar.z]
-
-# from <linux/sys/syslog.h>:
-# ===========================================================================
-# priorities/facilities are encoded into a single 32-bit quantity, where the
-# bottom 3 bits are the priority (0-7) and the top 28 bits are the facility
-# (0-big number).  Both the priorities and the facilities map roughly
-# one-to-one to strings in the syslogd(8) source code.  This mapping is
-# included in this file.
-#
-# priorities (these are ordered)
-
-LOG_EMERG                = 0                #  system is unusable
-LOG_ALERT                = 1                #  action must be taken immediately
-LOG_CRIT                 = 2                #  critical conditions
-LOG_ERR                  = 3                #  error conditions
-LOG_WARNING              = 4                #  warning conditions
-LOG_NOTICE               = 5                #  normal but significant condition
-LOG_INFO                 = 6                #  informational
-LOG_DEBUG                = 7                #  debug-level messages
-
-#  facility codes
-LOG_KERN                 = 0                #  kernel messages
-LOG_USER                 = 1                #  random user-level messages
-LOG_MAIL                 = 2                #  mail system
-LOG_DAEMON               = 3                #  system daemons
-LOG_AUTH                 = 4                #  security/authorization messages
-LOG_SYSLOG               = 5                #  messages generated internally by syslogd
-LOG_LPR                  = 6                #  line printer subsystem
-LOG_NEWS                 = 7                #  network news subsystem
-LOG_UUCP                 = 8                #  UUCP subsystem
-LOG_CRON                 = 9                #  clock daemon
-LOG_AUTHPRIV             = 10               #  security/authorization messages (private)
-
-#  other codes through 15 reserved for system use
-LOG_LOCAL0               = 16               #  reserved for local use
-LOG_LOCAL1               = 17               #  reserved for local use
-LOG_LOCAL2               = 18               #  reserved for local use
-LOG_LOCAL3               = 19               #  reserved for local use
-LOG_LOCAL4               = 20               #  reserved for local use
-LOG_LOCAL5               = 21               #  reserved for local use
-LOG_LOCAL6               = 22               #  reserved for local use
-LOG_LOCAL7               = 23               #  reserved for local use
-
-priority_names = {
-    "alert":         LOG_ALERT,
-    "crit":          LOG_CRIT,
-    "debug":         LOG_DEBUG,
-    "emerg":         LOG_EMERG,
-    "err":           LOG_ERR,
-    "error":         LOG_ERR,               #  DEPRECATED
-    "info":          LOG_INFO,
-    "notice":        LOG_NOTICE,
-    "panic":         LOG_EMERG,             #  DEPRECATED
-    "warn":          LOG_WARNING,           #  DEPRECATED
-    "warning":       LOG_WARNING,
-    }
-
-facility_names = {
-    "auth":          LOG_AUTH,
-    "authpriv":      LOG_AUTHPRIV,
-    "cron":          LOG_CRON,
-    "daemon":        LOG_DAEMON,
-    "kern":          LOG_KERN,
-    "lpr":           LOG_LPR,
-    "mail":          LOG_MAIL,
-    "news":          LOG_NEWS,
-    "security":      LOG_AUTH,              #  DEPRECATED
-    "syslog":        LOG_SYSLOG,
-    "user":          LOG_USER,
-    "uucp":          LOG_UUCP,
-    "local0":        LOG_LOCAL0,
-    "local1":        LOG_LOCAL1,
-    "local2":        LOG_LOCAL2,
-    "local3":        LOG_LOCAL3,
-    "local4":        LOG_LOCAL4,
-    "local5":        LOG_LOCAL5,
-    "local6":        LOG_LOCAL6,
-    "local7":        LOG_LOCAL7,
-    }
-
-import socket
-
-class syslog_client(object):
-
-    def __init__(self, address='/dev/log'):
-        self.address = address
-        if type(address) == type(''):
-            try: # APUE 13.4.2 specifes /dev/log as datagram socket
-                self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
-                self.socket.connect(address)
-            except: # older linux may create as stream socket
-                self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-                self.socket.connect(address)
-            self.unix = 1
-        else:
-            self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-            self.unix = 0
-
-
-    log_format_string = '<%d>%s\000'
-
-    def log(self, message, facility=LOG_USER, priority=LOG_INFO):
-        message = self.log_format_string % (
-            self.encode_priority(facility, priority),
-            message
-            )
-        if self.unix:
-            self.socket.send(message)
-        else:
-            self.socket.sendto(message, self.address)
-
-    def encode_priority(self, facility, priority):
-        if type(facility) == type(''):
-            facility = facility_names[facility]
-        if type(priority) == type(''):
-            priority = priority_names[priority]
-        return (facility<<3) | priority
-
-    def close(self):
-        if self.unix:
-            self.socket.close()
diff --git a/src/zope/server/logger/pythonlogger.py b/src/zope/server/logger/pythonlogger.py
index 3874e1e..e06e667 100644
--- a/src/zope/server/logger/pythonlogger.py
+++ b/src/zope/server/logger/pythonlogger.py
@@ -16,22 +16,35 @@
 import logging
 
 from zope.server.interfaces.logger import IMessageLogger
+from zope.server.interfaces.logger import IRequestLogger
 from zope.interface import implementer
 
-@implementer(IMessageLogger)
+@implementer(IMessageLogger, IRequestLogger)
 class PythonLogger(object):
     """Proxy for Python's logging module"""
 
     def __init__(self, name=None, level=logging.INFO):
+        """
+        :keyword str name: The name of a logger to use,
+           or the logger itself.
+        """
+        name = getattr(name, 'name', name)
         self.name = name
         self.level = level
         self.logger = logging.getLogger(name)
 
     def __repr__(self):
         return '<python logger: %s %s>' % (self.name,
-                    logging.getLevelName(self.level))
+                                           logging.getLevelName(self.level))
 
     def logMessage(self, message):
         """See IMessageLogger"""
         self.logger.log(self.level, message.rstrip())
 
+
+    def logRequest(self, ip, message):
+        """
+        Log request is implemented to call :meth:`logMessage` without
+        attempting any resolution.
+        """
+        self.logMessage('%s%s' % (ip, message))
diff --git a/src/zope/server/logger/rotatingfilelogger.py b/src/zope/server/logger/rotatingfilelogger.py
deleted file mode 100644
index d09547f..0000000
--- a/src/zope/server/logger/rotatingfilelogger.py
+++ /dev/null
@@ -1,91 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Rotating File Logger
-
-Rotates a log file in time intervals.
-"""
-import time
-import os
-import stat
-
-from zope.server.logger.filelogger import FileLogger
-
-class RotatingFileLogger(FileLogger):
-    """ If freq is non-None we back up 'daily', 'weekly', or
-        'monthly'.  Else if maxsize is non-None we back up whenever
-        the log gets to big.  If both are None we never back up.
-
-        Like a FileLogger, but it must be attached to a filename.
-        When the log gets too full, or a certain time has passed, it
-        backs up the log and starts a new one.  Note that backing up
-        the log is done via 'mv' because anything else (cp, gzip)
-        would take time, during which medusa would do nothing else.
-    """
-
-    def __init__(self, file, freq=None, maxsize=None, flush=1, mode='a'):
-        self.filename = file
-        self.mode = mode
-        self.file = open(file, mode)
-        self.freq = freq
-        self.maxsize = maxsize
-        self.rotate_when = self.next_backup(self.freq)
-        self.do_flush = flush
-
-    def __repr__(self):
-        return '<rotating-file logger: %s>' % self.file
-
-    # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
-    def next_backup(self, freq):
-        (yr, mo, day, hr, min, sec, wd, jday, dst) = \
-             time.localtime(time.time())
-        if freq == 'daily':
-            return time.mktime((yr,mo,day+1, 0,0,0, 0,0,-1))
-        elif freq == 'weekly':
-            # wd(monday)==0
-            return time.mktime((yr,mo,day-wd+7, 0,0,0, 0,0,-1))
-        elif freq == 'monthly':
-            return time.mktime((yr,mo+1,1, 0,0,0, 0,0,-1))
-        else:
-            return None                  # not a date-based backup
-
-    def maybe_flush(self):              # rotate first if necessary
-        self.maybe_rotate()
-        if self.do_flush:                # from file_logger()
-            self.file.flush()
-
-    def maybe_rotate(self):
-        if self.freq and time.time() > self.rotate_when:
-            self.rotate()
-            self.rotate_when = self.next_backup(self.freq)
-        elif self.maxsize:               # rotate when we get too big
-            try:
-                if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
-                    self.rotate()
-            except os.error:             # file not found, probably
-                self.rotate()            # will create a new file
-
-    def rotate(self):
-        yr, mo, day, hr, min, sec, wd, jday, dst = time.localtime(time.time())
-        try:
-            self.file.close()
-            newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
-            try:
-                open(newname, "r").close()      # check if file exists
-                newname = newname + "-%02d%02d%02d" % (hr, min, sec)
-            except IOError:     # concatenation of YEAR MO DY is unique
-                pass
-            os.rename(self.filename, newname)
-            self.file = open(self.filename, self.mode)
-        except IOError:
-            pass
diff --git a/src/zope/server/logger/socketlogger.py b/src/zope/server/logger/socketlogger.py
deleted file mode 100644
index 43de5c4..0000000
--- a/src/zope/server/logger/socketlogger.py
+++ /dev/null
@@ -1,45 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Socket Logger
-
-Sends logging messages to a socket.
-"""
-import asynchat
-import socket
-
-from zope.server.interfaces.logger import IMessageLogger
-from zope.interface import implementer
-
-@implementer(IMessageLogger)
-class SocketLogger(asynchat.async_chat):
-    """Log to a stream socket, asynchronously."""
-
-    def __init__(self, address):
-        if type(address) == type(''):
-            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
-        else:
-            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
-
-        self.connect(address)
-        self.address = address
-
-    def __repr__(self):
-        return '<socket logger: address=%s>' % (self.address)
-
-    def logMessage(self, message):
-        'See IMessageLogger'
-        if message[-2:] != '\r\n':
-            self.socket.push(message + '\r\n')
-        else:
-            self.socket.push(message)
diff --git a/src/zope/server/logger/sysloglogger.py b/src/zope/server/logger/sysloglogger.py
deleted file mode 100644
index 56b0b55..0000000
--- a/src/zope/server/logger/sysloglogger.py
+++ /dev/null
@@ -1,57 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Syslog Logger
-
-Writes log messages to syslog.
-"""
-
-import os
-from zope.server.logger import m_syslog
-
-from zope.server.interfaces.logger import IMessageLogger
-from zope.interface import implementer
-
-
-@implementer(IMessageLogger)
-class SyslogLogger(m_syslog.syslog_client):
-    """syslog is a line-oriented log protocol - this class would be
-       appropriate for FTP or HTTP logs, but not for dumping stderr
-       to.
-
-       TODO: a simple safety wrapper that will ensure that the line
-       sent to syslog is reasonable.
-
-       TODO: async version of syslog_client: now, log entries use
-       blocking send()
-    """
-
-    svc_name = 'zope'
-    pid_str  = str(os.getpid())
-
-    def __init__ (self, address, facility='user'):
-        m_syslog.syslog_client.__init__ (self, address)
-        self.facility = m_syslog.facility_names[facility]
-        self.address=address
-
-    def __repr__ (self):
-        return '<syslog logger address=%s>' % (repr(self.address))
-
-    def logMessage(self, message):
-        'See IMessageLogger'
-        m_syslog.syslog_client.log (
-            self,
-            '%s[%s]: %s' % (self.svc_name, self.pid_str, message),
-            facility=self.facility,
-            priority=m_syslog.LOG_INFO
-            )
diff --git a/src/zope/server/logger/taillogger.py b/src/zope/server/logger/taillogger.py
deleted file mode 100644
index a4199c1..0000000
--- a/src/zope/server/logger/taillogger.py
+++ /dev/null
@@ -1,39 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Tail Logger
-"""
-from zope.server.interfaces.logger import IMessageLogger
-from zope.interface import implementer
-
-@implementer(IMessageLogger)
-class TailLogger(object):
-    """Keep track of the last <size> log messages"""
-
-    def __init__(self, logger, size=500):
-        self.size = size
-        self.logger = logger
-        self.messages = []
-
-    def logMessage(self, message):
-        'See IMessageLogger'
-        self.messages.append(strip_eol(message))
-        if len(self.messages) > self.size:
-            del self.messages[0]
-        self.logger.logMessage(message)
-
-
-def strip_eol(line):
-    while line and line[-1] in '\r\n':
-        line = line[:-1]
-    return line
diff --git a/src/zope/server/logger/unresolvinglogger.py b/src/zope/server/logger/unresolvinglogger.py
deleted file mode 100644
index 91a6c7b..0000000
--- a/src/zope/server/logger/unresolvinglogger.py
+++ /dev/null
@@ -1,28 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Unresolving Logger
-"""
-from zope.server.interfaces.logger import IRequestLogger
-from zope.interface import implementer
-
-@implementer(IRequestLogger)
-class UnresolvingLogger(object):
-    """Just in case you don't want to resolve"""
-
-    def __init__(self, logger):
-        self.logger = logger
-
-    def logRequest(self, ip, message):
-        'See IRequestLogger'
-        self.logger.logMessage('%s%s' % (ip, message))

Does this package still need its own implementation of Rotating/Socket/SysLog handlers?
It includes them, but it doesn't use them. They are completely untested.

They have been mostly untouched since they were moved here. They appear to date back to Python 2.4 or earlier (surviving log message: "remove support for Python 2.4 and 2.5"). 2.3 is when the standard library logging module was introduced. As of 2.7, the stdlib has analogues to all of these handlers. ZConfig can configure them. The default in `CommonAccessLogger` is to redirect logging to the Python stdlib logging package, and `zope.app.server.wsgi` uses that default. `zope.app.server` also includes ZConfig directives for configuring the Python logger this winds up using. If the other handlers are used, I'm not finding where.

I'm working on test coverage for this package, and I'd prefer not to have to write entire test suites for these things if they're not used. Ideally we'd be able to drop all of them, and perhaps simplify CommonAccessLogger to directly use the stdlib package instead of redirecting through this module's PythonLogger (with some layers to account for un/resolving of addresses). Somewhat less ideally we would be able to implement them on top of the functionality that the stdlib provides.
diff --git a/src/zope/server/ftp/tests/test_logger.py b/src/zope/server/ftp/tests/test_logger.py
index 6b676bc..561f2d7 100644
--- a/src/zope/server/ftp/tests/test_logger.py
+++ b/src/zope/server/ftp/tests/test_logger.py
@@ -8,6 +8,8 @@ import unittest
 
 from zope.server.ftp import logger
 
+from zope.testing import loggingsupport
+
 class TestCommonFTPActivityLogger(unittest.TestCase):
 
     def test_log(self):
@@ -18,15 +20,12 @@ class TestCommonFTPActivityLogger(unittest.TestCase):
                 addr = ('localhost',)
             m_name = 'head'
 
-        msgs = []
-        class Output(object):
-            def logMessage(self, msg):
-                msgs.append(msg)
+        handler = loggingsupport.InstalledHandler("accesslog")
+        self.addCleanup(handler.uninstall)
 
-        output = Output()
-        cal = logger.CommonFTPActivityLogger(output)
+        cal = logger.CommonFTPActivityLogger()
         cal.log(Task)
 
-        self.assertEqual(1, len(msgs))
-        self.assertIn('localhost', msgs[0])
-        self.assertIn('user', msgs[0])
+        self.assertEqual(1, len(handler.records))
+        self.assertIn('localhost', str(handler))
+        self.assertIn('user', str(handler))
diff --git a/src/zope/server/http/tests/test_commonaccesslogger.py b/src/zope/server/http/tests/test_commonaccesslogger.py
index c9e8489..8846733 100644
--- a/src/zope/server/http/tests/test_commonaccesslogger.py
+++ b/src/zope/server/http/tests/test_commonaccesslogger.py
@@ -27,16 +27,13 @@ class CommonAccessLogger(_CommonAccessLogger):
 class TestCommonAccessLogger(unittest.TestCase):
 
     def test_default_constructor(self):
-
-        from zope.server.logger.unresolvinglogger import UnresolvingLogger
         from zope.server.logger.pythonlogger import PythonLogger
         logger = CommonAccessLogger()
         # CommonHitLogger is registered as an argumentless factory via
         # ZCML, so the defaults should be sensible
-        self.assertIsInstance(logger.output, UnresolvingLogger)
-        self.assertIsInstance(logger.output.logger, PythonLogger)
+        self.assertIsInstance(logger.output, PythonLogger)
         self.assertEqual(logger.output.logger.name, 'accesslog')
-        self.assertEqual(logger.output.logger.level, logging.INFO)
+        self.assertEqual(logger.output.level, logging.INFO)
 
     def test_compute_timezone_for_log_negative(self):
         tz = -3600
@@ -77,11 +74,9 @@ class TestCommonAccessLogger(unittest.TestCase):
 
     def test_log_request(self):
         import time
-
-        class Output(object):
-            msgs = ()
-            def logMessage(self, msg):
-                self.msgs += (msg,)
+        from zope.testing import loggingsupport
+        handler = loggingsupport.InstalledHandler("accesslog")
+        self.addCleanup(handler.uninstall)
 
         class Resolver(object):
             def resolve_ptr(self, ip, then):
@@ -105,14 +100,14 @@ class TestCommonAccessLogger(unittest.TestCase):
         time.altzone = -3600
         time.time = t
         try:
-            output = Output()
-            cal = CommonAccessLogger(output, Resolver())
+            cal = CommonAccessLogger(resolver=Resolver())
             cal.log(Task())
         finally:
             time.daylight = orig_dl
             time.altzone = orig_az
             time.time = orig_t
 
+        self.assertEqual(1, len(handler.records))
         self.assertEqual(
-            'host - anonymous [29/Nov/1973:21:33:09 +0100] "GET / HTTP/1.0" 200 OK 10 "" ""\n',
-            output.msgs[0])
+            'host - anonymous [29/Nov/1973:21:33:09 +0100] "GET / HTTP/1.0" 200 OK 10 "" ""',
+            handler.records[0].getMessage())

pip install -e .[test]
parse_log_pytest
pytest
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
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
multipart==1.1.0
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
Paste==3.10.1
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
python-gettext==4.1
pytz==2025.2
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work
zope.browser==2.4
zope.component==5.1.0
zope.configuration==4.4.1
zope.contenttype==4.6
zope.deprecation==4.4.0
zope.event==4.6
zope.exceptions==4.6
zope.hookable==5.4
zope.i18n==4.9.0
zope.i18nmessageid==5.1.1
zope.interface==5.5.2
zope.location==4.3
zope.proxy==4.6.1
zope.publisher==6.1.0
zope.schema==6.2.1
zope.security==5.8
-e git+https://github.com/zopefoundation/zope.server.git@4f54c909b0df4040732766080503535d80454410#egg=zope.server
zope.testing==5.0.1
zope.testrunner==5.6

name: zope.server
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=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:
      - multipart==1.1.0
      - paste==3.10.1
      - python-gettext==4.1
      - pytz==2025.2
      - six==1.17.0
      - zope-browser==2.4
      - zope-component==5.1.0
      - zope-configuration==4.4.1
      - zope-contenttype==4.6
      - zope-deprecation==4.4.0
      - zope-event==4.6
      - zope-exceptions==4.6
      - zope-hookable==5.4
      - zope-i18n==4.9.0
      - zope-i18nmessageid==5.1.1
      - zope-interface==5.5.2
      - zope-location==4.3
      - zope-proxy==4.6.1
      - zope-publisher==6.1.0
      - zope-schema==6.2.1
      - zope-security==5.8
      - zope-testing==5.0.1
      - zope-testrunner==5.6
prefix: /opt/conda/envs/zope.server

src/zope/server/http/tests/test_commonaccesslogger.py::TestCommonAccessLogger::test_default_constructor
src/zope/server/ftp/tests/test_logger.py::TestCommonFTPActivityLogger::test_log src/zope/server/http/tests/test_commonaccesslogger.py::TestCommonAccessLogger::test_compute_timezone_for_log_negative src/zope/server/http/tests/test_commonaccesslogger.py::TestCommonAccessLogger::test_compute_timezone_for_log_positive src/zope/server/http/tests/test_commonaccesslogger.py::TestCommonAccessLogger::test_log_date_string_daylight src/zope/server/http/tests/test_commonaccesslogger.py::TestCommonAccessLogger::test_log_date_string_non_daylight src/zope/server/http/tests/test_commonaccesslogger.py::TestCommonAccessLogger::test_log_request
src/zope/server/logger/pythonlogger.py src/zope/server/logger/m_syslog.py src/zope/server/logger/filelogger.py src/zope/server/logger/rotatingfilelogger.py src/zope/server/logger/socketlogger.py src/zope/server/http/commonaccesslogger.py src/zope/server/logger/taillogger.py src/zope/server/logger/unresolvinglogger.py .coveragerc CHANGES.rst src/zope/server/logger/sysloglogger.py
src/zope/server/logger/pythonlogger.py src/zope/server/logger/m_syslog.py src/zope/server/logger/filelogger.py src/zope/server/logger/rotatingfilelogger.py src/zope/server/logger/socketlogger.py src/zope/server/http/commonaccesslogger.py src/zope/server/logger/taillogger.py src/zope/server/logger/unresolvinglogger.py .coveragerc CHANGES.rst src/zope/server/logger/sysloglogger.py