Flask – how to write werkzeug logs to log file using RotatingFileHandler?

Question:

I’ve found some somewhat similar questions, but nothing that directly addresses this.

I’m trying to output all Werkzeug logging to a log file. I can get part of the logging to output to the file, but I cannot seem to capture any errors or anything beyond the basic route/request lines.

Here is what I have. How can I include ALL output from Werkzeug?

if __name__ == '__main__':
    configure_app(app)
    handler=RotatingFileHandler('server_werkzeug.log', maxBytes=10000000, backupCount=5)
    log = logging.getLogger('werkzeug')
    log.setLevel(logging.DEBUG)
    log.addHandler(handler)
Asked By: user797963

||

Answers:

This code works perfectly for logging. As for the problem of not logging errors it seems, that you have to specify other loggers, besides ‘werkzeug’. For traceback errors in Flask it’s app.logger what you’re looking for. And don’t forget to set the level to WARNING.

import logging
import logging.handlers

app = Flask(__name__)

handler = logging.handlers.RotatingFileHandler(
        'log.txt',
        maxBytes=1024 * 1024)
logging.getLogger('werkzeug').setLevel(logging.DEBUG)
logging.getLogger('werkzeug').addHandler(handler)
app.logger.setLevel(logging.WARNING)
app.logger.addHandler(handler)

You can add other loggers as well, if your app is using some third party tools for like database queries, cron jobs etc.

logging.getLogger('apscheduler.scheduler').setLevel(logging.DEBUG)
logging.getLogger('apscheduler.scheduler').addHandler(handler)

The name of a logger can be found in the terminal window:

INFO:werkzeug:127.0.0.1 - - [10/Mar/2018 15:41:15] "GET /file.js HTTP/1.1" 200 -
DEBUG:apscheduler.scheduler:Next wakeup is due at 2018-03-10 16:00:00+03:00 (in 1124.668881 seconds)
Answered By: Tim Nikitin

Use the dictConfig is a more extendable way.

from logging.config import dictConfig

dictConfig({
    'version': 1,
    'handlers': {
        'file.handler': {
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'server_werkzeug.log',
            'maxBytes': 10000000,
            'backupCount': 5,
            'level': 'DEBUG',
        },
    },
    'loggers': {
        'werkzeug': {
            'level': 'DEBUG',
            'handlers': ['file.handler'],
        },
    },
})
Answered By: Yan QiDong

If you are interested in separating the access log from other werkzeug logging calls, you can implement the following monkey patch.

This is handy for doing development work as access logs are very handy for troubleshooting but having werkzeug printing alongside your other logs is not very helpful for readability.

__access_log_enabled__ = False # Override to enable logger (eg when running code outside of wsgi server)

def log_request(self:werkzeug.serving.WSGIRequestHandler, 
                code: typing.Union[int, str] = "-", 
                size: typing.Union[int, str] = "-") -> None:
    if not __access_log_enabled__: return
    _server_.log_request(handler=self, code=code, size=size)
    # where _server_ is your own class for managing the server

werkzeug.serving.WSGIRequestHandler.log_request = log_request

Here is a example consuming this:

import werkzeug
import typing
import logging
from logging.handlers import RotatingFileHandler
from datetime import datetime

_server_ = None

class MyServer():
    def __init__(self, in_wsgi:bool):
        global _server_
        _server_ = self
        
        # ...
        log_werkzeug = logging.getLogger('werkzeug')
        log_werkzeug.setLevel(logging.ERROR)
        # This will suppress all logs from werkzeug below Error
        #  with the exception of the redirected access logs

        if not in_wsgi:
            self.acc_log = logging.getLogger('qflask_access_log')
            handler = RotatingFileHandler('./log/access_non_wsgi.log', 
                                          maxBytes=100000, 
                                          backupCount=5)
            handler.setFormatter(logging.Formatter("%(message)s"))
            self.acc_log.addHandler(handler)
            self.acc_log.setLevel(logging.INFO)
            global __access_log_enabled__
            __access_log_enabled__ = True

        # ...
            
    # ...
    def log_request(self, 
                    handler:werkzeug.serving.WSGIRequestHandler, 
                    code: typing.Union[int, str] = "-", 
                    size: typing.Union[int, str] = "-") -> None:
        try:
            path = werkzeug.urls.uri_to_iri(handler.path)
            msg = f"{handler.command} {path} {handler.request_version}"
        except AttributeError:
            # path isn't set if the requestline was bad
            msg = handler.requestline
        code = str(code)

        self.acc_log.warning(
            f"[{datetime.now().strftime('%y%m%d-%H%M%S.%f')}] {code} {msg} {size}")

This is probably a good feature to have built into werkezug. In fact with access_log_enabled = False, it will make any server a tiny bit faster.

Answered By: Timothy C. Quinn
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.