web-dev-qa-db-fra.com

Flask logging - Impossible de l'écrire dans un fichier

Ok, voici le code où j'ai tout configuré:

if __== '__main__':
    app.debug = False

    applogger = app.logger

    file_handler = FileHandler("error.log")
    file_handler.setLevel(logging.DEBUG)

    applogger.setLevel(logging.DEBUG)
    applogger.addHandler(file_handler)

    app.run(Host='0.0.0.0')

Ce qui se produit est

  1. error.log est créé
  2. Rien n'y est jamais écrit
  3. Bien que je n'ajoute pas de StreamHandler et que je définisse le débogage sur false, je reçois toujours tout sur STDOUT (cela peut être correct, mais semble toujours bizarre)

Suis-je totalement ici quelque part ou que se passe-t-il?

46
fleshgolem

Pourquoi ne pas le faire comme ça:

if __== '__main__':
    init_db()  # or whatever you need to do

    import logging
    logging.basicConfig(filename='error.log',level=logging.DEBUG)

    app.run(Host="0.0.0.0")

Si vous démarrez maintenant votre application, vous verrez que error.log contient:

INFO:werkzeug: * Running on http://0.0.0.0:5000/

Pour plus d'informations, visitez http://docs.python.org/2/howto/logging.html

D'accord, comme vous insistez sur le fait que vous ne pouvez pas avoir deux gestionnaires avec la méthode que je vous ai montrée, je vais ajouter un exemple qui rend cela très clair. Tout d'abord, ajoutez ce code de journalisation à votre principal:

import logging, logging.config, yaml
logging.config.dictConfig(yaml.load(open('logging.conf')))

Maintenant, ajoutez également du code de débogage, afin de voir que notre configuration fonctionne:

logfile    = logging.getLogger('file')
logconsole = logging.getLogger('console')
logfile.debug("Debug FILE")
logconsole.debug("Debug CONSOLE")

Tout ce qui reste est le programme "logging.conf". Utilisons cela:

version: 1
formatters:
  hiformat:
    format: 'HI %(asctime)s - %(name)s - %(levelname)s - %(message)s'
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: hiformat
    stream: ext://sys.stdout
  file:
    class: logging.FileHandler
    level: DEBUG
    formatter: simple
    filename: errors.log
loggers:
  console:
    level: DEBUG
    handlers: [console]
    propagate: no
  file:
    level: DEBUG
    handlers: [file]
    propagate: no
root:
  level: DEBUG
  handlers: [console,file]

Cette configuration est plus compliquée que nécessaire, mais elle montre également certaines fonctionnalités du module de journalisation.

Maintenant, lorsque nous exécutons notre application, nous voyons cette sortie (werkzeug- et console-logger):

HI 2013-07-22 16:36:13,475 - console - DEBUG - Debug CONSOLE
HI 2013-07-22 16:36:13,477 - werkzeug - INFO -  * Running on http://0.0.0.0:5000/

Notez également que le formateur personnalisé avec le "HI" a été utilisé.

Regardez maintenant le fichier "errors.log". Il contient:

2013-07-22 16:36:13,475 - file - DEBUG - Debug FILE
2013-07-22 16:36:13,477 - werkzeug - INFO -  * Running on http://0.0.0.0:5000/
79
HolgerSchurig

Ok, mon échec provient de deux idées fausses:

1) Flask ignorera apparemment tous vos journaux personnalisés à moins qu'il ne s'exécute en mode production)

2) debug = False n'est pas suffisant pour le laisser s'exécuter en mode production. Vous devez envelopper l'application dans n'importe quel type de serveur WSGI pour ce faire

Après avoir démarré l'application à partir du serveur WSGI de gevent (et déplacer l'initialisation de la journalisation vers un endroit plus approprié), tout semble bien fonctionner

13
fleshgolem

La sortie que vous voyez dans la console de votre application provient de l'enregistreur Werkzeug sous-jacent auquel vous pouvez accéder via logging.getLogger ('werkzeug').

Votre journalisation peut fonctionner à la fois en développement et en version en ajoutant également des gestionnaires à cet enregistreur ainsi qu'à celui Flask.

Plus d'informations et exemple de code: Write Flask Requests to an Access Log .

9
AsksAnyway

Je n'aimais pas les autres réponses, j'ai donc continué et il semble que j'ai dû faire ma configuration de journalisation APRÈS Flask a fait sa propre configuration.

@app.before_first_request
def initialize():

    logger = logging.getLogger("your_package_name")
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter(
    """%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n%(message)s"""
    )
    ch.setFormatter(formatter)
    logger.addHandler(ch)

Mon application est structurée comme

/package_name
    __main__.py <- where I put my logging configuration
    __init__.py <- conveniance for myself, not necessary
    /tests
    /package_name <- Actual flask app
    __init__.py
    /views
    /static
    /templates
    /lib

En suivant ces instructions http://flask.pocoo.org/docs/0.10/patterns/packages/

3
David

Pourquoi ne pas plonger dans le code et voir ...

Le module sur lequel nous atterrissons est flask.logging.py, Qui définit une fonction nommée create_logger(app). L'inspection de cette fonction donnera quelques indices sur les coupables potentiels lors du dépannage des problèmes de journalisation avec Flask.

EDIT: cette réponse était destinée à Flask avant la version 1. Le module flask.logging.py A considérablement changé depuis lors. La réponse aide toujours avec quelques mises en garde et conseils généraux concernant python logging, mais sachez que certaines des particularités de Flask à cet égard ont été corrigées dans la version 1 et pourraient ne plus s'appliquer.

La première cause possible de conflits dans cette fonction est cette ligne:

logger = getLogger(app.logger_name)

Voyons pourquoi:

La variable app.logger_name Est définie dans la méthode Flask.__init__() à la valeur de import_name, Qui est elle-même le paramètre de réception de Flask(__name__). C'est-à-dire que app.logger_name Se voit attribuer la valeur de __name__, Qui sera probablement le nom de votre package principal, appelons dans cet exemple "awesomeapp".

Maintenant, imaginez que vous avez décidé de configurer et de créer votre propre enregistreur manuellement. Selon vous, quelles sont les chances que si votre projet est nommé "awesomeapp", vous utiliseriez également ce nom pour configurer votre enregistreur, je pense que c'est très probable.

my_logger = logging.getLogger('awesomeapp') # doesn't seem like a bad idea
fh = logging.FileHandler('/tmp/my_own_log.log')
my_logger.setLevel(logging.DEBUG)
my_logger.addHandler(fh)

Il est logique de le faire ... sauf pour quelques problèmes.

Lorsque la propriété Flask.logger Est invoquée pour la première fois, elle appellera à son tour la fonction flask.logging.create_logger() et les actions suivantes s'ensuivront:

logger = getLogger(app.logger_name)

Rappelez-vous comment vous avez nommé votre enregistreur après le projet et comment app.logger_name Partage également ce nom? Ce qui se passe dans la ligne de code ci-dessus, c'est que la fonction logging.getLogger() a maintenant récupéré votre enregistreur précédemment créé et les instructions suivantes sont sur le point de le perturber d'une manière qui vous fera vous gratter la tête plus tard. Par exemple

del logger.handlers[:]

Poof, vous venez de perdre tous les gestionnaires que vous avez peut-être déjà enregistrés avec votre enregistreur.

D'autres choses qui se produisent dans la fonction, sans entrer dans les détails. Il crée et enregistre deux objets logging.StreamHandler Qui peuvent cracher en sys.stderr Et/ou Response objets. Un pour le niveau "journal" de débogage et un autre pour "production".

class DebugLogger(Logger):
    def getEffectiveLevel(self):
        if self.level == 0 and app.debug:
            return DEBUG
        return Logger.getEffectiveLevel(self)

class DebugHandler(StreamHandler):
    def emit(self, record):
        if app.debug and _should_log_for(app, 'debug'):
            StreamHandler.emit(self, record)

class ProductionHandler(StreamHandler):
    def emit(self, record):
        if not app.debug and _should_log_for(app, 'production'):
            StreamHandler.emit(self, record)

debug_handler = DebugHandler()
debug_handler.setLevel(DEBUG)
debug_handler.setFormatter(Formatter(DEBUG_LOG_FORMAT))

prod_handler = ProductionHandler(_proxy_stream)
prod_handler.setLevel(ERROR)
prod_handler.setFormatter(Formatter(PROD_LOG_FORMAT))

logger.__class__ = DebugLogger
logger.addHandler(debug_handler)
logger.addHandler(prod_handler)

Avec les détails ci-dessus éclairés, il devrait être plus clair pourquoi notre enregistreur et nos gestionnaires configurés manuellement se comportent mal lorsque Flask intervient. Les nouvelles informations nous offrent cependant de nouvelles options. Si vous souhaitez toujours conserver des gestionnaires séparés, l'approche la plus simple consiste à nommer votre enregistreur à quelque chose de différent du projet (par exemple my_logger = getLogger('awesomeapp_logger')). Une autre approche, si vous voulez être cohérent avec les protocoles de journalisation dans Flask, consiste à enregistrer un logging.FileHandler sur Flask.logger en utilisant une approche similaire à Flask.

import logging
def set_file_logging_handler(app):

    logging_path = app.config['LOGGING_PATH']

    class DebugFileHandler(logging.FileHandler):
        def emit(self, record):
            # if your app is configured for debugging
            # and the logger has been set to DEBUG level (the lowest)
            # Push the message to the file
            if app.debug and app.logger.level==logging.DEBUG:
                super(DebugFileHandler, self).emit(record)

    debug_file_handler = DebugFileHandler('/tmp/my_own_log.log')
    app.logger.addHandler(debug_file_handler)

app = Flask(__name__)
# the config presumably has the debug settings for your app
app.config.from_object(config)
set_file_logging_handler(app)

app.logger.info('show me something')
3
Michael Ekoka

Cela marche:

if __== '__main__':
    import logging
    logFormatStr = '[%(asctime)s] p%(process)s {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s'
    logging.basicConfig(format = logFormatStr, filename = "global.log", level=logging.DEBUG)
    formatter = logging.Formatter(logFormatStr,'%m-%d %H:%M:%S')
    fileHandler = logging.FileHandler("summary.log")
    fileHandler.setLevel(logging.DEBUG)
    fileHandler.setFormatter(formatter)
    streamHandler = logging.StreamHandler()
    streamHandler.setLevel(logging.DEBUG)
    streamHandler.setFormatter(formatter)
    app.logger.addHandler(fileHandler)
    app.logger.addHandler(streamHandler)
    app.logger.info("Logging is set up.")
    app.run(Host='0.0.0.0', port=8000, threaded=True)
2
nicodjimenez