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?
Related
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.
I have the following stack: Flask (python 2.7) + Apache2 with mod_wsgi.
I have this simple route function:
#app.route("/upload/<file_name>/", methods=['POST', 'HEAD'])
def upload(file_name):
if request.method == 'HEAD':
file_size = int(request.headers.get('Content-Length'))
file_path = os.path.join(storage_path, file_name)
if not os.path.exists(file_path):
return general_response(404)
else:
file_size_on_disk = Utils.get_size_of_file(file_path)
if file_size_on_disk < file_size:
print "1"
return general_response(201)
elif file_size_on_disk == file_size:
print "2"
return general_response(200)
else:
print "4"
return general_response(500)
# some code
If I send HEAD request without any headers - I receive proper response for my request in REST client, but if I send HEAD request with 'Content-Length' header with some value - I see in log:
2
And this is right behavior because that part of code should executed in that case, but no response from server after printing "2"...
If I abort connection in my REST client - I see the following line in log:
(70008)Partial results are valid but processing is incomplete: mod_wsgi (pid=11509): Unable to get bucket brigade for request
This is my apache2 config:
<VirtualHost *:443>
ServerAdmin Admin
ServerName site.com
SSLEngine on
SSLCertificateFile /path/to/cert
SSLCertificateKeyFile /path/to/key
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
WSGIScriptAlias / /path/to/app.wsgi
WSGIDaemonProcess app_name user=worker group=worker processes=1 threads=5
DocumentRoot /path/to/src/dir
<Directory /path/to/src/dir>
LimitRequestBody 4147483647
WSGIProcessGroup app_name
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
I really cannot realize why receiving this header with a value breaks wsgi logic at all.
Any help/advice will be appreciated!
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.
I have developed a Python web application using bottlepy. It takes in 7 input parameters and return a JSON string.
#route('/aggregation')
def service():
poi_data = request.GET.get('poi', default=None)
crime_data = request.GET.get('crime', default=None)
walkshed_collection = request.GET.get('walkshed_collection', default=None)
walkshed_union = request.GET.get('walkshed_union', default=None)
start_point = request.GET.get('start_point', default=None)
transit_data = request.GET.get('transit', default=None)
distance_decay_function = request.GET.get('distance_decay_function', default=None).lower()
walking_time_period = request.GET.get('walking_time_period', default=None)
if start_point and poi_data and crime_data and walkshed_collection and walkshed_union and transit_data and distance_decay_function and walking_time_period is not None:
return aggregation(start_point, poi_data, transit_data, crime_data, walkshed_collection, walkshed_union, distance_decay_function, walking_time_period)
run(host='0.0.0.0', port=9364, debug=True)
It works fine with both HTTP GET and POST when I run the application using the bottlepy web server using terminal, like python aggregation.py.
But when I managed to host it in Apache using mod-wsgi, it did not work; the Apache logged "URI Too Long" when using HTTP GET and "caught SIGTERM, shutting down" when using HTTP POST. Actually, some of the inputs are too long JSON strings like poi_data, crime_data, walkshed_collection_walkshed_union, and transit_data. For example, the length of KVP request is around 200KB.
Here is the Apache configuration:
Listen *:9364
<VirtualHost *:9364>
ServerName 127.0.0.1
WSGIDaemonProcess aggregation user=www-data group=www-data processes=1 threads=5
WSGIScriptAlias /aggregation /var/www/aggregation/adapter.wsgi
<Directory /var/www/aggregation>
WSGIProcessGroup aggregation
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
I should mention that I have developed couple of Python web applications that work fine with both bottle web server and Apache mod-wsgi. But they take in small input parameters. So I assume that the problem might be related to the size of request. Do you guys have any idea how I can fix the problem? Any suggestion would be much appreciated.
Thanks,
Ebrahim
Duplicate of:
https://groups.google.com/d/msg/modwsgi/8HJhaOleYwA/e29CtXY4_8UJ
Answer provided in the mod_wsgi mailing list.
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')