Jak zduplikować sys.stdout do pliku dziennika?

149

Edycja: Ponieważ wydaje się, że albo nie ma rozwiązania, albo robię coś tak niestandardowego, że nikt nie wie - zrewiduję moje pytanie, aby zapytać również: Jaki jest najlepszy sposób na wykonanie logowania, gdy aplikacja Pythona tworzy dużo wywołań systemowych?

Moja aplikacja ma dwa tryby. W trybie interaktywnym chcę, aby wszystkie dane wyjściowe były wyświetlane na ekranie, a także do pliku dziennika, w tym danych wyjściowych z wszelkich wywołań systemowych. W trybie demona wszystkie dane wyjściowe trafiają do dziennika. Tryb demona działa świetnie przy użyciu os.dup2(). Nie mogę znaleźć sposobu, aby wszystkie dane wyjściowe były przesyłane do dziennika w trybie interaktywnym bez modyfikowania każdego wywołania systemowego.


Innymi słowy, chcę mieć funkcjonalność wiersza poleceń „tee” dla każdego wyjścia generowanego przez aplikację w języku Python, w tym danych wyjściowych wywołań systemowych .

W celu wyjaśnienia:

Aby przekierować wszystkie dane wyjściowe, robię coś takiego i działa świetnie:

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

Zaletą tego jest to, że nie wymaga specjalnych wywołań print z pozostałej części kodu. Kod uruchamia również niektóre polecenia powłoki, więc miło nie jest też zajmować się każdym z ich wyników indywidualnie.

Po prostu chcę zrobić to samo, z wyjątkiem duplikowania zamiast przekierowywania.

Na początku pomyślałem, że po prostu odwrócenie instrukcji dup2powinno działać. Dlaczego nie? Oto mój test:

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)

Plik „a.log” powinien być identyczny z tym, co zostało wyświetlone na ekranie.

drue
źródło
Jeśli spojrzysz na stronę podręcznika ( manpagez.com/man/2/dup2 ), drugi argument dup2 jest zawsze zamknięty (jeśli jest już otwarty). Więc w twoim "zepsutym rozwiązaniu" zamyka się so i se, a następnie przenosi ich filenos do sys.stdout.
Jacob Gabrielson
1
Re: twoja edycja: to nie jest rzadkie, robiłem podobne kilka razy (w innych językach). Podczas gdy Unix zezwala na wiele „aliasów” dla tego samego uchwytu pliku, nie „dzieli” uchwytu pliku (kopiuje go do wielu innych). Więc musisz sam zaimplementować „tee” (lub po prostu użyj „tee”, zobacz moją prostą odpowiedź).
Jacob Gabrielson
Myślę, że odpowiedź JohnT jest lepsza niż faktycznie zaakceptowana. Możesz zmienić zaakceptowaną odpowiedź.
Phong
„Robię coś tak niestandardowego” - naprawdę tak jest, ludzie po prostu wysyłają swoje logi na stderr i zajmują się nimi z wiersza poleceń.
khachik

Odpowiedzi:

55

Ponieważ czujesz się komfortowo przy tworzeniu zewnętrznych procesów z kodu, możesz użyć teesamego siebie. Nie znam żadnych wywołań systemowych Uniksa, które robią dokładnie to, co teerobi.

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

Możesz również emulować teeużywając pakietu wieloprocesorowego (lub użyć przetwarzania, jeśli używasz Pythona 2.5 lub starszego).

Aktualizacja

Oto wersja zgodna z 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)
Jacob Gabrielson
źródło
28
Cóż, ta odpowiedź działa, więc ją zaakceptuję. Mimo to czuję się brudny.
drue
2
Właśnie opublikowałem implementację tee w czystym Pythonie (kompatybilną z py2 / 3), która może działać na dowolnej platformie, a także być używana w różnych konfiguracjach logowania. stackoverflow.com/questions/616645/ ...
sorin
8
Jeśli Python działa na jednej z moich maszyn, a rozwiązanie nie działa, to nie jest to rozwiązanie w języku Python. Z tego powodu przegłosowano.
anatoly techtonik
2
Zgodnie z tym postem linia sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)nie działa już od Pythona 3.3 (patrz PEP 3116)
Ken Myers
1
Pojawił się błąd „sys: 1: ResourceWarning: unclosed file <_io.BufferedWriter name = 5>”, więc musiałem dodać tee.stdin.close()na końcu mojego programu. Dostaję też „ResourceWarning: podproces 1842 nadal działa” i dodanie sys.stdout.close(); sys.stderr.close()na końcu programu rozwiązuje problem.
matthieu
136

Miałem ten sam problem wcześniej i uznałem ten fragment za bardzo przydatny:

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

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

John T.
źródło
7
+1 za wewnętrzną obsługę ponownego przypisania sys.stdout, aby można było zakończyć rejestrowanie, usuwając obiekt Tee
Ben Blank
12
Dodałbym do tego kolor. Np .: „self.file.flush ()”
Luke Stanley,
4
Nie zgadzam się co do modułu logowania. Doskonały do ​​zabawy. Rejestrowanie jest na to za duże.
Kobor42,
4
Pamiętaj, aby zanotować poprawioną wersję w tym uzupełnieniu do połączonej dyskusji w odpowiedzi.
martineau
4
To nie zadziała. __del__nie jest wywoływana do końca wykonywania. Zobacz stackoverflow.com/questions/6104535/…
Nux,
77

printOświadczenie będzie wywołać write()metodę dowolnego obiektu przypisanej sys.stdout.

Rozkręciłbym małą klasę, żeby pisać do dwóch miejsc naraz ...

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

Teraz printinstrukcja będzie zarówno echo na ekranie, jak i dołączenie do pliku dziennika:

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

Jest to oczywiście szybkie i brudne. Kilka uwag:

  • Prawdopodobnie powinieneś sparametryzować nazwę pliku dziennika.
  • Prawdopodobnie powinieneś przywrócić sys.stdout do, <stdout>jeśli nie będziesz logować się przez czas trwania programu.
  • Możesz chcieć mieć możliwość zapisywania w wielu plikach dziennika jednocześnie lub obsługi różnych poziomów dziennika itp.

Wszystko to jest na tyle proste, że wygodnie zostawiam je jako ćwiczenia dla czytelnika. Kluczową spostrzeżeniem jest to, że printpo prostu wywołuje przypisany do niego „obiekt podobny do pliku” sys.stdout.

Tryptyk
źródło
Dokładnie to, co zamierzałem opublikować, prawie. +1, gdy rozwiążesz problem z zapisem bez argumentu własnego. Poza tym, byłoby lepiej zaprojektować, aby plik, do którego napiszesz, został przekazany. Do diabła, może być też lepszy projekt, aby przekazać stdout.
Devin Jeanpierre
@Devin, tak, to było szybkie i brudne, zrobię kilka notatek dla możliwych wczesnych ulepszeń.
Tryptyk
7
Wybrałem tę odpowiedź zbyt wcześnie. Świetnie sprawdza się w przypadku "drukowania", ale nie tak bardzo w przypadku wyjścia poleceń zewnętrznych.
drue
2
Klasa Logger powinna również definiować metodę flush (), taką jak „def flush (): self.terminal.flush (); self.log.flush ()”
blokeley
5
Mówisz The print statement will call the write() method of any object you assign to sys.stdout. A co z innymi nieużywanymi funkcjami wysyłającymi dane na standardowe wyjście print. Na przykład, jeśli utworzę proces, używając subprocess.calljego danych wyjściowych, trafi do konsoli, ale nie do log.datpliku ... czy istnieje sposób, aby to naprawić?
jpo38
64

To, czego naprawdę potrzebujesz, to loggingmoduł z biblioteki standardowej. Utwórz rejestrator i dołącz dwa programy obsługi, jeden będzie zapisywał do pliku, a drugi do stdout lub stderr.

Aby uzyskać szczegółowe informacje, zobacz Rejestrowanie w wielu miejscach docelowych

Alexander Lebedev
źródło
9
Moduł rejestrowania nie rejestruje wyjątków i innych ważnych danych wyjściowych na standardowe wyjście, co może być przydatne podczas analizowania dzienników na serwerze kompilacji (na przykład).
anatoly techtonik
2
loggingmoduł nie przekierowuje danych wyjściowych z wywołań systemowych, takich jakos.write(1, b'stdout')
jfs
17

Oto inne rozwiązanie, które jest bardziej ogólne niż inne - obsługuje podział danych wyjściowych (zapisywanych do sys.stdout) na dowolną liczbę obiektów podobnych do plików. Nie ma wymogu, aby __stdout__sam był uwzględniony.

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

UWAGA: To jest dowód słuszności koncepcji. Implementacja tutaj nie jest kompletna, ponieważ zawija tylko metody obiektów podobnych do plików (np. write), Pomijając elementy członkowskie / properties / setattr, itp. Jednak prawdopodobnie jest wystarczająco dobra dla większości ludzi w obecnej postaci.

Co lubię o tym, inne niż jego ogólności, jest to, że jest czysty w tym sensie, że nie ma żadnych połączeń bezpośrednich do write, flush, os.dup2, itd

shx2
źródło
3
Musiałbym startowych wziąć pliki * nie pliki, ale w inny sposób, tak, to. Żadne z innych rozwiązań nie izoluje funkcji „tee” bez próby rozwiązania innych problemów. Jeśli chcesz umieścić przedrostek na wszystkim, co wyprowadzasz, możesz opakować tę klasę w klasę zapisującą prefiksy. (Jeśli chcesz umieścić prefiks tylko w jednym strumieniu, opakowujesz strumień i przekazujesz go tej klasie). Ten jeden ma również tę zaletę, że multifile ([]) tworzy plik, który ignoruje wszystko (np. Open ('/ dev /zero')).
Ben
Po co _wraptu w ogóle? Nie mógłbyś skopiować tam kodu __getattr__i działa tak samo?
Timotree
@Ben faktycznie multifile([])tworzy plik, który wywołuje UnboundLocalErrorwywołanie jednej z jego metod. ( resjest zwracany bez przypisania)
timotree
13

Jak opisano w innym miejscu, być może najlepszym rozwiązaniem jest bezpośrednie użycie modułu logowania:

import logging

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

Jednak są pewne (rzadkie) sytuacje, w których naprawdę chcesz przekierować standardowe wyjście. Miałem taką sytuację, kiedy rozszerzałem polecenie runerver django, które używa print: nie chciałem włamać się do źródła django, ale potrzebowałem instrukcji print, aby przejść do pliku.

Jest to sposób na przekierowanie stdout i stderr z dala od powłoki za pomocą modułu logującego:

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'

Powinieneś używać tej implementacji LogFile tylko wtedy, gdy naprawdę nie możesz bezpośrednio użyć modułu logowania.

blokeley
źródło
11

Napisałem tee()implementację w Pythonie, która powinna działać w większości przypadków i działa również w systemie Windows.

https://github.com/pycontribs/tendo

Możesz również użyć go w połączeniu z loggingmodułem z Pythona, jeśli chcesz.

sorin
źródło
Hmm - ten link już nie działa - gdzie indziej można go znaleźć?
Danny Staple
1
wow, twój pakiet rządzi, zwłaszcza jeśli wiesz, jak uciążliwa jest kultura konsoli Windows, ale nie poddałeś się, aby to zadziałało!
n611x007
8

(Ach, po prostu przeczytaj ponownie swoje pytanie i zobacz, że to nie do końca dotyczy.)

Oto przykładowy program, który wykorzystuje moduł logowania w języku Python . Ten moduł logowania był we wszystkich wersjach od 2.3. W tym przykładzie rejestrowanie można konfigurować za pomocą opcji wiersza poleceń.

W trybie cichym będzie logował się tylko do pliku, w trybie normalnym będzie logował się zarówno do pliku, jak i do konsoli.

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())
Atlas1j
źródło
Dobra odpowiedź. Widziałem naprawdę zawiłe sposoby replikowania logowania na konsolę, ale zrobienie StreamHandlera z stderr było odpowiedzią, której szukałem :)
meatvest
Kod jest fajny, mówiąc, że nie odpowiada na pytanie - zapisuje dziennik do pliku i stderr, pierwotne pytanie dotyczyło zduplikowania stderr do pliku dziennika.
emem
8

Aby uzupełnić odpowiedź Johna T: https://stackoverflow.com/a/616686/395687

Dodałem __enter__i __exit__metody, aby użyć go jako menedżera kontekstu ze withsłowem kluczowym, które daje ten kod

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

Może być następnie używany jako plik

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')
cladmi
źródło
1
Chciałbym przenieść __del__funkcjonalność do__exit__
vontrapp
1
Rzeczywiście, uważam, że używanie __del__to zły pomysł. Powinien zostać przeniesiony do wywoływanej funkcji „zamknij” __exit__.
cladmi
7

Wiem, że ta kwestia była wielokrotnie odpowiedział, ale za to wziąłem główną odpowiedź od Johna T za odpowiedź i modyfikować je tak, że zawiera sugerowany kolor i następnie jego połączoną wersję poprawioną. Dodałem również wejście i wyjście, jak wspomniano w odpowiedzi cladmi do użycia z instrukcją with. Ponadto dokumentacja wspomina o opróżnianiu plików za pomocą, os.fsync()więc również to dodałem. Nie wiem, czy naprawdę tego potrzebujesz, ale jest tam.

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

Możesz go następnie użyć

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

lub

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()
Status
źródło
Wiele Thnaks @Status rozwiązałeś moje pytanie ( stackoverflow.com/questions/39143417/… ). Umieszczę link do twojego rozwiązania.
Mohammad ElNesr
1
@MohammadElNesr Właśnie zdałem sobie sprawę z problemu z kodem, gdy jest używany z instrukcją with. Naprawiłem to i teraz poprawnie zamyka się na końcu bloku with.
Stan
1
To działało świetnie dla mnie, wystarczyło zmienić tryb na mode="ab"iw writefunkcjiself.file.write(message.encode("utf-8"))
ennetws
4

inne rozwiązanie wykorzystujące moduł logowania:

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'
Denis Barmenkov
źródło
3

Żadna z powyższych odpowiedzi nie wydaje się naprawdę odpowiadać na postawiony problem. Wiem, że to stary wątek, ale myślę, że ten problem jest o wiele prostszy niż wszyscy:

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)

Teraz to powtórzy wszystko do normalnego programu obsługi sys.stderr i twojego pliku. Utwórz kolejną klasę tee_outdla sys.stdout.

josianator
źródło
2
Podobna, lepsza odpowiedź została opublikowana ponad dwa lata przed tą: stackoverflow.com/a/616686 . Twoja metoda jest bardzo droga: każde wywołanie tee=tee_err();tee.write('');tee.write('');...opens + zamyka plik dla każdego write. Zobacz stackoverflow.com/q/4867468 i stackoverflow.com/q/164053, aby zapoznać się z argumentami przeciwko tej praktyce.
Rob W,
3

Jak na żądanie przez @ user5359531 w komentarzach pod @John T za odpowiedź , oto kopia odwołania pocztą na zmienionej wersji połączonej dyskusji w tej odpowiedzi:

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
martineau
źródło
1

Piszę skrypt do uruchamiania skryptów linii cmd. (Ponieważ w niektórych przypadkach po prostu nie ma realnego substytutu dla polecenia Linuksa - na przykład w przypadku rsync).

To, czego naprawdę chciałem, to użyć domyślnego mechanizmu logowania w Pythonie w każdym przypadku, gdy było to możliwe, ale nadal wychwytywać każdy błąd, gdy coś poszło nie tak, co było nieoczekiwane.

Ten kod wydaje się działać. Może nie być szczególnie eleganckie lub wydajne (chociaż nie używa string + = string, więc przynajmniej nie ma tego konkretnego potencjalnego wąskiego gardła). Publikuję to na wypadek, gdyby ktoś inny podał jakieś przydatne pomysły.

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)

Oczywiście, jeśli nie jesteś tak podatny na kaprysy jak ja, zamień LOG_IDENTIFIER na inny ciąg, którego nie lubisz widzieć, jak ktoś pisze do dziennika.

cognitiaclaeves
źródło
0

Jeśli chcesz zarejestrować wszystkie dane wyjściowe do pliku ORAZ zapisać je do pliku tekstowego, możesz wykonać następujące czynności. To trochę hacky, ale działa:

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

EDYCJA: Zauważ, że nie rejestruje to błędów, chyba że przekierujesz sys.stderr do sys.stdout

EDIT2: Drugą kwestią jest to, że musisz przekazać 1 argument w przeciwieństwie do funkcji wbudowanej.

EDIT3: Zobacz kod przed zapisaniem stdin i stdout w konsoli i pliku z stderr tylko przechodzącym do pliku

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
Jensen Taylor
źródło
-1

Napisałem pełny zamiennik dla sys.stderri po prostu zduplikowałem zmianę nazwy kodu stderrna, stdoutaby udostępnić go również do zastąpienia sys.stdout.

W tym celu należy utworzyć ten sam typ obiektu jako obecny stderri stdout, i przekazuje wszystkie metody do pierwotnego systemu stderri 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

Aby z tego skorzystać, możesz po prostu zadzwonić StdErrReplament::lock(logger)i StdOutReplament::lock(logger) przekazać rejestrator, którego chcesz użyć do wysłania tekstu wyjściowego. Na przykład:

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

Uruchamiając ten kod, zobaczysz na ekranie:

wprowadź opis obrazu tutaj

A na zawartości pliku:

wprowadź opis obrazu tutaj

Jeśli chcesz zobaczyć również zawartość log.debug wywołań na ekranie, musisz dodać obsługę strumienia do swojego rejestratora. W tym przypadku wyglądałoby to tak:

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

Który wyświetli się w ten sposób podczas uruchamiania:

wprowadź opis obrazu tutaj

Chociaż nadal zapisywałoby to do pliku my_log_file.txt :

wprowadź opis obrazu tutaj

Wyłączenie tego za pomocą StdErrReplament:unlock()przywróci tylko standardowe zachowanie stderrstrumienia, ponieważ dołączonego programu rejestrującego nie można nigdy odłączyć, ponieważ ktoś inny może mieć odniesienie do jego starszej wersji. Dlatego jest to globalny singleton, który nigdy nie umiera. Dlatego w przypadku ponownego załadowania tego modułu za pomocą implub czegoś innego, nigdy nie odzyska on prądu, sys.stderrponieważ został już do niego wstrzyknięty i zostanie zapisany wewnętrznie.

użytkownik
źródło
5
niesamowity poziom przypadkowej złożoności przy kopiowaniu strumienia.
Attila Lendvai