IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

使用nginx记日志

量子数科院 2012-01-27 18:49:29 累计浏览 6,705 次
本机暂存

做web服务和应用的时候,很多场景下需要记录日志。

如 访问日志,性能分析日志,打点日志,数据统计日志等。

假设有以下主机设置

server {
 
    listen       80;
 
    server_name abc.cc;
 
    root /etc/www/abc;
 
 
 
    access_log /var/log/www/abc/access.log;
 
    location / {
 
        index  index.htm index.htm;
 
    }
 
}

默认情况下,access_log 会使用 combined 的配置来记录访问日志

log_format combined '$remote_addr - $remote_user [$time_local]  '
 
    '"$request" $status $body_bytes_sent '
 
    '"$http_referer" "$http_user_agent"';

通常这样就足够了。

如果是为了更加方便的日志分析,通常我们会使用特殊字符(如 ^A) 来作为日志字段的分隔符,

这样无论是过滤还是排序都会十分方便。甚至可以直接导入 mysql/hive 中,使用强大的 sql 来做查询分析。

为了排版方便,所有特殊字符都使用了展开的写法,请自行替换 ^A 为 ctrl+v,ctrl+a (nginx 日志格式不支持 \1 的写法)。

自定义日志格式:

server {
 
    listen       80;
 
    server_name abc.cc;
 
    root /etc/www/abc;
 
 
 
    # 更多日志可用字段(基本上都是 nginx 的变量),见
 
    # http://wiki.nginx.org/NginxHttpLogModule#log_format
 
    # http://wiki.nginx.org/NginxHttpUpstreamModule#Variables
 
    # http://wiki.nginx.org/NginxHttpCoreModule#Variables
 
    log_format abc "$remote_addr^A$remote_user^A$time_local^A$request_method^A$uri^A$args^A$server_protocol"
 
        "^A$status^A$body_bytes_sent^A$http_referer"
 
        "^A$http_user_agent";
 
    access_log /var/log/www/abc/access.log abc;
 
    location / {
 
        index  index.htm index.htm;
 
    }
 
}

当把日志使用 ^A 分割以后,后续就可以使用 sort 和 grep 之类工具对特定url做分析了,

比如统计各url请求量倒排取前50个

awk -F^A '{print $5}' /var/log/www/abc/access.log | sort | uniq -c | sort -nr | head -50

有时候可能想对记录的字段做一些处理,比如 $arg_q 可能是搜索关键词,记录的时候如果 unescape 一下,

会更方便分析,存储上也会更小,

那么可以使用 NginxHttpSetMiscModule 模块提供的指令( http://wiki.nginx.org/NginxHttpSetMiscModule#set_unescape_uri )实现:

set_unescape_uri $q $arg_q;
 
log_format abc "$q";

有时候,我们需要对字段做 hash 转换,可以使用 HttpMapModule 提供的功能(http://wiki.nginx.org/HttpMapModule)

# 需要放到 http 里面,不能放到 server 里 :)

# 根据 url 地址计算分类,便于后续统计

# 具体根据需求做变换就好了

# 第一列是匹配规则,后面的是赋值 ~ 开头的匹配规则是正则

map $uri $typ {
  default               -;
  ~/login              user;
  ~/my                 user;
  ~/static             static;
}
 
log_format abc "$typ^A$uri";

如果使用 nginx 比较多,可能会尝试使用 if ,建议不要使用,因为nginx的if比较让人混乱。

如果有更多复杂的字段处理需求,可以使用 ngx_lua (http://wiki.nginx.org/HttpLuaModule)。

ngx_lua 里面操作 nginx 变量

# 实现上面 map 类似的功能
# 用法详见 http://wiki.nginx.org/HttpLuaModule#set_by_lua
# lua 语法见 http://www.lua.org/manual/5.1/manual.html#2.4
 
set_by_lua $typ "
 
local uri = ngx.var.uri
local _m = string.match
local v = '-'
 
if _m(uri, '^/login') then
    v = 'user'
elseif _m(uri, '^/my') then
    v = 'user'
elseif _m(uri, '^/static') then
    v = 'static'
end
 
return v
";

某些情况下,可能我们的字段处理需要查询缓存(如redis)、数据库(如mysql)等,

这些都是可以使用 ngx_openresty 高效完成的(http://openresty.org/)。

这些功能就不在这一篇详细描述了,后续篇章会补充这些功能。

再描述一些复杂的日志记录功能吧。

有时候我们希望根据请求,来判断是否需要记录这一条日志。

在web的访问日志中这种需求比较少,但是独立的日志收集服务器一般有这样的需求的。

比如我需要判定,请求参数 arg_id 必须存在且为数字的时候我才记录日志,可以这样实现

server {
 
    listen       80;
 
    server_name abc.cc;
 
    root /etc/www/abc;
 
 
    log_format abc "$msec^A$args^A$q^A$ie^A$oe^A$ref"
 
        "^A$http_user_agent";
 
    access_log off;
 
    location / {
 
        # 专门记日志的服务,对非合法请求,直接断开连接 或者根据需求302到自己的站点
 
        # 但是这种302一般不会被用户看到 可以综合考虑做法
 
        # 444 的意义见 http://wiki.nginx.org/HttpRewriteModule#return
 
        return 444;
 
    }
 
    location = /i-log {
 
        internal;
 
        set_unescape_uri $q $arg_q;
 
        set_unescape_uri $ie $arg_ie;
 
        set_unescape_uri $oe $arg_oe;
 
        set_unescape_uri $ref $arg_ref;
 
        # 这个很重要,否则不会记录的
 
        log_subrequest on;
 
        access_log /var/log/www/abc/access.log abc;
 
        # 这个指令需要 HttpEchoModule (http://wiki.nginx.org/HttpEchoModule#echo) 的支持
 
        # 因为这个地址只是为了辅助记录日志,所以不需要返回内容
 
        echo '';
 
    }
 
    location = /1.gif {
 
        default_type  image/gif;
 
        access_log off;
 
        access_by_lua "
 
local q = ngx.var.arg_q
 
if q then
 
    q = ngx.unescape_uri(q)
 
    if q and #q > 0 then
 
        ngx.location.capture('/i-log?' .. ngx.var.args)
 
    end
 
end
 
        ";
 
        # 这种请求一般不缓存
 
        add_header Expires "Fri, 01 Jan 1980 00:00:00 GMT";
 
        add_header Pragma "no-cache";
 
        add_header Cache-Control "no-cache, max-age=0, must-revalidate";
 
        # 一般独立记录日志的请求,都会返回一张 1x1 的空白gif图
        empty_gif;
    }
 
}

利用这个特性,我们甚至可以合并多个记录为1个http请求,在 ngx_lua 内再将多条记录拆分记录到日志文件

比如上例,我门可以添加一个字段 n 来标识有几条记录,并且给 q 之类的参数编号 q_1 .. q_n 。

# GET /1.gif?n=2&q_1=a&q_2=b&ie=gbk&oe=utf8&ref=
 
# 其他代码不变,只修改 1.gif 的 access_by_lua 为以下代码
 
local n = ngx.var.arg_n
 
if n then
 
    n = tonumber(n)
 
    if n > 0 then
 
        local logs = {}
 
        local prefix = string.format('/i-log?ie=%s&oe=%s&ref=%s&q=', ngx.var.arg_ie, ngx.var.arg_oe, ngx.var.arg_ref)
 
        while n >= 1 do
 
            -- 这里可以对 q_n 做进一步的校验,看是否需要记录下来
 
            table.insert(logs, {prefix .. (ngx.var['arg_q_' .. n] or '')})
            n = n - 1
        end
 
        -- 见 http://wiki.nginx.org/HttpLuaModule#ngx.location.capture_multi
 
        ngx.location.capture_multi(logs)
 
    end
 
end

如果请求量较大,一把需要添加写buffer,

方式为在每条 access_log 后面添加 buffer=32k 这样的设置(见 http://wiki.nginx.org/NginxHttpLogModule#access_log)

缓存的大小可以设置成 可以忍受丢失的记录数*每条记录的size

至此,各种记录功能都完成啦。

日志服务其他必要的功能,就是日志轮转了。

nginx 日志轮转的原理是

# 启动nginx收日志
 
# 启用cron任务
 
## 将日志文件move走 (文件名可以带上时间戳)
 
## 给 nginx master 进程发送 USR1 信号(nginx就会重新打开新的日志文件)
 
### 否则,日志会仍然记录到之前的日志文件中,虽然被 move 了
 
## cron 的频度可以根据日志大小调整,尽可能大些,能以天为单位就不要以半天为单位

具体实现可见 http://jk.aiwaly.com/wp/nginx-cut-log.html

以上操作中涉及较多 nginx 扩展模块,如果不想折腾,可以使用 ngx_openresty (http://openresty.org)

如果是淘宝系的同学,可以旺旺联系 定球 ,我们有现成的 rpm 可以使用:)

使用中有任何问题,欢迎发信到 kindy61  或者 微博 @定球呀球

同分类推荐文章

  1. Vibe新开源项目 - Vaala AI Gateway (2026-05-17 02:10:19)
  2. SmartPerfetto 架构文章 Q&A:8 个深度技术问答 (2026-04-10 11:00:00)
  3. 让 AI 把我的 PHP 博客重写成 Go (2026-03-27 18:33:54)

查看更多 后端 文章 →

建议继续学习

  1. 配置Nginx+uwsgi更方便地部署python应用 (累计阅读 106,964)
  2. 搜狐闪电邮箱的 Nginx/Postfix 使用模式 (累计阅读 33,822)
  3. 记录一个软中断问题 (累计阅读 16,885)
  4. 解析nginx负载均衡 (累计阅读 16,503)
  5. server日志的路径分析 (累计阅读 11,181)
  6. Nginx模块开发入门 (累计阅读 11,102)
  7. 检查nginx配置,重载配置以及重启的方法 (累计阅读 10,782)
  8. Cacti 添加 Nginx 监控 (累计阅读 10,521)
  9. 使用Squid缓存视频 (累计阅读 10,280)
  10. fsockopen 异步处理 (累计阅读 10,280)