Question or problem about Python programming:
I’m trying to add logging to a web application which uses Flask.
When hosted using the built-in server (i.e. python3 server.py), logging works. When hosted using Gunicorn, the log file is not created.
The simplest code which reproduces the problem is this one:
#!/usr/bin/env python import logging from flask import Flask flaskApp = Flask(__name__) @flaskApp.route('/') def index(): flaskApp.logger.info('Log message') print('Direct output') return 'Hello World\n' if __name__ == "__main__": logHandler = logging.FileHandler('/var/log/demo/app.log') logHandler.setLevel(logging.INFO) flaskApp.logger.addHandler(logHandler) flaskApp.logger.setLevel(logging.INFO) flaskApp.run()
The application is called using:
gunicorn server:flaskApp -b :80 -w 4 --access-gfile /var/log/demo/access.log --error-logfile /var/log/demo/error.log
When doing a request to the home page of the site, the following happens:
How to solve the problem:
When you use
python3 server.py you are running the server3.py script.
When you use
gunicorn server:flaskApp ... you are running the gunicorn startup script which then imports the module
server and looks for the variable
flaskApp in that module.
server.py is being imported the
__name__ var will contain
"__main__" and therefore you log handler setup code is not being run.
You could simply move the log handler setup code outside of the
if __name__ == "__main__": stanza. But ensure that you keep
flaskApp.run() in there since you do not want that to be run when gunicorn imports
More about what does
if __name__ == “__main__”: do?
This approach works for me: Import the Python logging module and add gunicorn’s error handlers to it. Then your logger will log into the gunicorn error log file:
import logging app = Flask(__name__) gunicorn_error_logger = logging.getLogger('gunicorn.error') app.logger.handlers.extend(gunicorn_error_logger.handlers) app.logger.setLevel(logging.DEBUG) app.logger.debug('this will show in the log')
My Gunicorn startup script is configured to output log entries to a file like so:
gunicorn main:app \ --workers 4 \ --bind 0.0.0.0:9000 \ --log-file /app/logs/gunicorn.log \ --log-level DEBUG \ --reload
There are a couple of reasons behind this: Gunicorn has its own loggers, and it’s controlling log level through that mechanism. A fix for this would be to add app.logger.setLevel(logging.DEBUG).
But what’s the problem with this approach? Well, first off, that’s hard-coded into the application itself. Yes, we could refactor that out into an environment variable, but then we have two different log levels: one for the Flask application, but a totally separate one for Gunicorn, which is set through the –log-level parameter (values like “debug”, “info”, “warning”, “error”, and “critical”).
A great solution to solve this problem is the following snippet:
import logging from flask import Flask, jsonify app = Flask(__name__) @app.route('/') def default_route(): """Default route""" app.logger.debug('this is a DEBUG message') app.logger.info('this is an INFO message') app.logger.warning('this is a WARNING message') app.logger.error('this is an ERROR message') app.logger.critical('this is a CRITICAL message') return jsonify('hello world') if __name__ == '__main__': app.run(host=0.0.0.0, port=8000, debug=True) else: gunicorn_logger = logging.getLogger('gunicorn.error') app.logger.handlers = gunicorn_logger.handlers app.logger.setLevel(gunicorn_logger.level)
Refrence: Code and Explanation is taken from here