logging flask errors with mod_wsgi - python

I've been trying to get this working since a long time but I'm really at my wits end now. I've tried to do everything that I could find on SO and flask documentation and still I cant a simple error log working so that I can debug my applcation. Below is the pasted code -
# main.py
from flask import Flask
import logging
app = Flask(__name__)
file_handler = logging.FileHandler(filename='/tmp/election_error.log')
file_handler.setLevel(logging.WARNING)
app.logger.addHandler(file_handler)
#app.route('/')
def hello():
return "hello
#missing quotes to generate error
if __name__ == "__main__":
app.run()
#wsgi file
import sys
import logging
sys.path.insert(0, "/var/www/voting_app")
logging.basicConfig(stream=sys.stderr)
from main import app as application
# apache2 conf file
WSGIDaemonProcess voting_app threads=5
WSGIScriptAlias /election /var/www/voting_app/voting_app.wsgi
LogLevel info
<Directory /var/www/voting_app>
WSGIProcessGroup voting_app
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
Please tell me where I'm going wrong. Thank you so much.

The specific error you created, which was a syntax error, would have caused failure of the WSGI script file to even load in mod_wsgi. The error for this would have ended up in the Apache error log file, not the log file you are setup using the logging module. Have you looked in the Apache error log file?
For an exception raised during request execution, Flask would by default turn it into a 500 error page and otherwise supress the display of the details. You need to set up Flask to mail or log such runtime exceptions in other ways per:
http://flask.pocoo.org/docs/errorhandling/
If you want a runtime exception to be displayed in the 500 page returned to the browser for development purposes, you need to enable Flask debug mode. This is done by setting app.debug to be True:
http://flask.pocoo.org/docs/config/?highlight=app%20debug
You should not have debug mode enabled on a user facing production system.

You'll need to generate a runtime exception, not a compile time exception. A missing quote is a compile time exception and your logging code will never be executed.
Just raise an exception instead:
#app.route('/')
def hello():
raise Exception('Deliberate exception raised')

Related

Flask/wsgi - Permission Denied while writing log files or touching random files

I'm writing a very simple Flask program that logs POST payload. It was working well in debug, so I wired it up to apache. Now I'm getting
"IOError: [Errno 13] Permission denied:" when attempting to log anywhere but /tmp.
I'm running apache v2.4.6 and wsgi v3.4 on RHEL 7.x.
Here's my stuff:
vhost file:
Listen *:8080
<VirtualHost *:8080>
ServerName www.test.com
ErrorLog /var/log/httpd/error2.log
CustomLog /var/log/httpd/access2.log combined
# didn't work
#<Directory />
# Options Indexes FollowSymLinks Includes ExecCGI
# AllowOverride All
# Require all granted
#</Directory>
WSGIDaemonProcess csplogger user=apache group=apache threads=5
WSGIProcessGroup csplogger
WSGIScriptAlias /report-to /var/www/FLASKAPPS/csplogger/csplogger.wsgi
</VirtualHost>
csplogger.wsgi file:
#!/bin/python
import sys
sys.path.insert(0,"/var/www/FLASKAPPS/")
from csplogger import app as application
And the python:
from flask import Flask, request
import json
import os
import logging
from logging import Formatter, FileHandler
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
if not app.debug:
logdir = '/opt/logs/'
file_handler = FileHandler('/tmp/access.log')
#file_handler = FileHandler(logdir + 'access.log')
#file_handler = RotatingFileHandler(logdir + 'access.log', maxBytes=20000000, backupCount=10)
file_handler.setFormatter(Formatter('%(asctime)s %(levelname)s: %(message)s'))
logging.getLogger('werkzeug').setLevel(logging.INFO)
logging.getLogger('werkzeug').addHandler(file_handler)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
# adding some debug
app.logger.info(os.environ)
app.logger.info(os.path.dirname(logdir).format())
app.logger.info(os.listdir(logdir))
#app.route('/', methods=['GET', 'POST'])
def home():
if request.method != 'POST':
app.logger.info('GET testing'.format())
return ('not a post\n', 200)
else:
data = request.get_json()
app.logger.info(json.dumps(data))
return ('', 204)
if __name__ =='__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
/opt/logs is the intended log destination. It's chown'd apache:apache (which is my apache user/group) and perm'd wide open. I know the python app can find these directories because I'm listing the contents and logging it (when logging to /tmp/access.log) as part of debugging. I mentioned this is RHEL but I didn't mention that SELinux is disabled.
Long story short, I can see files in a directory, both of which are permed 777 and owned by the apache user, but I can't write those log files.
Thanks for your time. Any help would be greatly appreciated.
I had this issue and the way I resolved it might not have been the direct solution to this, but it worked for me.
Instead of configuring app.logger, I directly created the logger from logging package:
if not app.debug:
logdir = '/opt/logs/'
file_handler = FileHandler('/tmp/access.log')
#file_handler = FileHandler(logdir + 'access.log')
#file_handler = RotatingFileHandler(logdir + 'access.log', maxBytes=20000000, backupCount=10)
file_handler.setFormatter(Formatter('%(asctime)s %(levelname)s: %(message)s'))
logging.getLogger('werkzeug').setLevel(logging.INFO)
logging.getLogger('werkzeug').addHandler(file_handler)
logger = logging.getLogger(__name__)
logger.addHandler(file_handler)
logger.setLevel(logging.INFO)
# adding some debug
logger.info(os.environ)
logger.info(os.path.dirname(logdir).format())
logger.info(os.listdir(logdir))
The actual solution that would keep app.logger in place might be related to this too. Something like: app.logger = logging.getLogger(__name__) but I haven't tried it myself.

All logs being duplicated into apache error log

I have a flask project with logging set up by a config file.
This works as desired when running locally but when I run it under apache wsgi all of the log messages (not just errors) are written to the error.log file set up in the vhost as well.
After some googling I found this issue which I thought could be related and tried setting app.logger_name and calling app.logger but I'm still having the same issue.
config/logging.yaml: pastebin
Vhost:
<VirtualHost *:80>
ServerName myapp.com
WSGIDaemonProcess myapp home=/var/www/vhosts/myapp/httpdocs
WSGIScriptAlias / /var/www/vhosts/myapp/httpdocs/myapp.wsgi
<Directory /var/www/vhosts/myapp/httpdocs>
WSGIProcessGroup myapp
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
ErrorLog /var/www/vhosts/myapp/logs/error.log
CustomLog /var/www/vhosts/myapp/logs/access.log combined
</VirtualHost>
myapp.wsgi:
activate_this = '/var/www/vhosts/myapp/httpdocs/env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))
import sys
sys.path.insert(0, '/var/www/vhosts/myapp/httpdocs')
from run_web import app as application
run_web.py:
import init
import logging
from web import app, api
init.add_to_syspath()
init.logging_conf()
# Flask removes all log handlers so our logs are written to the error log as well.
app.logger_name = "nowhere"
app.logger
logger = logging.getLogger('run_web')
logger.info('Starting web')
api.init()
if __name__ == '__main__':
logger.info('Flask running in debug mode')
app.debug = True
app.run()
init.logging_conf():
def logging_conf():
with open('conf/logging.yaml', 'r') as yaml_file:
logging_config = yaml.load(yaml_file)
dictConfig(logging_config)
I have found that writing to stdout writes to the Apache error log. I have opened a more relevant question: How can I stop sys.stdout logging to apache error file?

How to get apache to serve static files on Flask webapp

I'm getting a 500 internal error while trying to get Apache to serve my static files.
The application will be locally hosted (not www facing). There will be no DNS to resolve a 'www.domain.com' name. I want to be able to access the application by entering the IP address of the server when I'm on that network.
This is my httpd.conf file (I'm on RHEL):
<Directory /var/www/testapp>
Order allow,deny
Allow from all
</Directory>
WSGIScriptAlias / /var/www/testapp/service.wsgi
If I change the WSGIScriptAlias to WGSIScriptAlias /test /var/www/testapp/service.wsgi then I can view my static files when I type in the IP, but I still can't access the service.py script from [IP]/test.
In any case, I want to be able to service all GET/POST requests with the service.py script so I want my alias to start at /, not some other place.
All my static files are in /var/www/html (Apache was automatically displaying these files before I messed with the httpd.conf, now I'm just getting a 500).
This is my service.wsgi:
import sys
sys.path.insert(0, '/var/www/testapp')
from service import app as application
This is my service.py:
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello(environ, start_response):
status = '200 OK'
output = "Hello"
response_headers = [('Content-type', 'text/plain'), ('Content-length', str(len(output)))]
start_response(status, response_headers)
return output
if __name__=='__main__'
app.run()
Do I need keep my .wsgi files in the /var/www/html directory as well? Or can they go in a different folder? I can see that there might be some conflict between the message I am sending to the server ('Hello') and the static files that are already in the /var/www/html/ directory. That's why I tried setting the alias to /test but that didn't work either.
I just want my Flask application to service GET/POST requests and want apache to serve all the static files.
Fixing the 500 errors
You are currently getting 500 errors because your handler is a basic WSGI handler, but Flask handlers are not WSGI handlers (Flask / Werkzeug abstracts all that for you). Change your handler to:
#app.route("/")
def hello():
return "Hello"
and the 500 errors should go away.
Serving static files with Apache
The following techniques can be used when your application is serving the root of the domain (/), depending on whether you are using WSGIScriptAlias or AddHandler.
When using WSGIScriptAlias
When using the WSGIScriptAlias to mount a WSGI application at / you can use an Apache Alias directive to ensure that certain sub-routes are not handled by WSGIScriptAlias (this is further documented in mod_wsgi's wiki as well):
Alias "/static/" "/path/to/app/static/"
<Directory "/path/to/app/static/">
Order allow,deny
Allow from all
</Directory>
If you also want to support blueprint static folders as well you'll also need to use the AliasMatch directive:
AliasMatch "(?i)^/([^/]+)/static/(.*)$" "/path/to/app/blueprints-root/$1/static/$2"
<Directory ~ "/path/to/app/blueprints-root/[^/]+/static/.*">
Order allow,deny
Allow from all
</Directory>
See also: The Directory directive.
When using AddHandler
As Graham Dumpleton has pointed out in the comments, you can use mod_rewrite to pass requests off to Python if and only if a file does not exist in DocumentRoot. Quoting from the linked docs:
When using the AddHandler directive, with WSGI applications identified by the extension of the script file, the only way to make the WSGI application appear as the root of the server is to perform on the fly rewriting of the URL internal to Apache using mod_rewrite. The required rules for mod_rewrite to ensure that a WSGI application, implemented by the script file 'site.wsgi' in the root directory of the virtual host, appears as being mounted on the root of the virtual host would be:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /site.wsgi/$1 [QSA,PT,L]
Do note however that when the WSGI application is executed for a request the 'SCRIPT_NAME' variable indicating what the mount point of the application was will be '/site.wsgi'. This will mean that when a WSGI application constructs an absolute URL based on 'SCRIPT_NAME', it will include 'site.wsgi' in the URL rather than it being hidden. As this would probably be undesirable, many web frameworks provide an option to override what the value for the mount point is. If such a configuration option isn't available, it is just as easy to adjust the value of 'SCRIPT_NAME' in the 'site.wsgi' script file itself.
from your.app import app # Your Flask app
import posixpath
def application(environ, start_response):
# Wrapper to set SCRIPT_NAME to actual mount point.
environ['SCRIPT_NAME'] = posixpath.dirname(environ['SCRIPT_NAME'])
if environ['SCRIPT_NAME'] == '/':
environ['SCRIPT_NAME'] = ''
return app(environ, start_response)
This wrapper will ensure that 'site.wsgi' never appears in the URL as long as it wasn't included in the first place and that access was always via the root of the web site instead.

Running Tornado in apache

My end goal is to implement a WebSocket server using python.
I'm accomplishing this by importing tornado in my python scripts. I've also installed mod_wsgi in apache, and their script outputs Hello World!, so WSGI seems to be working fine. Tornado is also working fine as far as I can tell.
The issue comes when I use tornado's wsgi "Hello, world" script:
import tornado.web
import tornado.wsgi
import wsgiref.simple_server
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
if __name__ == "__main__":
application = tornado.wsgi.WSGIApplication([
(r"/", MainHandler),
])
server = wsgiref.simple_server.make_server('', 8888, application)
server.serve_forever()
First, I get a 500 error and the log tells me WSGI can't find 'application'.
So I remove if __name__ == "__main__", and the page loads infinitely.
I assume this is because of server.serve_forever() so I removed it in an attempt to see Hello, world
But now I just get 404: Not Found. It's not my apache 404 page, and I know that the server can find my main .wsgi file...
You can't use websockets with Tornado's WSGIApplication. To use Tornado's websocket support you have to use Tornado's HTTPServer, not apache.
The WSGIApplication handlers are relative to the webserver root. If your application url is /myapp, your 'application' must look like this:
application = tornado.wsgi.WSGIApplication([
(r"/myapp", MainHandler),
(r"/myapp/login/etc", LoginEtcHandler),
])
Oh, and it seems like the documentation is shit (as usual) and __name__ will look something like this when running under apache: _mod_wsgi_8a447ce1677c71c08069303864e1283e.
So! a correct "Hello World" python script will look like this:
/var/www/wsgi-scripts/myapp.wsgi:
import tornado.web
import tornado.wsgi
import wsgiref.simple_server
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write('Hello World')
application = tornado.wsgi.WSGIApplication([
(r"/myapp", MainHandler),
])
And in the apache config (not .htaccess):
WSGIScriptAlias /myapp /var/www/wsgi-scripts/myapp.wsgi
To use tornado in apache,add a mod-wsgi plugin to apache.
apt-get install libapache2-mod-wsgi
Write a tornado wsgi server with
.wsgi
NOTE:Dont use__name__
Configure the apache.conf to run your server.To configure use this mod-wsgi guide
If you still want to combine them both, you can use Apache as a proxy that will just be the 1st point in front of the user - but actually reroute the traffic to your local Tornado server ( In / Out )
In my case for example, my Apache listen in port 443 ( some default config )
Then I run my tornado in port 8080, and given a path - will redirect
#File: conf.d/myapp.conf
<VirtualHost *:80>
ErrorLog "logs/myapp_error_log"
ProxyPreserveHost On
ProxyRequests off
ProxyPreserveHost On
<Proxy *>
Require all granted
</Proxy>
RewriteEngine on
RewriteCond %{REQUEST_METHOD} ^TRACE
RewriteRule .* - [F]
ProxyPassMatch "/myapp/(.*)" "http://localhost:8080/myapp/$1"
ProxyPassReverse "/myapp/" "http://localhost:8080/myapp/"
</VirtualHost>
If you're using RedHat "family" OS also turn on the ability to forward network connections:
setsebool -P httpd_can_network_connect 1

Deploying a Flask application with CGI [duplicate]

This question already has answers here:
Deploy flask application on 1&1 shared hosting (with CGI)
(3 answers)
Closed 4 years ago.
I have written a small application using the Flask framework. I try to host this using cgi. Following the documentation I created a .cgi file with the following content:
#!/usr/bin/python
from wsgiref.handlers import CGIHandler
from yourapplication import app
CGIHandler().run(app)
Running the file results in following error:
...
File "/usr/lib/pymodules/python2.7/werkzeug/routing.py", line 1075, in bind_to_environ
wsgi_server_name = environ.get('HTTP_HOST', environ['SERVER_NAME'])
KeyError: 'SERVER_NAME'
Status: 500 Internal Server Error
Content-Type: text/plain
Content-Length: 59
In my application I have set:
app.config['SERVER_NAME'] = 'localhost:5000'
When I run the application with the Flask development server it works perfectly well.
As you can tell I'm very new to this stuff and I have search for others with similar errors but with no luck. All help is appreciated.
I will try to show what I've done and it is working in Godaddy sharing host account:
In the cgi-bin folder in MYSITE folder, I added the following cgi file:
#!/home/USERNAME/.local/bin/python3
from wsgiref.handlers import CGIHandler
from sys import path
path.insert(0, '/home/USERNAME/public_html/MYSITE/')
from __init__ import app
class ProxyFix(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ['SERVER_NAME'] = ""
environ['SERVER_PORT'] = "80"
environ['REQUEST_METHOD'] = "GET"
environ['SCRIPT_NAME'] = ""
environ['QUERY_STRING'] = ""
environ['SERVER_PROTOCOL'] = "HTTP/1.1"
return self.app(environ, start_response)
if __name__ == '__main__':
app.wsgi_app = ProxyFix(app.wsgi_app)
CGIHandler().run(app)
As you can see the init file in the MYSITE folder have the flask app.
The most important thing is to set the permissions right. I setted 755 to this folder permission AS WELL AS to "/home/USERNAME/.local/bin/python3" folder!! Remember that the system needs this permission to open flask.
To open the cgi I have the following .htaccess file in MYSITE folder:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /home/USERNAME/public_html/MYSITE/cgi-bin/application.cgi/$1 [L]
So it will render the cgi file when someone enters your page.
This is posted as an answer following the comments above for the sake of completeness.
As discussed above, cgi scripts should execute by some server. Here's the abstract from CGI 1.1 RFC:
The Common Gateway Interface (CGI) is a simple interface for running
external programs, software or gateways under an information server in
a platform-independent manner. Currently, the supported information
servers are HTTP servers.
For the environment variables (which were missing and triggered the error) see sectuib 4.1 in the RFC.

Categories