web-dev-qa-db-fra.com

Comment dupliquer sys.stdout dans un fichier journal?

Edit: Puisqu'il semble qu'il n'y a pas de solution, ou que je fais quelque chose de tellement inhabituel que personne ne le sait, je vais réviser ma question pour qu'elle pose également la question suivante: Quel est le meilleur moyen de journaliser beaucoup d'appels système?

Mon application a deux modes. En mode interactif, je souhaite que toutes les sorties soient affichées à l'écran ainsi que dans un fichier journal, y compris la sortie de tout appel système. En mode démon, toutes les sorties sont enregistrées dans le journal. Le mode démon fonctionne très bien avec os.dup2(). Je ne trouve pas de moyen de "relier" toutes les sorties vers un journal en mode interactif, sans modifier chaque appel système.


En d'autres termes, je souhaite utiliser la fonctionnalité de la ligne de commande 'tee' pour toute sortie générée par une application python, y compris la sortie de l'appel système .

Clarifier: 

Pour rediriger toutes les sorties, je fais quelque chose comme ça, et ça marche 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’elle ne nécessite aucun appel d’impression spécial du reste du code. Le code exécute également certaines commandes Shell, de sorte que Nice n'a pas à traiter individuellement chacune de leurs sorties.

Simplement, je veux faire la même chose, sauf dupliquer au lieu de rediriger.

Au début, j'ai pensé que le simple fait d'inverser le dup2 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.

133
drue

Puisque vous êtes à l'aise de générer des processus externes à partir de votre code, vous pouvez utiliser tee lui-même. Je ne connais aucun appel système Unix faisant exactement ce que tee fait.

import subprocess, os, sys

# Unbuffer output
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 en utilisant multiprocessing package (ou en utilisant processing si vous utilisez Python version 2.5 ou antérieure).

45
Jacob Gabrielson

J'ai eu ce même problème avant 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()

à partir de: http://mail.python.org/pipermail/python-list/2007-May/438106.html

127
John T

L'instruction print appellera la méthode write() de tout objet que vous affectez à sys.stdout.  

Je voudrais 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, l'instruction print fera écho à l'écran et sera ajoutée à votre fichier journal:

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

Ceci est évidemment rapide et sale. Quelques notes:

  • Vous devriez probablement paramétrer le nom du fichier journal.
  • Vous devriez probablement rétablir sys.stdout en <stdout> si vous ne vous connectez pas pendant la durée du programme.
  • Vous souhaiterez peut-être avoir la possibilité d'écrire dans plusieurs fichiers journaux à la fois, ou de gérer différents niveaux de journal, etc.

Celles-ci sont toutes assez simples pour que je puisse les laisser comme exercices pour le lecteur. L'idée clé ici est que print appelle simplement un "objet de type fichier" qui est attribué à sys.stdout.

68
Triptych

Ce que vous voulez vraiment, c'est le module logging de la bibliothèque standard. Créez un enregistreur et attachez deux gestionnaires, l'un écrivant dans un fichier et l'autre sur stdout ou stderr.

Voir Connexion à plusieurs destinations pour plus de détails

63

Voici une autre solution, plus générale que les autres: elle prend en charge le fractionnement de la sortie (écrite dans sys.stdout) en un nombre quelconque d’objets de type fichier. Il n'est pas nécessaire que __stdout__ soit inclus.

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

NOTE: Ceci est une preuve de concept. L’implémentation ici n’est pas complète, car elle enveloppe uniquement method des objets de type fichier (par exemple, write), en omettant les membres/propriétés/setattr, etc. Cependant, il est probablement suffisant pour la plupart des utilisateurs, car il des stands.

Ce qui me plaît à ce sujet, à part sa généralité, c’est qu’il est clair dans le sens où il n’appelle pas directement write, flush, os.dup2, etc.

16
shx2

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

import logging

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

Cependant, il existe quelques occasions (rares) où vous voulez vraiment rediriger stdout. J'étais dans cette situation lorsque j'étendais la commande runserver de Django qui utilise print: je ne voulais pas pirater le source Django, mais j'avais besoin des instructions d'impression pour aller dans un fichier.

C'est un moyen de rediriger stdout et stderr du shell à l'aide du 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 devriez utiliser cette implémentation LogFile que si vous ne pouvez vraiment pas utiliser directement le module de journalisation.

12
blokeley

J'ai écrit une implémentation tee() 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 module logging de Python si vous le souhaitez.

11
sorin

(Ah, il suffit de relire votre question et de voir 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 2.3. Dans cet exemple, la journalisation est configurable à l'aide d'options de ligne de commande. 

En mode silencieux, il se connecte uniquement à un fichier, en mode normal, il se connecte à 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 __== "__main__":
    sys.exit(main())
9
Atlas1j

Pour compléter la réponse de John T: https://stackoverflow.com/a/616686/395687

J'ai ajouté les méthodes __enter__ et __exit__ pour l'utiliser en tant que gestionnaire de contexte avec le mot clé with, 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 alors être utilisé comme

with Tee('outfile.log', 'w'):
    print('I am written to both stdout and outfile.log')
8
cladmi

Je sais que cette question a reçu une réponse répétée, mais pour cela, j'ai pris la réponse principale de John T's answer et l'ai modifiée afin qu'elle contienne le flush suggéré et a suivi sa version révisée liée. J'ai également ajouté l'entrée et la sortie comme indiqué dans cladmi's answer pour une utilisation avec l'instruction with. De plus, la documentation mentionne le vidage des fichiers à l’aide de os.fsync(), ce qui m’a aussi ajouté. Je ne sais pas si vous vraiment avez besoin de ça 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()
5
Status

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'
4
Denis Barmenkov

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

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)

Cela va maintenant tout répéter au gestionnaire sys.stderr normal et à votre fichier. Créez une autre classe tee_out pour sys.stdout.

3
josianator

Conformément à une demande de @ utilisateur5359531 dans les commentaires sous @John T answer , voici une copie de l'article référencé dans 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
2
martineau

J'écris un script pour exécuter des scripts cmd-line. (Parce que dans certains cas, il n’existe aucun substitut viable pour une commande Linux - comme dans le cas de rsync.)

Ce que je voulais vraiment, c’était d’utiliser le mécanisme de journalisation python par défaut dans tous les cas où cela était possible, mais de capturer toute erreur éventuelle 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, au moins il n'a donc pas ce potentiel de bouteille - Neck particulier). Je le publie 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)

Évidemment, 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 jamais écrire en écriture dans un journal.

1
cognitiaclaeves

Si vous souhaitez enregistrer toutes les sorties dans un fichier ET les exporter dans un fichier texte, vous pouvez procéder comme suit. 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 ne consigne 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 sur console et fichier avec stderr uniquement dans 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
0
Jensen Taylor