summaryrefslogtreecommitdiffstats
path: root/Lib/logging/handlers.py
diff options
context:
space:
mode:
authorVinay Sajip <vinay_sajip@yahoo.co.uk>2004-07-03 11:48:34 (GMT)
committerVinay Sajip <vinay_sajip@yahoo.co.uk>2004-07-03 11:48:34 (GMT)
commit17c52d84934ff85efc26db3a040ce85cfb154488 (patch)
tree24e245f9c93334658ac7d95b8cf25bd2ed7a7e43 /Lib/logging/handlers.py
parent779e0c93346ab5458a8417148c4a23c1d915f6b8 (diff)
downloadcpython-17c52d84934ff85efc26db3a040ce85cfb154488.zip
cpython-17c52d84934ff85efc26db3a040ce85cfb154488.tar.gz
cpython-17c52d84934ff85efc26db3a040ce85cfb154488.tar.bz2
Refactored RotatingFileHandler to create a base class for rotating handlers. Added TimedRotatingFileHandler.
Diffstat (limited to 'Lib/logging/handlers.py')
-rw-r--r--Lib/logging/handlers.py178
1 files changed, 167 insertions, 11 deletions
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py
index 36225b9..97632ea 100644
--- a/Lib/logging/handlers.py
+++ b/Lib/logging/handlers.py
@@ -27,7 +27,7 @@ Copyright (C) 2001-2004 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
-import sys, logging, socket, types, os, string, cPickle, struct, time
+import sys, logging, socket, types, os, string, cPickle, struct, time, glob
#
# Some constants...
@@ -39,8 +39,34 @@ DEFAULT_HTTP_LOGGING_PORT = 9022
DEFAULT_SOAP_LOGGING_PORT = 9023
SYSLOG_UDP_PORT = 514
+class BaseRotatingHandler(logging.FileHandler):
+ """
+ Base class for handlers that rotate log files at a certain point.
+ Not meant to be instantiated directly. Instead, use RotatingFileHandler
+ or TimedRotatingFileHandler.
+ """
+ def __init__(self, filename, mode):
+ """
+ Use the specified filename for streamed logging
+ """
+ logging.FileHandler.__init__(self, filename, mode)
+
+ def emit(self, record):
+ """
+ Emit a record.
+
+ Output the record to the file, catering for rollover as described
+ in doRollover().
+ """
+ if self.shouldRollover(record):
+ self.doRollover()
+ logging.FileHandler.emit(self, record)
-class RotatingFileHandler(logging.FileHandler):
+class RotatingFileHandler(BaseRotatingHandler):
+ """
+ Handler for logging to a set of files, which switches from one file
+ to the next when the current file reaches a certain size.
+ """
def __init__(self, filename, mode="a", maxBytes=0, backupCount=0):
"""
Open the specified file and use it as the stream for logging.
@@ -62,11 +88,12 @@ class RotatingFileHandler(logging.FileHandler):
If maxBytes is zero, rollover never occurs.
"""
- logging.FileHandler.__init__(self, filename, mode)
+ self.mode = mode
+ if maxBytes > 0:
+ self.mode = "a" # doesn't make sense otherwise!
+ BaseRotatingHandler.__init__(self, filename, self.mode)
self.maxBytes = maxBytes
self.backupCount = backupCount
- if maxBytes > 0:
- self.mode = "a"
def doRollover(self):
"""
@@ -90,20 +117,149 @@ class RotatingFileHandler(logging.FileHandler):
#print "%s -> %s" % (self.baseFilename, dfn)
self.stream = open(self.baseFilename, "w")
- def emit(self, record):
+ def shouldRollover(self, record):
"""
- Emit a record.
+ Determine if rollover should occur.
- Output the record to the file, catering for rollover as described
- in doRollover().
+ Basically, see if the supplied record would cause the file to exceed
+ the size limit we have.
"""
if self.maxBytes > 0: # are we rolling over?
msg = "%s\n" % self.format(record)
self.stream.seek(0, 2) #due to non-posix-compliant Windows feature
if self.stream.tell() + len(msg) >= self.maxBytes:
- self.doRollover()
- logging.FileHandler.emit(self, record)
+ return 1
+ return 0
+class TimedRotatingFileHandler(BaseRotatingHandler):
+ """
+ Handler for logging to a file, rotating the log file at certain timed
+ intervals.
+
+ If backupCount is > 0, when rollover is done, no more than backupCount
+ files are kept - the oldest ones are deleted.
+ """
+ def __init__(self, filename, when='h', interval=1, backupCount=0):
+ BaseRotatingHandler.__init__(self, filename, 'a')
+ self.when = string.upper(when)
+ self.backupCount = backupCount
+ # Calculate the real rollover interval, which is just the number of
+ # seconds between rollovers. Also set the filename suffix used when
+ # a rollover occurs. Current 'when' events supported:
+ # S - Seconds
+ # M - Minutes
+ # H - Hours
+ # D - Days
+ # midnight - roll over at midnight
+ # W{0-6} - roll over on a certain day; 0 - Monday
+ #
+ # Case of the 'when' specifier is not important; lower or upper case
+ # will work.
+ currentTime = int(time.time())
+ if self.when == 'S':
+ self.interval = 1 # one second
+ self.suffix = "%Y-%m-%d_%H-%M-%S"
+ elif self.when == 'M':
+ self.interval = 60 # one minute
+ self.suffix = "%Y-%m-%d_%H-%M"
+ elif self.when == 'H':
+ self.interval = 60 * 60 # one hour
+ self.suffix = "%Y-%m-%d_%H"
+ elif self.when == 'D' or self.when == 'MIDNIGHT':
+ self.interval = 60 * 60 * 24 # one day
+ self.suffix = "%Y-%m-%d"
+ elif self.when.startswith('W'):
+ self.interval = 60 * 60 * 24 * 7 # one week
+ if len(self.when) != 2:
+ raise ValueError("You must specify a day for weekly rollover from 0 to 6 (0 is Monday): %s" % self.when)
+ if self.when[1] < '0' or self.when[1] > '6':
+ raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
+ self.dayOfWeek = int(self.when[1])
+ self.suffix = "%Y-%m-%d"
+ else:
+ raise ValueError("Invalid rollover interval specified: %s" % self.when)
+
+ self.interval *= interval # multiply by units requested
+ self.rolloverAt = currentTime + self.interval
+
+ # If we are rolling over at midnight or weekly, then the interval is already known.
+ # What we need to figure out is WHEN the next interval is. In other words,
+ # if you are rolling over at midnight, then your base interval is 1 day,
+ # but you want to start that one day clock at midnight, not now. So, we
+ # have to fudge the rolloverAt value in order to trigger the first rollover
+ # at the right time. After that, the regular interval will take care of
+ # the rest. Note that this code doesn't care about leap seconds. :)
+ if self.when == 'MIDNIGHT' or self.when.startswith('W'):
+ # This could be done with less code, but I wanted it to be clear
+ t = time.localtime(currentTime)
+ currentHour = t[3]
+ currentMinute = t[4]
+ currentSecond = t[5]
+ # r is the number of seconds left between now and midnight
+ r = (24 - currentHour) * 60 * 60 # number of hours in seconds
+ r += (59 - currentMinute) * 60 # plus the number of minutes (in secs)
+ r += (59 - currentSecond) # plus the number of seconds
+ self.rolloverAt = currentTime + r
+ # If we are rolling over on a certain day, add in the number of days until
+ # the next rollover, but offset by 1 since we just calculated the time
+ # until the next day starts. There are three cases:
+ # Case 1) The day to rollover is today; in this case, do nothing
+ # Case 2) The day to rollover is further in the interval (i.e., today is
+ # day 2 (Wednesday) and rollover is on day 6 (Sunday). Days to
+ # next rollover is simply 6 - 2 - 1, or 3.
+ # Case 3) The day to rollover is behind us in the interval (i.e., today
+ # is day 5 (Saturday) and rollover is on day 3 (Thursday).
+ # Days to rollover is 6 - 5 + 3, or 4. In this case, it's the
+ # number of days left in the current week (1) plus the number
+ # of days in the next week until the rollover day (3).
+ if when.startswith('W'):
+ day = t[6] # 0 is Monday
+ if day > self.dayOfWeek:
+ daysToWait = (day - self.dayOfWeek) - 1
+ self.rolloverAt += (daysToWait * (60 * 60 * 24))
+ if day < self.dayOfWeek:
+ daysToWait = (6 - self.dayOfWeek) + day
+ self.rolloverAt += (daysToWait * (60 * 60 * 24))
+
+ print "Will rollover at %d, %d seconds from now" % (self.rolloverAt, self.rolloverAt - currentTime)
+
+ def shouldRollover(self, record):
+ """
+ Determine if rollover should occur
+
+ record is not used, as we are just comparing times, but it is needed so
+ the method siguratures are the same
+ """
+ t = int(time.time())
+ if t >= self.rolloverAt:
+ return 1
+ print "No need to rollover: %d, %d" % (t, self.rolloverAt)
+ return 0
+
+ def doRollover(self):
+ """
+ do a rollover; in this case, a date/time stamp is appended to the filename
+ when the rollover happens. However, you want the file to be named for the
+ start of the interval, not the current time. If there is a backup count,
+ then we have to get a list of matching filenames, sort them and remove
+ the one with the oldest suffix.
+ """
+ self.stream.close()
+ # get the time that this sequence started at and make it a TimeTuple
+ t = self.rolloverAt - self.interval
+ timeTuple = time.localtime(t)
+ dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
+ if os.path.exists(dfn):
+ os.remove(dfn)
+ os.rename(self.baseFilename, dfn)
+ if self.backupCount > 0:
+ # find the oldest log file and delete it
+ s = glob.glob(self.baseFilename + ".20*")
+ if len(s) > self.backupCount:
+ os.remove(s[0])
+ print "%s -> %s" % (self.baseFilename, dfn)
+ self.stream = open(self.baseFilename, "w")
+ self.rolloverAt = int(time.time()) + self.interval
class SocketHandler(logging.Handler):
"""