Comment dupliquer sys.stdout dans un fichier journal?

149

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 dup2s 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.

drue
la source
Si vous regardez la page de manuel ( manpagez.com/man/2/dup2 ), le 2ème argument de dup2 est toujours fermé (s'il est déjà ouvert). Donc, dans votre "solution cassée", il se ferme tel et tel, puis réaffecte leurs fichiers à sys.stdout.
Jacob Gabrielson
1
Re: votre modification: ce n'est pas rare, j'ai fait la même chose plusieurs fois (dans d'autres langues). Alors qu'Unix autorisera plusieurs "alias" pour le même descripteur de fichier, il ne "divisera" pas un descripteur de fichier (copiez-le dans plusieurs autres). Vous devez donc implémenter "tee" vous-même (ou simplement utiliser "tee", voir ma réponse grossière).
Jacob Gabrielson
Je pense que la réponse de JohnT est meilleure que celle acceptée. Vous souhaiterez peut-être modifier la réponse acceptée.
Phong
"Je fais quelque chose de si non standard" - vous l'êtes vraiment, les gens envoient simplement leurs journaux à stderr et s'en occupent depuis la ligne de commande.
khachik

Réponses:

55

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 que teefait.

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

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 +:

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
la source
28
Eh bien, cette réponse fonctionne, donc je l'accepterai. Pourtant, cela me fait me sentir sale.
drue
2
Je viens de publier une implémentation pure python de tee (compatible py2 / 3) qui peut fonctionner sur n'importe quelle plate-forme et également être utilisée dans différentes configurations de journalisation. stackoverflow.com/questions/616645/…
sorin
8
Si Python fonctionne sur l'une de mes machines et que la solution ne fonctionne pas, alors ce n'est pas une solution pythonique. Évalué à cause de cela.
anatoly techtonik
2
Selon ce post, la ligne sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)ne fonctionne plus depuis python 3.3 (voir PEP 3116)
Ken Myers
1
Vous avez l'erreur "sys: 1: ResourceWarning: fichier non fermé <_io.BufferedWriter name = 5>", donc j'ai dû ajouter 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'ajout sys.stdout.close(); sys.stderr.close()à la fin du programme le corrige.
matthieu
136

J'ai déjà eu ce même problème et j'ai trouvé cet extrait très utile:

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

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

John T
la source
7
+1 pour gérer la réaffectation sys.stdout en interne afin que vous puissiez terminer la journalisation en supprimant l'objet Tee
Ben Blank
12
J'ajouterais une couleur à cela. Par exemple: 'self.file.flush ()'
Luke Stanley
4
Je ne suis pas d'accord sur le module de journalisation. Excellent pour certains violons. La journalisation est trop importante pour cela.
Kobor42
4
Assurez-vous de noter la version révisée dans ce suivi de la discussion liée dans la réponse.
martineau
4
Ça ne marchera pas. __del__n'est pas appelée avant la fin de l'exécution. Voir stackoverflow.com/questions/6104535/…
Nux
77

L' printinstruction appellera la write()méthode de tout objet que vous attribuez à sys.stdout.

Je faisais tourner une petite classe pour écrire à deux endroits à la fois ...

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

Maintenant, la printdéclaration fera écho à l'écran et s'ajoutera à votre fichier journal:

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

C'est évidemment rapide et sale. Quelques notes:

  • Vous devriez probablement paramétrer le nom du fichier journal.
  • Vous devriez probablement revenir à sys.stdout <stdout>si vous ne vous connectez pas pendant la durée du programme.
  • Vous pouvez souhaiter la possibilité d'écrire dans plusieurs fichiers journaux à la fois, ou de gérer différents niveaux de journalisation, etc.

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 printappelle simplement un "objet de type fichier" qui est assigné sys.stdout.

Triptyque
la source
Exactement ce que j'allais publier, à peu près. +1 lorsque vous résolvez le problème d'écriture n'ayant pas d'argument personnel. En outre, il serait préférable de faire passer le fichier dans lequel vous allez écrire. Enfer, il serait peut-être préférable de faire passer stdout.
Devin Jeanpierre
@Devin, ouais c'était rapide et sale, je vais prendre quelques notes pour d'éventuelles améliorations préliminaires.
Triptyque du
7
J'ai sélectionné cette réponse trop tôt. Cela fonctionne très bien pour "imprimer", mais pas tant pour la sortie de commande externe.
drue
2
La classe Logger devrait également définir une méthode flush () telle que "def flush (): self.terminal.flush (); self.log.flush ()"
blokeley
5
Vous dites 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ées print. Par exemple, si je crée un processus en utilisant subprocess.callsa sortie va à la console mais pas au log.datfichier ... y a-t-il un moyen de résoudre ce problème?
jpo38
64

Ce que vous voulez vraiment, c'est un loggingmodule 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

Alexandre Lebedev
la source
9
Le module de journalisation n'enregistre pas les exceptions et autres sorties importantes sur stdout, ce qui peut être utile lors de l'analyse des journaux sur le serveur de build (par exemple).
anatoly techtonik
2
loggingle module ne redirigera pas la sortie des appels système tels queos.write(1, b'stdout')
jfs
17

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.

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

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.

shx2
la source
3
J'aurais init take * des fichiers pas des fichiers, mais sinon, oui, ceci. Aucune des autres solutions n'isole la fonctionnalité «tee» sans essayer de résoudre d'autres problèmes. Si vous voulez mettre un préfixe sur tout ce que vous produisez, vous pouvez envelopper cette classe dans une classe d'écrivain de préfixe. (Si vous voulez mettre un préfixe sur un seul flux, vous enveloppez un flux et le transmettez à cette classe.) Celui-ci présente également l'avantage que multifile ([]) crée un fichier qui ignore tout (comme open ('/ dev /nul')).
Ben
Pourquoi avoir _wrapici du tout? Ne pourriez-vous pas copier le code là __getattr__-dedans et cela fonctionne de la même manière?
timotree
@Ben multifile([])crée en fait un fichier qui déclenche un UnboundLocalErrorchaque fois que vous appelez l'une de ses méthodes. ( resest retourné sans être assigné)
timotree
13

Comme décrit ailleurs, la meilleure solution est peut-être d'utiliser le module de journalisation directement:

import logging

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

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:

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'

Vous ne devez utiliser cette implémentation LogFile que si vous ne pouvez vraiment pas utiliser directement le module de journalisation.

Blokeley
la source
11

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 loggingmodule de Python si vous le souhaitez.

Sorin
la source
Hmm - ce lien ne fonctionne plus - n'importe où ailleurs?
Danny Staple
1
wow, votre package est génial, surtout si vous savez à quel point la culture de la console Windows est lourde mais que vous n'avez pas abandonné pour le faire fonctionner!
n611x007
8

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

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
la source
Bonne réponse. J'ai vu des moyens vraiment compliqués de répliquer la journalisation sur la console, mais créer un StreamHandler avec stderr était la réponse que je recherchais :)
meatvest
Le code est gentil, il ne répond pas à la question - cela génère le journal dans un fichier et stderr, la question d'origine demandait de dupliquer le stderr dans un fichier journal.
emem
8

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 le withmot - clé, ce qui donne ce code

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

Il peut ensuite être utilisé comme

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')
cladmi
la source
1
Je déplacerais la __del__fonctionnalité dans__exit__
vontrapp
1
En effet, je pense que l'utilisation __del__est une mauvaise idée. Il doit être déplacé vers une fonction «fermer» qui est appelée __exit__.
cladmi
7

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à.

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

Vous pouvez ensuite l'utiliser

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

ou

Log=Logger('Sleeps_all.night')
print('works all day')
Log.close()
Statut
la source
Beaucoup de Thnaks @Status vous ont résolu ma question ( stackoverflow.com/questions/39143417/… ). Je vais mettre un lien vers votre solution.
Mohammad ElNesr
1
@MohammadElNesr Je viens de réaliser un problème avec le code lorsqu'il est utilisé avec une instruction with. Je l'ai corrigé et il se ferme maintenant correctement à la fin d'un bloc avec.
Statut le
1
Cela a très bien fonctionné pour moi, il suffisait de changer de mode vers mode="ab"et dans la writefonctionself.file.write(message.encode("utf-8"))
ennetws
4

une autre solution utilisant le module de journalisation:

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
la source
3

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:

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)

Maintenant, cela va tout répéter au gestionnaire sys.stderr normal et à votre fichier. Créez une autre classe tee_outpour sys.stdout.

Josianator
la source
2
Une réponse similaire et meilleure a été publiée plus de deux ans avant celle-ci: stackoverflow.com/a/616686 . Votre méthode est très coûteuse: chaque appel à tee=tee_err();tee.write('');tee.write('');...ouvre + ferme un fichier pour chacun write. Voir stackoverflow.com/q/4867468 et stackoverflow.com/q/164053 pour des arguments contre cette pratique.
Rob W
3

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:

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
la source
1

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.

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)

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.

cognitiaclaeves
la source
0

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:

import logging
debug = input("Debug or not")
if debug == "1":
    logging.basicConfig(level=logging.DEBUG, filename='./OUT.txt')
    old_print = print
    def print(string):
        old_print(string)
        logging.info(string)
print("OMG it works!")

EDIT: 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

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
la source
-1

J'ai écrit un remplacement complet sys.stderret je viens de dupliquer le code en renommant stderrpour stdoutle rendre également disponible pour le remplacer sys.stdout.

Pour ce faire, je crée le même type d'objet que le courant stderret stdout, et je transmets toutes les méthodes au système d'origine stderret 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

Pour l'utiliser, vous pouvez simplement appeler StdErrReplament::lock(logger)et StdOutReplament::lock(logger) transmettre l'enregistreur que vous souhaitez utiliser pour envoyer le texte de sortie. Par exemple:

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

En exécutant ce code, vous verrez à l'écran:

entrez la description de l'image ici

Et sur le contenu du fichier:

entrez la description de l'image ici

Si vous souhaitez également voir le contenu des log.debugappels à l'écran, vous devrez ajouter un gestionnaire de flux à votre enregistreur. Dans ce cas, ce serait comme ceci:

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

Ce qui produirait comme ceci lors de l'exécution:

entrez la description de l'image ici

Bien qu'il l'enregistre toujours dans le fichier my_log_file.txt:

entrez la description de l'image ici

Lorsque vous désactivez cela avec StdErrReplament:unlock(), il ne restaure que le comportement standard du stderrflux, 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 avec impou autre chose, il ne récupérera jamais le courant sys.stderrcar il a déjà été injecté dessus et le sauvegardera en interne.

utilisateur
la source
5
un niveau incroyable de complexité accidentelle pour dupliquer un flux.
Attila Lendvai