Commit ef81cb1c 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 fe645271
......@@ -22,7 +22,7 @@ __version__='$Revision$'[11:-2]
import os, sys, time
import logging
from BaseLogger import BaseLogger
from LogHandlers import FileHandler, NullHandler, SysLogHandler
from ZConfig.components.logger import loghandler
from logging import StreamHandler, Formatter
# Custom logging levels
......@@ -36,7 +36,7 @@ class EventLogger(BaseLogger):
# Get our logger object:
logger = logging.getLogger('event')
# Add a null handler to prevent warnings about loggers with no handlers:
logger.addHandler(NullHandler())
logger.addHandler(loghandler.NullHandler())
logger.propagate = 0
def log(self, subsystem, severity, summary, detail, error):
......@@ -174,14 +174,14 @@ def initialize_from_environment():
# set up syslog handler if necessary
facility, syslogdest = get_env_syslog_info()
if syslogdest:
handler = SysLogHandler(syslogdest, facility)
handler = loghandler.SysLogHandler(syslogdest, facility)
handler.setFormatter(formatters['syslog'])
handlers.append(handler)
# set up file handler if necessary
filedest = get_env_file_info()
if filedest:
handler = FileHandler(filedest)
handler = loghandler.FileHandler(filedest)
handler.setFormatter(formatters['file'])
handlers.append(handler)
elif filedest == '':
......@@ -192,7 +192,7 @@ def initialize_from_environment():
else:
# log to nowhere, but install a 'null' handler in order to
# prevent error messages from emanating due to a missing handler
handlers.append(NullHandler())
handlers.append(loghandler.NullHandler())
severity = get_env_severity_info()
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">
<abstracttype name="zLOG.loghandler"/>
<sectiontype name="zLOG.base-log-handler">
<description>
Base type for most log handlers. This is cannot be used as a
loghandler directly since it doesn't implement the loghandler
abstract section type.
</description>
<key name="dateformat" default="%Y-%m-%dT%H:%M:%S"/>
<key name="level" default="notset" datatype=".logging_level"/>
</sectiontype>
<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="*"/>
<import package="ZConfig.components.logger" file="abstract.xml"/>
<import package="ZConfig.components.logger" file="handlers.xml"/>
<sectiontype name="eventlog"
datatype=".EventLogFactory">
<key name="level"
datatype="ZConfig.components.logger.datatypes.logging_level"
default="info"/>
<multisection name="*"
type="ZConfig.logger.handler"
attribute="handlers"
/>
</sectiontype>
</component>
......@@ -14,191 +14,10 @@
"""ZConfig datatypes for logging support."""
import sys
import zLOG
from zLOG import EventLogger
from zLOG.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)
from ZConfig.components.logger.factory import Factory
class EventLogFactory(Factory):
......@@ -227,7 +46,7 @@ class EventLogFactory(Factory):
handler = handler_factory()
logger.addHandler(handler)
else:
from zLOG.LogHandlers import NullHandler
from ZConfig.components.logger.loghandler import NullHandler
logger.addHandler(NullHandler())
return logger
......@@ -252,23 +71,3 @@ class EventLogFactory(Factory):
def startup(self):
zLOG.set_initializer(self.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 @@
import cStringIO as StringIO
import logging
import sys
import tempfile
import unittest
import ZConfig
import zLOG.datatypes
import zLOG.LogHandlers
from ZConfig.components.logger import loghandler
class TestzLOGConfig(unittest.TestCase):
......@@ -34,7 +32,7 @@ class TestzLOGConfig(unittest.TestCase):
self._old_logger = logging.getLogger("event")
self._old_level = self._old_logger.level
self._old_handlers = self._old_logger.handlers[:]
self._old_logger.handlers[:] = [zLOG.LogHandlers.NullHandler()]
self._old_logger.handlers[:] = [loghandler.NullHandler()]
def tearDown(self):
for h in self._old_logger.handlers:
......@@ -63,36 +61,6 @@ class TestzLOGConfig(unittest.TestCase):
self.assert_(not handler)
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):
conf = self.get_config("")
self.assert_(conf.eventlog is None)
......@@ -103,109 +71,7 @@ class TestzLOGConfig(unittest.TestCase):
# printed if there are no handlers:
self.assertEqual(len(logger.handlers), 1)
self.assert_(isinstance(logger.handlers[0],
zLOG.LogHandlers.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)
loghandler.NullHandler))
def check_simple_logger(self, text, level=logging.INFO):
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