Commit 52344cf4 authored by Fred Drake's avatar Fred Drake

Preliminary support for configuring logging using ZConfig.

Needs more work, but can deal with at least logging to files and STDERR.
Log rotation has been tested.
parent a2238de1
<component prefix="zLOG.datatypes">
<abstracttype name="loghandler"/>
<sectiontype name="file-handler" datatype=".file_handler"
implements="loghandler">
<key name="path" required="yes"/>
<key name="format" default="------\n%(asctime)s %(message)s"
datatype=".log_format"/>
<key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
<key name="level" default="info" datatype="logging-level"/>
</sectiontype>
<sectiontype name="syslog-handler" datatype=".syslog_handler"
implements="loghandler">
<key name="facility" default="user" datatype=".syslog_facility"/>
<key name="address" datatype="socket-address" required="yes"/>
<key name="format" default="%(message)s"
datatype=".log_format"/>
<key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
<key name="level" default="info" datatype="logging-level"/>
</sectiontype>
<!--
<sectiontype name="nteventlog_handler" datatype=".nteventlog_handler"
implements="loghandler">
<key name="appname" default="Zope"/>
<key name="format" default="%(message)s"
datatype=".log_format"/>
<key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
<key name="level" default="info" datatype="logging-level"/>
</sectiontype>
<sectiontype name="http_handler" datatype=".http_handler"
implements="loghandler">
<key name="url" default="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"/>
<key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
<key name="level" default="info" datatype="logging-level"/>
</sectiontype>
<sectiontype name="smtp_handler" datatype=".smtp_handler"
implements="loghandler">
<key name="fromaddr" required="yes"/>
<multikey name="toaddr" required="yes" attribute="toaddrs"/>
<key name="subject" default="Message from Zope"/>
<key name="host" default="localhost" datatype="inet-address"/>
<key name="format" default="%(asctime)s %(message)s"
datatype=".log_format"/>
<key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
<key name="level" default="info" datatype="logging-level"/>
</sectiontype>
<sectiontype name="null_handler" datatype=".null_handler"
implements="loghandler"/>
<sectiontype name="custom_handler" datatype=".custom_handler"
implements="loghandler">
<key name="constructor" datatype="constructor" required="yes"/>
<key name="formatter" datatype="constructor"
default="logging.Formatter()"/>
<key name="level" default="info" datatype="logging-level"/>
</sectiontype>
-->
<sectiontype name="logger" datatype=".logger">
<key name="level" datatype="logging-level" default="info"/>
<multisection type="loghandler" attribute="handlers" name="*"/>
</sectiontype>
</component>
##############################################################################
#
# Copyright (c) 2002, 2003 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.
#
##############################################################################
"""ZConfig datatypes for logging support."""
import sys
from zLOG.factory import Factory
# log-related datatypes
_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
def file_handler(section):
path = section.path
def callback(inst,
format=section.format,
dateformat=section.dateformat,
level=section.level):
import logging
inst.setFormatter(logging.Formatter(format, dateformat))
inst.setLevel(level)
if path == "STDERR":
# XXX should pick up sys.stderr when the factory is invoked
return Factory('zLOG.LogHandlers.StreamHandler', callback, sys.stderr)
else:
return Factory('zLOG.LogHandlers.FileHandler', callback, 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):
raise ValueError(
"Syslog facility must be one of "
+ ", ".join(_syslog_facilities.keys()))
return value
def syslog_handler(section):
def callback(inst,
format=section.format,
dateformat=section.dateformat,
level=section.level):
import logging
inst.setFormatter(logging.Formatter(format, dateformat))
inst.setLevel(level)
return Factory('zLOG.LogHandlers.SysLogHandler', callback,
section.address.address,
section.facility)
## def nteventlog_handler(section):
## appname = section.appname
## format = section.format
## dateformat = section.dateformat
## level = section.level
## formatter = Factory('logging.Formatter', None, format, dateformat)
## def callback(inst, formatter=formatter, level=level):
## inst.setFormatter(formatter())
## inst.setLevel(level)
## return Factory('zLOG.LogHandlers.NTEventLogHandler', callback, appname)
## def http_handler_url(value):
## import urlparse
## scheme, netloc, path, query, fragment = urlparse.urlsplit(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 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 '
## '%s' % repr(value))
## return value
## def http_handler(section):
## host, url = section.url
## method = section.method
## format = section.format
## dateformat = section.dateformat
## level = section.level
## formatter = Factory('logging.Formatter', None, format, dateformat)
## def callback(inst, formatter=formatter, level=level):
## inst.setFormatter(formatter())
## inst.setLevel(level)
## return Factory('zLOG.LogHandlers.HTTPHandler', callback, host, url, method)
## def smtp_handler(section):
## fromaddr = section.fromaddr
## toaddrs = section.toaddrs
## subject = section.subject
## host, port = section.host
## format = section.format
## dateformat = section.dateformat
## level = section.level
## if not port:
## mailhost = host
## else:
## mailhost = host, port
## formatter = Factory('logging.Formatter', None, format, dateformat)
## def callback(inst, formatter=formatter, level=level):
## inst.setFormatter(formatter())
## inst.setLevel(level)
## return Factory('zLOG.LogHandlers.SMTPHandler', callback,
## mailhost, fromaddr, toaddrs, subject)
## def null_handler(section):
## return Factory('zLOG.LogHandlers.NullHandler', None)
## def custom_handler(section):
## formatter_klass, formatter_pos, formatter_kw = section.formatter
## handler_klass, handler_pos, handler_kw = section.constructor
## level = section.level
## formatter = Factory(formatter_klass, None, formatter_pos, formatter_kw)
## def callback(inst, formatter=formatter, level=level):
## inst.setFormatter(formatter())
## inst.setLevel(level)
## return Factory(handler_klass, callback, *handler_pos, **handler_kw)
def logger(section):
return LoggerWrapper(section.level, section.handlers)
_marker = []
class LoggerWrapper:
"""
A wrapper used to create loggers while delaying actual logger
instance construction. We need to do this because we may
want to reference a logger before actually instantiating it (for example,
to allow the app time to set an effective user).
An instance of this wrapper is a callable which, when called, returns a
logger object.
"""
def __init__(self, level, handler_factories):
self.level = level
self.handler_factories = handler_factories
self.resolved = _marker
def __call__(self):
if self.resolved is _marker:
# set the logger up
import logging
logger = logging.getLogger("event")
logger.handlers = []
logger.propagate = 0
logger.setLevel(self.level)
for handler_factory in self.handler_factories:
handler = handler_factory()
logger.addHandler(handler)
self.resolved = logger
return self.resolved
##############################################################################
#
# Copyright (c) 2002, 2003 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 = []
def importer(name):
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:
name = '.'.join(modulenames)
package = __import__(name, g, g, component)
return package
class Factory:
"""
A generic wrapper for instance construction and function calling used
to delay construction/call until necessary. The class path is the dotted
name to the class or function, args are the positional args, kw are the
keyword args. If it is specified, 'callback' is a function which will be
called back after constructing an instance or calling a function. It must
take the instance (or the result of the function) as a single argument.
"""
def __init__(self, class_path, callback, *args, **kw):
self.class_path = class_path
self.callback = callback
self.setArgs(list(args), kw)
self.resolved = _marker
def __repr__(self):
return ('<Factory instance for class "%s" with positional args "%s" '
'and keword args "%s"' % (self.class_path, self.args, self.kw))
__str__ = __repr__
def __call__(self):
if self.resolved is _marker:
package = importer(self.class_path)
inst = package(*self.args, **self.kw)
if self.callback:
self.callback(inst)
self.resolved = inst
return self.resolved
def setArgs(self, args, kw):
self.args = args
self.kw = kw
def getArgs(self):
return (self.args, self.kw)
##############################################################################
#
# Copyright (c) 2002, 2003 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.
#
##############################################################################
"""Tests for zLOG configuration via ZConfig."""
import cStringIO as StringIO
import logging
import unittest
import ZConfig
import zLOG.tests
class TestzLOGConfig(unittest.TestCase):
_schema = None
_schematext = """
<schema>
<import package='zLOG'/>
<section type='logger' name='*' attribute='logger'/>
</schema>
"""
def get_schema(self):
if self._schema is None:
sio = StringIO.StringIO(self._schematext)
self.__class__._schema = ZConfig.loadSchemaFile(sio)
return self._schema
def get_config(self, text):
conf, handler = ZConfig.loadConfigFile(self.get_schema(),
StringIO.StringIO(text))
self.assert_(not handler)
return conf
def test_config_without_logger(self):
conf = self.get_config("")
self.assert_(conf.logger is None)
def test_config_without_handlers(self):
conf = self.get_config("<logger/>")
self.assert_(conf.logger is not None)
self.assertEqual(conf.logger.level, logging.INFO)
logger = conf.logger()
self.assert_(isinstance(logger, logging.Logger))
self.assertEqual(logger.handlers, [])
# XXX need to make sure each loghandler datatype gets exercised.
def test_suite():
return unittest.makeSuite(TestzLOGConfig)
if __name__ == '__main__':
unittest.main(defaultTest="test_suite")
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