Forked from SerhoLiu/safe_rotating_file_handler.py
Last active
June 17, 2024 11:32
-
-
Save smiley-yoyo/5988bad893a3e04ea5c0ea55da8eefb9 to your computer and use it in GitHub Desktop.
python multiprocessing safe time rotating file handler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import logging | |
| import os | |
| import sys | |
| import time | |
| from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler | |
| from NamedAtomicLock import NamedAtomicLock | |
| class MultiProcessLock(NamedAtomicLock): | |
| def __enter__(self): | |
| self.acquire() | |
| return self | |
| def __exit__(self, exc_type, exc_val, exc_tb): | |
| self.release() | |
| return False | |
| class SafeTimedRotatingFileHandler(TimedRotatingFileHandler): | |
| """ | |
| 多进程下 TimedRotatingFileHandler 会出现问题 | |
| """ | |
| _rollover_lock = MultiProcessLock('rollover_lock') | |
| def emit(self, record): | |
| """ | |
| Emit a record. | |
| Output the record to the file, catering for rollover as described | |
| in doRollover(). | |
| """ | |
| try: | |
| if self.shouldRollover(record): | |
| with self._rollover_lock: | |
| if self.shouldRollover(record): | |
| self.doRollover() | |
| logging.FileHandler.emit(self, record) | |
| except (KeyboardInterrupt, SystemExit): | |
| raise | |
| except Exception: | |
| self.handleError(record) | |
| def shouldRollover(self, record): | |
| if self._should_rollover(): | |
| # if some other process already did the rollover we might | |
| # checked log.1, so we reopen the stream and check again on | |
| # the right log file | |
| if self.stream: | |
| self.stream.close() | |
| self.stream = self._open() | |
| return self._should_rollover() | |
| return 0 | |
| def _should_rollover(self): | |
| """ | |
| 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 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 | |
| # 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) | |
| dstThen = timeTuple[-1] | |
| if dstNow != dstThen: | |
| if dstNow: | |
| addend = 3600 | |
| else: | |
| addend = -3600 | |
| timeTuple = time.localtime(t + addend) | |
| dfn = self.rotation_filename(self.baseFilename + "." + | |
| time.strftime(self.suffix, timeTuple)) | |
| if not os.path.exists(dfn): | |
| 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 | |
| def get_logger(): | |
| logger = logging.getLogger(__name__) | |
| logger.setLevel(logging.DEBUG) | |
| handler = SafeTimedRotatingFileHandler(f'./app.log', when='MIDNIGHT', backupCount=10) | |
| handler.setFormatter(logging.Formatter('[%(asctime)s][%(levelname)s][%(message)s]')) | |
| logger.addHandler(handler) | |
| return logger | |
| def test_log(loop=120): | |
| logger = get_logger() | |
| argv = sys.argv | |
| for i in range(loop): | |
| logger.info(f'{argv} {i}: good day! blahblahblah...') | |
| time.sleep(1) | |
| if __name__ == '__main__': | |
| test_log() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment