This commit is contained in:
ton
2024-10-07 10:13:40 +07:00
parent aa1631742f
commit 3a7d696db6
9729 changed files with 1832837 additions and 161742 deletions

View File

@@ -1521,7 +1521,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
logger.debug("Houston, we have a %s", "thorny problem", exc_info=True)
"""
if self.isEnabledFor(DEBUG):
self._log(DEBUG, msg, args, **kwargs)
@@ -1533,7 +1533,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.info("Houston, we have a %s", "notable problem", exc_info=1)
logger.info("Houston, we have a %s", "notable problem", exc_info=True)
"""
if self.isEnabledFor(INFO):
self._log(INFO, msg, args, **kwargs)
@@ -1545,7 +1545,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1)
logger.warning("Houston, we have a %s", "bit of a problem", exc_info=True)
"""
if self.isEnabledFor(WARNING):
self._log(WARNING, msg, args, **kwargs)
@@ -1562,7 +1562,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.error("Houston, we have a %s", "major problem", exc_info=1)
logger.error("Houston, we have a %s", "major problem", exc_info=True)
"""
if self.isEnabledFor(ERROR):
self._log(ERROR, msg, args, **kwargs)
@@ -1580,7 +1580,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.critical("Houston, we have a %s", "major disaster", exc_info=1)
logger.critical("Houston, we have a %s", "major disaster", exc_info=True)
"""
if self.isEnabledFor(CRITICAL):
self._log(CRITICAL, msg, args, **kwargs)
@@ -1598,7 +1598,7 @@ class Logger(Filterer):
To pass exception information, use the keyword argument exc_info with
a true value, e.g.
logger.log(level, "We have a %s", "mysterious problem", exc_info=1)
logger.log(level, "We have a %s", "mysterious problem", exc_info=True)
"""
if not isinstance(level, int):
if raiseExceptions:
@@ -1985,18 +1985,11 @@ class LoggerAdapter(object):
"""
return self.logger.hasHandlers()
def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
def _log(self, level, msg, args, **kwargs):
"""
Low-level log implementation, proxied to allow nested logger adapters.
"""
return self.logger._log(
level,
msg,
args,
exc_info=exc_info,
extra=extra,
stack_info=stack_info,
)
return self.logger._log(level, msg, args, **kwargs)
@property
def manager(self):
@@ -2056,7 +2049,7 @@ def basicConfig(**kwargs):
that this argument is incompatible with 'filename' - if both
are present, 'stream' is ignored.
handlers If specified, this should be an iterable of already created
handlers, which will be added to the root handler. Any handler
handlers, which will be added to the root logger. Any handler
in the list which does not have a formatter assigned will be
assigned the formatter created in this function.
force If this keyword is specified as true, any existing handlers

View File

@@ -1,4 +1,4 @@
# Copyright 2001-2022 by Vinay Sajip. All Rights Reserved.
# Copyright 2001-2023 by Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
@@ -485,10 +485,10 @@ class BaseConfigurator(object):
c = config.pop('()')
if not callable(c):
c = self.resolve(c)
props = config.pop('.', None)
# Check for valid identifiers
kwargs = {k: config[k] for k in config if valid_ident(k)}
kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
result = c(**kwargs)
props = config.pop('.', None)
if props:
for name, value in props.items():
setattr(result, name, value)
@@ -500,6 +500,33 @@ class BaseConfigurator(object):
value = tuple(value)
return value
def _is_queue_like_object(obj):
"""Check that *obj* implements the Queue API."""
if isinstance(obj, queue.Queue):
return True
# defer importing multiprocessing as much as possible
from multiprocessing.queues import Queue as MPQueue
if isinstance(obj, MPQueue):
return True
# Depending on the multiprocessing start context, we cannot create
# a multiprocessing.managers.BaseManager instance 'mm' to get the
# runtime type of mm.Queue() or mm.JoinableQueue() (see gh-119819).
#
# Since we only need an object implementing the Queue API, we only
# do a protocol check, but we do not use typing.runtime_checkable()
# and typing.Protocol to reduce import time (see gh-121723).
#
# Ideally, we would have wanted to simply use strict type checking
# instead of a protocol-based type checking since the latter does
# not check the method signatures.
queue_interface = [
'empty', 'full', 'get', 'get_nowait',
'put', 'put_nowait', 'join', 'qsize',
'task_done',
]
return all(callable(getattr(obj, method, None))
for method in queue_interface)
class DictConfigurator(BaseConfigurator):
"""
Configure logging using a dictionary-like object to describe the
@@ -732,16 +759,16 @@ class DictConfigurator(BaseConfigurator):
def _configure_queue_handler(self, klass, **kwargs):
if 'queue' in kwargs:
q = kwargs['queue']
q = kwargs.pop('queue')
else:
q = queue.Queue() # unbounded
rhl = kwargs.get('respect_handler_level', False)
if 'listener' in kwargs:
lklass = kwargs['listener']
else:
lklass = logging.handlers.QueueListener
listener = lklass(q, *kwargs['handlers'], respect_handler_level=rhl)
handler = klass(q)
rhl = kwargs.pop('respect_handler_level', False)
lklass = kwargs.pop('listener', logging.handlers.QueueListener)
handlers = kwargs.pop('handlers', [])
listener = lklass(q, *handlers, respect_handler_level=rhl)
handler = klass(q, **kwargs)
handler.listener = listener
return handler
@@ -768,37 +795,39 @@ class DictConfigurator(BaseConfigurator):
klass = cname
else:
klass = self.resolve(cname)
if issubclass(klass, logging.handlers.MemoryHandler) and\
'target' in config:
# Special case for handler which refers to another handler
try:
tn = config['target']
th = self.config['handlers'][tn]
if not isinstance(th, logging.Handler):
config.update(config_copy) # restore for deferred cfg
raise TypeError('target not configured yet')
config['target'] = th
except Exception as e:
raise ValueError('Unable to set target handler %r' % tn) from e
if issubclass(klass, logging.handlers.MemoryHandler):
if 'flushLevel' in config:
config['flushLevel'] = logging._checkLevel(config['flushLevel'])
if 'target' in config:
# Special case for handler which refers to another handler
try:
tn = config['target']
th = self.config['handlers'][tn]
if not isinstance(th, logging.Handler):
config.update(config_copy) # restore for deferred cfg
raise TypeError('target not configured yet')
config['target'] = th
except Exception as e:
raise ValueError('Unable to set target handler %r' % tn) from e
elif issubclass(klass, logging.handlers.QueueHandler):
# Another special case for handler which refers to other handlers
if 'handlers' not in config:
raise ValueError('No handlers specified for a QueueHandler')
# if 'handlers' not in config:
# raise ValueError('No handlers specified for a QueueHandler')
if 'queue' in config:
qspec = config['queue']
if not isinstance(qspec, queue.Queue):
if isinstance(qspec, str):
q = self.resolve(qspec)
if not callable(q):
raise TypeError('Invalid queue specifier %r' % qspec)
q = q()
elif isinstance(qspec, dict):
if '()' not in qspec:
raise TypeError('Invalid queue specifier %r' % qspec)
q = self.configure_custom(dict(qspec))
else:
if isinstance(qspec, str):
q = self.resolve(qspec)
if not callable(q):
raise TypeError('Invalid queue specifier %r' % qspec)
config['queue'] = q
config['queue'] = q()
elif isinstance(qspec, dict):
if '()' not in qspec:
raise TypeError('Invalid queue specifier %r' % qspec)
config['queue'] = self.configure_custom(dict(qspec))
elif not _is_queue_like_object(qspec):
raise TypeError('Invalid queue specifier %r' % qspec)
if 'listener' in config:
lspec = config['listener']
if isinstance(lspec, type):
@@ -819,18 +848,19 @@ class DictConfigurator(BaseConfigurator):
if not callable(listener):
raise TypeError('Invalid listener specifier %r' % lspec)
config['listener'] = listener
hlist = []
try:
for hn in config['handlers']:
h = self.config['handlers'][hn]
if not isinstance(h, logging.Handler):
config.update(config_copy) # restore for deferred cfg
raise TypeError('Required handler %r '
'is not configured yet' % hn)
hlist.append(h)
except Exception as e:
raise ValueError('Unable to set required handler %r' % hn) from e
config['handlers'] = hlist
if 'handlers' in config:
hlist = []
try:
for hn in config['handlers']:
h = self.config['handlers'][hn]
if not isinstance(h, logging.Handler):
config.update(config_copy) # restore for deferred cfg
raise TypeError('Required handler %r '
'is not configured yet' % hn)
hlist.append(h)
except Exception as e:
raise ValueError('Unable to set required handler %r' % hn) from e
config['handlers'] = hlist
elif issubclass(klass, logging.handlers.SMTPHandler) and\
'mailhost' in config:
config['mailhost'] = self.as_tuple(config['mailhost'])
@@ -841,8 +871,7 @@ class DictConfigurator(BaseConfigurator):
factory = functools.partial(self._configure_queue_handler, klass)
else:
factory = klass
props = config.pop('.', None)
kwargs = {k: config[k] for k in config if valid_ident(k)}
kwargs = {k: config[k] for k in config if (k != '.' and valid_ident(k))}
try:
result = factory(**kwargs)
except TypeError as te:
@@ -860,6 +889,7 @@ class DictConfigurator(BaseConfigurator):
result.setLevel(logging._checkLevel(level))
if filters:
self.add_filters(result, filters)
props = config.pop('.', None)
if props:
for name, value in props.items():
setattr(result, name, value)

View File

@@ -187,15 +187,18 @@ class RotatingFileHandler(BaseRotatingHandler):
Basically, see if the supplied record would cause the file to exceed
the size limit we have.
"""
# See bpo-45401: Never rollover anything other than regular files
if os.path.exists(self.baseFilename) and not os.path.isfile(self.baseFilename):
return False
if self.stream is None: # delay was set...
self.stream = self._open()
if self.maxBytes > 0: # are we rolling over?
pos = self.stream.tell()
if not pos:
# gh-116263: Never rollover an empty file
return False
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:
if pos + len(msg) >= self.maxBytes:
# See bpo-45401: Never rollover anything other than regular files
if os.path.exists(self.baseFilename) and not os.path.isfile(self.baseFilename):
return False
return True
return False
@@ -232,19 +235,19 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
if self.when == 'S':
self.interval = 1 # one second
self.suffix = "%Y-%m-%d_%H-%M-%S"
self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$"
extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(?!\d)"
elif self.when == 'M':
self.interval = 60 # one minute
self.suffix = "%Y-%m-%d_%H-%M"
self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(\.\w+)?$"
extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(?!\d)"
elif self.when == 'H':
self.interval = 60 * 60 # one hour
self.suffix = "%Y-%m-%d_%H"
self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}(\.\w+)?$"
extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}_\d{2}(?!\d)"
elif self.when == 'D' or self.when == 'MIDNIGHT':
self.interval = 60 * 60 * 24 # one day
self.suffix = "%Y-%m-%d"
self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}(?!\d)"
elif self.when.startswith('W'):
self.interval = 60 * 60 * 24 * 7 # one week
if len(self.when) != 2:
@@ -253,11 +256,17 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
raise ValueError("Invalid day specified for weekly rollover: %s" % self.when)
self.dayOfWeek = int(self.when[1])
self.suffix = "%Y-%m-%d"
self.extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
extMatch = r"(?<!\d)\d{4}-\d{2}-\d{2}(?!\d)"
else:
raise ValueError("Invalid rollover interval specified: %s" % self.when)
self.extMatch = re.compile(self.extMatch, re.ASCII)
# extMatch is a pattern for matching a datetime suffix in a file name.
# After custom naming, it is no longer guaranteed to be separated by
# periods from other parts of the filename. The lookup statements
# (?<!\d) and (?!\d) ensure that the datetime suffix (which itself
# starts and ends with digits) is not preceded or followed by digits.
# This reduces the number of false matches and improves performance.
self.extMatch = re.compile(extMatch, re.ASCII)
self.interval = self.interval * interval # multiply by units requested
# The following line added because the filename passed in could be a
# path object (see Issue #27493), but self.baseFilename will be a string
@@ -299,7 +308,7 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
r = rotate_ts - ((currentHour * 60 + currentMinute) * 60 +
currentSecond)
if r < 0:
if r <= 0:
# Rotate time is before the current time (for example when
# self.rotateAt is 13:45 and it now 14:15), rotation is
# tomorrow.
@@ -328,17 +337,21 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
daysToWait = self.dayOfWeek - day
else:
daysToWait = 6 - day + self.dayOfWeek + 1
newRolloverAt = result + (daysToWait * (60 * 60 * 24))
if not self.utc:
dstNow = t[-1]
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
addend = -3600
else: # DST bows out before next rollover, so we need to add an hour
addend = 3600
newRolloverAt += addend
result = newRolloverAt
result += daysToWait * _MIDNIGHT
result += self.interval - _MIDNIGHT * 7
else:
result += self.interval - _MIDNIGHT
if not self.utc:
dstNow = t[-1]
dstAtRollover = time.localtime(result)[-1]
if dstNow != dstAtRollover:
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
addend = -3600
if not time.localtime(result-3600)[-1]:
addend = 0
else: # DST bows out before next rollover, so we need to add an hour
addend = 3600
result += addend
return result
def shouldRollover(self, record):
@@ -369,32 +382,28 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
dirName, baseName = os.path.split(self.baseFilename)
fileNames = os.listdir(dirName)
result = []
# See bpo-44753: Don't use the extension when computing the prefix.
n, e = os.path.splitext(baseName)
prefix = n + '.'
plen = len(prefix)
for fileName in fileNames:
if self.namer is None:
# Our files will always start with baseName
if not fileName.startswith(baseName):
continue
else:
# Our files could be just about anything after custom naming, but
# likely candidates are of the form
# foo.log.DATETIME_SUFFIX or foo.DATETIME_SUFFIX.log
if (not fileName.startswith(baseName) and fileName.endswith(e) and
len(fileName) > (plen + 1) and not fileName[plen+1].isdigit()):
continue
if fileName[:plen] == prefix:
suffix = fileName[plen:]
# See bpo-45628: The date/time suffix could be anywhere in the
# filename
parts = suffix.split('.')
for part in parts:
if self.extMatch.match(part):
if self.namer is None:
prefix = baseName + '.'
plen = len(prefix)
for fileName in fileNames:
if fileName[:plen] == prefix:
suffix = fileName[plen:]
if self.extMatch.fullmatch(suffix):
result.append(os.path.join(dirName, fileName))
else:
for fileName in fileNames:
# Our files could be just about anything after custom naming,
# but they should contain the datetime suffix.
# Try to find the datetime suffix in the file name and verify
# that the file name can be generated by this handler.
m = self.extMatch.search(fileName)
while m:
dfn = self.namer(self.baseFilename + "." + m[0])
if os.path.basename(dfn) == fileName:
result.append(os.path.join(dirName, fileName))
break
m = self.extMatch.search(fileName, m.start() + 1)
if len(result) < self.backupCount:
result = []
else:
@@ -410,17 +419,14 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
then we have to get a list of matching filenames, sort them and remove
the one with the oldest suffix.
"""
if self.stream:
self.stream.close()
self.stream = None
# get the time that this sequence started at and make it a TimeTuple
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
t = self.rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
dstNow = time.localtime(currentTime)[-1]
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
@@ -431,26 +437,19 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
dfn = self.rotation_filename(self.baseFilename + "." +
time.strftime(self.suffix, timeTuple))
if os.path.exists(dfn):
os.remove(dfn)
# Already rolled over.
return
if self.stream:
self.stream.close()
self.stream = None
self.rotate(self.baseFilename, dfn)
if self.backupCount > 0:
for s in self.getFilesToDelete():
os.remove(s)
if not self.delay:
self.stream = self._open()
newRolloverAt = self.computeRollover(currentTime)
while newRolloverAt <= currentTime:
newRolloverAt = newRolloverAt + self.interval
#If DST changes and midnight or weekly rollover, adjust for this.
if (self.when == 'MIDNIGHT' or self.when.startswith('W')) and not self.utc:
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow: # DST kicks in before next rollover, so we need to deduct an hour
addend = -3600
else: # DST bows out before next rollover, so we need to add an hour
addend = 3600
newRolloverAt += addend
self.rolloverAt = newRolloverAt
self.rolloverAt = self.computeRollover(currentTime)
class WatchedFileHandler(logging.FileHandler):
"""
@@ -833,10 +832,8 @@ class SysLogHandler(logging.Handler):
"local7": LOG_LOCAL7,
}
#The map below appears to be trivially lowercasing the key. However,
#there's more to it than meets the eye - in some locales, lowercasing
#gives unexpected results. See SF #1524081: in the Turkish locale,
#"INFO".lower() != "info"
# Originally added to work around GH-43683. Unnecessary since GH-50043 but kept
# for backwards compatibility.
priority_map = {
"DEBUG" : "debug",
"INFO" : "info",