Edit: Puisqu'il semble qu'il n'y a pas de solution, ou que je fais quelque chose de si non standard que personne ne le sait - je réviserai ma question pour demander également: Quelle est la meilleure façon d'accomplir la journalisation lorsqu'une application python crée un beaucoup d'appels système?
Mon application dispose de deux modes. En mode interactif, je veux que toute la sortie aille à l'écran ainsi qu'à un fichier journal, y compris la sortie de tout appel système. En mode démon, toute la sortie va dans le journal. Le mode démon fonctionne très bien avec os.dup2()
. Je ne peux pas trouver un moyen de "té" toute la sortie dans un journal en mode interactif, sans modifier chaque appel système.
En d'autres termes, je veux la fonctionnalité de la ligne de commande «tee» pour toute sortie générée par une application python, y compris la sortie d'appel système .
Clarifier:
Pour rediriger toute la sortie, je fais quelque chose comme ça, et cela fonctionne très bien:
# 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())
La bonne chose à ce sujet est qu'il ne nécessite aucun appel d'impression spécial du reste du code. Le code exécute également certaines commandes shell, donc c'est bien de ne pas avoir à traiter chacune de leurs sorties individuellement.
Simplement, je veux faire la même chose, sauf dupliquer au lieu de rediriger.
Au premier abord, j'ai pensé que le simple fait d'inverser les dup2
s devrait fonctionner. Pourquoi pas? Voici mon 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)
Le fichier "a.log" doit être identique à ce qui était affiché à l'écran.
Réponses:
Puisque vous êtes à l'aise pour générer des processus externes à partir de votre code, vous pouvez vous en servir
tee
. Je ne connais aucun appel système Unix qui fasse exactement ce quetee
fait.Vous pouvez également émuler
tee
à l'aide du package multitraitement (ou utiliser le traitement si vous utilisez Python 2.5 ou une version antérieure).Mettre à jour
Voici une version compatible Python 3.3 +:
la source
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
ne fonctionne plus depuis python 3.3 (voir PEP 3116)tee.stdin.close()
à la fin de mon programme. J'obtiens également "ResourceWarning: le sous-processus 1842 est toujours en cours d'exécution", et l'ajoutsys.stdout.close(); sys.stderr.close()
à la fin du programme le corrige.J'ai déjà eu ce même problème et j'ai trouvé cet extrait très utile:
depuis: http://mail.python.org/pipermail/python-list/2007-May/438106.html
la source
__del__
n'est pas appelée avant la fin de l'exécution. Voir stackoverflow.com/questions/6104535/…L'
print
instruction appellera lawrite()
méthode de tout objet que vous attribuez à sys.stdout.Je faisais tourner une petite classe pour écrire à deux endroits à la fois ...
Maintenant, la
print
déclaration fera écho à l'écran et s'ajoutera à votre fichier journal:C'est évidemment rapide et sale. Quelques notes:
<stdout>
si vous ne vous connectez pas pendant la durée du programme.Ce sont tous assez simples pour que je sois à l'aise de les laisser comme exercices pour le lecteur. L'insight clé ici est qu'il
print
appelle simplement un "objet de type fichier" qui est assignésys.stdout
.la source
The print statement will call the write() method of any object you assign to sys.stdout
. Et qu'en est-il des autres fonctions envoyant des données à stdout non utiliséesprint
. Par exemple, si je crée un processus en utilisantsubprocess.call
sa sortie va à la console mais pas aulog.dat
fichier ... y a-t-il un moyen de résoudre ce problème?Ce que vous voulez vraiment, c'est un
logging
module de la bibliothèque standard. Créez un enregistreur et attachez deux gestionnaires, l'un écrivant dans un fichier et l'autre dans stdout ou stderr.Voir Connexion à plusieurs destinations pour plus de détails
la source
logging
le module ne redirigera pas la sortie des appels système tels queos.write(1, b'stdout')
Voici une autre solution, plus générale que les autres: elle prend en charge la division de la sortie (écrite dans
sys.stdout
) vers n'importe quel nombre d'objets de type fichier. Il n'y a aucune exigence qui__stdout__
soit incluse.REMARQUE: il s'agit d'une preuve de concept. L'implémentation ici n'est pas complète, car elle encapsule uniquement les méthodes des objets de type fichier (par exemple
write
), en laissant de côté membres / properties / setattr, etc. Cependant, elle est probablement suffisante pour la plupart des gens dans sa forme actuelle.Ce qui me plaît à ce sujet, autre que sa généralité, est qu'il est propre dans le sens , il ne fait aucun appel directement à
write
,flush
,os.dup2
, etc.la source
_wrap
ici du tout? Ne pourriez-vous pas copier le code là__getattr__
-dedans et cela fonctionne de la même manière?multifile([])
crée en fait un fichier qui déclenche unUnboundLocalError
chaque fois que vous appelez l'une de ses méthodes. (res
est retourné sans être assigné)Comme décrit ailleurs, la meilleure solution est peut-être d'utiliser le module de journalisation directement:
Cependant, il y a quelques (rares) occasions où vous voulez vraiment rediriger stdout. J'ai eu cette situation lorsque j'étendais la commande runserver de django qui utilise print: je ne voulais pas pirater la source de django mais j'avais besoin des instructions print pour aller dans un fichier.
C'est une façon de rediriger stdout et stderr loin du shell en utilisant le module de journalisation:
Vous ne devez utiliser cette implémentation LogFile que si vous ne pouvez vraiment pas utiliser directement le module de journalisation.
la source
J'ai écrit une
tee()
implémentation en Python qui devrait fonctionner dans la plupart des cas, et cela fonctionne également sous Windows.https://github.com/pycontribs/tendo
En outre, vous pouvez l'utiliser en combinaison avec le
logging
module de Python si vous le souhaitez.la source
(Ah, relisez simplement votre question et voyez que cela ne s'applique pas tout à fait.)
Voici un exemple de programme qui utilise le module de journalisation python . Ce module de journalisation existe dans toutes les versions depuis la version 2.3. Dans cet exemple, la journalisation est configurable par les options de ligne de commande.
En mode tout à fait, il ne se connectera qu'à un fichier, en mode normal, il se connectera à la fois à un fichier et à la console.
la source
Pour compléter la réponse de John T: https://stackoverflow.com/a/616686/395687
J'ai ajouté
__enter__
et des__exit__
méthodes pour l'utiliser comme gestionnaire de contexte avec lewith
mot - clé, ce qui donne ce codeIl peut ensuite être utilisé comme
la source
__del__
fonctionnalité dans__exit__
__del__
est une mauvaise idée. Il doit être déplacé vers une fonction «fermer» qui est appelée__exit__
.Je sais que cette question a reçu une réponse à plusieurs reprises, mais pour cela, j'ai pris la réponse principale de la réponse de John T et l' ai modifiée pour qu'elle contienne le flush suggéré et ai suivi sa version révisée liée. J'ai également ajouté l'entrée et la sortie comme mentionné dans la réponse de cladmi à utiliser avec l'instruction with. De plus, la documentation mentionne le vidage des fichiers à l'aide
os.fsync()
, je l'ai donc ajouté. Je ne sais pas si vous en avez vraiment besoin mais c'est là.Vous pouvez ensuite l'utiliser
ou
la source
mode="ab"
et dans lawrite
fonctionself.file.write(message.encode("utf-8"))
une autre solution utilisant le module de journalisation:
la source
Aucune des réponses ci-dessus ne semble vraiment répondre au problème posé. Je sais que c'est un vieux fil, mais je pense que ce problème est beaucoup plus simple que tout le monde ne le fait:
Maintenant, cela va tout répéter au gestionnaire sys.stderr normal et à votre fichier. Créez une autre classe
tee_out
poursys.stdout
.la source
tee=tee_err();tee.write('');tee.write('');...
ouvre + ferme un fichier pour chacunwrite
. Voir stackoverflow.com/q/4867468 et stackoverflow.com/q/164053 pour des arguments contre cette pratique.Conformément à une demande de @ user5359531 dans les commentaires sous la réponse de @John T , voici une copie du message référencé à la version révisée de la discussion liée dans cette réponse:
la source
J'écris un script pour exécuter des scripts en ligne cmd. (Parce que dans certains cas, il n'y a tout simplement pas de substitut viable pour une commande Linux - comme le cas de rsync.)
Ce que je voulais vraiment, c'était utiliser le mécanisme de journalisation python par défaut dans tous les cas où il était possible de le faire, mais pour toujours capturer toute erreur en cas de problème imprévu.
Ce code semble faire l'affaire. Il peut ne pas être particulièrement élégant ou efficace (bien qu'il n'utilise pas string + = string, donc au moins il n'a pas ce goulot d'étranglement potentiel particulier). Je le poste au cas où cela donnerait à quelqu'un d'autre des idées utiles.
De toute évidence, si vous n'êtes pas aussi sujet à la fantaisie que moi, remplacez LOG_IDENTIFIER par une autre chaîne que vous n'aimez pas voir quelqu'un écrire dans un journal.
la source
Si vous souhaitez enregistrer toutes les sorties dans un fichier ET les exporter dans un fichier texte, vous pouvez effectuer les opérations suivantes. C'est un peu hacky mais ça marche:
EDIT: Notez que cela n'enregistre pas les erreurs sauf si vous redirigez sys.stderr vers sys.stdout
EDIT2: Un deuxième problème est que vous devez passer 1 argument contrairement à la fonction intégrée.
EDIT3: Voir le code avant d'écrire stdin et stdout dans la console et le fichier avec stderr allant uniquement dans le fichier
la source
J'ai écrit un remplacement complet
sys.stderr
et je viens de dupliquer le code en renommantstderr
pourstdout
le rendre également disponible pour le remplacersys.stdout
.Pour ce faire, je crée le même type d'objet que le courant
stderr
etstdout
, et je transmets toutes les méthodes au système d'originestderr
etstdout
:Pour l'utiliser, vous pouvez simplement appeler
StdErrReplament::lock(logger)
etStdOutReplament::lock(logger)
transmettre l'enregistreur que vous souhaitez utiliser pour envoyer le texte de sortie. Par exemple:En exécutant ce code, vous verrez à l'écran:
Et sur le contenu du fichier:
Si vous souhaitez également voir le contenu des
log.debug
appels à l'écran, vous devrez ajouter un gestionnaire de flux à votre enregistreur. Dans ce cas, ce serait comme ceci:Ce qui produirait comme ceci lors de l'exécution:
Bien qu'il l'enregistre toujours dans le fichier
my_log_file.txt
:Lorsque vous désactivez cela avec
StdErrReplament:unlock()
, il ne restaure que le comportement standard dustderr
flux, car le journal attaché ne peut jamais être détaché car quelqu'un d'autre peut avoir une référence à son ancienne version. C'est pourquoi c'est un singleton global qui ne peut jamais mourir. Par conséquent, en cas de rechargement de ce module avecimp
ou autre chose, il ne récupérera jamais le courantsys.stderr
car il a déjà été injecté dessus et le sauvegardera en interne.la source