11

我有几个进程都有自己的线程都在写入stdout,我可以接受。现在,我必须添加另一个线程,它将大量垃圾转储到标准输出,我不想要这个。

有什么办法可以将stdout单个线程重定向到文件?

更新

正如owobeid所提到的,我试过这个......我正在重定向stderr到一个文件......

def startServer():
    fd = os.open("foo.txt", os.O_RDWR|os.O_CREAT )
    fd2 = 2
    os.dup2(fd, fd2)

    # rest of the code

if __name__ == '__main__':
    threading.Thread(target=startServer).start()
    raise Exception("My Exception")

问题:stderr整个应用程序被重定向。异常消息也会被重定向到文件中,即使它在线程之外。

4

4 回答 4

9

我在寻找一种方法来做到这一点时偶然发现了这篇文章。我想制作一个交互式 Python 控制台,它使用 ajax 将请求代理到服务器,并仅返回执行线程的所有输出。我最终弄清楚了,并想分享我的解决方案。

werkzeugpython库附带一个类,local.LocalProxy它可以使模块级函数表现得像一个属性。例如,这将使sys.stdout行为完全正常,但它将通过LocalProxy类代理。

import sys
import werkzeug
sys.stdout = werkzeug.local.LocalProxy(lambda: sys.stdout)

对此进行扩展,然后我编写了一个函数来代替上面的函数,如果它是不同的线程,则lambda返回一个对象:StringIO

import threading
import sys
import cStringIO
import werkzeug

thread_proxies = {}
def redirect():
    ident = threading.currentThread().ident
    thread_proxies[ident] = cStringIO.StringIO()
    return thread_proxies[ident]

def proxy():
    ident = threading.currentThread().ident
    return thread_proxies.get(ident, sys.stdout)

sys.stdout = werkzeug.local.LocalProxy(proxy)

然后在我想要重定向的任何线程中,我可以调用:

string_io = redirect()

并且所有将去的输出sys.stdout现在都写入StringIO对象。


可是等等!我需要捕获sys.stdoutsys.__stdout__sys.stderrsys.__stderr__,所以我编写了这个库,我stdout_helpers在我的代码库中调用了它:

import threading
import sys
import cStringIO
from werkzeug import local

# Save all of the objects for use later.
orig___stdout__ = sys.__stdout__
orig___stderr__ = sys.__stderr__
orig_stdout = sys.stdout
orig_stderr = sys.stderr
thread_proxies = {}


def redirect():
    """
    Enables the redirect for the current thread's output to a single cStringIO
    object and returns the object.

    :return: The StringIO object.
    :rtype: ``cStringIO.StringIO``
    """
    # Get the current thread's identity.
    ident = threading.currentThread().ident

    # Enable the redirect and return the cStringIO object.
    thread_proxies[ident] = cStringIO.StringIO()
    return thread_proxies[ident]


def stop_redirect():
    """
    Enables the redirect for the current thread's output to a single cStringIO
    object and returns the object.

    :return: The final string value.
    :rtype: ``str``
    """
    # Get the current thread's identity.
    ident = threading.currentThread().ident

    # Only act on proxied threads.
    if ident not in thread_proxies:
        return

    # Read the value, close/remove the buffer, and return the value.
    retval = thread_proxies[ident].getvalue()
    thread_proxies[ident].close()
    del thread_proxies[ident]
    return retval


def _get_stream(original):
    """
    Returns the inner function for use in the LocalProxy object.

    :param original: The stream to be returned if thread is not proxied.
    :type original: ``file``
    :return: The inner function for use in the LocalProxy object.
    :rtype: ``function``
    """
    def proxy():
        """
        Returns the original stream if the current thread is not proxied,
        otherwise we return the proxied item.

        :return: The stream object for the current thread.
        :rtype: ``file``
        """
        # Get the current thread's identity.
        ident = threading.currentThread().ident

        # Return the proxy, otherwise return the original.
        return thread_proxies.get(ident, original)

    # Return the inner function.
    return proxy


def enable_proxy():
    """
    Overwrites __stdout__, __stderr__, stdout, and stderr with the proxied
    objects.
    """
    sys.__stdout__ = local.LocalProxy(_get_stream(sys.__stdout__))
    sys.__stderr__ = local.LocalProxy(_get_stream(sys.__stderr__))
    sys.stdout = local.LocalProxy(_get_stream(sys.stdout))
    sys.stderr = local.LocalProxy(_get_stream(sys.stderr))


def disable_proxy():
    """
    Overwrites __stdout__, __stderr__, stdout, and stderr with the original
    objects.
    """
    sys.__stdout__ = orig___stdout__
    sys.__stderr__ = orig___stderr__
    sys.stdout = orig_stdout
    sys.stderr = orig_stderr

现在在我的应用程序开始时,我调用:

stdout_helpers.enable_proxy()

在我现在调用的任何线程中:

string_io = stdout_helpers.redirect()
于 2017-04-27T20:29:51.653 回答
2

这个答案允许重定向到特定文件,但也可以有多个输出(sys.stdout + 文件),并且还可以将主线程日志重定向到每个线程文件日志(这在我的情况下非常有用)。

首先,让我们为新的重定向定义一个类:

class SysRedirect(object):
    def __init__(self):
        self.terminal = sys.stdout                  # To continue writing to terminal
        self.log={}                                 # A dictionary of file pointers for file logging

    def register(self,filename):                    # To start redirecting to filename
        ident = threading.currentThread().ident     # Get thread ident (thanks @michscoots)
        if ident in self.log:                       # If already in dictionary :
            self.log[ident].close()                 # Closing current file pointer
        self.log[ident] = open(filename, "a")       # Creating a new file pointed associated with thread id

    def write(self, message):
        self.terminal.write(message)                # Write in terminal (comment this line to remove terminal logging)
        ident = threading.currentThread().ident     # Get Thread id
        if ident in self.log:                       # Check if file pointer exists
            self.log[ident].write(message)          # write in file
        else:                                       # if no file pointer 
            for ident in self.log:                  # write in all thread (this can be replaced by a Write in terminal)
                 self.log[ident].write(message)  
     def flush(self):
            #this flush method is needed for python 3 compatibility.
            #this handles the flush command by doing nothing.
            #you might want to specify some extra behavior here.
            pass    

然后,我只需要在我的主线程中初始化

sys.stdout=SysRedirect()

然后在每个线程中,我只需要注册并指定一个文件名

sys.stdout.register('threadX.log')

在主线程中,我可以重定向例如:

sys.stdout.register('mainthread.log')

但在我的情况下,我不想注册主线程,所以主线程中的所有 std 都写在所有其他日志中

于 2019-09-18T16:16:43.137 回答
0

您不能为单个线程重定向标准输出,但您可以通过例如打开一个文件并write()从该线程调用它来写入不同的 fd。stdout 和 stderr 映射到作为整个过程的 fd 表的一部分的特定 fd,因此在任何一个线程中弄乱 stdout/stderr 都会弄乱所有线程。

因此,在这种情况下,您不能简单地在单个线程下弄乱标准输出,以便您仍然可以调用 vanilla 'print'。先前答案中产生的线程和主线程的区别是不相关的。您需要选择的线程实际打印到单独的 fd:

with open('blah', 'w') as f:
    f.write('bla blah\n')

我知道这不会给你想要的东西,但对于后来的读者来说,了解这一点很重要。

在概念上可以有所帮助的是了解 print 命令在幕后的作用。fd.write()本质上,它与(以格式化细节为模)做同样的事情,但它专门使用 stdout 作为文件句柄。您可以自己使用sys.stdout.write().

于 2016-11-10T01:24:08.803 回答
-2

利用dup2将输出重定向到您选择的文件。将 fd 设置为您的文件描述符,并将 fd2 设置为 1(标准输出)。

注意:在生成的线程中执行此操作,而不是在主线程中。

于 2013-02-15T08:56:53.337 回答