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;

url -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
   {
(gdb) l
 
 
   static ngx_int_t
   ngx_http_variable_request_body(ngx_http_request_t *r,
       ngx_http_variable_value_t *v, uintptr_t data)
   {
       u_char       *p;
       size_t        len;
       ngx_buf_t    *buf;
       ngx_chain_t  *cl;
(gdb) l
 
       if (r->request_body == NULL
           || r->request_body->bufs == NULL
           || r->request_body->temp_file)
       {
           v->not_found = 1;
 
           return NGX_OK;
       }
 
(gdb) p r->request_body
$1 = (ngx_http_request_body_t *) 0x0
(gdb)

equest_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。

还没有评论,快来抢沙发!

发表评论

  • 😉
  • 😐
  • 😡
  • 😈
  • 🙂
  • 😯
  • 🙁
  • 🙄
  • 😛
  • 😳
  • 😮
  • emoji-mrgree
  • 😆
  • 💡
  • 😀
  • 👿
  • 😥
  • 😎
  • ➡
  • 😕
  • ❓
  • ❗
  • 65 queries in 0.416 seconds