J'utilise TimedRotatingFileHandler pour créer mes journaux. Je veux que mes fichiers journaux soient créés toutes les minutes, conserver au maximum 2 fichiers journaux et supprimer les plus anciens. Voici l'exemple de code:
21-01.log 21-02.log 21-03.log ...
Si j'exécute ce code plusieurs fois (avec un intervalle d'une minute), j'obtiens plusieurs fichiers dans mon répertoire de journaux comme ceci:
import logging import logging.handlers import datetime logger = logging.getLogger('MyLogger') logger.setLevel(logging.DEBUG) handler = logging.handlers.TimedRotatingFileHandler( "logs/{:%H-%M}.log".format(datetime.datetime.now()), when="M", backupCount=2) logger.addHandler(handler) logger.debug("PLEASE DELETE PREVIOUS FILES")
3 Réponses :
Merci @Bsquare. J'ai fait une erreur car mon problème n'est pas exactement le même que la question initiale. J'utilise un nom de fichier constant. Mais quand même, j'ai trouvé qu'il y avait un bogue dans l'analyseur du fichier .ini
. Il ne charge pas l'argument backupCount, et donc le constructeur de TimedRotatingFileHandler
obtient backupCount = 0
. Lorsque je l'ai contourné chirurgicalement, la suppression rotative fonctionne. Je cherchais une solution de patch-package non originale. Puisque vous avez répondu à la question initiale - je vais attribuer la prime - pour être juste. Si par hasard vous pouvez également m'aider à résoudre mon problème, je serai obligé.
Merci pour votre fairplay. Bien sûr, j'aiderai si je peux. Maintenant, votre problème est que l'argument backupCount n'est PAS pris en charge sans pirater le module, non?
En effet. Tel est le problème. Lors du débogage, le paramètre backupCount n'est tout simplement pas lu à partir du fichier ini. Autre que d'ouvrir un problème avec le projet d'origine (ce qui prendra du temps), je peux peut-être étendre TimedRotationFileHandler pour résoudre ce problème? Je ne sais pas comment étendre un nouveau gestionnaire et ajouter des paramètres à lire à partir du fichier ini.
D'accord. Cela semble fonctionner dans mon environnement de test local. Pourriez-vous fournir votre code source exact?
@eran \ @MykhailoSeniutovych Sur Stackoverflow, vous pouvez donner vote positif aux réponses utiles des gens pour les remercier et en sélectionner une de la réponse comme bonne réponse sur tous.
Comme d'autres personnes l'ont déjà indiqué, backupCount
ne fonctionnera que si vous vous connectez toujours au fichier avec le même nom de fichier, puis faites une rotation de temps en temps. Ensuite, vous aurez des fichiers journaux comme @Bsquare indiqué.
Cependant, dans mon cas, je devais faire une rotation tous les jours et faire en sorte que mes fichiers journaux portent les noms suivants: 2019-07-06.log
, 2019-07-07.log
, 2019-07-07.log
, ...
J'ai découvert qu'il n'est pas possible d'utiliser l'implémentation actuelle de TimedRotatingFileHandler
J'ai donc créé ma propre fonctionnalité de suppression qui répond à mes besoins en plus de FileHandler
Ceci est un exemple simple de classe de journalisation qui utilise FileHandler
et s'assurera que les anciens fichiers journaux sont supprimés chaque fois que vous créez une instance de cette classe:
logger = Logger() logger.log_info("This is a log message")
Et puis vous l'utiliseriez comme ceci:
import os import datetime import logging import re import pathlib class Logger: # Maximum number of logs to store LOGS_COUNT = 3 # Directory to log to LOGS_DIRECTORY = "logs" def __init__(self): # Make sure logs directory is created self.__create_directory(Logger.LOGS_DIRECTORY) # Clean old logs every time you create a logger self.__clean_old_logs() self.logger = logging.getLogger("Logger") # If condition will make sure logger handlers will be initialize only once when this object is created if not self.logger.handlers: self.logger.setLevel(logging.INFO) formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") file_handler = logging.FileHandler("logs/{:%Y-%m-%d}.log".format(datetime.datetime.now())) file_handler.setFormatter(formatter) self.logger.addHandler(file_handler) def log_info(self, message): self.logger.info(message) def log_error(self, message): self.logger.error(message) def __clean_old_logs(self): for name in self.__get_old_logs(): path = os.path.join(Logger.LOGS_DIRECTORY, name) self.__delete_file(path) def __get_old_logs(self): logs = [name for name in self.__get_file_names(Logger.LOGS_DIRECTORY) if re.match("([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))\.log", name)] logs.sort(reverse=True) return logs[Logger.LOGS_COUNT:] def __get_file_names(self, path): return [item.name for item in pathlib.Path(path).glob("*") if item.is_file()] def __delete_file(self, path): os.remove(path) def __create_directory(self, directory): if not os.path.exists(directory): os.makedirs(directory)
Pourquoi mettre cette logique dans le logger et non dans une sous-classe du gestionnaire? Rendre le logger responsable signifie que vous ne pouvez pas l'utiliser avec des bibliothèques qui utilisent uniquement l'infrastructure de journalisation standard.
@MartijnPieters c'est un code simple que j'ai créé qui répond à mes besoins, pas une solution universelle. N'hésitez pas à l'améliorer.
Vous ne pouvez pas utiliser TimedRotatingFileHandler
, tel que conçu, pour votre cas d'utilisation. Le gestionnaire s'attend à ce que le nom du fichier journal "actuel" reste stable et définit la rotation comme le déplacement des fichiers journaux existants vers une sauvegarde en renommant . Ce sont les sauvegardes qui sont conservées ou supprimées. Les sauvegardes de rotation sont créées à partir du nom de fichier de base plus un suffixe avec l'horodatage de rotation . Ainsi, l'implémentation fait la distinction entre le fichier journal (stocké dans baseFilename
) et les fichiers de rotation (générés dans le doRotate ()
méthode . Notez que les sauvegardes ne sont supprimées que lorsque la rotation a lieu, donc après que le gestionnaire a été en cours d'utilisation pendant au moins un intervalle complet.
Vous voulez à la place que le nom du fichier de base lui-même contienne les informations d'heure, et que vous modifiez ainsi le nom du fichier journal lui-même . il n'y a pas de «sauvegarde» dans ce scénario, vous ouvrez simplement un nouveau fichier aux moments de rotation. De plus, vous semblez exécuter du code Python de courte durée , vous voulez donc que les anciens fichiers soient supprimés immédiatement, pas seulement au moment rotation explicite, ce qui peut ne jamais être atteint.
C'est pourquoi TimedRotatingFileHandler
ne supprimera aucun fichier, car * il ne pourra jamais créer de fichiers de sauvegarde. Aucune sauvegarde signifie que Il n'y a aucune sauvegarde à supprimer. Pour faire pivoter les fichiers, l'implémentation actuelle du gestionnaire s'attend à être en charge de la génération des noms de fichiers et ne peut pas s'attendre à connaître les noms de fichiers qu'il ne générerait pas lui-même. Lorsque vous le configurez avec la fréquence de rotation par minute "M"
, il est configuré pour faire pivoter les fichiers vers des fichiers de sauvegarde avec le modèle {baseFileame}. {Now:% Y-% m- % d_% H_% M}
, et ne supprimera donc jamais que les fichiers de sauvegarde en rotation qui correspondent à ce modèle. Consultez la documentation :
Le système enregistrera les anciens fichiers journaux en ajoutant des extensions au nom de fichier. Les extensions sont basées sur la date et l'heure, en utilisant le format strftime
% Y-% m-% d_% H-% M-% S
ou une partie de début de celui-ci, en fonction de l'intervalle de basculement.
Au lieu de cela, vous voulez un nom de fichier de base qui porte lui-même l'horodatage, et que lors de l'ouverture d'un nouveau fichier journal avec un nom différent, les anciens fichiers journaux (pas les fichiers de sauvegarde) sont supprimés. Pour cela, vous devez créer un gestionnaire personnalisé.
Heureusement, la hiérarchie des classes est spécialement conçue pour une personnalisation facile. Vous pouvez sous-classer BaseRotatingHandler
ici , et fournissez votre propre logique de suppression:
handler = TimedPatternFileHandler("logs/%H-%M.log", when="M", backupCount=2)
Utilisez ceci avec time.strftime ()
espaces réservés dans le nom du fichier journal, et ceux-ci seront remplis pour vous:
import os import time from itertools import islice from logging.handlers import BaseRotatingHandler, TimedRotatingFileHandler # rotation intervals in seconds _intervals = { "S": 1, "M": 60, "H": 60 * 60, "D": 60 * 60 * 24, "MIDNIGHT": 60 * 60 * 24, "W": 60 * 60 * 24 * 7, } class TimedPatternFileHandler(BaseRotatingHandler): """File handler that uses the current time in the log filename. The time is quantisized to a configured interval. See TimedRotatingFileHandler for the meaning of the when, interval, utc and atTime arguments. If backupCount is non-zero, then older filenames that match the base filename are deleted to only leave the backupCount most recent copies, whenever opening a new log file with a different name. """ def __init__( self, filenamePattern, when="h", interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None, ): self.when = when.upper() self.backupCount = backupCount self.utc = utc self.atTime = atTime try: key = "W" if self.when.startswith("W") else self.when self.interval = _intervals[key] except KeyError: raise ValueError( f"Invalid rollover interval specified: {self.when}" ) from None if self.when.startswith("W"): if len(self.when) != 2: raise ValueError( "You must specify a day for weekly rollover from 0 to 6 " f"(0 is Monday): {self.when}" ) if not "0" <= self.when[1] <= "6": raise ValueError( f"Invalid day specified for weekly rollover: {self.when}" ) self.dayOfWeek = int(self.when[1]) self.interval = self.interval * interval self.pattern = os.path.abspath(os.fspath(filenamePattern)) # determine best time to base our rollover times on # prefer the creation time of the most recently created log file. t = now = time.time() entry = next(self._matching_files(), None) if entry is not None: t = entry.stat().st_ctime while t + self.interval < now: t += self.interval self.rolloverAt = self.computeRollover(t) # delete older files on startup and not delaying if not delay and backupCount > 0: keep = backupCount if os.path.exists(self.baseFilename): keep += 1 delete = islice(self._matching_files(), keep, None) for entry in delete: os.remove(entry.path) # Will set self.baseFilename indirectly, and then may use # self.baseFilename to open. So by this point self.rolloverAt and # self.interval must be known. super().__init__(filenamePattern, "a", encoding, delay) @property def baseFilename(self): """Generate the 'current' filename to open""" # use the start of *this* interval, not the next t = self.rolloverAt - self.interval if self.utc: time_tuple = time.gmtime(t) else: time_tuple = time.localtime(t) dst = time.localtime(self.rolloverAt)[-1] if dst != time_tuple[-1] and self.interval > 3600: # DST switches between t and self.rolloverAt, adjust addend = 3600 if dst else -3600 time_tuple = time.localtime(t + addend) return time.strftime(self.pattern, time_tuple) @baseFilename.setter def baseFilename(self, _): # assigned to by FileHandler, just ignore this as we use self.pattern # instead pass def _matching_files(self): """Generate DirEntry entries that match the filename pattern. The files are ordered by their last modification time, most recent files first. """ matches = [] pattern = self.pattern for entry in os.scandir(os.path.dirname(pattern)): if not entry.is_file(): continue try: time.strptime(entry.path, pattern) matches.append(entry) except ValueError: continue matches.sort(key=lambda e: e.stat().st_mtime, reverse=True) return iter(matches) def doRollover(self): """Do a roll-over. This basically needs to open a new generated filename. """ if self.stream: self.stream.close() self.stream = None if self.backupCount > 0: delete = islice(self._matching_files(), self.backupCount, None) for entry in delete: os.remove(entry.path) now = int(time.time()) rollover = self.computeRollover(now) while rollover <= now: rollover += self.interval if not self.utc: # If DST changes and midnight or weekly rollover, adjust for this. if self.when == "MIDNIGHT" or self.when.startswith("W"): dst = time.localtime(now)[-1] if dst != time.localtime(rollover)[-1]: rollover += 3600 if dst else -3600 self.rolloverAt = rollover if not self.delay: self.stream = self._open() # borrow *some* TimedRotatingFileHandler methods computeRollover = TimedRotatingFileHandler.computeRollover shouldRollover = TimedRotatingFileHandler.shouldRollover
Avez-vous déjà résolu cela? J'ai le même problème
Il semble que
_install_handlers
dans logging / config.py ne lit tout simplement pas cette section pourbackupCount
et donc le constructeur obtient toujoursbackupCount = 0
pour < code> TimedRotatingFileHandler . Cela ressemble à un bug. La question est de savoir comment résoudre cela sans attendre un problème sur la branche principale@eran ne sait pas si cela correspond à votre problème, mais le problème ici est le nom du fichier journal. Le remplacement du fichier journal se produit lorsque vous définissez un nom de journal fixe. Par exemple.
TimedRotatingFileHandler ('my.log', when = 's', backupCount = 2)
fera pivoter les journaux une fois par seconde, vous obtiendrez doncmy.log
et les deux derniers sauvegardes (fichiers avec des nomsmy.log.YYYY-MM-DD_HH-MM-SS
). Le code de l'OP crée des fichiers journaux avec de nouveaux noms à chaque fois, donc aucun basculement ne se produira et le nombre de sauvegardes ne sera jamais atteint.@hoefling vous avez raison. Ce n'est pas exactement mon problème puisque la personne d'origine qui pose la question semble ajouter la date aux noms de fichiers par elle-même. Cela m'a un peu manqué lorsque j'ai fourni une prime. Cependant, le problème persiste. J'ai trouvé qu'il y avait un bogue dans l'analyseur du fichier
.ini
. Il ne charge pas l'argumentbackupCount
, et donc le constructeur deTimedRotatingFileHandler
obtient backupCount = 0. Lorsque je l'ai contourné chirurgicalement, la suppression rotative fonctionne. Je cherchais une solution de patch-package non originale.