前言
Nginx日志对于统计、系统服务排错很有用。Nginx日志主要分为两种:access_log
(访问日志)和error_log
(错误日志)。通过访问日志我们可以得到用户的IP地址、浏览器的信息,请求的处理时间等信息。错误日志记录了访问出错的信息,可以帮助我们定位错误的原因。本文将详细描述一下如何配置Nginx日志。
设置access_log
访问日志主要记录客户端的请求。客户端向Nginx服务器发起的每一次请求都记录在这里。客户端IP,浏览器信息,referer,请求处理时间,请求URL等都可以在访问日志中得到。当然具体要记录哪些信息,你可以通过log_format
指令定义。
语法
access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]]; # 设置访问日志
access_log off; # 关闭访问日志
- path 指定日志的存放位置。
- format 指定日志的格式。默认使用预定义的
combined
。 - buffer 用来指定日志写入时的缓存大小。默认是64k。
- gzip 日志写入前先进行压缩。压缩率可以指定,从1到9数值越大压缩比越高,同时压缩的速度也越慢。默认是1。
- flush 设置缓存的有效时间。如果超过flush指定的时间,缓存中的内容将被清空。
- if 条件判断。如果指定的条件计算为0或空字符串,那么该请求不会写入日志。
另外,还有一个特殊的值off。如果指定了该值,当前作用域下的所有的请求日志都被关闭。
作用域
可以应用access_log
指令的作用域分别有http
,server
,location
,limit_except
。也就是说,在这几个作用域外使用该指令,Nginx会报错。
以上是access_log指令的基本语法和参数的含义。下面我们看一几个例子加深一下理解。
基本用法
access_log /var/logs/nginx-access.log
该例子指定日志的写入路径为/var/logs/nginx-access.log
,日志格式使用默认的combined
。
access_log /var/logs/nginx-access.log buffer=32k gzip flush=1m
该例子指定日志的写入路径为/var/logs/nginx-access.log
,日志格式使用默认的combined
,指定日志的缓存大小为32k,日志写入前启用gzip进行压缩,压缩比使用默认值1,缓存数据有效时间为1分钟。
使用log_format自定义日志格式
Nginx预定义了名为combined
日志格式,如果没有明确指定日志格式默认使用该格式:
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
如果不想使用Nginx预定义的格式,可以通过log_format
指令来自定义。
语法
log_format name [escape=default|json] string ...;
- name 格式名称。在access_log指令中引用。
- escape 设置变量中的字符编码方式是
json
还是default
,默认是default
。 - string 要定义的日志格式内容。该参数可以有多个。参数中可以使用Nginx变量。
下面是log_format
指令中常用的一些变量:
变量 | 含义 |
$bytes_sent | 发送给客户端的总字节数 |
$body_bytes_sent | 发送给客户端的字节数,不包括响应头的大小 |
$connection | 连接序列号 |
$connection_requests | 当前通过连接发出的请求数量 |
$msec | 日志写入时间,单位为秒,精度是毫秒 |
$pipe | 如果请求是通过http流水线发送,则其值为"p",否则为“." |
$request_length | 请求长度(包括请求行,请求头和请求体) |
$request_time | 请求处理时长,单位为秒,精度为毫秒,从读入客户端的第一个字节开始,直到把最后一个字符发送张客户端进行日志写入为止 |
$status | 响应状态码 |
$time_iso8601 | 标准格式的本地时间,形如“2017-05-24T18:31:27+08:00” |
$time_local | 通用日志格式下的本地时间,如"24/May/2017:18:31:27 +0800" |
$http_referer | 请求的referer地址。 |
$http_user_agent | 客户端浏览器信息。 |
$remote_addr | 客户端IP |
$http_x_forwarded_for | 当前端有代理服务器时,设置web节点记录客户端地址的配置,此参数生效的前提是代理服务器也要进行相关的x_forwarded_for设置。 |
$request | 完整的原始请求行,如 "GET / HTTP/1.1" |
$remote_user | 客户端用户名称,针对启用了用户认证的请求 |
$request_uri | 完整的请求地址,如 "https://daojia.com/" |
下面演示一下自定义日志格式的使用:
access_log /var/logs/nginx-access.log main
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
我们使用log_format
指令定义了一个main
的格式,并在access_log
指令中引用了它。假如客户端有发起请求:https://suyunfe.com/
,我们看一下我截取的一个请求的日志记录:
112.195.209.90 - - [20/Feb/2018:12:12:14 +0800] "GET / HTTP/1.1" 200 190 "-" "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36" "-"
我们看到最终的日志记录中$remote_user
、$http_referer
、$http_x_forwarded_for
都对应了一个-
,这是因为这几个变量为空。
设置error_log
错误日志在Nginx中是通过error_log
指令实现的。该指令记录服务器和请求处理过程中的错误信息。
语法
配置错误日志文件的路径和日志级别。
error_log file [level];
Default:
error_log logs/error.log error;
第一个参数指定日志的写入位置。
第二个参数指定日志的级别。level可以是debug
, info
, notice
, warn
, error
, crit
, alert
,emerg
中的任意值。可以看到其取值范围是按紧急程度从低到高排列的。只有日志的错误级别等于或高于level指定的值才会写入错误日志中。默认值是error
。
基本用法
error_log /var/logs/nginx/nginx-error.log
它可以配置在:main
, http
, mail
, stream
, server
, location
作用域。
例子中指定了错误日志的路径为:/var/logs/nginx/nginx-error.log
,日志级别使用默认的error
。
open_log_file_cache
每一条日志记录的写入都是先打开文件再写入记录,然后关闭日志文件。如果你的日志文件路径中使用了变量,如access_log /var/logs/$host/nginx-access.log
,为提高性能,可以使用open_log_file_cache
指令设置日志文件描述符的缓存。
语法
open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
- max 设置缓存中最多容纳的文件描述符数量,如果被占满,采用LRU算法将描述符关闭。
- inactive 设置缓存存活时间,默认是10s。
- min_uses 在inactive时间段内,日志文件最少使用几次,该日志文件描述符记入缓存,默认是1次。
- valid:设置多久对日志文件名进行检查,看是否发生变化,默认是60s。
- off:不使用缓存。默认为off。
基本用法
open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2;
它可以配置在http
、server
、location
作用域中。
例子中,设置缓存最多缓存1000个日志文件描述符,20s内如果缓存中的日志文件描述符至少被被访问2次,才不会被缓存关闭。每隔1分钟检查缓存中的文件描述符的文件名是否还存在。
其他
Nginx中通过access_log
和error_log
指令配置访问日志和错误日志,通过log_format
我们可以自定义日志格式。如果日志文件路径中使用了变量,我们可以通过open_log_file_cache
指令来设置缓存,提升性能。
另外,在access_log
和log_format
中使用了很多变量,这些变量没有一一列举出来,详细的变量信息可以参考Nginx官方文档
问题:
最近一个需求,要更具Status返回值记录不同日志。网上查了下,stackoverflow里找到了方法。
https://stackoverflow.com/questions/19011719/how-to-write-only-logs-with-200-status
nginx 1.7.0+ allows using an if condition in access_log
directive itself.
access_log path [format [buffer=size [flush=time]] [if=condition]];
The if parameter (1.7.0) enables conditional logging.
A request will not be logged if the condition evaluates to “0” or an empty string
Combined with map
directive its possible to send log events to different logs based on various conditions.
http {
map $status $normal {
~^2 1;
default 0;
}
map $status $abnormal {
~^2 0;
default 1;
}
map $remote_addr $islocal {
~^127 1;
default 0;
}
server {
access_log logs/access.log combined if=$normal;
access_log logs/access_abnormal.log combined if=$abnormal;
access_log logs/access_local.log combined if=$islocal;
}
}
http://nginx.org/en/docs/http/ngx_http_log_module.htmlhttp://nginx.org/en/docs/http/ngx_http_map_module.html
从nginx 1.7版本开始,access_log日志文件中支持if语句判断。根据这个功能,我们可以根据status值分割nginx日志,正常200的访问记录放一个文件,404或者500等再放另外一个文件。对于后续分析nginx日志可能有用。
语法:
access_log path [format [buffer=size [flush=time]] [if=condition]];
The if parameter (1.7.0) enables conditional logging.
A request will not be logged if the condition evaluates to “0” or an empty string
值是0时,access_log就不会记录日志。具体配置方法如下。
在nginx.conf代码块中添加如下内容:
map $status $normal {
~^2 1;
default 0;
}
map $status $abnormal {
~^2 0;
default 1;
}
map $remote_addr $islocal {
~^127 1;
default 0;
}
这个配置的意思大概是status值2开头的都正常,其他都是不正常访问。如果remote_addr是127,那么说明是本地内部调用。
定义好判断值后,在具体server代码块中添加类似如下:
server {
access_log logs/access.log combined if=$normal;
access_log logs/access_abnormal.log combined if=$abnormal;
access_log logs/access_local.log combined if=$islocal;
}
添加后reload重新载入nginx,可以看到nginx日志已经根据Status返回值正常分割。
Nginx预定义了名为combined
日志格式,如果没有明确指定日志格式默认使用该格式:
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
复制代码
如果不想使用Nginx预定义的格式,可以通过log_format
指令来自定义。
################################
ngx_http_log_module 模块可以按照特定的格式记录访问日志。
它是基于 location 上下文的,所以如果有内部跳转,那么有些变量可能会变。
Syntax: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
access_log off;
Default: access_log logs/access.log combined;
Context: http, server, location, if in location, limit_except
combined 默认格式定义如下。
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
如果指定了 gzip 压缩,那么 缓冲区的数据会压缩后才会写入到文件,需要zlib库的支持。
access_log /path/to/log.gz combined gzip flush=5m;
flush=5m代表缓冲区5分钟后到期,就算没满也刷新的磁盘。如果开启了缓冲区那么只要满足3个条件就会刷新到磁盘:
1.缓冲区满了。
2.缓冲区到期。
3.nginx -s reload。
如果日志路径 path里包含变量,那么缓冲区不生效,在每次记录日志时文件将会打开和关闭,非常耗性能。但是可以指定 open_log_file_cache缓存来避免频繁的打开关闭文件。每次写日志的时候都会去校验根目录是否存在,如果不存在,日志将不创建。所以最好同时指定 root 和 access_log。
server {
root /spool/vhost/data/$host;
access_log /spool/vhost/logs/$host;
...
}
同时也支持 if 条件,满足则记录。
map $status $loggable {
~^[23] 0;
default 1;
}
access_log /path/to/access.log combined if=$loggable;
Syntax: log_format name [escape=default|json|none] string ...;
Default: log_format combined "...";
Context: http
指定日志格式,escape指定转义,none代表不转义。
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
Syntax: open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
open_log_file_cache off;
Default: open_log_file_cache off;
Context: http, server, location
打开文件缓存,要来避免在path里包含变量时频繁的打开和关闭文件。
max 指定缓存的文件描述符最大数量,超过了把最前面缓存的删除。
inactive 指定没访问的文件的超时时间,超过了自动关闭文件。
min_uses 就算超时了,如果文件使用次数没超过此数,也不关闭。
valid 有效时间,超过了这个时间就会重新去校验文件是否存在。
off 关闭。
open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2;
记录syslog
access_log syslog:server=192.168.1.202:514,tag=nginx,facility=local7,severity=crit combined;
上面的意思是,将访问日志通过udp方式发送到 192.168.1.202:514,标签为 nginx,priority标记为 local7.crit,日志格式为默认的 combined。
192.168.1.202 /etc/rsyslog.conf 部分配置如下,开启了udp,local7.*记录到 /var/log/boot.log文件里。
# Provides UDP syslog reception
$ModLoad imudp
$UDPServerRun 514
# Save boot messages also to boot.log
local7.* /var/log/boot.log
假设我们的nginx服务器在 192.168.1.10 上。
curl localhost
那么在 202的日志文件 /var/log/boot.log 就会看到如下一条记录。
Jul 18 11:53:01 localhost.localdomain nginx: 127.0.0.1 - - \
[18/Jul/2018:11:53:01 +0800] "GET / HTTP/1.1" 200 18 "-" "curl/7.29.0"
如果在 access_log 配置上加上 nohostname 标记,那么上面的 localhost.localdomain 就会消失。
当然也支持 本地 unix domain socket。
access_log syslog:server=unix:/run/x.sock,tag=nginx,facility=local7,severity=crit combined;
syslog详细知识点请参考 linux c syslog 和 linux命令logger。
多条件判断
功能:所有日志(正确和错误日志)都写人access.log,特殊错误日志写入access_abnormal.log
map $status $abnormal { #返回码筛选
~^2 0;
default 1;
}map $request_uri $log_uriignore { #屏蔽的url
~^/err/a 0;
default 1;
}
server {
access_log logs/access.log combined; #所有日志(正确和错误日志)都写人access.log location /err/a/1 {
set $flag "${abnormal}${log_uriignore}";
if ($flag = "11") { #作用域当前location ,如果
进入,将屏蔽server的access_log 配置
access_log logs/access.log combined; #不加这个 ,log_uriignore日志不会写入access.log
access_log logs/access_abnormal.log combined ;
} set $ret $abnormal&$log_uriignore;
set $ret_body '{"code": "V00001","msg": "111"} $abnormal $log_uriignore ${abnormal}${log_uriignore} $flag';
return 404 $ret_body;
} location /err/b/2 {
set $flag "${abnormal}${log_uriignore}";
if ($flag = "11") {
#作用域当前location ,如果进入,将屏蔽server的access_log 配置
access_log logs/access.log combined; #不加这个 ,log_uriignore日志不会写入access.log
access_log logs/access_abnormal.log combined ;
} set $ret_body '{"code": "V00002","msg": "222"} $abnormal $log_uriignore ${abnormal}${log_uriignore} $flag';
return 404 $ret_body;
}
}
参考官方文档即可:
https://nginx.org/r/access_log
access_log 可以使用的范围:http, server, location, if in location, limit_except
比如这个配置:
http {
access_log a.log;
server {
access_log b.log;
location /xx {
access_log c.log;
if yy {
access_log d.log;
}
}
location /zz {
access_log off;
}
}
}
请求最终匹配到哪个 {} block,这个 block 配置了 access_log,就会覆盖上一级的 access_log
- 符合 if 的条件,则请求日志写到 d.log
- 不符合 if, 那 /xx 开头的请求会写日志到 c.log
- 匹配这个 server 的其他日志写到 b.log
- 其余日志写到 a.log
- 还可以对部分请求关闭日志
其他
加条件打印nginx调试日志。
但是打印日志也很郁闷,只要将nginx的日志级别调整到DEBUG,CORE就无法重现。为什么?因为DEBUG的日志信息量非常大,频繁地写磁盘严重影响了NGINX的性能,打开DEBUG后性能由几十万直线下降到几百qps。
调整到其他级别比如 INFO,性能虽然好了,但是日志信息量太少,没有帮助。尽管如此,日志却是个很好的工具,于是又尝试过以下办法:
- 针对特定客户端IP开启debug日志,比如IP是10.1.1.1就打印DEBUG,其他IP就打印最高级别的日志,nginx本身就支持这样的配置。
- 关闭DEBUG日志,自己在一些关键路径添加高级别的调试日志,将调试信息通过EMERG级别打印出来。
- nginx只开启一个进程和少量的connection数。抽样打印连接编号(比如尾号是1)的调试日志。
总体思路依然是在不明显降低性能的前提下打印尽量详细的调试日志,遗憾的是,上述办法还是不能帮助问题定位,当然了,在不断的日志调试中,对代码和逻辑越来越熟悉。