使用Logging
编写log.conf配置文件
###############################################
[loggers]
keys=root,fastapi_project
[logger_root]
level=INFO
handlers=stream
[logger_fastapi_project]
level=INFO
handlers=fastapi_project
qualname=fastapi_project
propagate=0
###############################################
[handlers]
keys=stream,fastapi_project
[handler_stream]
class=StreamHandler
level=INFO
formatter=myApp
args=(sys.stderr,)
[handler_fastapi_project]
class=app.utils.custom_logging.EnhancedTimedRotatingFileHandler
level=INFO
formatter=myApp
args=('logs/%(app_name)s.log', 'H', 1, 168)
###############################################
[formatters]
keys=myApp
[formatter_myApp]
format=[%(asctime)s][%(name)s][%(filename)s:%(lineno)d][%(levelname)s] %(message)s
datefmt=%Y-%m-%d %H:%M:%S
编写对应的Handler
import os
import time
import re
from stat import ST_MTIME
from logging.handlers import BaseRotatingHandler
_MIDNIGHT = 24 * 60 * 60
class EnhancedTimedRotatingFileHandler(BaseRotatingHandler):
suffix_extMatch = {
'%Y': r'\d{4}',
'%m': r'\d{2}',
'%d': r'\d{2}',
'%H': r'\d{2}',
'%M': r'\d{2}',
'%S': r'\d{2}',
'%X': r'\d{2}:\d{2}:\d{2}',
}
when_suffix = {
'S': '%Y-%m-%d_%H-%M-%S',
'M': '%Y-%m-%d_%H-%M',
'H': '%Y-%m-%d_%H',
'D': '%Y-%m-%d',
'MIDNIGHT': '%Y-%m-%d',
'W0': '%Y-%m-%d',
'W1': '%Y-%m-%d',
'W2': '%Y-%m-%d',
'W3': '%Y-%m-%d',
'W4': '%Y-%m-%d',
'W5': '%Y-%m-%d',
'W6': '%Y-%m-%d',
}
when_interval = {
'S': 1,
'M': 60,
'H': 60 * 60,
'D': 60 * 60 * 24,
'MIDNIGHT': 60 * 60 * 24,
'W0': 60 * 60 * 24 * 7,
'W1': 60 * 60 * 24 * 7,
'W2': 60 * 60 * 24 * 7,
'W3': 60 * 60 * 24 * 7,
'W4': 60 * 60 * 24 * 7,
'W5': 60 * 60 * 24 * 7,
'W6': 60 * 60 * 24 * 7,
}
def __init__(self, filename, when='h', interval=1, backupCount=0,
encoding=None, delay=False, utc=False, atTime=None,
suffix=None):
self.when = when.upper()
self.backupCount = backupCount
self.utc = utc
self.atTime = atTime
self.baseFilename = os.path.abspath(filename)
if self.when not in self.when_suffix:
if self.when.startswith('W'):
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)
raise ValueError(
"Invalid rollover interval specified: %s" % self.when)
if suffix and self.is_suffix_reasonable(self.when, suffix):
self.suffix = suffix
else:
self.suffix = self.when_suffix[self.when]
self.interval = self.when_interval[self.when]
self.interval = self.interval * interval
if self.when.startswith('W'):
self.dayOfWeek = int(self.when[1])
if not os.path.exists(os.path.dirname(self.baseFilename)):
os.makedirs(os.path.dirname(self.baseFilename))
self.extMatch = self._extMatch_from_suffix(self.suffix)
self.extMatch = re.compile(self.extMatch)
if os.path.exists(self.baseFilename):
self.rename_old_file_to_new_format(self.baseFilename)
current_filename = self._current_filename()
if os.path.exists(current_filename):
t = os.stat(current_filename)[ST_MTIME]
else:
t = int(time.time())
self.rolloverAt = self.computeRollover(t)
BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
def rename_old_file_to_new_format(self, filename):
t = os.stat(filename)[ST_MTIME]
rolloverAt = self.computeRollover(t)
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
t = rolloverAt - self.interval
if self.utc:
timeTuple = time.gmtime(t)
else:
timeTuple = time.localtime(t)
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
addend = 3600
else:
addend = -3600
timeTuple = time.localtime(t + addend)
dfn = filename + "." + time.strftime(self.suffix, timeTuple)
if os.path.exists(dfn):
os.remove(dfn)
os.rename(filename, dfn)
def _extMatch_from_suffix(self, suffix):
extMatch = suffix
for _s in self.suffix_extMatch:
_m = self.suffix_extMatch[_s]
extMatch = extMatch.replace(_s, _m)
extMatch = r'^' + extMatch + r'(\.\w+)?$'
return extMatch
def is_suffix_reasonable(self, when, suffix):
if when == 'S':
return '%S' in suffix or '%X' in suffix
if when == 'M':
return '%M' in suffix or '%X' in suffix
if when == 'H':
return '%H' in suffix or '%X' in suffix
if when == 'D' or when == 'MIDNIGHT' or when.startswith('W'):
return '%d' in suffix
return False
def _current_filename(self):
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
if self.utc:
timeTuple = time.gmtime(currentTime)
else:
timeTuple = time.localtime(currentTime)
dstThen = timeTuple[-1]
if dstNow != dstThen:
if dstNow:
addend = 3600
else:
addend = -3600
timeTuple = time.localtime(currentTime + addend)
filename = self.baseFilename + "." + \
time.strftime(self.suffix, timeTuple)
return filename
def _open(self):
"""
Open the current base file with the (original) mode and encoding.
Return the resulting stream.
"""
current_filename = self._current_filename()
return open(current_filename, self.mode)
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 signatures are the same
"""
t = int(time.time())
if t >= self.rolloverAt:
return 1
return 0
def getFilesToDelete(self):
"""
Determine the files to delete when rolling over.
More specific than the earlier method, which just used glob.glob().
"""
dirName, baseName = os.path.split(self.baseFilename)
fileNames = os.listdir(dirName)
result = []
prefix = baseName + "."
plen = len(prefix)
for fileName in fileNames:
if fileName[:plen] == prefix:
suffix = fileName[plen:]
if self.extMatch.match(suffix):
result.append(os.path.join(dirName, fileName))
result.sort()
if len(result) < self.backupCount:
result = []
else:
result = result[:len(result) - self.backupCount]
return result
def computeRollover(self, currentTime):
"""
Work out the rollover time based on the specified time.
"""
result = currentTime + self.interval
if self.when == 'S':
return result
else:
if self.utc:
t = time.gmtime(currentTime)
else:
t = time.localtime(currentTime)
currentHour = t[3]
currentMinute = t[4]
currentSecond = t[5]
currentDay = t[6]
if self.when == 'M':
r = 60 - currentSecond
elif self.when == 'H':
r = 60 * 60 - (currentMinute * 60 + currentSecond)
elif self.when == 'MIDNIGHT' or self.when == 'D' or self.when.startswith('W'):
if self.atTime is None:
rotate_ts = _MIDNIGHT
else:
rotate_ts = ((self.atTime.hour * 60 + self.atTime.minute)
* 60 + self.atTime.second)
r = rotate_ts - ((currentHour * 60 + currentMinute) * 60 +
currentSecond)
if r < 0:
r += _MIDNIGHT
currentDay = (currentDay + 1) % 7
result = currentTime + r
if self.when.startswith('W'):
day = currentDay
if day != self.dayOfWeek:
if day < self.dayOfWeek:
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:
addend = -3600
else:
addend = 3600
newRolloverAt += addend
result = newRolloverAt
return result
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.
"""
if self.stream:
self.stream.close()
self.stream = None
currentTime = int(time.time())
dstNow = time.localtime(currentTime)[-1]
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 (self.when == 'MIDNIGHT' or self.when.startswith('W')) \
and not self.utc:
dstAtRollover = time.localtime(newRolloverAt)[-1]
if dstNow != dstAtRollover:
if not dstNow:
addend = -3600
else:
addend = 3600
newRolloverAt += addend
self.rolloverAt = newRolloverAt
使用Loguru
按日期滚动
from loguru import logger
logger.add("file.log", rotation="12:00")
logger.debug("a log!")
按大小滚动
from loguru import logger
logger.add("file.log", rotation="1 MB")
logger.debug("a log!")
记录Trace
logger.add("out.log", backtrace=True, diagnose=True)
logger.exception("ooooh")
按级别分文件
folder_ = "./log/"
prefix_ = "polaris-"
rotation_ = "10 MB"
retention_ = "30 days"
encoding_ = "utf-8"
backtrace_ = True
diagnose_ = True
format_ = '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> ' \
'| <magenta>{process}</magenta>:<yellow>{thread}</yellow> ' \
'| <cyan>{name}</cyan>:<cyan>{function}</cyan>:<yellow>{line}</yellow> - <level>{message}</level>'
logger.add(folder_ + prefix_ + "debug.log", level="DEBUG", backtrace=backtrace_, diagnose=diagnose_,
format=format_, colorize=False,
rotation=rotation_, retention=retention_, encoding=encoding_,
filter=lambda record: record["level"].no >= logger.level("DEBUG").no)
logger.add(folder_ + prefix_ + "info.log", level="INFO", backtrace=backtrace_, diagnose=diagnose_,
format=format_, colorize=False,
rotation=rotation_, retention=retention_, encoding=encoding_,
filter=lambda record: record["level"].no >= logger.level("INFO").no)
logger.add(folder_ + prefix_ + "warning.log", level="WARNING", backtrace=backtrace_, diagnose=diagnose_,
format=format_, colorize=False,
rotation=rotation_, retention=retention_, encoding=encoding_,
filter=lambda record: record["level"].no >= logger.level("WARNING").no)
logger.add(folder_ + prefix_ + "error.log", level="ERROR", backtrace=backtrace_, diagnose=diagnose_,
format=format_, colorize=False,
rotation=rotation_, retention=retention_, encoding=encoding_,
filter=lambda record: record["level"].no >= logger.level("ERROR").no)
logger.add(folder_ + prefix_ + "critical.log", level="CRITICAL", backtrace=backtrace_, diagnose=diagnose_,
format=format_, colorize=False,
rotation=rotation_, retention=retention_, encoding=encoding_,
filter=lambda record: record["level"].no >= logger.level("CRITICAL").no)
logger.add(sys.stderr, level="CRITICAL", backtrace=backtrace_, diagnose=diagnose_,
format=format_, colorize=True,
filter=lambda record: record["level"].no >= logger.level("CRITICAL").no)