程序员

Logging Muti-Progress

2018-08-25  本文已影响0人  煮酒客_Mikoy

<center>

Author: Mikoy Date: 2018-08-25

</center>

Introduction:

  Recently, I have tried to record some infomations occurred during progress running automatically. Because the main language is Python, so I choose the logging module to achieve it.

  Firstly, I can record log when I use logging module. But when I tried to write log infomation two times or more, I found that the log file will be re-written with cleaning up all my infomation written last time. And then I have tested it by making two progress to write log infomating using logging module to one log file. This time I found that if the two progresses write infomation at the same time, Python Global Interpreter Lock(GIL) will prevent multiple progress form executing the I/O operations. So the fact is just one progress writted log infomation in log file.

  But why the log file will be cleaned up? I searched my question in google and found the result. Because the logging module include class TimedRotatingFileHandler(BaseRotatingHandler), and we see it sources code, strengthen the reson code:

def doRollover(self):
    ...
    ...
    dfn = self.rotation_filename(self.baseFilename + "." +
                                     time.strftime(self.suffix, timeTuple))
    if os.path.exists(dfn):
        os.remove(dfn)
    self.rotate(self.baseFilename, dfn)
    if self.backupCount > 0:
        for s in self.getFilesToDelete():
            os.remove(s)        
    ...
    ...  

  As you see, it will remove the log file if it existed. So we try to create a class like TimedRotatingFileHandler, but support for Muti-Progress.

The coding thinking:

graph TD
a[Log file Request] --> b{shouldRollover?}
b -->|True| c[Create a new log file]
b -->|False| d[file.stream.close]
d -->f[open log file]
f --> g[if file exists, create a symlink to the currentTime log file and make the I/O stream point it.]

  When a progress request I/O operation, firstly judge it whether should Rollover? if this log file is the currenttime file you want to record it. if is True return True, while False return False. And the doRollover receive the parameter to do rollover. When it rollover, it makes a symlink pointing to the current time file and then record it.

The main code:

...
...
...

def shouldRollover(self, record):
        if self.currentFileName != self._compute_fn():
            return True
        return False


def _open(self):
        if self.encoding is None:
            stream = open(self.currentFileName, self.mode)
        else:
            stream = codecs.open(self.currentFileName, self.mode, self.encoding)
        # simulate file name structure of `logging.TimedRotatingFileHandler`
        if os.path.exists(self.baseFilename):
            try:
                os.remove(self.baseFilename)
            except OSError:
                pass
        try:
            # Create the symlink point the lastest log file.
            os.symlink(self.currentFileName, self.baseFilename)
        except OSError:
            pass
        return stream
...
...
...

The whole source code:

class MultiProcessSafeDailyRotatingFileHandler(BaseRotatingHandler):
    """
    Similar with `logging.TimedRotatingFileHandler`, while this one is
    - Multi process safe
    - Rotate at midnight only
    - Utc not supported
    - This log will create day by day.
    """
    def __init__(self, filename, encoding=None, delay=False, utc=False, **kwargs):
        self.utc = utc
        self.suffix = "%Y%m%d"
        self.baseFilename = os.path.abspath(filename)
        self.currentFileName = self._compute_fn()
        BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)

    def shouldRollover(self, record):
        if self.currentFileName != self._compute_fn():
            return True
        return False

    def doRollover(self):
        if self.stream:
            self.stream.close()
            self.stream = None
        self.currentFileName = self._compute_fn()

    def _compute_fn(self):
        return self.baseFilename + "." + time.strftime(self.suffix, time.localtime())

    def _open(self):
        if self.encoding is None:
            stream = open(self.currentFileName, self.mode)
        else:
            stream = codecs.open(self.currentFileName, self.mode, self.encoding)
        # simulate file name structure of `logging.TimedRotatingFileHandler`
        if os.path.exists(self.baseFilename):
            try:
                os.remove(self.baseFilename)
            except OSError:
                pass
        try:
            # Create the symlink point the lastest log file.
            os.symlink(self.currentFileName, self.baseFilename)
        except OSError:
            pass
        return stream

Finally

  Let me tell you some usages of logging module: This module defines functions and classes which implement a flexible event logging system for applications and libraries. The key benefit of having the logging API provided by a standard library module is that all Python modules can participate in logging, so your application log can include your own messages integrated with messages from third-party modules.

Usage:

logger --> handler --> formatter --> output

  1. Create logger
    logger = logging.getLogger('user')
    logger.setLevel('DEBUG')
  1. Create handler and set leverl
    handler = logging.StreamHandler()
    handler.setLevel('DEBUG')
  1. Create formatter
    handler.setFormatter('%(levelname)s: %(message)s') 
  1. Add to logger
    logger.addHandler(handler)

My simple:

"""
    Author: Mikoy
    Github: github.com/MikoyChinese
    The name of the log file changes automatically based on time.
    LOGGING_CONFIG: the config file about the logging.
    <logging.config.dictConfig>: such like next,
    logger --> handler --> formatter --> output
    # Create logger
    logger = logging.getLogger('user')
    logger.setLevel('DEBUG')
    # Create handler and set leverl
    handler = logging.StreamHandler()
    handler.setLevel('DEBUG')
    # Create formatter
    handler.setFormatter('%(levelname)s: %(message)s')
    # Add to logger
    logger.addHandler(handler)
"""


from logging.handlers import BaseRotatingHandler
import os, time, codecs, logging, logging.config


class MyLogging(object):
    def __init__(self, logger_name='user'):
        # 1.The configuration below has no file to output.
        self.LOGGING_CONFIG = {
            'version': 1,
            'disable_existing_loggers': False,
            'formatters': {
                'standard': {
                    'format': '[%(levelname)s] %(asctime)s %(module)s %('
                              'process)d %(thread)d: %(message)s',
                    'datefmt': '%H:%M:%S'
                },
                'simple': {
                    'format': '[%(levelname)s]: %(message)s'
                }
            },
            'handlers': {
                'console': {
                    'level': 'DEBUG',
                    'class': 'logging.StreamHandler',
                    'formatter': 'simple'
                },
                'default': {
                    'level': 'DEBUG',
                    'class': 'loggingModule.MultiProcessSafeDailyRotatingFileHandler',
                    'formatter': 'standard',
                    'filename': './log/info.log'
                }
            },
            'loggers': {
                'user': {
                    'level': 'DEBUG',
                    'handlers': ['console', 'default']
                }
            }
        }
        # 2. To get the absolute path of the log file.
        # Log file is stored under the 'log' folder. If it not existed,
        # create it auto.
        dir_path = os.path.join(os.path.dirname(__file__), 'log')
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
        # 3. Loading the config, and make it effect.
        # This command can not be executed after the logging.getLogger function
        # because there is no specific logger name.
        logging.config.dictConfig(self.LOGGING_CONFIG)
        self.logger = logging.getLogger(logger_name)  # return a logger


class MultiProcessSafeDailyRotatingFileHandler(BaseRotatingHandler):
    """
    Similar with `logging.TimedRotatingFileHandler`, while this one is
    - Multi process safe
    - Rotate at midnight only
    - Utc not supported
    - This log will create day by day.
    """
    def __init__(self, filename, encoding=None, delay=False, utc=False, **kwargs):
        self.utc = utc
        self.suffix = "%Y%m%d"
        self.baseFilename = os.path.abspath(filename)
        self.currentFileName = self._compute_fn()
        BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)

    def shouldRollover(self, record):
        if self.currentFileName != self._compute_fn():
            return True
        return False

    def doRollover(self):
        if self.stream:
            self.stream.close()
            self.stream = None
        self.currentFileName = self._compute_fn()

    def _compute_fn(self):
        return self.baseFilename + "." + time.strftime(self.suffix, time.localtime())

    def _open(self):
        if self.encoding is None:
            stream = open(self.currentFileName, self.mode)
        else:
            stream = codecs.open(self.currentFileName, self.mode, self.encoding)
        # simulate file name structure of `logging.TimedRotatingFileHandler`
        if os.path.exists(self.baseFilename):
            try:
                os.remove(self.baseFilename)
            except OSError:
                pass
        try:
            # Create the symlink point the lastest log file.
            os.symlink(self.currentFileName, self.baseFilename)
        except OSError:
            pass
        return stream


if __name__ == '__main__':
    mylogging = MyLogging(logger_name='user')  # This is a customized class;
    while 1:
        mylogging.logger.debug('%s' % time.time())
        time.sleep(5)
上一篇下一篇

猜你喜欢

热点阅读