/usr/lib/python2.7/dist-packages/oops_datedir_repo/uniquefileallocator.py is in python-oops-datedir-repo 0.0.17-0ubuntu2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | # Copyright (c) 2010, 2011, Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, version 3 only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# GNU Lesser General Public License version 3 (see the file LICENSE).
"""Create uniquely named log files on disk."""
__all__ = ['UniqueFileAllocator']
__metaclass__ = type
import datetime
import errno
import os.path
import stat
import threading
import pytz
UTC = pytz.utc
# the section of the ID before the instance identifier is the
# days since the epoch, which is defined as the start of 2006.
epoch = datetime.datetime(2006, 01, 01, 00, 00, 00, tzinfo=UTC)
class UniqueFileAllocator:
"""Assign unique file names to logs being written from an app/script.
UniqueFileAllocator causes logs written from one process to be uniquely
named. It is not safe for use in multiple processes with the same output
root - each process must have a unique output root.
"""
def __init__(self, output_root, log_type, log_subtype):
"""Create a UniqueFileAllocator.
:param output_root: The root directory that logs should be placed in.
:param log_type: A string to use as a prefix in the ID assigned to new
logs. For instance, "OOPS".
:param log_subtype: A string to insert in the generate log filenames
between the day number and the serial. For instance "T" for
"Testing".
"""
self._lock = threading.Lock()
self._output_root = output_root
self._last_serial = 0
self._last_output_dir = None
self._log_type = log_type
self._log_subtype = log_subtype
self._log_token = ""
def _findHighestSerialFilename(self, directory=None, time=None):
"""Find details of the last log present in the given directory.
This function only considers logs with the currently
configured log_subtype.
One of directory, time must be supplied.
:param directory: Look in this directory.
:param time: Look in the directory that a log written at this time
would have been written to. If supplied, supercedes directory.
:return: a tuple (log_serial, log_filename), which will be (0,
None) if no logs are found. log_filename is a usable path, not
simply the basename.
"""
if directory is None:
directory = self.output_dir(time)
prefix = self.get_log_infix()
lastid = 0
lastfilename = None
for filename in os.listdir(directory):
logid = filename.rsplit('.', 1)[1]
if not logid.startswith(prefix):
continue
logid = logid[len(prefix):]
if logid.isdigit() and (lastid is None or int(logid) > lastid):
lastid = int(logid)
lastfilename = filename
if lastfilename is not None:
lastfilename = os.path.join(directory, lastfilename)
return lastid, lastfilename
def _findHighestSerial(self, directory):
"""Find the last serial actually applied to disk in directory.
The purpose of this function is to not repeat sequence numbers
if the logging application is restarted.
This method is not thread safe, and only intended to be called
from the constructor (but it is called from other places in
integration tests).
"""
return self._findHighestSerialFilename(directory)[0]
def getFilename(self, log_serial, time):
"""Get the filename for a given log serial and time."""
log_subtype = self.get_log_infix()
# TODO: Calling output_dir causes a global lock to be taken and a
# directory scan, which is bad for performance. It would be better
# to have a split out 'directory name for time' function which the
# 'want to use this directory now' function can call.
output_dir = self.output_dir(time)
second_in_day = time.hour * 3600 + time.minute * 60 + time.second
return os.path.join(
output_dir, '%05d.%s%s' % (
second_in_day, log_subtype, log_serial))
def get_log_infix(self):
"""Return the current log infix to use in ids and file names."""
return self._log_subtype + self._log_token
def newId(self, now=None):
"""Returns an (id, filename) pair for use by the caller.
The ID is composed of a short string to identify the Launchpad
instance followed by an ID that is unique for the day.
The filename is composed of the zero padded second in the day
followed by the ID. This ensures that reports are in date order when
sorted lexically.
"""
if now is not None:
now = now.astimezone(UTC)
else:
now = datetime.datetime.now(UTC)
# We look up the error directory before allocating a new ID,
# because if the day has changed, errordir() will reset the ID
# counter to zero.
self.output_dir(now)
self._lock.acquire()
try:
self._last_serial += 1
newid = self._last_serial
finally:
self._lock.release()
subtype = self.get_log_infix()
day_number = (now - epoch).days + 1
log_id = '%s-%d%s%d' % (self._log_type, day_number, subtype, newid)
filename = self.getFilename(newid, now)
return log_id, filename
def output_dir(self, now=None):
"""Find or make the directory to allocate log names in.
Log names are assigned within subdirectories containing the date the
assignment happened.
"""
if now is not None:
now = now.astimezone(UTC)
else:
now = datetime.datetime.now(UTC)
date = now.strftime('%Y-%m-%d')
result = os.path.join(self._output_root, date)
if result != self._last_output_dir:
self._lock.acquire()
try:
self._last_output_dir = result
# make sure the directory exists
try:
os.makedirs(result)
except OSError, e:
if e.errno != errno.EEXIST:
raise
# Make sure the directory permission is set to: rwxr-xr-x
permission = (
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
os.chmod(result, permission)
# TODO: Note that only one process can do this safely: its not
# cross-process safe, and also not entirely threadsafe:
# another # thread that has a new log and hasn't written it
# could then use that serial number. We should either make it
# really safe, or remove the contention entirely and log
# uniquely per thread of execution.
self._last_serial = self._findHighestSerial(result)
finally:
self._lock.release()
return result
def listRecentReportFiles(self):
now = datetime.datetime.now(UTC)
yesterday = now - datetime.timedelta(days=1)
directories = [self.output_dir(now), self.output_dir(yesterday)]
for directory in directories:
report_names = os.listdir(directory)
for name in sorted(report_names, reverse=True):
yield directory, name
def setToken(self, token):
"""Append a string to the log subtype in filenames and log ids.
:param token: a string to append..
Scripts that run multiple processes can use this to create a
unique identifier for each process.
"""
self._log_token = token
|