Skip to content

Commit db012ee

Browse files
author
Richard Jones
committed
added basic logging support
1 parent 838bec6 commit db012ee

File tree

8 files changed

+268
-4
lines changed

8 files changed

+268
-4
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Feature:
88
- implement __nonzero__ for HTMLProperty
99
- remove "manual" locking of sqlite database
1010
- create a new RDBMS cursor after committing
11+
- added basic logging, and configuration of it and python's logging module
1112

1213

1314
2004-??-?? 0.7.4

doc/customizing.txt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Customising Roundup
33
===================
44

5-
:Version: $Revision: 1.136 $
5+
:Version: $Revision: 1.137 $
66

77
.. This document borrows from the ZopeBook section on ZPT. The original is at:
88
http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -156,6 +156,22 @@ The configuration variables available are:
156156
also useful if you want to switch your 'new message' notification to
157157
a central user.
158158

159+
**LOGGGING_CONFIG** - ``os.path.join(TRACKER_HOME, 'logging.ini')``
160+
This variable activates logging of Roundup's internal messages using the
161+
standard python ``logging`` module. The module is configured using the
162+
indicated configuration file.
163+
164+
**LOGGING_FILENAME** - ``'/path/to/log file'``
165+
This variable activates logging of Roundup's internal messages using a
166+
built-in minimal logging service which appends messages to the indicated
167+
file.
168+
169+
**LOGGING_LEVEL** - ``'DEBUG'``, ``'INFO'``, ``'WARNING'`` or ``'ERROR'``
170+
This variable determines the level of messages logged by the built-in
171+
logging service - messages of the level named and higher will be sent to
172+
the ``LOGGING_FILENAME`` file. Not used when the ``LOGGGING_CONFIG``
173+
variable is set.
174+
159175
**MESSAGES_TO_AUTHOR** - ``'new'``, ``'yes'`` or``'no'``
160176
Send nosy messages to the author of the message?
161177
If 'new' is used, then the author will only be sent the message when the
@@ -244,6 +260,19 @@ tracker is attempted.::
244260
# trouble
245261
ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
246262

263+
# These variables define where to log Roundup's internal messages to.
264+
# You have two choices - either using the standard Python logging module
265+
# or a minimal logging facility built into Roundup. The former is activated
266+
# when you provide a LOGGING_CONFIG variable below which contains the
267+
# configuration of the logging module. The latter is activated when you
268+
# provide the LOGGING_FILENAME and optionally LOGGING_LEVEL variables. If
269+
# none of these variables are defined then only errors will be logged, and
270+
# they will go to stderr.
271+
# LOGGGING_CONFIG = os.path.join(TRACKER_HOME, 'logging.ini')
272+
# or,
273+
# LOGGING_FILENAME = '/path/to/log file'
274+
# LOGGING_LEVEL = 'INFO' # one of 'DEBUG', 'INFO', 'WARNING', 'ERROR'
275+
247276
# Additional text to include in the "name" part of the From: address
248277
# used in nosy messages. If the sending user is "Foo Bar", the From:
249278
# line is usually: "Foo Bar" <[email protected]>

doc/whatsnew-0.8.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=========================
2+
What's New in Roundup 0.8
3+
=========================
4+
5+
For those completely new to Roundup, you might want to look over the very
6+
terse features__ page.
7+
8+
__ features.html
9+
10+
.. contents::
11+
12+
Logging of internal messages
13+
============================
14+
15+
TODO
16+
17+
18+
.. _`customisation documentation`: customizing.html

roundup/instance.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: instance.py,v 1.13 2004-05-10 00:34:48 richard Exp $
18+
# $Id: instance.py,v 1.14 2004-06-08 05:29:18 richard Exp $
1919

2020
'''Tracker handling (open tracker).
2121
@@ -24,6 +24,7 @@
2424
__docformat__ = 'restructuredtext'
2525

2626
import os
27+
from roundup import rlog
2728

2829
class Vars:
2930
''' I'm just a container '''
@@ -91,6 +92,24 @@ def open(self, tracker_home):
9192
raise TrackerError, \
9293
'Required tracker attribute "%s" missing'%required
9394

95+
# init the logging
96+
config = tracker.config
97+
if hasattr(config, 'LOGGING_CONFIG'):
98+
try:
99+
import logging
100+
config.logging = logging
101+
except ImportError, msg:
102+
raise TrackerError, 'Python logging module unavailable: %s'%msg
103+
config.logging.fileConfig(config.LOGGING_CONFIG)
104+
elif hasattr(config, 'LOGGING_FILENAME'):
105+
config.logging = rlog.BasicLogging()
106+
config.logging.setFile(config.LOGGING_FILENAME)
107+
if hasattr(config, 'LOGGING_LEVEL'):
108+
config.logging.setLevel(config.LOGGING_LEVEL)
109+
else:
110+
config.logging = rlog.BasicLogging()
111+
config.logging.setLevel('ERROR')
112+
94113
return tracker
95114

96115
OldStyleTrackers = OldStyleTrackers()

roundup/rlog.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
'''Roundup basic logging support.
2+
3+
This module will use the standard Python logging implementation when
4+
available. If not, then a basic logging implementation, BasicLogging and
5+
BasicLoggingChannel will be used.
6+
7+
Configuration for "logging" module:
8+
- tracker configuration file specifies the location of a logging
9+
configration file as LOGGING_CONFIG
10+
- roundup-server and roundup-mailgw specify the location of a logging
11+
configuration file on the command line
12+
Configuration for "BasicLogging" implementation:
13+
- tracker configuration file specifies the location of a log file
14+
LOGGING_FILENAME
15+
- tracker configuration file specifies the level to log to as
16+
LOGGING_LEVEL
17+
- roundup-server and roundup-mailgw specify the location of a log
18+
file on the command line
19+
- roundup-server and roundup-mailgw specify the level to log to on
20+
the command line
21+
22+
In both cases, if no logfile is specified then logging will simply be sent
23+
to sys.stderr with only logging of ERROR messages.
24+
25+
In terms of the Roundup code using the logging implementation:
26+
- a "logging" object will be available on the "config" object for each
27+
tracker
28+
- the roundup-server and roundup-mailgw code will have a global "logging"
29+
object.
30+
31+
It is intended that the logger API implementation here be the same as (or
32+
close enough to) that of the standard Python library "logging" module.
33+
'''
34+
import sys, time, traceback
35+
36+
class BasicLogging:
37+
LVL_DEBUG = 4
38+
LVL_INFO = 3
39+
LVL_WARNING= 2
40+
LVL_ERROR= 1
41+
LVL_NONE = 0
42+
NAMES = {
43+
LVL_DEBUG: 'DEBUG',
44+
LVL_INFO: 'INFO',
45+
LVL_WARNING: 'WARNING',
46+
LVL_ERROR: 'ERROR',
47+
}
48+
level = LVL_INFO
49+
loggers = {}
50+
file = None
51+
def getLogger(self, name):
52+
return self.loggers.setdefault(name, BasicLogger(self.file, self.level))
53+
def fileConfig(self, filename):
54+
'''User is attempting to use a config file, but the basic logger
55+
doesn't support that.'''
56+
raise RuntimeError, "File-based logging configuration requires "\
57+
"the logging package."
58+
def setFile(self, file):
59+
'''Set the file to log to. "file" is either an open file object or
60+
a string filename to append entries to.
61+
'''
62+
if isinstance(file, type('')):
63+
file = open(file, 'a')
64+
self.file = file
65+
def setLevel(self, level):
66+
'''Set the maximum logging level. "level" is either a level number
67+
(one of the LVL_ values) or a string level name.
68+
'''
69+
if isinstance(level, type('')):
70+
for num, name in self.NAMES:
71+
if name == level:
72+
level = num
73+
self.level = level
74+
75+
class BasicLogger:
76+
'''Used when the standard Python library logging module isn't available.
77+
78+
Supports basic configuration through the tracker config file vars
79+
LOGGING_LEVEL and LOGGING_FILENAME.'''
80+
def __init__(self, file, level):
81+
self.file = file
82+
self.level = level
83+
84+
def setFile(self, file):
85+
'''Set the file to log to. "file" is either an open file object or
86+
a string filename to append entries to.
87+
'''
88+
if isinstance(file, type('')):
89+
file = open(file, 'a')
90+
self.file = file
91+
def setLevel(self, level):
92+
'''Set the maximum logging level. "level" is either a level number
93+
(one of the LVL_ values) or a string level name.
94+
'''
95+
if isinstance(level, type('')):
96+
for num, name in BasicLogging.NAMES:
97+
if name == level:
98+
level = num
99+
self.level = level
100+
def write(self, level, message):
101+
message = '%s %s %s'%(time.strftime('%Y-%m-%d %H:%M:%D'),
102+
BasicLogging.NAMES[level], message)
103+
self._write(message)
104+
def _write(self, text):
105+
file = self.file or sys.stderr
106+
file.write(text)
107+
def debug(self, message):
108+
if self.level < BasicLogging.LVL_DEBUG: return
109+
self.write(BasicLogging.LVL_DEBUG, message)
110+
def info(self, message):
111+
if self.level < BasicLogging.LVL_INFO: return
112+
self.write(BasicLogging.LVL_INFO, message)
113+
def warning(self, message):
114+
if self.level < BasicLogging.LVL_WARNING: return
115+
self.write(BasicLogging.LVL_WARNING, message)
116+
def error(self, message):
117+
if self.level < BasicLogging.LVL_ERROR: return
118+
self.write(BasicLogging.LVL_ERROR, message)
119+
def exception(self, message):
120+
if self.level < BasicLogging.LVL_ERROR: return
121+
self.write(BasicLogging.LVL_ERROR, message)
122+
self._write(traceback.format_exception(*(sys.exc_info())))
123+

templates/classic/config.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: config.py,v 1.8 2004-03-26 23:45:34 richard Exp $
18+
# $Id: config.py,v 1.9 2004-06-08 05:29:18 richard Exp $
1919

2020
import os
2121

@@ -63,6 +63,19 @@
6363
# The email address that roundup will complain to if it runs into trouble
6464
ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
6565

66+
# These variables define where to log Roundup's internal messages to.
67+
# You have two choices - either using the standard Python logging module
68+
# or a minimal logging facility built into Roundup. The former is activated
69+
# when you provide a LOGGING_CONFIG variable below which contains the
70+
# configuration of the logging module. The latter is activated when you
71+
# provide the LOGGING_FILENAME and optionally LOGGING_LEVEL variables. If
72+
# none of these variables are defined then only errors will be logged, and
73+
# they will go to stderr.
74+
# LOGGGING_CONFIG = os.path.join(TRACKER_HOME, 'logging.ini')
75+
# or,
76+
# LOGGING_FILENAME = '/path/to/log file'
77+
# LOGGING_LEVEL = 'INFO' # one of 'DEBUG', 'INFO', 'WARNING', 'ERROR'
78+
6679
# The 'dispatcher' is a role that can get notified of new items to the
6780
# database. It is used by the ERROR_MESSAGES_TO config setting.
6881
DISPATCHER_EMAIL = ADMIN_EMAIL

templates/minimal/config.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: config.py,v 1.6 2004-03-26 23:45:34 richard Exp $
18+
# $Id: config.py,v 1.7 2004-06-08 05:29:18 richard Exp $
1919

2020
import os
2121

@@ -63,6 +63,19 @@
6363
# The email address that roundup will complain to if it runs into trouble
6464
ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN
6565

66+
# These variables define where to log Roundup's internal messages to.
67+
# You have two choices - either using the standard Python logging module
68+
# or a minimal logging facility built into Roundup. The former is activated
69+
# when you provide a LOGGING_CONFIG variable below which contains the
70+
# configuration of the logging module. The latter is activated when you
71+
# provide the LOGGING_FILENAME and optionally LOGGING_LEVEL variables. If
72+
# none of these variables are defined then only errors will be logged, and
73+
# they will go to stderr.
74+
# LOGGGING_CONFIG = os.path.join(TRACKER_HOME, 'logging.ini')
75+
# or,
76+
# LOGGING_FILENAME = '/path/to/log file'
77+
# LOGGING_LEVEL = 'INFO' # one of 'DEBUG', 'INFO', 'WARNING', 'ERROR'
78+
6679
# The 'dispatcher' is a role that can get notified of new items to the database.
6780
DISPATCHER_EMAIL = ADMIN_EMAIL
6881

test/test_rlog.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import unittest, StringIO
2+
3+
from roundup import rlog
4+
5+
class LoggingTestCase(unittest.TestCase):
6+
def setUp(self):
7+
self.logging = rlog.BasicLogging()
8+
self.file = StringIO.StringIO()
9+
self.logging.setFile(self.file)
10+
def testLevels(self):
11+
logger = self.logging.getLogger('test')
12+
v1 = self.file.getvalue()
13+
logger.debug('test')
14+
v2 = self.file.getvalue()
15+
self.assertEqual(v1, v2, 'Logged when should not have')
16+
17+
v1 = self.file.getvalue()
18+
logger.info('test')
19+
v2 = self.file.getvalue()
20+
self.assertNotEqual(v1, v2, 'Nothing logged')
21+
22+
v1 = self.file.getvalue()
23+
logger.warning('test')
24+
v2 = self.file.getvalue()
25+
self.assertNotEqual(v1, v2, 'Nothing logged')
26+
27+
v1 = self.file.getvalue()
28+
logger.error('test')
29+
v2 = self.file.getvalue()
30+
self.assertNotEqual(v1, v2, 'Nothing logged')
31+
32+
v1 = self.file.getvalue()
33+
try:
34+
1/0
35+
except:
36+
logger.exception('test')
37+
v2 = self.file.getvalue()
38+
self.assertNotEqual(v1, v2, 'Nothing logged')
39+
40+
def test_suite():
41+
suite = unittest.TestSuite()
42+
suite.addTest(unittest.makeSuite(LoggingTestCase))
43+
return suite
44+
45+
if __name__ == '__main__':
46+
runner = unittest.TextTestRunner()
47+
unittest.main(testRunner=runner)
48+

0 commit comments

Comments
 (0)