使用nginx + lua 自定义access.log

说起nginx自定义access.log,可能大家都不陌生,有的同学会说,那不就是定义一下format, format里面可以使用nginx内置的变量$remote_addr、$status、$http_user_agent、$time_local etc… (更多nginx内置的变量) , 这种咱们就不说了,这个简单,基本大家都会。

那是自定义access.log的名字? 比如在一个多个虚拟主机的nginx中,我们想根据不同的server去生成不同的access.log。这样我们去查access.log的时候也方便,从名字一眼就能看出要去哪个文件去查。你也可以通过上面nginx内置的变量去给文件命名,比如我用server_name去命名,我就可以这样写:

log_format log/$http_host.access.log

然后reload一下nginx就可以了。当然我要说的也不是这个。

其实我想说的是这种:自定义log_format 在log_format里面添加一些自己自定义的变量(openresty群里一个朋友问到这个问题)。

比如我想给每个HTTP请求的access.log中添加一个UUID字符串。你可能要问这东西有什么用呢?你想一下,对于一些POST请求(也可能是GET请求),你从URL上看不出差别,但是因为POST的body太大,你又不想记到nginx的access.log里面,还有一种可能是你POST的body里面是二进制,即便你记录到access.log里面了,等你遇到问题要去查的时候你就发现”然并卵啊”,记录这东西也没啥用(是不是想哭),不用哭,今天就是介绍这个的。

我们可以通过一个唯一的字符串去标记一个请求,然后这个串可以通过HTTP的Header或者通过QueryString传给我们的A\B\C\D…服务器,直到一个请求周期结束。然后我们在需要记录log的地方可以将这个unique的串也记录下来,如果你把日志都集中存储到了一个地方(比如ELK),当你去查问题的时候,你可以通过这个字符串就搜索到了这个请求的整个生命周期,是不是有点爽呢。 不扯淡了,下面就说说怎么搞的,这里用了nginx和lua去做,至于为啥用lua,说因为任性可以不,其实就是为了玩。

如果没听过lua或者nginx,再如果不敢兴趣,那只能说不好意思,这位客官,你可能只能观一下了。想了解的同学可以自行搜索一下这俩玩意。如果已经了解了这俩东西不了解ngx_lua(或者openresty)的也可以去搜索一下,这俩项目都是@agentzh (章亦春)春哥的大作。这里我为了简单,就选用了ngx_lua。

  1. 先说下lua生产随机数字吧,我把代码放到util.lua里面了

    local os = require('os')
    local math = require('math')
    local io = require("io")
    
    local _M = {} 
    _M.uuid = function()
        local template = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        d = io.open("/dev/urandom", "r"):read(4)
        math.randomseed(os.time() + d:byte(1) + (d:byte(2) * 256) + (d:byte(3) * 65536) + (d:byte(4) * 4294967296))
        return string.gsub( template, "x",function (c) 
        local v = (c == "x") and math.random(0, 0xf) or math.random(8, 0xb)
        return string.format("%x", v)
    end)          
    end            
    return _M
    

    这里采用了从/dev/urandom去读取随机数,读了前4位,然后根据时间、前四位字符的ascii分作为随机数的种子,然后去随机参数字符替换template里面的x,你也可以再加其他去作为随机数发射器的种子比如pid,不过在lua中,你可能还需要posix这个module,最终生成一个32位的字符串。

  2. 那就直接亮出nginx的配置吧

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for" "$uuid"';
    access_log  logs/access.log  main buff=4k;
    
    sendfile        on;
    tcp_nopush     on;
    
    #keepalive_timeout  0;
    keepalive_timeout  65;
    lua_package_path "$prefix/lua/?.lua;/test/lua/?.lua;";
    init_by_lua_file lua/util.lua;
    

    注意看,其他字段都没改,懒得连main都没改,只是最后添加了一个自定义的字段叫’”$uuid”‘, 还有你的\lua_package_path以及init_by_lua_file位置一定要写正确,不然会因为找不到文件报错。然后看下相关server里面的配置。

    server {
           listen       80 backlog=512;
        server_name  localhost;
    
        charset utf-8;
        underscores_in_headers on; 
    
        set_by_lua_block $uuid {
               local util = require('util')
               local uniq_id, _ = util.uuid()
               return uniq_id
        }
    
        location /redirect {
               return 301 http://127.1/lua; 
        }
    
        location /lua {
               default_type "text/plain";
               content_by_lua_block {
              local args = ngx.req.get_uri_args()
              for key, val in pairs(args) do
                 if type(val) == "table" then
                         ngx.say(key, ": ", table.concat(val, ", "))
                  else
                      ngx.say(key, ": ", val)
                  end
              end
              ngx.say(ngx.var.arg_b)
           }
    }
    ...
    

    只贴这么多关键部分吧,然后去check一下配置是否有问题

    [sky@10_211_55_6_VM_CENTOS go]> sudo /opt/soft/nginx/sbin/nginx -t 
    nginx: [alert] lua\_code\_cache is off; this will hurt performance in /opt/soft/    nginx1.9.3/conf/nginx.conf:35
    nginx: the configuration file /opt/soft/nginx1.9.3/conf/nginx.conf syntax is ok
    nginx: configuration file /opt/soft/nginx1.9.3/conf/nginx.conf test is successful
    

    这个警告是因为开发阶段lua_code_cache没开,生产环境是一定要开的,为了我们修改lua代码能及时生效。那我们去试试看下效果先

    [sky@10_211_55_6_VM_CENTOS go]> curl -iL 'http://127.1/redirect?a=1&b=2&c=3'
    HTTP/1.1 301 Moved Permanently
    Server: nginx/1.9.3
    Date: Thu, 05 Nov 2015 16:34:03 GMT
    Content-Type: text/html
    Content-Length: 184
    Connection: keep-alive
    Location: http://127.1/lua
    

    然后去看下access.log的内容

    [sky@10_211_55_6_VM_CENTOS go]> tail -n 2 /opt/soft/nginx/logs/access.log  
    127.0.0.1 - - [05/Nov/2015:13:32:09 +0800] "GET /lua HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" "8272ba28283350d51056995be1f0c244"
    127.0.0.1 - - [05/Nov/2015:23:56:55 +0800] "GET /lua HTTP/1.1" 200 14 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" "118ab63e5bf8e8c0a2d04e7ab898120d"
    
  3. 透传给应用层

    • 如果你使用的是proxy模式你可以使用proxy_set_headers
    • 如果你通过fastcgi协议传递给后端的PHP,你可以使用fastcgi_param

      proxy_set_header HTTP_UUID $uuid;

      fastcgi_param HTTP_UUID $uuid;

注意:nginx对对header name的字符做了限制,默认 underscores_in_headers 为off,表示如果header name中包含下划线,则忽略掉。

解决办法:

  • 配置中http部分 增加underscores_in_headers on; 配置
  • 用减号-替代下划线符号_,避免这种变态问题。