python - python日志线程安全

标签 python multithreading logging file-io thread-safety

我试图了解以下情况下python日志记录到底是线程安全的:
1.我在主线程中为同一文件创建了不同的处理程序。然后要求一个线程登录。
2.我从不同的线程中创建指向相同文件的多个处理程序。

我在FileHandler的源代码中看到,每个Handler创建的都有自己的锁,但是在我提到的情况下这如何工作?
如果我对它的理解正确,那么锁的作用域就是FileHandler对象,这意味着如果我创建2个不同的处理程序,它们将不共享锁,并且它们可能会陷入竞争状态。

所以我的问题是:这个线程安全性如何?

这是相关的代码。请注意,FileHandler类继承了StreamHandler,后者又继承了Handler。


class StreamHandler(Handler):
    """
    A handler class which writes logging records, appropriately formatted,
    to a stream. Note that this class does not close the stream, as
    sys.stdout or sys.stderr may be used.
    """

    terminator = '\n'

    def __init__(self, stream=None):
        """
        Initialize the handler.

        If stream is not specified, sys.stderr is used.
        """
        Handler.__init__(self)
        if stream is None:
            stream = sys.stderr
        self.stream = stream

    def flush(self):
        """
        Flushes the stream.
        """
        self.acquire()
        try:
            if self.stream and hasattr(self.stream, "flush"):
                self.stream.flush()
        finally:
            self.release()

    def emit(self, record):
        """
        Emit a record.

        If a formatter is specified, it is used to format the record.
        The record is then written to the stream with a trailing newline.  If
        exception information is present, it is formatted using
        traceback.print_exception and appended to the stream.  If the stream
        has an 'encoding' attribute, it is used to determine how to do the
        output to the stream.
        """
        try:
            msg = self.format(record)
            stream = self.stream
            stream.write(msg)
            stream.write(self.terminator)
            self.flush()
        except Exception:
            self.handleError(record)

    def __repr__(self):
        level = getLevelName(self.level)
        name = getattr(self.stream, 'name', '')
        if name:
            name += ' '
        return '<%s %s(%s)>' % (self.__class__.__name__, name, level)


class FileHandler(StreamHandler):
    """
    A handler class which writes formatted logging records to disk files.
    """
    def __init__(self, filename, mode='a', encoding=None, delay=False):
        """
        Open the specified file and use it as the stream for logging.
        """
        # Issue #27493: add support for Path objects to be passed in
        filename = os.fspath(filename)
        #keep the absolute path, otherwise derived classes which use this
        #may come a cropper when the current directory changes
        self.baseFilename = os.path.abspath(filename)
        self.mode = mode
        self.encoding = encoding
        self.delay = delay
        if delay:
            #We don't open the stream, but we still need to call the
            #Handler constructor to set level, formatter, lock etc.
            Handler.__init__(self)
            self.stream = None
        else:
            StreamHandler.__init__(self, self._open())

    def close(self):
        """
        Closes the stream.
        """
        self.acquire()
        try:
            try:
                if self.stream:
                    try:
                        self.flush()
                    finally:
                        stream = self.stream
                        self.stream = None
                        if hasattr(stream, "close"):
                            stream.close()
            finally:
                # Issue #19523: call unconditionally to
                # prevent a handler leak when delay is set
                StreamHandler.close(self)
        finally:
            self.release()

    def _open(self):
        """
        Open the current base file with the (original) mode and encoding.
        Return the resulting stream.
        """
        return open(self.baseFilename, self.mode, encoding=self.encoding)

    def emit(self, record):
        """
        Emit a record.

        If the stream was not opened because 'delay' was specified in the
        constructor, open it before calling the superclass's emit.
        """
        if self.stream is None:
            self.stream = self._open()
        StreamHandler.emit(self, record)

    def __repr__(self):
        level = getLevelName(self.level)
        return '<%s %s (%s)>' % (self.__class__.__name__, self.baseFilename, level)

最佳答案

这是一个好问题,需要进行一些逆向工程。

一个简单的答案:在这种情况下,FileHandler本身不是线程安全的,但是没有使用构造函数创建它。而是使用工厂方法,它们确保所有内容都是线程安全的:

# see here: https://github.com/python/cpython/blob/586be6f3ff68ab4034e555f1434a4427e129ad0b/Lib/logging/__init__.py#L1985
 if handlers is None:
                filename = kwargs.pop("filename", None)
                mode = kwargs.pop("filemode", 'a')
                if filename:
                    if 'b'in mode:
                        errors = None
                    h = FileHandler(filename, mode,
                                    encoding=encoding, errors=errors)
                else:
                    stream = kwargs.pop("stream", None)
                    h = StreamHandler(stream)
                handlers = [h]

和:

# https://github.com/python/cpython/blob/586be6f3ff68ab4034e555f1434a4427e129ad0b/Lib/logging/__init__.py#L1272
    def getLogger(self, name):
        """
        Get a logger with the specified name (channel name), creating it
        if it doesn't yet exist. This name is a dot-separated hierarchical
        name, such as "a", "a.b", "a.b.c" or similar.
        If a PlaceHolder existed for the specified name [i.e. the logger
        didn't exist but a child of it did], replace it with the created
        logger and fix up the parent/child references which pointed to the
        placeholder to now point to the logger.
        """
        rv = None
        if not isinstance(name, str):
            raise TypeError('A logger name must be a string')
        _acquireLock()
        try:
            if name in self.loggerDict:
                rv = self.loggerDict[name]
                if isinstance(rv, PlaceHolder):
                    ph = rv
                    rv = (self.loggerClass or _loggerClass)(name)
                    rv.manager = self
                    self.loggerDict[name] = rv
                    self._fixupChildren(ph, rv)
                    self._fixupParents(rv)
            else:
                rv = (self.loggerClass or _loggerClass)(name)
                rv.manager = self
                self.loggerDict[name] = rv
                self._fixupParents(rv)
        finally:
            _releaseLock()
        return rv

请注意此处发生的两件事:
1)在创建新的Handler之前获取锁
2)如果名称的记录器已创建-将其返回。因此,对于同一个文件,应该只获得一个FileHandler实例。

关于python - python日志线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62178545/

相关文章:

java - "private final Object"锁在java多线程中有什么用?

c - 运行两个线程 [c]

PHP 向传递给 mysql_query 的字符串添加乱码

python - 使用 python 的 mock 临时从 dict 中删除一个对象

python - 在 Windows 7 机器上安装 MySQL-python 时出错

c# - 使用 Control.Invoke() 代替 lock(Control)

android - 如何在 android 中配置 java.util.logging.logger 以使用放置在 sdcard 上的日志记录属性文件?

python - Django : TemplateDoesNotExist at/. ../

python - 如何在多线程中使用python多处理代理对象

ruby-on-rails - 如何在 Rails 中记录 API 请求?