nginx中将POST数据写到日志里面的正确方式
起初以为是个很简单的问题,网上一大片“让nginx日志支持记录POST请求”之类的文章,于是照做,nginx.conf配置为:
log_format main '$remote_addr\t$remote_user\t[$time_local]\t"$request"\t$status\t$bytes_sent\t' '"$http_referer"\t"$http_user_agent"\t"$http_cookie"\t"$request_body"'; access_log logs/access.log main;
curl -d试之,无效。心想nginx不至于这么坑,一定是打开方式不对,去官网详细参阅了一番$request_body的说明:http://wiki.nginx.org/NginxHttpCoreModule#.24request_body
$request_body
This variable(0.7.58+) contains the body of the request. The significance of this variable appears in locations with directives proxy_pass or fastcgi_pass.
被弄得一头雾水:只有在用了proxy_pass或者fastcgi_pass标记的location{ }里面变量$request_body才会生效?
想来不会有这么无厘头的限制,我和nginx wiki之间一定是有一个坏掉了,决定去nginx代码里面探究一番。
先找到"request_body"字样:
$ grep -A 2 -F "\"request_body\"" -r ./* ./src/http/ngx_http_variables.c: { ngx_string("request_body"), NULL, ./src/http/ngx_http_variables.c- ngx_http_variable_request_body, ./src/http/ngx_http_variables.c- 0, 0, 0 },
想来ngx_http_variable_request_body()这货就是用来读取$request_body变量的回调函数了,下断点gdb之:
(gdb) b ngx_http_variable_request_body Breakpoint 1 at 0x44cb90: file src/http/ngx_http_variables.c, line 1813. (gdb) c Continuing. [Switching to Thread 182894133248 (LWP 25124)] Breakpoint 1, ngx_http_variable_request_body (r=0x66a7c0, v=0x66b300, data=0) at src/http/ngx_http_variables.c:1813 1813 { (gdb) l 1808 1809 1810 static ngx_int_t 1811 ngx_http_variable_request_body(ngx_http_request_t *r, 1812 ngx_http_variable_value_t *v, uintptr_t data) 1813 { 1814 u_char *p; 1815 size_t len; 1816 ngx_buf_t *buf; 1817 ngx_chain_t *cl; (gdb) l 1818 1819 if (r->request_body == NULL 1820 || r->request_body->bufs == NULL 1821 || r->request_body->temp_file) 1822 { 1823 v->not_found = 1; 1824 1825 return NGX_OK; 1826 } 1827 (gdb) p r->request_body $1 = (ngx_http_request_body_t *) 0x0 (gdb)
request_body居然是0x0,难怪获取不到。
再次google求助一番,发现一个麻烦的事情:nginx中读取POST数据必须要调用ngx_http_read_client_request_body()函数,而默认情况下,这个函数是不会被调用的。
那么nginx代码中到底哪些地方会调用这个函数呢?
$ grep "ngx_http_read_client_request_body(" -r ./* ./src/http/ngx_http.h:ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ./src/http/modules/ngx_http_fastcgi_module.c: rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); ./src/http/modules/ngx_http_uwsgi_module.c: rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); ./src/http/modules/ngx_http_scgi_module.c: rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); ./src/http/modules/ngx_http_proxy_module.c: rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); ./src/http/modules/ngx_http_dav_module.c: rc = ngx_http_read_client_request_body(r, ngx_http_dav_put_handler); ./src/http/modules/ngx_http_proxy_module.c.orig: rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); ./src/http/modules/perl/nginx.xs: ngx_http_read_client_request_body(r, ngx_http_perl_handle_request); ./src/http/ngx_http_request_body.c: * on completion ngx_http_read_client_request_body() adds to ./src/http/ngx_http_request_body.c:ngx_http_read_client_request_body(ngx_http_request_t *r,
印证了前面wiki中的那一段话。
于是解决方法就很明显了:要么hack代码强行调用一下,要么找一个module能不那么费事地帮忙调用一下。
天无绝人之路,这里可以用ngx_lua解决,只要在输出log之前读一遍request_body即可,照着例子做:http://wiki.nginx.org/HttpLuaModule#Synopsis
location /test { lua_need_request_body on; content_by_lua 'local s = ngx.var.request_body'; ... }
在location里面加上两行即解决问题,POST内容成功输出到了日志中,赞美Lua。