On 04/07/2014, at 7:58 PM, Graham Dumpleton <[email protected]> wrote:
> On 04/07/2014, at 5:52 PM, theliuy <[email protected]> wrote:
>> I have several web services using Apache + mod_wsgi + Flask. For some debug 
>> purpose, I want to listen the incoming requests and log them. I don't want 
>> to modify flask applications, or wsgi scripts neither. Because there are too 
>> many existing projects, I can't do it one by one. And they are going to be 
>> deployed in multiple environment, I just need this feature in one or two of 
>> them.
>> 
>> One possible solution come to my mind is to modify Flask framework. 
>> Implement a "logger" inside flask. I think it will be better if I can find a 
>> way to register a script in mod_wsgi. Let mod_wsgi run it whenever requests 
>> are coming.
>> 
>> I don't know if there is any better way to make it. Please give me some 
>> hint. 
> 
> What about the request are you wanting to log exactly and for what purpose?
> 
> I can explain a way of doing what you want which avoids you needing to change 
> either the code of your application, Flask or any other package, but the 
> context of what you are trying to capture and why will help as I will then 
> know what sort of WSGI middleware I will need to employ to capture what you 
> need.
> 
> So if you can respond with that extra information and in the mean time I will 
> validate that my idea for how to do it will work.

I would still like to know what you are trying to capture, but if you really 
want to avoid making changes to any existing code or third party modules, you 
can do the following. The technique uses monkey patching, employing the `wrapt` 
library to simplify the process and ensure how the monkey patching is done is 
correct.

First up, you need to have the `wrapt` module installed.

pip install wrapt

Next, create a file called 'patch.py' which contains:

from __future__ import print_function

from wrapt import when_imported, wrap_function_wrapper

def flask_wsgi_app_wrapper(wrapped, instance, args, kwargs):
    def bind_call_args(environ, *args, **kwargs):
        return environ

    environ = bind_call_args(*args, **kwargs)

    print(10*'>')
    for key in sorted(environ.keys()):
        print('%s: %s' % (key, repr(environ[key])))
    print(10*'<')

    return wrapped(*args, **kwargs)

@when_imported('flask.app')
def instrument_flask_app(module):
    wrap_function_wrapper(module, 'Flask.wsgi_app', flask_wsgi_app_wrapper)

What needs to be done now is that this 'patch.py' file has to be imported 
somehow and it ideally needs to be imported before the actual WSGI application 
script file is imported. It preferably would just have been imported at the 
start of the specific WSGI script file for the application you want to add 
debugging to. But since you don't want to do that, then you will need to modify 
the Apache configuration file and use mod_wsgi to preload it.

What exactly you need to do here may depend on whether you are using mod_wsgi 
embedded mode or daemon mode. If using daemon mode, it may also depend on 
whether the WSGIDaemonProcess directives are specified inside of the context of 
a VirtualHost, or outside at global server scope.

The problem here is that the WSGIImportScript can only be defined at global 
server scope. That is, it cannot be specified inside of a VirtualHost, but has 
to be outside.

Right now I can't remember the rules about whether a WSGIImportScript can refer 
to a daemon process group specified using WSGIDaemonProcess directive inside of 
a VirtualHost.

If it can, then the WSGIImportScript directive will at least have to be added 
after the VirtualHost containing the WSGIDaemonProcess.

Anyway, if you are using embedded mode at least, then you need to use the 
WSGIImportScript directive as:

WSGIImportScript /some/path/patch.py process-group=%{GLOBAL} 
application-group=%{GLOBAL}

The application-group option should be set to the specific application group 
context that the WSGI application you want to debug, is running in.

What will happen is that when the Apache child worker processes are started up 
and Python gets initialised, mod_wsgi will preload the patch.py file into the 
Python interpreter.

In being imported, the @with_imported decorator will register that the 
instrument_flask_app() function should be called when the 'flask.app' module is 
imported by the WSGI application.

When the module is the flask.app module is import, the instrument_flask_app() 
function is executed at that point, it will be passed the flask.app module 
before the import even returns the module to the code that imported it. The 
Flask.wsgi_app() method will then have a function wrapper applied using 
wrap_function_wrapper().

Later when handling a WSGI request, the Flask.wsgi_app() will be called. At 
that point the flask_wsgi_app_wrapper() wrapper will actually be called. Inside 
of that we bind the function arguments to extract the 'environ' argument passed 
to the WSGI application entry point for Flask. The wrapper function can then 
print out what it wants from 'environ'. Finally, the wrapper will call the 
original wrapped Flask.wsgi_app() method so the WSGI application can handle the 
request.

So that is the principle of how it works, but that Apache configuration only 
works with embedded mode for sure. If using daemon mode we may not be able to 
use WSGIImportScript however and may have to use another trick.

For daemon mode therefore, if the WSGIDaemonProcess directive is actually 
specified outside of any VirtualHost, then we can still use WSGIImportScript. 
Thus you would use:

WSGIImportScript /some/path/patch.py process-group=group 
application-group=%{GLOBAL}

The process-group option would be set to the daemon process group name, and 
application-group option again set to the specific application group context 
that the WSGI application you want to debug, is running in.

The WSGIImportScript in this case should come after the WSGIDaemonProcess 
directive it is referring to.

Now if the WSGIDaemonProcess group directive is inside of the VirtualHost, I am 
not sure if one can do:

<VirtualHost *:80>
...
WSGIDaemonProcess group
...
</VirtualHost>

WSGIImportScript /some/path/patch.py process-group=group 
application-group=%{GLOBAL}

If that doesn't work, we have to cheat a bit and do:

<VirtualHost *:80>
...
WSGIDaemonProcess group
WSGIScriptAlias /.patch /some/path/patch.py process-group=group 
application-group=%{GLOBAL}
<Location /.patch>
Deny from all
</Location>
...
</VirtualHost>

In other words, we actually use WSGIScriptAlias and the fact that if both 
process-group and application-group are specified, that the WSGI script file 
would normally be preloaded.

Since we have to specify a URL as the mount point and there will be no actual 
WSGI application in the script file, then we use a Location block to block 
access to the URL to force a forbidden HTTP response if someone tries to access 
that URL.

That therefore represents a technical solution for what I believe you want. The 
question now is if you really need to do what you think you do.

Note that if you are really after a monitoring solution for evaluating 
application performance, then there are better ways of doing what you want.

If you do try it and have the case of using WSGIDaemonProcess inside of a 
VirtualHost, do tell me if WSGIImportScript outside of the VirtualHost can 
still refer to it. I will try and test it myself, but it isn't convenient to do 
so right now.

Graham

-- 
You received this message because you are subscribed to the Google Groups 
"modwsgi" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/modwsgi.
For more options, visit https://groups.google.com/d/optout.

Reply via email to