Wie dupliziere ich sys.stdout in eine Protokolldatei?

149

Bearbeiten: Da es den Anschein hat, dass es entweder keine Lösung gibt oder ich etwas mache, das nicht dem Standard entspricht, das niemand kennt, werde ich meine Frage überarbeiten, um auch zu fragen: Was ist der beste Weg, um die Protokollierung durchzuführen, wenn eine Python-App eine erstellt? viele Systemaufrufe?

Meine App hat zwei Modi. Im interaktiven Modus soll die gesamte Ausgabe sowohl auf dem Bildschirm als auch in einer Protokolldatei angezeigt werden, einschließlich der Ausgabe von Systemaufrufen. Im Daemon-Modus gehen alle Ausgaben in das Protokoll. Der Daemon-Modus funktioniert hervorragend mit os.dup2(). Ich kann im interaktiven Modus keine Möglichkeit finden, alle Ausgaben in ein Protokoll zu "tippen", ohne jeden einzelnen Systemaufruf zu ändern.


Mit anderen Worten, ich möchte die Funktionalität der Befehlszeile 'tee' für jede Ausgabe, die von einer Python-App generiert wird, einschließlich der Ausgabe von Systemaufrufen .

Zu klären:

Um alle Ausgaben umzuleiten, mache ich so etwas und es funktioniert großartig:

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

Das Schöne daran ist, dass für den Rest des Codes keine speziellen Druckaufrufe erforderlich sind. Der Code führt auch einige Shell-Befehle aus, so dass es schön ist, nicht jede Ausgabe einzeln behandeln zu müssen.

Ich möchte einfach das Gleiche tun, außer dass ich dupliziere anstatt umzuleiten.

Beim ersten Gedanken dachte ich, dass es dup2funktionieren sollte , einfach die 's umzukehren . Warum nicht? Hier ist mein 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)

Die Datei "a.log" sollte mit der auf dem Bildschirm angezeigten identisch sein.

drue
quelle
Wenn Sie sich die Manpage ( manpagez.com/man/2/dup2 ) ansehen, ist das 2. Argument für dup2 immer geschlossen (falls es bereits geöffnet ist). In Ihrer "kaputten Lösung" schließt es also so und so und weist dann ihre Dateien sys.stdout zu.
Jacob Gabrielson
1
Betreff: Ihre Bearbeitung: Dies ist nicht ungewöhnlich, ich habe ein paar Mal ähnlich gemacht (in anderen langs). Während Unix mehrere "Aliase" für dasselbe Dateihandle zulässt, wird ein Dateihandle nicht "aufgeteilt" (in mehrere andere kopieren). Sie müssen also "tee" selbst implementieren (oder einfach "tee" verwenden, siehe meine grobe Antwort).
Jacob Gabrielson
Ich denke, JohnT Antwort ist besser als die tatsächlich akzeptierte. Möglicherweise möchten Sie die akzeptierte Antwort ändern.
Phong
"Ich mache etwas, das nicht dem Standard entspricht" - Sie sind es wirklich, die Leute senden einfach ihre Protokolle an stderr und kümmern sich über die Befehlszeile darum.
Khachik

Antworten:

55

Da Sie gerne externe Prozesse aus Ihrem Code erzeugen, können Sie sich teeselbst verwenden. Ich kenne keine Unix-Systemaufrufe, die genau das tun, was teesie tun .

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

Sie können auch teedas Multiprocessing- Paket emulieren (oder die Verarbeitung verwenden, wenn Sie Python 2.5 oder früher verwenden).

Aktualisieren

Hier ist eine Python 3.3 + -kompatible Version:

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
quelle
28
Nun, diese Antwort funktioniert, also werde ich sie akzeptieren. Trotzdem fühle ich mich schmutzig.
Drue
2
Ich habe gerade eine reine Python-Implementierung von tee (py2 / 3-kompatibel) veröffentlicht, die auf jeder Plattform ausgeführt werden kann und auch in verschiedenen Protokollierungskonfigurationen verwendet werden kann. stackoverflow.com/questions/616645/…
Sorin
8
Wenn Python auf einem meiner Computer ausgeführt wird und die Lösung dies nicht tut, ist dies keine pythonische Lösung. Aus diesem Grund herabgestimmt.
Anatoly Techtonik
2
Laut diesem Beitragsys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) funktioniert die Zeile seit Python 3.3 nicht mehr (siehe PEP 3116)
Ken Myers
1
Ich habe den Fehler "sys: 1: ResourceWarning: nicht geschlossene Datei <_io.BufferedWriter name = 5>" erhalten, daher musste ich tee.stdin.close()am Ende meines Programms hinzufügen . Ich bekomme auch "ResourceWarning: Unterprozess 1842 läuft noch" und das Hinzufügen sys.stdout.close(); sys.stderr.close()am Ende des Programms behebt es.
Matthieu
136

Ich hatte das gleiche Problem zuvor und fand dieses Snippet sehr nützlich:

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

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

John T.
quelle
7
+1 für die interne Behandlung der Neuzuweisung von sys.stdout, damit Sie die Protokollierung beenden können, indem Sie das Tee-Objekt löschen
Ben Blank
12
Ich würde dem einen Flush hinzufügen. ZB: 'self.file.flush ()'
Luke Stanley
4
Ich bin nicht einverstanden mit dem Protokollierungsmodul. Hervorragend zum Spielen geeignet. Die Protokollierung ist dafür zu groß.
Kobor42
4
Beachten Sie unbedingt die überarbeitete Version in diesem Follow-up zu der verknüpften Diskussion in der Antwort.
Martineau
4
Das wird nicht funktionieren. __del__wird erst am Ende der Ausführung aufgerufen. Siehe stackoverflow.com/questions/6104535/…
Nux
77

Die printAnweisung ruft die write()Methode eines Objekts auf, das Sie sys.stdout zuweisen.

Ich würde eine kleine Klasse gründen, um an zwei Stellen gleichzeitig zu schreiben ...

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

Jetzt wird die printAnweisung sowohl auf dem Bildschirm angezeigt als auch an Ihre Protokolldatei angehängt:

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

Das ist offensichtlich schnell und schmutzig. Einige Notizen:

  • Sie sollten wahrscheinlich den Protokolldateinamen parametrisieren.
  • Sie sollten wahrscheinlich sys.stdout auf zurücksetzen, <stdout>wenn Sie für die Dauer des Programms nicht protokollieren.
  • Möglicherweise möchten Sie in mehrere Protokolldateien gleichzeitig schreiben oder verschiedene Protokollstufen usw. verarbeiten können.

Diese sind alle so einfach, dass ich sie gerne als Übungen für den Leser belasse. Die wichtigste Erkenntnis hier ist, dass printnur ein "dateiähnliches Objekt" aufgerufen wird, das zugewiesen ist sys.stdout.

Triptychon
quelle
Genau das, was ich posten wollte. +1, wenn Sie das Problem beheben, dass das Schreiben kein Selbstargument hat. Außerdem wäre es besser, wenn die Datei, in die Sie schreiben möchten, übergeben würde. Verdammt, es wäre auch besser, wenn stdout übergeben würde.
Devin Jeanpierre
@Devin, ja das war schnell und dreckig, ich mache mir ein paar Notizen für mögliche frühe Verbesserungen.
Triptychon
7
Ich habe diese Antwort zu früh ausgewählt. Es funktioniert hervorragend für "Drucken", aber nicht so sehr für die Ausgabe externer Befehle.
drue
2
Die Logger-Klasse sollte auch eine flush () -Methode wie "def flush (): self.terminal.flush (); self.log.flush ()" definieren
blokeley
5
Du sagst The print statement will call the write() method of any object you assign to sys.stdout. Und was ist mit anderen Funktionen Daten an stdout Senden nicht verwenden print. Wenn ich zum Beispiel einen Prozess mit subprocess.callseiner Ausgabe erstelle, geht er zur Konsole, aber nicht zur log.datDatei ... gibt es eine Möglichkeit, dies zu beheben?
jpo38
64

Was Sie wirklich wollen, ist ein loggingModul aus der Standardbibliothek. Erstellen Sie einen Logger und fügen Sie zwei Handler hinzu, von denen einer in eine Datei und der andere in stdout oder stderr schreibt.

Weitere Informationen finden Sie unter Protokollieren an mehreren Zielen

Alexander Lebedev
quelle
9
Das Protokollierungsmodul zeichnet keine Ausnahmen und andere wichtige Ausgaben in stdout auf. Dies kann beispielsweise bei der Analyse von Protokollen auf dem Build-Server hilfreich sein.
Anatoly Techtonik
2
loggingModul leitet die Ausgabe von Systemaufrufen wieos.write(1, b'stdout')
jfs
17

Hier ist eine andere Lösung, die allgemeiner ist als die anderen: Sie unterstützt die Aufteilung der Ausgabe (geschrieben in sys.stdout) auf eine beliebige Anzahl dateiähnlicher Objekte. Es gibt keine Anforderung, dass __stdout__selbst enthalten ist.

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

HINWEIS: Dies ist ein Proof-of-Concept. Die Implementierung hier ist nicht vollständig, da nur Methoden der dateiähnlichen Objekte (z. B. write) umschlossen werden, wobei Mitglieder / Eigenschaften / Setattr usw. weggelassen werden. Für die meisten Benutzer ist sie jedoch wahrscheinlich gut genug.

Was ich an es mag, mit Ausnahme seiner Allgemeinheit ist, dass es im Sinne sauber ist es macht keine direkten Anrufe write, flush, os.dup2etc.

shx2
quelle
3
Ich hätte init take * files nicht files, aber sonst ja das. Keine der anderen Lösungen isoliert die "Tee" -Funktionalität, ohne zu versuchen, andere Probleme zu lösen. Wenn Sie alles, was Sie ausgeben, mit einem Präfix versehen möchten, können Sie diese Klasse in eine Präfix-Writer-Klasse einschließen. (Wenn Sie nur einem Stream ein Präfix hinzufügen möchten, wickeln Sie einen Stream ein und übergeben ihn dieser Klasse.) Dieser hat auch den Vorteil, dass multifile ([]) eine Datei erstellt, die alles ignoriert (wie open ('/ dev) /Null')).
Ben
Warum überhaupt _wraphier haben ? Könnten Sie den Code dort nicht kopieren __getattr__und er funktioniert genauso?
Timotree
@Ben multifile([])erstellt tatsächlich eine Datei, die bei UnboundLocalErrorjedem Aufruf einer ihrer Methoden eine auslöst . ( reswird zurückgegeben, ohne zugewiesen zu werden)
Timotree
13

Wie an anderer Stelle beschrieben, besteht die vielleicht beste Lösung darin, das Protokollierungsmodul direkt zu verwenden:

import logging

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

Es gibt jedoch einige (seltene) Fälle, in denen Sie stdout wirklich umleiten möchten . Ich hatte diese Situation, als ich den Befehl runserver von django erweiterte, der print verwendet: Ich wollte die django-Quelle nicht hacken, brauchte aber die print-Anweisungen, um zu einer Datei zu gelangen.

Dies ist eine Möglichkeit, stdout und stderr mithilfe des Protokollierungsmoduls von der Shell wegzuleiten:

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'

Sie sollten diese LogFile-Implementierung nur verwenden, wenn Sie das Protokollierungsmodul wirklich nicht direkt verwenden können.

Blokeley
quelle
11

Ich habe eine geschrieben tee() Implementierung in Python geschrieben, die in den meisten Fällen funktionieren sollte, und sie funktioniert auch unter Windows.

https://github.com/pycontribs/tendo

Sie können es auch in Kombination mit einem loggingModul von Python verwenden, wenn Sie möchten.

Sorin
quelle
Hmm - dieser Link funktioniert nicht mehr - wo sonst kann er gefunden werden?
Danny Staple
1
Wow, Ihr Paket rockt, besonders wenn Sie wissen, wie umständlich die Windows-Konsolenkultur ist, aber nicht aufgegeben haben, damit es funktioniert!
n611x007
8

(Ah, lesen Sie Ihre Frage einfach noch einmal durch und stellen Sie fest, dass dies nicht ganz zutrifft.)

Hier ist ein Beispielprogramm, das das Python-Protokollierungsmodul verwendet . Dieses Protokollierungsmodul ist seit 2.3 in allen Versionen verfügbar. In diesem Beispiel kann die Protokollierung über Befehlszeilenoptionen konfiguriert werden.

Im ruhigen Modus wird nur in einer Datei protokolliert, im normalen Modus wird sowohl in einer Datei als auch in der Konsole protokolliert.

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
quelle
Gute Antwort. Ich habe einige wirklich komplizierte Möglichkeiten gesehen, die Protokollierung auf der Konsole zu replizieren, aber einen StreamHandler mit stderr zu erstellen, war die Antwort, nach der ich gesucht habe :)
fleischige Ernte
Der Code ist nett, er beantwortet die Frage nicht - dies gibt das Protokoll in eine Datei und stderr aus, die ursprüngliche Frage war, das stderr in eine Protokolldatei zu duplizieren.
Emem
8

Um die Antwort von John T zu vervollständigen: https://stackoverflow.com/a/616686/395687

Ich habe __enter__und __exit__Methoden hinzugefügt , um es als Kontextmanager mit dem withSchlüsselwort zu verwenden, das diesen Code gibt

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

Es kann dann als verwendet werden

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')
cladmi
quelle
1
Ich würde die __del__Funktionalität in__exit__
vontrapp
1
In der Tat denke ich, dass die Verwendung __del__eine schlechte Idee ist. Es sollte in eine 'Schließen'-Funktion verschoben werden, die aufgerufen wird __exit__.
Cladmi
7

Ich weiß, dass diese Frage wiederholt beantwortet wurde, aber dafür habe ich die Hauptantwort aus John Ts Antwort genommen und sie so geändert, dass sie den vorgeschlagenen Flush enthält und der verknüpften überarbeiteten Version folgt. Ich habe auch das Ein- und Aussteigen hinzugefügt, wie in Cladmis Antwort für die Verwendung mit der with-Anweisung erwähnt. Darüber hinaus wird in der Dokumentation erwähnt, dass Dateien mit gelöscht werden sollen, os.fsync()sodass ich dies ebenfalls hinzugefügt habe. Ich weiß nicht, ob du das wirklich brauchst, aber es ist da.

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

Sie können es dann verwenden

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

oder

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()
Status
quelle
Viele Thnaks @Status Sie haben meine Frage gelöst ( stackoverflow.com/questions/39143417/… ). Ich werde einen Link zu Ihrer Lösung setzen.
Mohammad ElNesr
1
@MohammadElNesr Ich habe gerade ein Problem mit dem Code festgestellt, als er mit einer with-Anweisung verwendet wurde. Ich habe es behoben und es schließt jetzt korrekt am Ende eines with-Blocks.
Status
1
Dies funktionierte großartig für mich, musste nur den Modus auf mode="ab"und in der writeFunktion self.file.write(message.encode("utf-8"))
ändern
4

eine andere Lösung mit Protokollierungsmodul:

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
quelle
3

Keine der obigen Antworten scheint das gestellte Problem wirklich zu beantworten. Ich weiß, dass dies ein alter Thread ist, aber ich denke, dieses Problem ist viel einfacher als jeder es macht:

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)

Dies wiederholt nun alles für den normalen sys.stderr-Handler und Ihre Datei. Erstellen Sie eine weitere Klasse tee_outfür sys.stdout.

Josianator
quelle
2
Eine ähnliche, bessere Antwort wurde über zwei Jahre zuvor veröffentlicht: stackoverflow.com/a/616686 . Ihre Methode ist sehr teuer: Jeder Aufruf zum tee=tee_err();tee.write('');tee.write('');...Öffnen + Schließen schließt jeweils eine Datei write. Argumente gegen diese Vorgehensweise finden Sie unter stackoverflow.com/q/4867468 und stackoverflow.com/q/164053 .
Rob W
3

Gemäß einer Anfrage von @ user5359531 in den Kommentaren unter der Antwort von @John T ist hier eine Kopie des Beitrags, auf den in der überarbeiteten Version der verknüpften Diskussion in dieser Antwort verwiesen wird:

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
quelle
1

Ich schreibe ein Skript zum Ausführen von Cmd-Line-Skripten. (In einigen Fällen gibt es einfach keinen brauchbaren Ersatz für einen Linux-Befehl - wie im Fall von rsync.)

Was ich wirklich wollte, war, den Standard-Python-Protokollierungsmechanismus in jedem Fall zu verwenden, in dem dies möglich war, aber dennoch Fehler zu erfassen, wenn ein unerwarteter Fehler auftrat.

Dieser Code scheint den Trick zu tun. Es ist möglicherweise nicht besonders elegant oder effizient (obwohl es keine Zeichenfolge + = Zeichenfolge verwendet, so dass es zumindest keinen bestimmten potenziellen Flaschenhals hat). Ich poste es für den Fall, dass es jemand anderem nützliche Ideen gibt.

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)

Wenn Sie nicht so launisch sind wie ich, ersetzen Sie LOG_IDENTIFIER natürlich durch eine andere Zeichenfolge, die Sie nie sehen möchten, wenn jemand in ein Protokoll schreibt.

Cognitiaclaeves
quelle
0

Wenn Sie alle Ausgaben in einer Datei protokollieren UND in eine Textdatei ausgeben möchten, können Sie Folgendes tun. Es ist ein bisschen hacky, aber es funktioniert:

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

BEARBEITEN: Beachten Sie, dass dies keine Fehler protokolliert, es sei denn, Sie leiten sys.stderr zu sys.stdout um

EDIT2: Ein zweites Problem ist, dass Sie im Gegensatz zur integrierten Funktion 1 Argument übergeben müssen.

EDIT3: Lesen Sie den vorherigen Code, um stdin und stdout in die Konsole und Datei zu schreiben, wobei stderr nur in die Datei geht

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
quelle
-1

Ich schrieb einen vollwertiger Ersatz für sys.stderrund dupliziert nur den Code Umbenennung stderrzu , stdoutsie ersetzen auch zur Verfügung zu stellen sys.stdout.

Dazu erstelle ich den gleichen Objekttyp wie das aktuelle stderrund stdoutund leite alle Methoden an das ursprüngliche System weiter stderrund 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

Um dies zu verwenden, können Sie einfach den Logger aufrufen StdErrReplament::lock(logger)und StdOutReplament::lock(logger)übergeben, mit dem Sie den Ausgabetext senden möchten. Beispielsweise:

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

Wenn Sie diesen Code ausführen, wird auf dem Bildschirm Folgendes angezeigt:

Geben Sie hier die Bildbeschreibung ein

Und zum Dateiinhalt:

Geben Sie hier die Bildbeschreibung ein

Wenn Sie auch den Inhalt der log.debugAufrufe auf dem Bildschirm sehen möchten , müssen Sie Ihrem Logger einen Stream-Handler hinzufügen. In diesem Fall wäre es so:

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

Welches würde beim Ausführen so ausgeben:

Geben Sie hier die Bildbeschreibung ein

Während es noch in der Datei speichern würde my_log_file.txt:

Geben Sie hier die Bildbeschreibung ein

Wenn Sie diese Option deaktivieren StdErrReplament:unlock(), wird nur das Standardverhalten des stderrStreams wiederhergestellt , da der angehängte Logger niemals getrennt werden kann, da eine andere Person einen Verweis auf die ältere Version haben kann. Deshalb ist es ein globaler Singleton, der niemals sterben kann. Daher wird beim erneuten Laden dieses Moduls mit impoder etwas anderem der Strom sys.stderr, der bereits eingespeist wurde , nie wieder erfasst und intern gespeichert.

Benutzer
quelle
5
Ein erstaunliches Maß an versehentlicher Komplexität beim Duplizieren eines Streams.
Attila Lendvai