Отдельный вывод из разных потоков

Весь мой код записывает журнал через say() в sys.stderr и перенаправляет ошибку. На многопоточном сервере я хочу, чтобы каждый поток записывался в отдельный файл журнала. Могу ли я сделать это, не переписывая весь код, используемый потоками?

from threading import Thread
from time import sleep

def say(*args):
    print(*args, file=sys.stderr)

def worker(num):
    for _ in range(5):
        say("worker", num, "working")
        sleep(.1)

for num in range(4):
    Thread(target=worker, args=(num,)).start()

Вывод смешанный, цель состоит в том, чтобы перенаправить его в другой файл журнала для каждого потока:

worker 0 working
worker 1 working
worker 2 working
worker 3 working
worker 0 working
worker 1 working
worker 3 working
worker 2 working
. . .

Насколько я понимаю, если я попытаюсь перенаправить stderr в файл внутри потока, перенаправление будет общим для всех потоков:

def worker(num):
    sys.stderr = open('worker{}.log'.format(num), 'w')
    for _ in range(5):
        say("worker", num, "working")

Результат, как и ожидалось:

$ cat worker3.log
worker 1 working
worker 1 working
worker 1 working
worker 3 working
worker 3 working
worker 3 working
worker 3 working
worker 3 working

Обновления

@Amber, у меня уже есть уникальный идентификатор, и я могу использовать его как имя темы:

def say(*args, end='\n'):
    print(currentThread().getName(), *args, file=sys.stderr, end=end)
    sys.stderr.flush()

t_worker = Thread(name=str(num), target=worker, args=(num,))

Я не могу динамически выбирать файл журнала для каждого потока, потому что say() является глобальным — мне нужно либо поместить семафор в это глобальное хранилище файлов журнала, либо передать объект журнала каждой отдельной функции, которая регистрирует ход выполнения.


person Muposat    schedule 11.12.2014    source источник
comment
@Amber, у меня уже есть уникальный идентификатор, и я могу использовать его как имя потока:   -  person Muposat    schedule 12.12.2014


Ответы (2)


Вы можете использовать локальное хранилище потока (через класс threading.local) для хранения отдельного файлового объекта для каждого потока. Затем функция say могла бы найти нужный файл:

local = threading.local()

def say(*args):
    if not hasattr(local, "logfile"):
        local.logfile = open("logfile{}".format(threading.get_ident()), "a")

    print(*args, file=local.logfile)

Я использую threading.get_ident, чтобы получить уникальное значение для создания имени файла журнала. Если есть более логичный способ именования файлов в вашей программе, я бы предложил вам использовать его. Возможно, вы даже захотите отделить создание файла от функции say. Например, вы можете сделать его частью стартового кода потока:

local = threading.local()

def say(*args):
    print(*args, filename=local.logfile)

def worker1():
    local.logfile = open("logfile_worker1", "a")

    # do stuff here, including calling `say()` as necessary
person Blckknght    schedule 11.12.2014
comment
много грасиас, сеньор - person Muposat; 12.12.2014

Вы можете вызвать threading.get_ident() в своей функции say(), чтобы решить, куда писать линия. Это не идеально (поскольку номера идентификаторов потоков разрешено повторно использовать после завершения потока и создания другого), но если ваши потоки работают долго, это может сработать для ваших целей.

person Amber    schedule 11.12.2014