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)
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)
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'],
},
},
})
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.
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)
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)
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'],
},
},
})
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.