Commit e639221e authored by Fred Drake's avatar Fred Drake

update zLOG configuration component to make use of the ZConfig support for

the logging package; this avoids lots of code duplication
parent 2f87a608
...@@ -103,7 +103,7 @@ class ZopeStarter: ...@@ -103,7 +103,7 @@ class ZopeStarter:
# if we're not in debug mode). # if we're not in debug mode).
import zLOG import zLOG
from zLOG.LogHandlers import StartupHandler from ZConfig.components.logger.loghandler import StartupHandler
if self.cfg.eventlog is not None: if self.cfg.eventlog is not None:
# get the lowest handler level. This is the effective level # get the lowest handler level. This is the effective level
......
...@@ -16,8 +16,12 @@ ...@@ -16,8 +16,12 @@
logging; event logging is handled by the zLOG package, which logging; event logging is handled by the zLOG package, which
provides the loghandler type used here. provides the loghandler type used here.
</description> </description>
<key name="level" datatype="zLOG.datatypes.logging_level" default="info"/> <key name="level"
<multisection type="zLOG.loghandler" attribute="handlers" name="*" datatype="ZConfig.components.logger.datatypes.logging_level"
default="info"/>
<multisection name="*"
type="ZConfig.logger.handler"
attribute="handlers"
required="yes"/> required="yes"/>
</sectiontype> </sectiontype>
......
...@@ -22,7 +22,7 @@ __version__='$Revision$'[11:-2] ...@@ -22,7 +22,7 @@ __version__='$Revision$'[11:-2]
import os, sys, time import os, sys, time
import logging import logging
from BaseLogger import BaseLogger from BaseLogger import BaseLogger
from LogHandlers import FileHandler, NullHandler, SysLogHandler from ZConfig.components.logger import loghandler
from logging import StreamHandler, Formatter from logging import StreamHandler, Formatter
# Custom logging levels # Custom logging levels
...@@ -36,7 +36,7 @@ class EventLogger(BaseLogger): ...@@ -36,7 +36,7 @@ class EventLogger(BaseLogger):
# Get our logger object: # Get our logger object:
logger = logging.getLogger('event') logger = logging.getLogger('event')
# Add a null handler to prevent warnings about loggers with no handlers: # Add a null handler to prevent warnings about loggers with no handlers:
logger.addHandler(NullHandler()) logger.addHandler(loghandler.NullHandler())
logger.propagate = 0 logger.propagate = 0
def log(self, subsystem, severity, summary, detail, error): def log(self, subsystem, severity, summary, detail, error):
...@@ -174,14 +174,14 @@ def initialize_from_environment(): ...@@ -174,14 +174,14 @@ def initialize_from_environment():
# set up syslog handler if necessary # set up syslog handler if necessary
facility, syslogdest = get_env_syslog_info() facility, syslogdest = get_env_syslog_info()
if syslogdest: if syslogdest:
handler = SysLogHandler(syslogdest, facility) handler = loghandler.SysLogHandler(syslogdest, facility)
handler.setFormatter(formatters['syslog']) handler.setFormatter(formatters['syslog'])
handlers.append(handler) handlers.append(handler)
# set up file handler if necessary # set up file handler if necessary
filedest = get_env_file_info() filedest = get_env_file_info()
if filedest: if filedest:
handler = FileHandler(filedest) handler = loghandler.FileHandler(filedest)
handler.setFormatter(formatters['file']) handler.setFormatter(formatters['file'])
handlers.append(handler) handlers.append(handler)
elif filedest == '': elif filedest == '':
...@@ -192,7 +192,7 @@ def initialize_from_environment(): ...@@ -192,7 +192,7 @@ def initialize_from_environment():
else: else:
# log to nowhere, but install a 'null' handler in order to # log to nowhere, but install a 'null' handler in order to
# prevent error messages from emanating due to a missing handler # prevent error messages from emanating due to a missing handler
handlers.append(NullHandler()) handlers.append(loghandler.NullHandler())
severity = get_env_severity_info() severity = get_env_severity_info()
severity = zlog_to_pep282_severity(severity) severity = zlog_to_pep282_severity(severity)
......
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
#
##############################################################################
"""Handlers which can plug into a PEP 282 logger."""
import sys
from logging import Handler, StreamHandler
from logging.handlers import SysLogHandler
from logging.handlers import HTTPHandler, SMTPHandler
from logging.handlers import NTEventLogHandler as Win32EventLogHandler
class FileHandler(StreamHandler):
"""
A file handler which allows for reopening of logs in favor of the
'rollover' features of the standard PEP282 FileHandler.
"""
def __init__(self, filename, mode="a"):
StreamHandler.__init__(self, open(filename, mode))
self.baseFilename = filename
self.mode = mode
def close(self):
self.stream.close()
def reopen(self):
self.close()
self.stream = open(self.baseFilename, self.mode)
class NullHandler(Handler):
"""
A null handler. Does nothing.
"""
def emit(self, record):
pass
def handle(self, record):
pass
class StartupHandler(Handler):
"""
A handler which outputs messages to a stream but also buffers them until
they can be flushed to a target handler. Useful at startup before we can
know that we can safely write to a config-specified handler.
"""
def __init__(self, stream=None):
Handler.__init__(self)
if not stream:
stream = sys.stderr
self.stream = stream
self.buffer = []
def emit(self, record):
try:
self.buffer.append(record)
msg = self.format(record)
self.stream.write("%s\n" % msg)
self.flush()
except:
self.handleError(record)
def flush(self):
self.stream.flush()
def flushBufferTo(self, target):
for record in self.buffer:
target.handle(record)
self.buffer = []
<component prefix="zLOG.datatypes"> <component prefix="zLOG.datatypes">
<abstracttype name="zLOG.loghandler"/> <import package="ZConfig.components.logger" file="abstract.xml"/>
<import package="ZConfig.components.logger" file="handlers.xml"/>
<sectiontype name="zLOG.base-log-handler">
<description> <sectiontype name="eventlog"
Base type for most log handlers. This is cannot be used as a datatype=".EventLogFactory">
loghandler directly since it doesn't implement the loghandler <key name="level"
abstract section type. datatype="ZConfig.components.logger.datatypes.logging_level"
</description> default="info"/>
<key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/> <multisection name="*"
<key name="level" default="notset" datatype=".logging_level"/> type="ZConfig.logger.handler"
</sectiontype> attribute="handlers"
/>
<sectiontype name="logfile" datatype=".FileHandlerFactory"
implements="zLOG.loghandler" extends="zLOG.base-log-handler">
<key name="path" required="yes"/>
<key name="format" default="------\n%(asctime)s %(message)s"
datatype=".log_format"/>
</sectiontype>
<sectiontype name="syslog" datatype=".SyslogHandlerFactory"
implements="zLOG.loghandler" extends="zLOG.base-log-handler">
<key name="facility" default="user" datatype=".syslog_facility"/>
<key name="address" datatype="socket-address" default="localhost:514"/>
<key name="format" default="%(message)s"
datatype=".log_format"/>
</sectiontype>
<sectiontype name="win32-eventlog" datatype=".Win32EventLogFactory"
implements="zLOG.loghandler" extends="zLOG.base-log-handler">
<key name="appname" default="Zope"/>
<key name="format" default="%(message)s"
datatype=".log_format"/>
</sectiontype>
<sectiontype name="http-logger" datatype=".HTTPHandlerFactory"
implements="zLOG.loghandler" extends="zLOG.base-log-handler">
<key name="url" default="http://localhost/" datatype=".http_handler_url"/>
<key name="method" default="GET" datatype=".get_or_post"/>
<key name="format" default="%(asctime)s %(message)s"
datatype=".log_format"/>
</sectiontype>
<sectiontype name="email-notifier" datatype=".SMTPHandlerFactory"
implements="zLOG.loghandler" extends="zLOG.base-log-handler">
<key name="from" required="yes" attribute="fromaddr"/>
<multikey name="to" required="yes" attribute="toaddrs"/>
<key name="subject" default="Message from Zope"/>
<key name="smtp-server" default="localhost" datatype="inet-address"/>
<key name="format" default="%(asctime)s %(message)s"
datatype=".log_format"/>
</sectiontype>
<sectiontype name="eventlog" datatype=".EventLogFactory">
<key name="level" datatype=".logging_level" default="info"/>
<multisection type="zLOG.loghandler" attribute="handlers" name="*"/>
</sectiontype> </sectiontype>
</component> </component>
...@@ -14,191 +14,10 @@ ...@@ -14,191 +14,10 @@
"""ZConfig datatypes for logging support.""" """ZConfig datatypes for logging support."""
import sys
import zLOG import zLOG
from zLOG import EventLogger from zLOG import EventLogger
from zLOG.factory import Factory from ZConfig.components.logger.factory import Factory
# log-related datatypes
_logging_levels = {
"critical": 50,
"fatal": 50,
"error": 40,
"warn": 30,
"warning": 30,
"info": 20,
"blather": 15,
"debug": 10,
"trace": 5,
"all": 1,
"notset": 0,
}
def logging_level(value):
s = str(value).lower()
if _logging_levels.has_key(s):
return _logging_levels[s]
else:
v = int(s)
if v < 0 or v > 50:
raise ValueError("log level not in range: " + `v`)
return v
_log_format_variables = {
'name': '',
'levelno': '3',
'levelname': 'DEBUG',
'pathname': 'apath',
'filename': 'afile',
'module': 'amodule',
'lineno': 1,
'created': 1.1,
'asctime': 'atime',
'msecs': 1,
'relativeCreated': 1,
'thread': 1,
'message': 'amessage',
}
def log_format(value):
value = ctrl_char_insert(value)
try:
# Make sure the format string uses only names that will be
# provided, and has reasonable type flags for each, and does
# not expect positional args.
value % _log_format_variables
except (ValueError, KeyError):
raise ValueError, 'Invalid log format string %s' % value
return value
_control_char_rewrites = {r'\n': '\n', r'\t': '\t', r'\b': '\b',
r'\f': '\f', r'\r': '\r'}.items()
def ctrl_char_insert(value):
for pattern, replacement in _control_char_rewrites:
value = value.replace(pattern, replacement)
return value
class HandlerFactory(Factory):
def __init__(self, section):
Factory.__init__(self)
self.section = section
def create_loghandler(self):
raise NotImplementedError(
"subclasses must override create_loghandler()")
def create(self):
import logging
logger = self.create_loghandler()
logger.setFormatter(logging.Formatter(self.section.format,
self.section.dateformat))
logger.setLevel(self.section.level)
return logger
def getLevel(self):
return self.section.level
class FileHandlerFactory(HandlerFactory):
def create_loghandler(self):
from zLOG.LogHandlers import StreamHandler, FileHandler
path = self.section.path
if path == "STDERR":
return StreamHandler(sys.stderr)
if path == "STDOUT":
return StreamHandler(sys.stdout)
return FileHandler(path)
_syslog_facilities = {
"auth": 1,
"authpriv": 1,
"cron": 1,
"daemon": 1,
"kern": 1,
"lpr": 1,
"mail": 1,
"news": 1,
"security": 1,
"syslog": 1,
"user": 1,
"uucp": 1,
"local0": 1,
"local1": 1,
"local2": 1,
"local3": 1,
"local4": 1,
"local5": 1,
"local6": 1,
"local7": 1,
}
def syslog_facility(value):
value = value.lower()
if not _syslog_facilities.has_key(value):
L = _syslog_facilities.keys()
L.sort()
raise ValueError("Syslog facility must be one of " + ", ".join(L))
return value
class SyslogHandlerFactory(HandlerFactory):
def create_loghandler(self):
from zLOG.LogHandlers import SysLogHandler
return SysLogHandler(self.section.address.address,
self.section.facility)
class Win32EventLogFactory(HandlerFactory):
def create_loghandler(self):
from zLOG.LogHandlers import Win32EventLogHandler
return Win32EventLogHandler(self.section.appname)
def http_handler_url(value):
import urlparse
scheme, netloc, path, param, query, fragment = urlparse.urlparse(value)
if scheme != 'http':
raise ValueError, 'url must be an http url'
if not netloc:
raise ValueError, 'url must specify a location'
if not path:
raise ValueError, 'url must specify a path'
q = []
if param:
q.append(';')
q.append(param)
if query:
q.append('?')
q.append(query)
if fragment:
q.append('#')
q.append(fragment)
return (netloc, path + ''.join(q))
def get_or_post(value):
value = value.upper()
if value not in ('GET', 'POST'):
raise ValueError('method must be "GET" or "POST", instead received: '
+ repr(value))
return value
class HTTPHandlerFactory(HandlerFactory):
def create_loghandler(self):
from zLOG.LogHandlers import HTTPHandler
host, selector = self.section.url
return HTTPHandler(host, selector, self.section.method)
class SMTPHandlerFactory(HandlerFactory):
def create_loghandler(self):
from zLOG.LogHandlers import SMTPHandler
host, port = self.section.smtp_server
if not port:
mailhost = host
else:
mailhost = host, port
return SMTPHandler(mailhost, self.section.fromaddr,
self.section.toaddrs, self.section.subject)
class EventLogFactory(Factory): class EventLogFactory(Factory):
...@@ -227,7 +46,7 @@ class EventLogFactory(Factory): ...@@ -227,7 +46,7 @@ class EventLogFactory(Factory):
handler = handler_factory() handler = handler_factory()
logger.addHandler(handler) logger.addHandler(handler)
else: else:
from zLOG.LogHandlers import NullHandler from ZConfig.components.logger.loghandler import NullHandler
logger.addHandler(NullHandler()) logger.addHandler(NullHandler())
return logger return logger
...@@ -252,23 +71,3 @@ class EventLogFactory(Factory): ...@@ -252,23 +71,3 @@ class EventLogFactory(Factory):
def startup(self): def startup(self):
zLOG.set_initializer(self.initialize) zLOG.set_initializer(self.initialize)
zLOG.initialize() zLOG.initialize()
def importable_name(name):
try:
components = name.split('.')
start = components[0]
g = globals()
package = __import__(start, g, g)
modulenames = [start]
for component in components[1:]:
modulenames.append(component)
try:
package = getattr(package, component)
except AttributeError:
n = '.'.join(modulenames)
package = __import__(n, g, g, component)
return package
except ImportError:
raise ValueError, (
'The object named by "%s" could not be imported' % name )
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
_marker = []
class Factory:
"""Generic wrapper for instance construction.
Calling the factory causes the instance to be created if it hasn't
already been created, and returns the object. Calling the factory
multiple times returns the same object.
The instance is created using the factory's create() method, which
must be overriden by subclasses.
"""
def __init__(self):
self.instance = _marker
def __call__(self):
if self.instance is _marker:
self.instance = self.create()
return self.instance
def create(self):
raise NotImplementedError("subclasses need to override create()")
...@@ -16,13 +16,11 @@ ...@@ -16,13 +16,11 @@
import cStringIO as StringIO import cStringIO as StringIO
import logging import logging
import sys
import tempfile
import unittest import unittest
import ZConfig import ZConfig
import zLOG.datatypes
import zLOG.LogHandlers from ZConfig.components.logger import loghandler
class TestzLOGConfig(unittest.TestCase): class TestzLOGConfig(unittest.TestCase):
...@@ -34,7 +32,7 @@ class TestzLOGConfig(unittest.TestCase): ...@@ -34,7 +32,7 @@ class TestzLOGConfig(unittest.TestCase):
self._old_logger = logging.getLogger("event") self._old_logger = logging.getLogger("event")
self._old_level = self._old_logger.level self._old_level = self._old_logger.level
self._old_handlers = self._old_logger.handlers[:] self._old_handlers = self._old_logger.handlers[:]
self._old_logger.handlers[:] = [zLOG.LogHandlers.NullHandler()] self._old_logger.handlers[:] = [loghandler.NullHandler()]
def tearDown(self): def tearDown(self):
for h in self._old_logger.handlers: for h in self._old_logger.handlers:
...@@ -63,36 +61,6 @@ class TestzLOGConfig(unittest.TestCase): ...@@ -63,36 +61,6 @@ class TestzLOGConfig(unittest.TestCase):
self.assert_(not handler) self.assert_(not handler)
return conf return conf
def test_logging_level(self):
# Make sure the expected names are supported; it's not clear
# how to check the values in a meaningful way.
# Just make sure they're case-insensitive.
convert = zLOG.datatypes.logging_level
for name in ["notset", "all", "trace", "debug", "blather",
"info", "warn", "warning", "error", "fatal",
"critical"]:
self.assertEqual(convert(name), convert(name.upper()))
self.assertRaises(ValueError, convert, "hopefully-not-a-valid-value")
def test_http_method(self):
convert = zLOG.datatypes.get_or_post
self.assertEqual(convert("get"), "GET")
self.assertEqual(convert("GET"), "GET")
self.assertEqual(convert("post"), "POST")
self.assertEqual(convert("POST"), "POST")
self.assertRaises(ValueError, convert, "")
self.assertRaises(ValueError, convert, "foo")
def test_syslog_facility(self):
convert = zLOG.datatypes.syslog_facility
for name in ["auth", "authpriv", "cron", "daemon", "kern",
"lpr", "mail", "news", "security", "syslog",
"user", "uucp", "local0", "local1", "local2",
"local3", "local4", "local5", "local6", "local7"]:
self.assertEqual(convert(name), name)
self.assertEqual(convert(name.upper()), name)
self.assertRaises(ValueError, convert, "hopefully-never-a-valid-value")
def test_config_without_logger(self): def test_config_without_logger(self):
conf = self.get_config("") conf = self.get_config("")
self.assert_(conf.eventlog is None) self.assert_(conf.eventlog is None)
...@@ -103,109 +71,7 @@ class TestzLOGConfig(unittest.TestCase): ...@@ -103,109 +71,7 @@ class TestzLOGConfig(unittest.TestCase):
# printed if there are no handlers: # printed if there are no handlers:
self.assertEqual(len(logger.handlers), 1) self.assertEqual(len(logger.handlers), 1)
self.assert_(isinstance(logger.handlers[0], self.assert_(isinstance(logger.handlers[0],
zLOG.LogHandlers.NullHandler)) loghandler.NullHandler))
def test_with_logfile(self):
import os
fn = tempfile.mktemp()
logger = self.check_simple_logger("<eventlog>\n"
" <logfile>\n"
" path %s\n"
" level debug\n"
" </logfile>\n"
"</eventlog>" % fn)
logfile = logger.handlers[0]
self.assertEqual(logfile.level, logging.DEBUG)
self.assert_(isinstance(logfile, zLOG.LogHandlers.FileHandler))
logfile.close()
os.remove(fn)
def test_with_stderr(self):
self.check_standard_stream("stderr")
def test_with_stdout(self):
self.check_standard_stream("stdout")
def check_standard_stream(self, name):
old_stream = getattr(sys, name)
conf = self.get_config("""
<eventlog>
<logfile>
level info
path %s
</logfile>
</eventlog>
""" % name.upper())
self.assert_(conf.eventlog is not None)
# The factory has already been created; make sure it picks up
# the stderr we set here when we create the logger and
# handlers:
sio = StringIO.StringIO()
setattr(sys, name, sio)
try:
logger = conf.eventlog()
finally:
setattr(sys, name, old_stream)
logger.warn("woohoo!")
self.assert_(sio.getvalue().find("woohoo!") >= 0)
def test_with_syslog(self):
logger = self.check_simple_logger("<eventlog>\n"
" <syslog>\n"
" level error\n"
" facility local3\n"
" </syslog>\n"
"</eventlog>")
syslog = logger.handlers[0]
self.assertEqual(syslog.level, logging.ERROR)
self.assert_(isinstance(syslog, zLOG.LogHandlers.SysLogHandler))
def test_with_http_logger_localhost(self):
logger = self.check_simple_logger("<eventlog>\n"
" <http-logger>\n"
" level error\n"
" method post\n"
" </http-logger>\n"
"</eventlog>")
handler = logger.handlers[0]
self.assertEqual(handler.host, "localhost")
# XXX The "url" attribute of the handler is misnamed; it
# really means just the selector portion of the URL.
self.assertEqual(handler.url, "/")
self.assertEqual(handler.level, logging.ERROR)
self.assertEqual(handler.method, "POST")
self.assert_(isinstance(handler, zLOG.LogHandlers.HTTPHandler))
def test_with_http_logger_remote_host(self):
logger = self.check_simple_logger("<eventlog>\n"
" <http-logger>\n"
" method get\n"
" url http://example.com/log/\n"
" </http-logger>\n"
"</eventlog>")
handler = logger.handlers[0]
self.assertEqual(handler.host, "example.com")
# XXX The "url" attribute of the handler is misnamed; it
# really means just the selector portion of the URL.
self.assertEqual(handler.url, "/log/")
self.assertEqual(handler.level, logging.NOTSET)
self.assertEqual(handler.method, "GET")
self.assert_(isinstance(handler, zLOG.LogHandlers.HTTPHandler))
def test_with_email_notifier(self):
logger = self.check_simple_logger("<eventlog>\n"
" <email-notifier>\n"
" to sysadmin@example.com\n"
" to sa-pager@example.com\n"
" from zlog-user@example.com\n"
" level fatal\n"
" </email-notifier>\n"
"</eventlog>")
handler = logger.handlers[0]
self.assertEqual(handler.toaddrs, ["sysadmin@example.com",
"sa-pager@example.com"])
self.assertEqual(handler.fromaddr, "zlog-user@example.com")
self.assertEqual(handler.level, logging.FATAL)
def check_simple_logger(self, text, level=logging.INFO): def check_simple_logger(self, text, level=logging.INFO):
conf = self.get_config(text) conf = self.get_config(text)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment