web-dev-qa-db-fra.com

Rediriger Python 'imprimer' la sortie vers l'enregistreur

J'ai un script Python qui utilise 'Print' pour l'impression sur stdout. J'ai récemment ajouté la journalisation via Python Logger et j'aimerais le faire afin que ces instructions d'impression soient envoyées à l'enregistreur si la journalisation est activée. Je ne souhaite pas modifier ou supprimer ces instructions d'impression.

Je peux me connecter en faisant 'log.info ("quelques infos msg")'. Je veux pouvoir faire quelque chose comme ça:

if logging_enabled:
  sys.stdout=log.info
print("test")

Si la journalisation est activée, "test" doit être enregistré comme si je l'avais fait log.info ("test"). Si la journalisation n'est pas activée, "test" doit simplement être imprimé à l'écran.

Est-ce possible? Je sais que je peux diriger stdout vers un fichier de la même manière (voir: rediriger les impressions vers le fichier journal )

25
Rauffle

Vous avez deux options:

  1. Ouvrez un fichier journal et remplacez sys.stdout par lui, pas une fonction:

    log = open("myprog.log", "a")
    sys.stdout = log
    
    >>> print("Hello")
    >>> # nothing is printed because it goes to the log file instead.
    
  2. Remplacez print par votre fonction de journal:

    # If you're using python 2.x, uncomment the next line
    #from __future__ import print_function
    print = log.info
    
    >>> print("Hello!")
    >>> # nothing is printed because log.info is called instead of print
    
22
C0deH4cker

Une autre méthode consiste à encapsuler l'enregistreur dans un objet qui traduit les appels en write en la méthode log de l'enregistreur.

Ferry Boender fait exactement cela, fourni sous la licence GPL dans n message sur son site Web . Le code ci-dessous est basé sur cela mais résout deux problèmes avec l'original:

  1. La classe n'implémente pas la méthode flush qui est appelée lorsque le programme se ferme.
  2. La classe ne met pas en mémoire tampon les écritures sur la nouvelle ligne car les objets io.TextIOWrapper sont supposés entraîner des nouvelles lignes à des points impairs.
import logging
import sys


class StreamToLogger(object):
    """
    Fake file-like stream object that redirects writes to a logger instance.
    """
    def __init__(self, logger, log_level=logging.INFO):
        self.logger = logger
        self.log_level = log_level
        self.linebuf = ''

    def write(self, buf):
        temp_linebuf = self.linebuf + buf
        self.linebuf = ''
        for line in temp_linebuf.splitlines(True):
            # From the io.TextIOWrapper docs:
            #   On output, if newline is None, any '\n' characters written
            #   are translated to the system default line separator.
            # By default sys.stdout.write() expects '\n' newlines and then
            # translates them so this is still cross platform.
            if line[-1] == '\n':
                self.logger.log(self.log_level, line.rstrip())
            else:
                self.linebuf += line

    def flush(self):
        if self.linebuf != '':
            self.logger.log(self.log_level, self.linebuf.rstrip())
        self.linebuf = ''


logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s:%(levelname)s:%(name)s:%(message)s',
    filename="out.log",
    filemode='a'
)

stdout_logger = logging.getLogger('STDOUT')
sl = StreamToLogger(stdout_logger, logging.INFO)
sys.stdout = sl

stderr_logger = logging.getLogger('STDERR')
sl = StreamToLogger(stderr_logger, logging.ERROR)
sys.stderr = sl

Cela vous permet de router facilement toutes les sorties vers un enregistreur de votre choix. Si nécessaire, vous pouvez enregistrer sys.stdout et/ou sys.stderr comme mentionné par d'autres dans ce fil avant de le remplacer si vous devez le restaurer plus tard.

13
Cory Klein

Bien sûr, vous pouvez à la fois imprimer sur la sortie standard et l'ajouter à un fichier journal, comme ceci:

# Uncomment the line below for python 2.x
#from __future__ import print_function

import logging

logging.basicConfig(level=logging.INFO, format='%(message)s')
logger = logging.getLogger()
logger.addHandler(logging.FileHandler('test.log', 'a'))
print = logger.info

print('yo!')
12
mgmalheiros

Une option beaucoup plus simple,

import logging, sys
logging.basicConfig(filename='path/to/logfile', level=logging.DEBUG)
logger = logging.getLogger()
sys.stderr.write = logger.error
sys.stdout.write = logger.info

Une fois que vous avez défini votre enregistreur, utilisez-le pour rediriger l'impression vers l'enregistreur même avec plusieurs paramètres d'impression.

print = lambda *tup : logger.info(str(" ".join([str(x) for x in tup]))) 
2
Mikus

Vous devriez vraiment le faire dans l'autre sens: en ajustant votre configuration de journalisation pour utiliser des instructions print ou autre chose, selon les paramètres. N'écrasez pas le comportement de print, car certains des paramètres qui pourraient être introduits à l'avenir (par exemple, par vous ou par quelqu'un d'autre utilisant votre module) peuvent en fait le renvoyer vers le stdout et vous aura des problèmes.

Il existe un gestionnaire qui est censé rediriger vos messages de journal vers le flux approprié (fichier, stdout ou autre chose semblable à un fichier). Il s'appelle StreamHandler et il est fourni avec le module logging.

Donc, à mon avis, vous devriez faire ce que vous avez déclaré ne pas vouloir faire: remplacer les instructions print par la journalisation réelle.

2
Tadeck

Ci-dessous, il fonctionne parfaitement dans mon code PySpark. Si quelqu'un a besoin de compréhension ->

import os
import sys
import logging
import logging.handlers

log = logging.getLogger(__name_)

handler = logging.FileHandler("spam.log")
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
log.addHandler(handler)
sys.stderr.write = log.error 
sys.stdout.write = log.info 

(enregistrera chaque erreur dans "spam.log" dans le même répertoire, rien ne sera sur console/stdout)

(enregistrera toutes les informations dans "spam.log" dans le même répertoire, rien ne sera sur console/stdout)

pour imprimer les informations/erreurs de sortie dans les deux fichiers ainsi que dans la console, supprimez au-dessus de deux lignes.

Happy Coding Cheers !!!

0
Dean