Как скопировать sys.stdout в файл журнала?


149

Изменить: так как кажется, что либо нет решения, или я делаю что-то настолько нестандартное, что никто не знает - я пересмотрю свой вопрос, чтобы также спросить: Каков наилучший способ ведения журнала, когда приложение python много системных вызовов?

Мое приложение имеет два режима. В интерактивном режиме я хочу, чтобы весь вывод выводился на экран, а также в файл журнала, включая вывод любых системных вызовов. В режиме демона весь вывод идет в журнал. Режим демона прекрасно работает, используя os.dup2(). Я не могу найти способ «все» выводить в журнал в интерактивном режиме, не изменяя каждый системный вызов.


Другими словами, мне нужна функциональность командной строки 'tee' для любого вывода, генерируемого приложением python, включая вывод системного вызова .

Чтобы уточнить:

Чтобы перенаправить весь вывод, я делаю что-то вроде этого, и это прекрасно работает:

# open our log file
so = se = open("%s.log" % self.name, 'w', 0)

# re-open stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

# redirect stdout and stderr to the log file opened above
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())

Хорошая вещь об этом - то, что это не требует специальных вызовов печати от остальной части кода. Код также запускает некоторые команды оболочки, поэтому хорошо не иметь дело с каждым их выводом в отдельности.

Просто я хочу сделать то же самое, за исключением дублирования вместо перенаправления.

Сначала подумал, я подумал, что просто задний dup2ход должен работать. Почему не так? Вот мой тест:

import os, sys

### my broken solution:
so = se = open("a.log", 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

os.dup2(sys.stdout.fileno(), so.fileno())
os.dup2(sys.stderr.fileno(), se.fileno())
###

print("foo bar")

os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

Файл «a.log» должен быть идентичен тому, что отображался на экране.


Если вы посмотрите на страницу руководства ( manpagez.com/man/2/dup2 ), 2-й аргумент для dup2 всегда закрыт (если он уже открыт). Таким образом, в вашем «неработающем решении» он закрывается, а затем переставляет их filenos в sys.stdout.
Джейкоб Габриэльсон

1
Re: ваше редактирование: это не редкость, я делал подобное несколько раз (в других языках). В то время как Unix разрешит несколько «псевдонимов» для одного и того же дескриптора файла, он не «разделит» дескриптор файла (скопируйте его на несколько других). Таким образом, вы должны реализовать «тройник» самостоятельно (или просто использовать «тройник», см. Мой грубый ответ).
Джейкоб Габриэльсон

Я думаю, что ответ JohnT лучше, чем фактический принятый. Вы можете изменить принятый ответ.
Фонг

«Я делаю что-то настолько нестандартное», - на самом деле, люди просто отправляют свои журналы в stderr и имеют дело с командной строкой.
Хачик

Ответы:


55

Поскольку вы можете создавать внешние процессы из своего кода, вы можете использовать его teeсами. Я не знаю ни одного системного вызова Unix, который делает именно то, что teeделает.

# Note this version was written circa Python 2.6, see below for
# an updated 3.3+-compatible version.
import subprocess, os, sys

# Unbuffer output (this ensures the output is in the correct order)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

print "\nstdout"
print >>sys.stderr, "stderr"
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

Вы также можете эмулировать, teeиспользуя многопроцессорный пакет (или использовать обработку, если вы используете Python 2.5 или более раннюю версию).

Обновить

Вот Python 3.3 + -совместимая версия:

import subprocess, os, sys

tee = subprocess.Popen(["tee", "log.txt"], stdin=subprocess.PIPE)
# Cause tee's stdin to get a copy of our stdin/stdout (as well as that
# of any child processes we spawn)
os.dup2(tee.stdin.fileno(), sys.stdout.fileno())
os.dup2(tee.stdin.fileno(), sys.stderr.fileno())

# The flush flag is needed to guarantee these lines are written before
# the two spawned /bin/ls processes emit any output
print("\nstdout", flush=True)
print("stderr", file=sys.stderr, flush=True)

# These child processes' stdin/stdout are 
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)

28
Ну, этот ответ работает, поэтому я приму его. Тем не менее, это заставляет меня чувствовать себя грязным.
Drue

2
Я только что опубликовал чистую реализацию tee на python (совместимую с py2 / 3), которая может работать на любой платформе, а также использоваться в различных конфигурациях журналирования. stackoverflow.com/questions/616645/…
сорин

8
Если Python работает на одном из моих компьютеров, а решение - нет, то это не pythonic решение. За это проголосовали.
анатолий техтоник

2
Согласно этому сообщению, строка sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)больше не работает с python 3.3 (см. PEP 3116)
Кен Майерс

1
Получил ошибку «sys: 1: ResourceWarning: нераскрытый файл <_io.BufferedWriter name = 5>», поэтому мне пришлось добавить tee.stdin.close()в конце моей программы. Я также получаю «ResourceWarning: подпроцесс 1842 все еще работает», и добавление sys.stdout.close(); sys.stderr.close()в конце программы исправляет это.
Матье

136

У меня была такая же проблема, и я нашел этот фрагмент очень полезным:

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self
    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()
    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)
    def flush(self):
        self.file.flush()

от: http://mail.python.org/pipermail/python-list/2007-May/438106.html


7
+1 за внутреннюю обработку sys.stdout, чтобы можно было завершить запись, удалив объект Tee
Бен Бланк

12
Я бы добавил к этому флеш. Например: 'self.file.flush ()'
Люк Стэнли

4
Я не согласен с модулем регистрации. Отлично подходит для игры Ведение журнала слишком велико для этого.
Kobor42

4
Обязательно обратите внимание на пересмотренную версию в этом дополнении к связанному обсуждению в ответе.
Мартино

4
Это не будет работать. __del__не вызывается до конца исполнения. См. Stackoverflow.com/questions/6104535/…
Nux

77

printЗаявление будет вызывать write()метод любого объекта , присвоенный sys.stdout.

Я бы раскрутил небольшой класс, чтобы написать в двух местах одновременно ...

import sys

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("log.dat", "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

sys.stdout = Logger()

Теперь printоператор будет отображаться на экране и добавляться в ваш файл журнала:

# prints "1 2" to <stdout> AND log.dat
print "%d %d" % (1,2)

Это очевидно быстро и грязно. Некоторые заметки:

  • Вы, вероятно, должны параметризовать имя файла журнала.
  • Вам, вероятно, следует вернуться к sys.stdout, <stdout>если вы не будете входить в систему на протяжении всей программы.
  • Вам может потребоваться возможность записи в несколько файлов журнала одновременно или обработка разных уровней журнала и т. Д.

Все это достаточно просто, так что мне удобно оставлять их как упражнения для читателя. Ключевым моментом здесь является то, что printпросто вызывается «подобный файлу объект», который назначен sys.stdout.


Именно то, что я собирался опубликовать, в значительной степени. +1, когда вы исправите проблему с write, не имея аргумента self. Кроме того, было бы лучше создать файл, в который вы собираетесь записать. Черт, возможно, лучше было бы также передать стандартный вывод.
Девин Жанпьер

@ Девин, да, это было быстро и грязно, я сделаю некоторые заметки для возможных ранних улучшений.
Триптих

7
Я выбрал этот ответ слишком рано. Он отлично работает для «печати», но не так много для вывода внешних команд.
Drue

2
Класс Logger также должен определять метод flush (), такой как «def flush (): self.terminal.flush (); self.log.flush ()»
blokeley

5
Вы говорите The print statement will call the write() method of any object you assign to sys.stdout. А как насчет других функций, отправляющих данные на стандартный вывод, не использующийся print? Например, если я создаю процесс, используя subprocess.callего вывод, он идет в консоль, а не в log.datфайл ... есть ли способ это исправить?
jpo38

64

То, что вы действительно хотите, это loggingмодуль из стандартной библиотеки. Создайте регистратор и присоедините два обработчика, один будет записывать в файл, а другой - в stdout или stderr.

См Logging несколько адресатов для получения подробной информации


9
Модуль ведения журнала не записывает исключения и другие важные выходные данные в stdout, что может быть полезно при анализе журналов на сервере сборки (например).
анатолий техтоник

2
loggingмодуль не будет перенаправлять вывод из системных вызовов, таких какos.write(1, b'stdout')
JFS

17

Вот еще одно решение, которое является более общим, чем другие - оно поддерживает разделение вывода (записанного sys.stdout) на любое количество файловоподобных объектов. Там нет требования, которое __stdout__само включено.

import sys

class multifile(object):
    def __init__(self, files):
        self._files = files
    def __getattr__(self, attr, *args):
        return self._wrap(attr, *args)
    def _wrap(self, attr, *args):
        def g(*a, **kw):
            for f in self._files:
                res = getattr(f, attr, *args)(*a, **kw)
            return res
        return g

# for a tee-like behavior, use like this:
sys.stdout = multifile([ sys.stdout, open('myfile.txt', 'w') ])

# all these forms work:
print 'abc'
print >>sys.stdout, 'line2'
sys.stdout.write('line3\n')

ПРИМЕЧАНИЕ. Это подтверждение концепции. Реализация здесь не завершена, так как она только оборачивает методыwrite файловоподобных объектов (например ), опуская members / properties / setattr и т. Д. Однако, это, вероятно, достаточно хорошо для большинства людей в нынешнем виде.

Что мне нравится об этом, кроме своей общности, является то , что он чист в том смысле , что не делает никаких прямых вызовов write, flush, os.dup2и т.д.


3
Я хотел бы, чтобы init взял * файлы, а не файлы, но в противном случае, да, это. Ни одно из других решений не изолирует функциональность "тройник", не пытаясь решить другие проблемы. Если вы хотите поставить префикс для всего, что вы выводите, вы можете обернуть этот класс в класс, пишущий префикс. (Если вы хотите поместить префикс только в один поток, вы оборачиваете поток и передаете его этому классу.) Этот также имеет то преимущество, что multifile ([]) создает файл, который игнорирует все (например, open ('/ dev) /ноль')).
Бен

Зачем _wrapздесь вообще? Не могли бы вы скопировать туда код, __getattr__и он работает так же?
timotree

@Ben фактически multifile([])создает файл, который вызывает UnboundLocalErrorкаждый раз, когда вы вызываете один из его методов. ( resвозвращается без назначения)
timotree

13

Как описано в другом месте, возможно, лучшее решение - использовать модуль регистрации напрямую:

import logging

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')
logging.info('this should to write to the log file')

Однако есть некоторые (редкие) случаи, когда вы действительно хотите перенаправить стандартный вывод. У меня была такая ситуация, когда я расширял команду runserver в django, которая использует print: я не хотел взламывать источник django, но нуждался в операторах print, чтобы перейти в файл.

Это способ перенаправить stdout и stderr из оболочки с помощью модуля logging:

import logging, sys

class LogFile(object):
    """File-like object to log text using the `logging` module."""

    def __init__(self, name=None):
        self.logger = logging.getLogger(name)

    def write(self, msg, level=logging.INFO):
        self.logger.log(level, msg)

    def flush(self):
        for handler in self.logger.handlers:
            handler.flush()

logging.basicConfig(level=logging.DEBUG, filename='mylog.log')

# Redirect stdout and stderr
sys.stdout = LogFile('stdout')
sys.stderr = LogFile('stderr')

print 'this should to write to the log file'

Эту реализацию LogFile следует использовать только в том случае, если вы действительно не можете использовать модуль ведения журнала напрямую.


11

Я написал tee()реализацию на Python, которая должна работать в большинстве случаев, и она также работает в Windows.

https://github.com/pycontribs/tendo

Также вы можете использовать его в сочетании с loggingмодулем из Python, если хотите.


Хм - эта ссылка больше не работает - где-нибудь еще ее можно найти?
Дэнни Стейпл

1
вау, ваш пакет качается, особенно если вы знаете, насколько громоздкой является консольная культура Windows, но не сдавались, чтобы заставить ее работать!
n611x007

8

(Ах, просто перечитайте свой вопрос и увидите, что это не совсем применимо.)

Вот пример программы, которая использует модуль регистрации Python . Этот модуль регистрации был во всех версиях с 2.3. В этом примере регистрация настраивается с помощью параметров командной строки.

В тихом режиме он будет входить только в файл, в обычном режиме - как в файл, так и на консоль.

import os
import sys
import logging
from optparse import OptionParser

def initialize_logging(options):
    """ Log information based upon users options"""

    logger = logging.getLogger('project')
    formatter = logging.Formatter('%(asctime)s %(levelname)s\t%(message)s')
    level = logging.__dict__.get(options.loglevel.upper(),logging.DEBUG)
    logger.setLevel(level)

    # Output logging information to screen
    if not options.quiet:
        hdlr = logging.StreamHandler(sys.stderr)
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)

    # Output logging information to file
    logfile = os.path.join(options.logdir, "project.log")
    if options.clean and os.path.isfile(logfile):
        os.remove(logfile)
    hdlr2 = logging.FileHandler(logfile)
    hdlr2.setFormatter(formatter)
    logger.addHandler(hdlr2)

    return logger

def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    # Setup command line options
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-l", "--logdir", dest="logdir", default=".", help="log DIRECTORY (default ./)")
    parser.add_option("-v", "--loglevel", dest="loglevel", default="debug", help="logging level (debug, info, error)")
    parser.add_option("-q", "--quiet", action="store_true", dest="quiet", help="do not log to console")
    parser.add_option("-c", "--clean", dest="clean", action="store_true", default=False, help="remove old log file")

    # Process command line options
    (options, args) = parser.parse_args(argv)

    # Setup logger format and output locations
    logger = initialize_logging(options)

    # Examples
    logger.error("This is an error message.")
    logger.info("This is an info message.")
    logger.debug("This is a debug message.")

if __name__ == "__main__":
    sys.exit(main())

Хороший ответ. Я видел несколько действительно запутанных способов репликации журналов на консоль, но создание StreamHandler с помощью stderr было ответом, который я искал :)
meatvest

Код хороший, он не отвечает на вопрос - это выводит журнал в файл и stderr, первоначальный вопрос заключался в том, чтобы дублировать stderr в файл журнала.
День

8

Чтобы завершить Джон Т ответ: https://stackoverflow.com/a/616686/395687

Я добавил __enter__и __exit__методы, чтобы использовать его в качестве менеджера контекста с withключевым словом, которое дает этот код

class Tee(object):
    def __init__(self, name, mode):
        self.file = open(name, mode)
        self.stdout = sys.stdout
        sys.stdout = self

    def __del__(self):
        sys.stdout = self.stdout
        self.file.close()

    def write(self, data):
        self.file.write(data)
        self.stdout.write(data)

    def __enter__(self):
        pass

    def __exit__(self, _type, _value, _traceback):
        pass

Затем его можно использовать как

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')

1
Я хотел бы перенести __del__функциональность в__exit__
vontrapp

1
На самом деле, я думаю, что использование __del__это плохая идея. Его следует переместить в функцию «close», которая вызывается в __exit__.
cladmi

7

Я знаю, что на этот вопрос неоднократно отвечали, но для этого я взял основной ответ из ответа Джона Т. и изменил его так, чтобы он содержал предложенный сброс и следовал за связанной пересмотренной версией. Я также добавил вход и выход, как указано в ответе cladmi, для использования с оператором with. Кроме того, в документации упоминается очистка файлов с использованием, os.fsync()поэтому я также добавил это. Я не знаю, действительно ли вам это нужно, но оно есть.

import sys, os

class Logger(object):
    "Lumberjack class - duplicates sys.stdout to a log file and it's okay"
    #source: https://stackoverflow.com/q/616645
    def __init__(self, filename="Red.Wood", mode="a", buff=0):
        self.stdout = sys.stdout
        self.file = open(filename, mode, buff)
        sys.stdout = self

    def __del__(self):
        self.close()

    def __enter__(self):
        pass

    def __exit__(self, *args):
        self.close()

    def write(self, message):
        self.stdout.write(message)
        self.file.write(message)

    def flush(self):
        self.stdout.flush()
        self.file.flush()
        os.fsync(self.file.fileno())

    def close(self):
        if self.stdout != None:
            sys.stdout = self.stdout
            self.stdout = None

        if self.file != None:
            self.file.close()
            self.file = None

Вы можете использовать его

with Logger('My_best_girlie_by_my.side'):
    print("we'd sing sing sing")

или

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()

Многие Thnaks @Status вы решили мой вопрос ( stackoverflow.com/questions/39143417/… ). Я поставлю ссылку на ваше решение.
Мохаммад ЭльНеср

1
@MohammadElNesr Я только что понял проблему с кодом, когда он используется с оператором with. Я исправил это, и теперь он правильно закрывается в конце блока с.
Статус

1
Это прекрасно сработало для меня, нужно было только изменить режим на mode="ab"и в writeфункцииself.file.write(message.encode("utf-8"))
ennetws

4

другое решение с использованием модуля логирования:

import logging
import sys

log = logging.getLogger('stdxxx')

class StreamLogger(object):

    def __init__(self, stream, prefix=''):
        self.stream = stream
        self.prefix = prefix
        self.data = ''

    def write(self, data):
        self.stream.write(data)
        self.stream.flush()

        self.data += data
        tmp = str(self.data)
        if '\x0a' in tmp or '\x0d' in tmp:
            tmp = tmp.rstrip('\x0a\x0d')
            log.info('%s%s' % (self.prefix, tmp))
            self.data = ''


logging.basicConfig(level=logging.INFO,
                    filename='text.log',
                    filemode='a')

sys.stdout = StreamLogger(sys.stdout, '[stdout] ')

print 'test for stdout'

3

Похоже, что ни один из приведенных выше ответов не отвечает поставленной проблеме. Я знаю, что это старый поток, но я думаю, что эта проблема намного проще, чем все делают это:

class tee_err(object):

 def __init__(self):
    self.errout = sys.stderr

    sys.stderr = self

    self.log = 'logfile.log'
    log = open(self.log,'w')
    log.close()

 def write(self, line):

    log = open(self.log,'a')
    log.write(line)
    log.close()   

    self.errout.write(line)

Теперь это будет повторять все до обычного обработчика sys.stderr и вашего файла. Создайте другой класс tee_outдля sys.stdout.


2
Подобный лучший ответ был опубликован за два года до этого: stackoverflow.com/a/616686 . Ваш метод очень дорогой: каждый вызов tee=tee_err();tee.write('');tee.write('');...opens + закрывает файл для каждого write. См. Stackoverflow.com/q/4867468 и stackoverflow.com/q/164053 для аргументов против этой практики.
Роб W

3

По просьбе @ user5359531 в комментариях под @John T в ответ , вот копия ссылочного поста к пересмотренной версии связанного обсуждения в этом ответе:

Issue of redirecting the stdout to both file and screen
Gabriel Genellina gagsl-py2 at yahoo.com.ar
Mon May 28 12:45:51 CEST 2007

    Previous message: Issue of redirecting the stdout to both file and screen
    Next message: Formal interfaces with Python
    Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

En Mon, 28 May 2007 06:17:39 -0300, 人言落日是天涯,望极天涯不见家
<kelvin.you at gmail.com> escribió:

> I wanna print the log to both the screen and file, so I simulatered a
> 'tee'
>
> class Tee(file):
>
>     def __init__(self, name, mode):
>         file.__init__(self, name, mode)
>         self.stdout = sys.stdout
>         sys.stdout = self
>
>     def __del__(self):
>         sys.stdout = self.stdout
>         self.close()
>
>     def write(self, data):
>         file.write(self, data)
>         self.stdout.write(data)
>
> Tee('logfile', 'w')
> print >>sys.stdout, 'abcdefg'
>
> I found that it only output to the file, nothing to screen. Why?
> It seems the 'write' function was not called when I *print* something.

You create a Tee instance and it is immediately garbage collected. I'd
restore sys.stdout on Tee.close, not __del__ (you forgot to call the
inherited __del__ method, btw).
Mmm, doesn't work. I think there is an optimization somewhere: if it looks
like a real file object, it uses the original file write method, not yours.
The trick would be to use an object that does NOT inherit from file:

import sys
class TeeNoFile(object):
     def __init__(self, name, mode):
         self.file = open(name, mode)
         self.stdout = sys.stdout
         sys.stdout = self
     def close(self):
         if self.stdout is not None:
             sys.stdout = self.stdout
             self.stdout = None
         if self.file is not None:
             self.file.close()
             self.file = None
     def write(self, data):
         self.file.write(data)
         self.stdout.write(data)
     def flush(self):
         self.file.flush()
         self.stdout.flush()
     def __del__(self):
         self.close()

tee=TeeNoFile('logfile', 'w')
print 'abcdefg'
print 'another line'
tee.close()
print 'screen only'
del tee # should do nothing

--
Gabriel Genellina

1

Я пишу скрипт для запуска скриптов cmd-line. (Поскольку в некоторых случаях просто невозможно заменить команду Linux - например, в случае rsync.)

Что я действительно хотел, так это использовать механизм регистрации Python по умолчанию в каждом случае, когда это было возможно, но все равно фиксировать любую ошибку, когда что-то пошло не так, как было непредвиденно.

Этот код, кажется, делает свое дело. Это может быть не особенно элегантно или эффективно (хотя в нем не используется строка + = строка, так что, по крайней мере, у него нет такого потенциального узкого места). Я публикую это на тот случай, если это даст кому-то еще какие-нибудь полезные идеи.

import logging
import os, sys
import datetime

# Get name of module, use as application name
try:
  ME=os.path.split(__file__)[-1].split('.')[0]
except:
  ME='pyExec_'

LOG_IDENTIFIER="uuu___( o O )___uuu "
LOG_IDR_LENGTH=len(LOG_IDENTIFIER)

class PyExec(object):

  # Use this to capture all possible error / output to log
  class SuperTee(object):
      # Original reference: http://mail.python.org/pipermail/python-list/2007-May/442737.html
      def __init__(self, name, mode):
          self.fl = open(name, mode)
          self.fl.write('\n')
          self.stdout = sys.stdout
          self.stdout.write('\n')
          self.stderr = sys.stderr

          sys.stdout = self
          sys.stderr = self

      def __del__(self):
          self.fl.write('\n')
          self.fl.flush()
          sys.stderr = self.stderr
          sys.stdout = self.stdout
          self.fl.close()

      def write(self, data):
          # If the data to write includes the log identifier prefix, then it is already formatted
          if data[0:LOG_IDR_LENGTH]==LOG_IDENTIFIER:
            self.fl.write("%s\n" % data[LOG_IDR_LENGTH:])
            self.stdout.write(data[LOG_IDR_LENGTH:])

          # Otherwise, we can give it a timestamp
          else:

            timestamp=str(datetime.datetime.now())
            if 'Traceback' == data[0:9]:
              data='%s: %s' % (timestamp, data)
              self.fl.write(data)
            else:
              self.fl.write(data)

            self.stdout.write(data)


  def __init__(self, aName, aCmd, logFileName='', outFileName=''):

    # Using name for 'logger' (context?), which is separate from the module or the function
    baseFormatter=logging.Formatter("%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")
    errorFormatter=logging.Formatter(LOG_IDENTIFIER + "%(asctime)s \t %(levelname)s \t %(name)s:%(module)s:%(lineno)d \t %(message)s")

    if logFileName:
      # open passed filename as append
      fl=logging.FileHandler("%s.log" % aName)
    else:
      # otherwise, use log filename as a one-time use file
      fl=logging.FileHandler("%s.log" % aName, 'w')

    fl.setLevel(logging.DEBUG)
    fl.setFormatter(baseFormatter)

    # This will capture stdout and CRITICAL and beyond errors

    if outFileName:
      teeFile=PyExec.SuperTee("%s_out.log" % aName)
    else:
      teeFile=PyExec.SuperTee("%s_out.log" % aName, 'w')

    fl_out=logging.StreamHandler( teeFile )
    fl_out.setLevel(logging.CRITICAL)
    fl_out.setFormatter(errorFormatter)

    # Set up logging
    self.log=logging.getLogger('pyExec_main')
    log=self.log

    log.addHandler(fl)
    log.addHandler(fl_out)

    print "Test print statement."

    log.setLevel(logging.DEBUG)

    log.info("Starting %s", ME)
    log.critical("Critical.")

    # Caught exception
    try:
      raise Exception('Exception test.')
    except Exception,e:
      log.exception(str(e))

    # Uncaught exception
    a=2/0


PyExec('test_pyExec',None)

Очевидно, что если вы не так подвержены прихотям, как я, замените LOG_IDENTIFIER другой строкой, которую вы никогда не увидите, чтобы кто-то записывал в журнал.


0

Если вы хотите записать весь вывод в файл И вывести его в текстовый файл, то вы можете сделать следующее. Это немного глупо, но это работает:

import logging
debug = input("Debug or not")
if debug == "1":
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string):
        old_print(string)
        logging.info(string)
print("OMG it works!")

РЕДАКТИРОВАТЬ: Обратите внимание, что это не регистрирует ошибки, если вы не перенаправите sys.stderr в sys.stdout

РЕДАКТИРОВАТЬ 2: Вторая проблема заключается в том, что вы должны передать 1 аргумент в отличие от встроенной функции.

РЕДАКТИРОВАТЬ 3: см. Код, прежде чем записать stdin и stdout в консоль и файл с stderr только собирается в файл

import logging, sys
debug = input("Debug or not")
if debug == "1":
    old_input = input
    sys.stderr.write = logging.info
    def input(string=""):
        string_in = old_input(string)
        logging.info("STRING IN " + string_in)
        return string_in
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string="", string2=""):
        old_print(string, string2)
        logging.info(string)
        logging.info(string2)
print("OMG")
b = input()
print(a) ## Deliberate error for testing

-1

Я написал полную замену sys.stderrи просто дублируется код переименования stderrдля , stdoutчтобы сделать его также можно заменить sys.stdout.

Для этого я создаю тот же тип объекта , как текущие stderrи stdout, и вперед все методы к исходной системе stderrи stdout:

import os
import sys
import logging

class StdErrReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        /programming/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        /programming/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stderr` permanently.
        """
        global _stderr_singleton
        global _stderr_default
        global _stderr_default_class_type

        # On Sublime Text, `sys.__stderr__` is set to None, because they already replaced `sys.stderr`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stderr__:
            sys.__stderr__ = sys.stderr

        try:
            _stderr_default
            _stderr_default_class_type

        except NameError:
            _stderr_default = sys.stderr
            _stderr_default_class_type = type( _stderr_default )

        # Recreate the sys.stderr logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stderr_write = _stderr_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stderr_write
            global _sys_stderr_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stderr_write`
            def _sys_stderr_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    /programming/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stderr_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stderr_write` function pointer ever
            try:
                _sys_stderr_write

            except NameError:

                def _sys_stderr_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stderr.write` and our custom wrapper around it.
                    """
                    _sys_stderr_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stderr_singleton

        except NameError:

            class StdErrReplamentHidden(_stderr_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    /programming/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stderr_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stderr_default.__abstractmethods__

                if hasattr( _stderr_default, "__base__" ):
                    __base__ = _stderr_default.__base__

                if hasattr( _stderr_default, "__bases__" ):
                    __bases__ = _stderr_default.__bases__

                if hasattr( _stderr_default, "__basicsize__" ):
                    __basicsize__ = _stderr_default.__basicsize__

                if hasattr( _stderr_default, "__call__" ):
                    __call__ = _stderr_default.__call__

                if hasattr( _stderr_default, "__class__" ):
                    __class__ = _stderr_default.__class__

                if hasattr( _stderr_default, "__delattr__" ):
                    __delattr__ = _stderr_default.__delattr__

                if hasattr( _stderr_default, "__dict__" ):
                    __dict__ = _stderr_default.__dict__

                if hasattr( _stderr_default, "__dictoffset__" ):
                    __dictoffset__ = _stderr_default.__dictoffset__

                if hasattr( _stderr_default, "__dir__" ):
                    __dir__ = _stderr_default.__dir__

                if hasattr( _stderr_default, "__doc__" ):
                    __doc__ = _stderr_default.__doc__

                if hasattr( _stderr_default, "__eq__" ):
                    __eq__ = _stderr_default.__eq__

                if hasattr( _stderr_default, "__flags__" ):
                    __flags__ = _stderr_default.__flags__

                if hasattr( _stderr_default, "__format__" ):
                    __format__ = _stderr_default.__format__

                if hasattr( _stderr_default, "__ge__" ):
                    __ge__ = _stderr_default.__ge__

                if hasattr( _stderr_default, "__getattribute__" ):
                    __getattribute__ = _stderr_default.__getattribute__

                if hasattr( _stderr_default, "__gt__" ):
                    __gt__ = _stderr_default.__gt__

                if hasattr( _stderr_default, "__hash__" ):
                    __hash__ = _stderr_default.__hash__

                if hasattr( _stderr_default, "__init__" ):
                    __init__ = _stderr_default.__init__

                if hasattr( _stderr_default, "__init_subclass__" ):
                    __init_subclass__ = _stderr_default.__init_subclass__

                if hasattr( _stderr_default, "__instancecheck__" ):
                    __instancecheck__ = _stderr_default.__instancecheck__

                if hasattr( _stderr_default, "__itemsize__" ):
                    __itemsize__ = _stderr_default.__itemsize__

                if hasattr( _stderr_default, "__le__" ):
                    __le__ = _stderr_default.__le__

                if hasattr( _stderr_default, "__lt__" ):
                    __lt__ = _stderr_default.__lt__

                if hasattr( _stderr_default, "__module__" ):
                    __module__ = _stderr_default.__module__

                if hasattr( _stderr_default, "__mro__" ):
                    __mro__ = _stderr_default.__mro__

                if hasattr( _stderr_default, "__name__" ):
                    __name__ = _stderr_default.__name__

                if hasattr( _stderr_default, "__ne__" ):
                    __ne__ = _stderr_default.__ne__

                if hasattr( _stderr_default, "__new__" ):
                    __new__ = _stderr_default.__new__

                if hasattr( _stderr_default, "__prepare__" ):
                    __prepare__ = _stderr_default.__prepare__

                if hasattr( _stderr_default, "__qualname__" ):
                    __qualname__ = _stderr_default.__qualname__

                if hasattr( _stderr_default, "__reduce__" ):
                    __reduce__ = _stderr_default.__reduce__

                if hasattr( _stderr_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stderr_default.__reduce_ex__

                if hasattr( _stderr_default, "__repr__" ):
                    __repr__ = _stderr_default.__repr__

                if hasattr( _stderr_default, "__setattr__" ):
                    __setattr__ = _stderr_default.__setattr__

                if hasattr( _stderr_default, "__sizeof__" ):
                    __sizeof__ = _stderr_default.__sizeof__

                if hasattr( _stderr_default, "__str__" ):
                    __str__ = _stderr_default.__str__

                if hasattr( _stderr_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stderr_default.__subclasscheck__

                if hasattr( _stderr_default, "__subclasses__" ):
                    __subclasses__ = _stderr_default.__subclasses__

                if hasattr( _stderr_default, "__subclasshook__" ):
                    __subclasshook__ = _stderr_default.__subclasshook__

                if hasattr( _stderr_default, "__text_signature__" ):
                    __text_signature__ = _stderr_default.__text_signature__

                if hasattr( _stderr_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stderr_default.__weakrefoffset__

                if hasattr( _stderr_default, "mro" ):
                    mro = _stderr_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stderr_default )` constructor, so we can 
                        instantiate any kind of `sys.stderr` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stderr_default, attribute ):

                            base_class_attribute = super( _stderr_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stderr_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stderr.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stderr_write

                    try:
                        return _stderr_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stderr_default_class_type, _stderr_default ).__getattribute__( item )

            _stderr_singleton = StdErrReplamentHidden()
            sys.stderr = _stderr_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stderr` writer from `sys.stderr` and allow the next call to `lock()` create
            a new writer for the stderr.
        """

        if cls.is_active:
            global _sys_stderr_write_hidden

            cls.is_active = False
            _sys_stderr_write_hidden = _stderr_default.write



class StdOutReplament(object):
    """
        How to redirect stdout and stderr to logger in Python
        /programming/19425736/how-to-redirect-stdout-and-stderr-to-logger-in-python

        Set a Read-Only Attribute in Python?
        /programming/24497316/set-a-read-only-attribute-in-python
    """
    is_active = False

    @classmethod
    def lock(cls, logger):
        """
            Attach this singleton logger to the `sys.stdout` permanently.
        """
        global _stdout_singleton
        global _stdout_default
        global _stdout_default_class_type

        # On Sublime Text, `sys.__stdout__` is set to None, because they already replaced `sys.stdout`
        # by some `_LogWriter()` class, then just save the current one over there.
        if not sys.__stdout__:
            sys.__stdout__ = sys.stdout

        try:
            _stdout_default
            _stdout_default_class_type

        except NameError:
            _stdout_default = sys.stdout
            _stdout_default_class_type = type( _stdout_default )

        # Recreate the sys.stdout logger when it was reset by `unlock()`
        if not cls.is_active:
            cls.is_active = True
            _stdout_write = _stdout_default.write

            logger_call = logger.debug
            clean_formatter = logger.clean_formatter

            global _sys_stdout_write
            global _sys_stdout_write_hidden

            if sys.version_info <= (3,2):
                logger.file_handler.terminator = '\n'

            # Always recreate/override the internal write function used by `_sys_stdout_write`
            def _sys_stdout_write_hidden(*args, **kwargs):
                """
                    Suppress newline in Python logging module
                    /programming/7168790/suppress-newline-in-python-logging-module
                """

                try:
                    _stdout_write( *args, **kwargs )
                    file_handler = logger.file_handler

                    formatter = file_handler.formatter
                    terminator = file_handler.terminator

                    file_handler.formatter = clean_formatter
                    file_handler.terminator = ""

                    kwargs['extra'] = {'_duplicated_from_file': True}
                    logger_call( *args, **kwargs )

                    file_handler.formatter = formatter
                    file_handler.terminator = terminator

                except Exception:
                    logger.exception( "Could not write to the file_handler: %s(%s)", file_handler, logger )
                    cls.unlock()

            # Only create one `_sys_stdout_write` function pointer ever
            try:
                _sys_stdout_write

            except NameError:

                def _sys_stdout_write(*args, **kwargs):
                    """
                        Hides the actual function pointer. This allow the external function pointer to
                        be cached while the internal written can be exchanged between the standard
                        `sys.stdout.write` and our custom wrapper around it.
                    """
                    _sys_stdout_write_hidden( *args, **kwargs )

        try:
            # Only create one singleton instance ever
            _stdout_singleton

        except NameError:

            class StdOutReplamentHidden(_stdout_default_class_type):
                """
                    Which special methods bypasses __getattribute__ in Python?
                    /programming/12872695/which-special-methods-bypasses-getattribute-in-python
                """

                if hasattr( _stdout_default, "__abstractmethods__" ):
                    __abstractmethods__ = _stdout_default.__abstractmethods__

                if hasattr( _stdout_default, "__base__" ):
                    __base__ = _stdout_default.__base__

                if hasattr( _stdout_default, "__bases__" ):
                    __bases__ = _stdout_default.__bases__

                if hasattr( _stdout_default, "__basicsize__" ):
                    __basicsize__ = _stdout_default.__basicsize__

                if hasattr( _stdout_default, "__call__" ):
                    __call__ = _stdout_default.__call__

                if hasattr( _stdout_default, "__class__" ):
                    __class__ = _stdout_default.__class__

                if hasattr( _stdout_default, "__delattr__" ):
                    __delattr__ = _stdout_default.__delattr__

                if hasattr( _stdout_default, "__dict__" ):
                    __dict__ = _stdout_default.__dict__

                if hasattr( _stdout_default, "__dictoffset__" ):
                    __dictoffset__ = _stdout_default.__dictoffset__

                if hasattr( _stdout_default, "__dir__" ):
                    __dir__ = _stdout_default.__dir__

                if hasattr( _stdout_default, "__doc__" ):
                    __doc__ = _stdout_default.__doc__

                if hasattr( _stdout_default, "__eq__" ):
                    __eq__ = _stdout_default.__eq__

                if hasattr( _stdout_default, "__flags__" ):
                    __flags__ = _stdout_default.__flags__

                if hasattr( _stdout_default, "__format__" ):
                    __format__ = _stdout_default.__format__

                if hasattr( _stdout_default, "__ge__" ):
                    __ge__ = _stdout_default.__ge__

                if hasattr( _stdout_default, "__getattribute__" ):
                    __getattribute__ = _stdout_default.__getattribute__

                if hasattr( _stdout_default, "__gt__" ):
                    __gt__ = _stdout_default.__gt__

                if hasattr( _stdout_default, "__hash__" ):
                    __hash__ = _stdout_default.__hash__

                if hasattr( _stdout_default, "__init__" ):
                    __init__ = _stdout_default.__init__

                if hasattr( _stdout_default, "__init_subclass__" ):
                    __init_subclass__ = _stdout_default.__init_subclass__

                if hasattr( _stdout_default, "__instancecheck__" ):
                    __instancecheck__ = _stdout_default.__instancecheck__

                if hasattr( _stdout_default, "__itemsize__" ):
                    __itemsize__ = _stdout_default.__itemsize__

                if hasattr( _stdout_default, "__le__" ):
                    __le__ = _stdout_default.__le__

                if hasattr( _stdout_default, "__lt__" ):
                    __lt__ = _stdout_default.__lt__

                if hasattr( _stdout_default, "__module__" ):
                    __module__ = _stdout_default.__module__

                if hasattr( _stdout_default, "__mro__" ):
                    __mro__ = _stdout_default.__mro__

                if hasattr( _stdout_default, "__name__" ):
                    __name__ = _stdout_default.__name__

                if hasattr( _stdout_default, "__ne__" ):
                    __ne__ = _stdout_default.__ne__

                if hasattr( _stdout_default, "__new__" ):
                    __new__ = _stdout_default.__new__

                if hasattr( _stdout_default, "__prepare__" ):
                    __prepare__ = _stdout_default.__prepare__

                if hasattr( _stdout_default, "__qualname__" ):
                    __qualname__ = _stdout_default.__qualname__

                if hasattr( _stdout_default, "__reduce__" ):
                    __reduce__ = _stdout_default.__reduce__

                if hasattr( _stdout_default, "__reduce_ex__" ):
                    __reduce_ex__ = _stdout_default.__reduce_ex__

                if hasattr( _stdout_default, "__repr__" ):
                    __repr__ = _stdout_default.__repr__

                if hasattr( _stdout_default, "__setattr__" ):
                    __setattr__ = _stdout_default.__setattr__

                if hasattr( _stdout_default, "__sizeof__" ):
                    __sizeof__ = _stdout_default.__sizeof__

                if hasattr( _stdout_default, "__str__" ):
                    __str__ = _stdout_default.__str__

                if hasattr( _stdout_default, "__subclasscheck__" ):
                    __subclasscheck__ = _stdout_default.__subclasscheck__

                if hasattr( _stdout_default, "__subclasses__" ):
                    __subclasses__ = _stdout_default.__subclasses__

                if hasattr( _stdout_default, "__subclasshook__" ):
                    __subclasshook__ = _stdout_default.__subclasshook__

                if hasattr( _stdout_default, "__text_signature__" ):
                    __text_signature__ = _stdout_default.__text_signature__

                if hasattr( _stdout_default, "__weakrefoffset__" ):
                    __weakrefoffset__ = _stdout_default.__weakrefoffset__

                if hasattr( _stdout_default, "mro" ):
                    mro = _stdout_default.mro

                def __init__(self):
                    """
                        Override any super class `type( _stdout_default )` constructor, so we can 
                        instantiate any kind of `sys.stdout` replacement object, in case it was already 
                        replaced by something else like on Sublime Text with `_LogWriter()`.

                        Assures all attributes were statically replaced just above. This should happen in case
                        some new attribute is added to the python language.

                        This also ignores the only two methods which are not equal, `__init__()` and `__getattribute__()`.
                    """
                    different_methods = ("__init__", "__getattribute__")
                    attributes_to_check = set( dir( object ) + dir( type ) )

                    for attribute in attributes_to_check:

                        if attribute not in different_methods \
                                and hasattr( _stdout_default, attribute ):

                            base_class_attribute = super( _stdout_default_class_type, self ).__getattribute__( attribute )
                            target_class_attribute = _stdout_default.__getattribute__( attribute )

                            if base_class_attribute != target_class_attribute:
                                sys.stdout.write( "    The base class attribute `%s` is different from the target class:\n%s\n%s\n\n" % (
                                        attribute, base_class_attribute, target_class_attribute ) )

                def __getattribute__(self, item):

                    if item == 'write':
                        return _sys_stdout_write

                    try:
                        return _stdout_default.__getattribute__( item )

                    except AttributeError:
                        return super( _stdout_default_class_type, _stdout_default ).__getattribute__( item )

            _stdout_singleton = StdOutReplamentHidden()
            sys.stdout = _stdout_singleton

        return cls

    @classmethod
    def unlock(cls):
        """
            Detach this `stdout` writer from `sys.stdout` and allow the next call to `lock()` create
            a new writer for the stdout.
        """

        if cls.is_active:
            global _sys_stdout_write_hidden

            cls.is_active = False
            _sys_stdout_write_hidden = _stdout_default.write

Чтобы использовать это, вы можете просто позвонить StdErrReplament::lock(logger)и StdOutReplament::lock(logger) передать регистратор, который вы хотите использовать для отправки выходного текста. Например:

import os
import sys
import logging

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

file_handler = logging.FileHandler( log_file_path, 'a' )
file_handler.formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )

log.file_handler = file_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

Запустив этот код, вы увидите на экране:

введите описание изображения здесь

И по содержанию файла:

введите описание изображения здесь

Если вы также хотите видеть содержимое log.debugвызовов на экране, вам нужно добавить обработчик потока в ваш логгер. В этом случае это будет так:

import os
import sys
import logging

class ContextFilter(logging.Filter):
    """ This filter avoids duplicated information to be displayed to the StreamHandler log. """
    def filter(self, record):
        return not "_duplicated_from_file" in record.__dict__

current_folder = os.path.dirname( os.path.realpath( __file__ ) )
log_file_path = os.path.join( current_folder, "my_log_file.txt" )

stream_handler = logging.StreamHandler()
file_handler = logging.FileHandler( log_file_path, 'a' )

formatter = logging.Formatter( "%(asctime)s %(name)s %(levelname)s - %(message)s", "%Y-%m-%d %H:%M:%S" )
file_handler.formatter = formatter
stream_handler.formatter = formatter
stream_handler.addFilter( ContextFilter() )

log = logging.getLogger( __name__ )
log.setLevel( "DEBUG" )
log.addHandler( file_handler )
log.addHandler( stream_handler )

log.file_handler = file_handler
log.stream_handler = stream_handler
log.clean_formatter = logging.Formatter( "", "" )

StdOutReplament.lock( log )
StdErrReplament.lock( log )

log.debug( "I am doing usual logging debug..." )
sys.stderr.write( "Tests 1...\n" )
sys.stdout.write( "Tests 2...\n" )

Который будет выводить как это при запуске:

введите описание изображения здесь

Хотя это все равно сохранит это в файл my_log_file.txt:

введите описание изображения здесь

При отключении этого с помощью StdErrReplament:unlock()он только восстановит стандартное поведение stderrпотока, поскольку подключенный регистратор никогда не может быть отсоединен, потому что кто-то может иметь ссылку на его более старую версию. Вот почему это глобальный синглтон, который никогда не умрет. Следовательно, в случае перезагрузки этого модуля с помощью impчего-либо еще, он никогда не будет повторно захватывать ток, так sys.stderrкак он уже был введен в него, и сохранять его внутри.


5
удивительный уровень случайной сложности для дублирования потока.
Аттила Лендвай
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.