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 dup2
funktionieren 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.
Antworten:
Da Sie gerne externe Prozesse aus Ihrem Code erzeugen, können Sie sich
tee
selbst verwenden. Ich kenne keine Unix-Systemaufrufe, die genau das tun, wastee
sie tun .Sie können auch
tee
das 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:
quelle
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
funktioniert die Zeile seit Python 3.3 nicht mehr (siehe PEP 3116)tee.stdin.close()
am Ende meines Programms hinzufügen . Ich bekomme auch "ResourceWarning: Unterprozess 1842 läuft noch" und das Hinzufügensys.stdout.close(); sys.stderr.close()
am Ende des Programms behebt es.Ich hatte das gleiche Problem zuvor und fand dieses Snippet sehr nützlich:
von: http://mail.python.org/pipermail/python-list/2007-May/438106.html
quelle
__del__
wird erst am Ende der Ausführung aufgerufen. Siehe stackoverflow.com/questions/6104535/…Die
print
Anweisung ruft diewrite()
Methode eines Objekts auf, das Sie sys.stdout zuweisen.Ich würde eine kleine Klasse gründen, um an zwei Stellen gleichzeitig zu schreiben ...
Jetzt wird die
print
Anweisung sowohl auf dem Bildschirm angezeigt als auch an Ihre Protokolldatei angehängt:Das ist offensichtlich schnell und schmutzig. Einige Notizen:
<stdout>
wenn Sie für die Dauer des Programms nicht protokollieren.Diese sind alle so einfach, dass ich sie gerne als Übungen für den Leser belasse. Die wichtigste Erkenntnis hier ist, dass
print
nur ein "dateiähnliches Objekt" aufgerufen wird, das zugewiesen istsys.stdout
.quelle
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 verwendenprint
. Wenn ich zum Beispiel einen Prozess mitsubprocess.call
seiner Ausgabe erstelle, geht er zur Konsole, aber nicht zurlog.dat
Datei ... gibt es eine Möglichkeit, dies zu beheben?Was Sie wirklich wollen, ist ein
logging
Modul 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
quelle
logging
Modul leitet die Ausgabe von Systemaufrufen wieos.write(1, b'stdout')
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.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.dup2
etc.quelle
_wrap
hier haben ? Könnten Sie den Code dort nicht kopieren__getattr__
und er funktioniert genauso?multifile([])
erstellt tatsächlich eine Datei, die beiUnboundLocalError
jedem Aufruf einer ihrer Methoden eine auslöst . (res
wird zurückgegeben, ohne zugewiesen zu werden)Wie an anderer Stelle beschrieben, besteht die vielleicht beste Lösung darin, das Protokollierungsmodul direkt zu verwenden:
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:
Sie sollten diese LogFile-Implementierung nur verwenden, wenn Sie das Protokollierungsmodul wirklich nicht direkt verwenden können.
quelle
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
logging
Modul von Python verwenden, wenn Sie möchten.quelle
(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.
quelle
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 demwith
Schlüsselwort zu verwenden, das diesen Code gibtEs kann dann als verwendet werden
quelle
__del__
Funktionalität in__exit__
__del__
eine schlechte Idee ist. Es sollte in eine 'Schließen'-Funktion verschoben werden, die aufgerufen wird__exit__
.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.Sie können es dann verwenden
oder
quelle
mode="ab"
und in derwrite
Funktionself.file.write(message.encode("utf-8"))
eine andere Lösung mit Protokollierungsmodul:
quelle
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:
Dies wiederholt nun alles für den normalen sys.stderr-Handler und Ihre Datei. Erstellen Sie eine weitere Klasse
tee_out
fürsys.stdout
.quelle
tee=tee_err();tee.write('');tee.write('');...
Öffnen + Schließen schließt jeweils eine Dateiwrite
. Argumente gegen diese Vorgehensweise finden Sie unter stackoverflow.com/q/4867468 und stackoverflow.com/q/164053 .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:
quelle
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.
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.
quelle
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:
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
quelle
Ich schrieb einen vollwertiger Ersatz für
sys.stderr
und dupliziert nur den Code Umbenennungstderr
zu ,stdout
sie ersetzen auch zur Verfügung zu stellensys.stdout
.Dazu erstelle ich den gleichen Objekttyp wie das aktuelle
stderr
undstdout
und leite alle Methoden an das ursprüngliche System weiterstderr
undstdout
:Um dies zu verwenden, können Sie einfach den Logger aufrufen
StdErrReplament::lock(logger)
undStdOutReplament::lock(logger)
übergeben, mit dem Sie den Ausgabetext senden möchten. Beispielsweise:Wenn Sie diesen Code ausführen, wird auf dem Bildschirm Folgendes angezeigt:
Und zum Dateiinhalt:
Wenn Sie auch den Inhalt der
log.debug
Aufrufe auf dem Bildschirm sehen möchten , müssen Sie Ihrem Logger einen Stream-Handler hinzufügen. In diesem Fall wäre es so:Welches würde beim Ausführen so ausgeben:
Während es noch in der Datei speichern würde
my_log_file.txt
:Wenn Sie diese Option deaktivieren
StdErrReplament:unlock()
, wird nur das Standardverhalten desstderr
Streams 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 mitimp
oder etwas anderem der Stromsys.stderr
, der bereits eingespeist wurde , nie wieder erfasst und intern gespeichert.quelle