Como duplicar o sys.stdout em um arquivo de log?


149

Edit: Como parece que não há solução ou estou fazendo algo tão fora do padrão que ninguém sabe - revisarei minha pergunta para perguntar também: Qual é a melhor maneira de realizar o log quando um aplicativo python está fazendo um muitas chamadas de sistema?

Meu aplicativo tem dois modos. No modo interativo, quero que toda a saída vá para a tela e também para um arquivo de log, incluindo a saída de qualquer chamada do sistema. No modo daemon, toda a saída vai para o log. O modo Daemon funciona muito bem usando os.dup2(). Não consigo encontrar uma maneira de "tee" toda a saída para um log no modo interativo, sem modificar cada chamada do sistema.


Em outras palavras, desejo a funcionalidade da linha de comando 'tee' para qualquer saída gerada por um aplicativo python, incluindo saída de chamada do sistema .

Esclarecer:

Para redirecionar todas as saídas, faço algo assim, e funciona muito bem:

# 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())

O bom disso é que ele não requer chamadas de impressão especiais do restante do código. O código também executa alguns comandos do shell, por isso é bom não ter que lidar com cada saída individualmente também.

Simplesmente, quero fazer o mesmo, exceto duplicar em vez de redirecionar.

À primeira vista, pensei que simplesmente reverter os dup2funcionaria. Por que não? Aqui está o meu teste:

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)

O arquivo "a.log" deve ser idêntico ao que foi exibido na tela.


Se você olhar para a página do manual ( manpagez.com/man/2/dup2 ), o segundo argumento para o dup2 estará sempre fechado (se já estiver aberto). Então, na sua "solução quebrada", ele está fechando so e se e, em seguida, reatribuindo seus arquivos para sys.stdout.
26630 Jacob Gabrielson

1
Re: sua edição: isso não é incomum, eu fiz o mesmo algumas vezes (em outros idiomas). Embora o Unix permita vários "aliases" para o mesmo identificador de arquivo, ele não "dividirá" um identificador de arquivo (copie-o para vários outros). Então você tem que implementar "tee" você mesmo (ou apenas usar "tee", veja minha resposta grosseira).
23630 Jacob Gabrielson

Acho que a resposta JohnT é melhor do que a resposta realmente aceita. Você pode alterar a resposta aceita.
22414 Phong

"Estou fazendo algo tão fora do padrão" - você realmente é, as pessoas enviam seus logs para o stderr e lidam com elas na linha de comando.
Khachik

Respostas:


55

Como você se sente confortável gerando processos externos a partir do seu código, você pode usar a teesi próprio. Não conheço nenhuma chamada de sistema Unix que faça exatamente o que teefaz.

# 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)

Você também pode emular teeusando o pacote de multiprocessamento (ou usar o processamento se estiver usando o Python 2.5 ou anterior).

Atualizar

Aqui está uma versão compatível com 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
Bem, esta resposta funciona, então eu vou aceitá-la. Ainda assim, isso me faz sentir suja.
drue 18/03/09

2
Acabei de publicar uma implementação python pura do tee (compatível com py2 / 3) que pode ser executada em qualquer plataforma e também ser usada em diferentes configurações de log. stackoverflow.com/questions/616645/…
sorin

8
Se o Python é executado em uma das minhas máquinas e a solução não, essa não é uma solução python. Votado por causa disso.
Anatoly techtonik

2
De acordo com este post a linha sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)não funciona mais desde python 3.3 (ver PEP 3116)
Ken Myers

1
Obtive o erro "sys: 1: ResourceWarning: arquivo não fechado <_io.BufferedWriter name = 5>", então tive que adicionar tee.stdin.close()no final do meu programa. Também recebo "ResourceWarning: o subprocesso 1842 ainda está em execução" e a adição sys.stdout.close(); sys.stderr.close()no final do programa o corrige.
matthieu

136

Eu tive esse mesmo problema antes e achei este snippet muito útil:

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()

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


7
+1 para lidar com a mudança de sys.stdout internamente para que você pode terminar o registo, excluindo o objeto Tee
Ben Blank

12
Eu adicionaria um flush a isso. Por exemplo: 'self.file.flush ()'
Luke Stanley

4
Não concordo com o módulo de registro. Excelente para alguns mexer. O registro é muito grande para isso.
Kobor42

4
Observe a versão revisada neste acompanhamento para a discussão vinculada na resposta.
martineau

4
Isso não vai funcionar. __del__não é chamado até o final da execução. Veja stackoverflow.com/questions/6104535/…
Nux

77

A printinstrução chamará o write()método de qualquer objeto que você atribuir ao sys.stdout.

Gostaria de criar uma turma pequena para escrever para dois lugares ao mesmo tempo ...

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()

Agora, a printinstrução ecoará na tela e será anexada ao seu arquivo de log:

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

Isto é obviamente rápido e sujo. Algumas notas:

  • Você provavelmente deve parametrizar o nome do arquivo do log.
  • Você provavelmente deve reverter sys.stdout para <stdout>se não estiver registrando durante o programa.
  • Você pode querer gravar em vários arquivos de log de uma só vez ou lidar com diferentes níveis de log etc.

Tudo isso é direto o suficiente para que eu me sinta confortável em deixá-los como exercícios para o leitor. A principal dica aqui é que printapenas chama um "objeto semelhante a arquivo" ao qual está atribuído sys.stdout.


Exatamente o que eu ia postar, basicamente. +1 quando você corrige o problema com a gravação sem argumento próprio. . Além disso, seria melhor projeto para que o arquivo que você está indo para escrever a passou no inferno, ele também pode ser melhor projeto ter passado stdout no.
Devin Jeanpierre

@ Kevin, sim, isso foi rápido e sujo, vou fazer algumas anotações para possíveis melhorias antecipadas.
Triptych

7
Selecionei esta resposta muito cedo. Funciona muito bem para "impressão", mas não tanto para saída de comando externo.
drue 5/03/09

2
A classe Logger também deve definir um método flush () como "def flush (): self.terminal.flush (); self.log.flush ()"
blokeley

5
Você diz The print statement will call the write() method of any object you assign to sys.stdout. E quanto a outras funções que enviam dados para o stdout que não está usando print. Por exemplo, se eu criar um processo usando subprocess.callsua saída, vá para o console, mas não para o log.datarquivo ... existe uma maneira de corrigir isso?
jpo38

64

O que você realmente deseja é um loggingmódulo da biblioteca padrão. Crie um logger e anexe dois manipuladores, um gravando em um arquivo e o outro em stdout ou stderr.

Consulte Registrando em vários destinos para obter detalhes


9
O módulo de log não registra exceções e outras saídas importantes no stdout, o que pode ser útil ao analisar logs no servidor de compilação (por exemplo).
Anatoly techtonik

2
loggingmódulo de saída não redirecionamento de chamadas do sistema, tais comoos.write(1, b'stdout')
jfs

17

Aqui está outra solução, que é mais geral que as outras - ele suporta a divisão de saída (gravada em sys.stdout) para qualquer número de objetos semelhantes a arquivos. Não há exigência de que __stdout__ele esteja incluído.

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')

NOTA: Essa é uma prova de conceito. A implementação aqui não está completa, pois envolve apenas métodos de objetos semelhantes a arquivos (por exemplo write), excluindo membros / propriedades / setattr, etc. No entanto, provavelmente é boa o suficiente para a maioria das pessoas, como está atualmente.

O que eu gosto sobre isso, a não ser sua generalidade, é que ele é limpo, no sentido de não fazer quaisquer chamadas diretas para write, flush, os.dup2, etc.


3
Eu teria init tomar * arquivos não arquivos, mas caso contrário, sim, isso. Nenhuma das outras soluções isola a funcionalidade "tee" sem tentar resolver outros problemas. Se você deseja colocar um prefixo em tudo que você produz, pode agrupar essa classe em uma classe de prefix-writer. (Se você quiser colocar um prefixo em apenas um fluxo, envolva-o e entregue-o a essa classe.) Esse também tem a vantagem de multifile ([]) criar um arquivo que ignora tudo (como open ('/ dev /nulo')).
Ben

Por que tem _wrapaqui? Você não conseguiu copiar o código __getattr__e o mesmo funciona?
timotree

O @Ben na verdade multifile([])cria um arquivo que gera um UnboundLocalErrorsempre que você chama um de seus métodos. ( resÉ devolvido sem ser atribuído)
timotree

13

Como descrito em outro lugar, talvez a melhor solução seja usar o módulo de registro diretamente:

import logging

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

No entanto, existem algumas (raras) ocasiões em que você realmente deseja redirecionar o stdout. Eu tive essa situação quando estendia o comando runserver do django, que usa print: não queria invadir a fonte do django, mas precisava das instruções de impressão para ir para um arquivo.

Esta é uma maneira de redirecionar stdout e stderr para fora do shell usando o módulo de log:

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'

Você só deve usar esta implementação do LogFile se realmente não puder usar o módulo de log diretamente.


11

Eu escrevi uma tee()implementação em Python que deve funcionar na maioria dos casos e funciona no Windows também.

https://github.com/pycontribs/tendo

Além disso, você pode usá-lo em combinação com o loggingmódulo do Python, se desejar.


Hmm - esse link não funciona mais - em outro lugar ele pode ser encontrado?
Danny Staple

1
uau, seu pacote é ótimo, especialmente se você sabe o quão pesada é a cultura de console do Windows, mas não desistiu de fazê-la funcionar!
N611x007

8

(Ah, basta reler sua pergunta e ver se isso não se aplica.)

Aqui está um exemplo de programa que utiliza o módulo de log python . Este módulo de registro está em todas as versões desde 2.3. Nesta amostra, o log é configurável pelas opções da linha de comandos.

No modo completo, ele registrará apenas em um arquivo; no modo normal, registrará em um arquivo e no console.

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())

Boa resposta. Eu vi algumas maneiras realmente complicada de replicação de registro para o console, mas fazer um StreamHandler com stderr foi a resposta que eu estive procurando :)
meatvest

O código é legal: ele não responde à pergunta - isso gera o log em um arquivo e o stderr, a pergunta original estava pedindo para duplicar o stderr em um arquivo de log.
emem 29/08/19

8

Para concluir a resposta de John T: https://stackoverflow.com/a/616686/395687

Eu adicionei __enter__e __exit__métodos para usá-lo como um gerenciador de contexto com a withpalavra - chave, que fornece esse código

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

Pode então ser usado como

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

1
Gostaria de passar a __del__funcionalidade para__exit__
vontrapp

1
Na verdade, acho que usar __del__é uma má ideia. Ele deve ser movido para uma função 'close' que é chamada __exit__.
cladmi

7

Sei que esta pergunta foi respondida repetidamente, mas, para isso, peguei a resposta principal da resposta de John T e a modifiquei para que ela contenha o flush sugerido e segui sua versão revisada vinculada. Também adicionei o enter e exit, como mencionado na resposta do cladmi para uso com a instrução with. Além disso, a documentação menciona a liberação de arquivos usando, os.fsync()então eu adicionei isso também. Não sei se você realmente precisa disso, mas está lá.

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

Você pode usá-lo

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

ou

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

Muitos Thnaks @Status você resolveu minha pergunta ( stackoverflow.com/questions/39143417/… ). Vou colocar um link para sua solução.
Mohammad ElNesr 28/08

1
@MohammadElNesr Acabei de perceber um problema com o código quando ele é usado com uma declaração with. Eu o corrigi e agora ele fecha corretamente no final de um bloco com.
Status

1
Isso funcionou muito bem para mim, só precisava modo de mudança para mode="ab"e na writefunçãoself.file.write(message.encode("utf-8"))
ennetws

4

outra solução usando o módulo de log:

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

Nenhuma das respostas acima parece realmente responder ao problema proposto. Eu sei que esse é um tópico antigo, mas acho que esse problema é muito mais simples do que todo mundo está criando:

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)

Agora isso repetirá tudo para o manipulador sys.stderr normal e seu arquivo. Crie outra classe tee_outpara sys.stdout.


2
Uma resposta semelhante e melhor foi publicada mais de dois anos antes desta: stackoverflow.com/a/616686 . Seu método é muito caro: cada chamada para tee=tee_err();tee.write('');tee.write('');...abre + fecha um arquivo para cada write. Consulte stackoverflow.com/q/4867468 e stackoverflow.com/q/164053 para obter argumentos contra essa prática.
27712 Rob Rob W

3

De acordo com uma solicitação de @ user5359531 nos comentários na resposta de @John T , aqui está uma cópia da postagem referenciada na versão revisada da discussão vinculada nessa resposta:

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

Estou escrevendo um script para executar scripts de linha cmd. (Porque em alguns casos, simplesmente não há substituto viável para um comando Linux - como o caso do rsync.)

O que eu realmente queria era usar o mecanismo de log python padrão em todos os casos em que fosse possível fazê-lo, mas ainda assim capturar qualquer erro quando algo der errado que não era previsto.

Este código parece fazer o truque. Pode não ser particularmente elegante ou eficiente (embora não use string + = string, pelo menos não possui esse potencial gargalo em particular). Estou publicando, caso isso dê a alguém alguma idéia útil.

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)

Obviamente, se você não está tão sujeito a um capricho quanto eu, substitua LOG_IDENTIFIER por outra sequência que você não gostaria de ver alguém escrever em um log.


0

Se você deseja registrar toda a saída em um arquivo E enviá-la para um arquivo de texto, faça o seguinte. É um pouco hacky, mas funciona:

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!")

EDIT: Observe que isso não registra erros, a menos que você redirecione sys.stderr para sys.stdout

EDIT2: Uma segunda questão é que você precisa passar 1 argumento diferente da função interna.

EDIT3: Veja o código antes para gravar stdin e stdout no console e arquivo, com o stderr indo apenas para o arquivo

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

Eu escrevi um substituto completo para sys.stderre apenas dupliquei o código renomeando stderrpara stdouttorná-lo disponível também para substituir sys.stdout.

Para fazer isso eu criar o mesmo tipo de objeto como a corrente stderre stdout, e encaminhar todos os métodos para o sistema original stderre 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

Para usar isso, basta ligar StdErrReplament::lock(logger)e StdOutReplament::lock(logger) passar o logger que deseja usar para enviar o texto de saída. Por exemplo:

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" )

Executando este código, você verá na tela:

insira a descrição da imagem aqui

E no conteúdo do arquivo:

insira a descrição da imagem aqui

Se você também quiser ver o conteúdo das log.debugchamadas na tela, precisará adicionar um manipulador de fluxo ao seu criador de logs. Nesse caso, seria assim:

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" )

Qual seria o resultado dessa saída ao executar:

insira a descrição da imagem aqui

Enquanto isso ainda salvaria isso no arquivo my_log_file.txt:

insira a descrição da imagem aqui

Ao desabilitar isso StdErrReplament:unlock(), ele restaurará apenas o comportamento padrão do stderrfluxo, pois o criador de logs anexado nunca poderá ser desanexado porque outra pessoa pode ter uma referência à sua versão mais antiga. É por isso que é um singleton global que nunca morre. Portanto, no caso de recarregar este módulo impou algo mais, ele nunca recuperará a corrente sys.stderrcomo já foi injetada e a salvará internamente.


5
um incrível nível de complexidade acidental para duplicar um fluxo.
Attila Lendvai
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.