来源: DevOpSec公众号 作者: DevOpSec
背景
小明在维护nginx时通过自己写脚本切割日志,这里弊端有二:
一、日志可能丢失,在日志切割和新日志产生的一瞬间有日志的丢失。
二、维护成本高,由于业务量的增加nginx横向扩容了几台机器,小明很快就把nginx扩容上,但日志切割的定时任务忘记增加了。
在日常运维过程中我们希望日志自动的按天切割,而不是通过脚本加定时任务实现,增加了运维维护的复杂度,下面让我们看看怎么操作吧。
日志切割
我们知道nginx配置文件里是可以引用nginx的内置变量的,nginx的日志文件名是否可以把带有时间的变量拼接一起,随着时间的变化日志文件自动切割呢?
答案是可以的。下面看nginx内的的部分变量,我们通过time_iso8601变量分别实现http协议和tcp协议下nginx日志自动切割。
首先我们先看一下nginx内置常用变量,对我们定义打印日志内容有帮助。
1. nginx内置变量
| 变量名称 | 变量描述 |
|---|---|
| $time_iso8601 | 本地ISO 8601时间 |
| $arg_PARAMETER | 客户端GET请求中PARAMETER 字段的值 |
| $args | 客户端请求中的参数 |
| $binary_remote_addr | 远程地址的二进制表示 |
| $body_bytes_sent | 已发送的消息体字节数 |
| $content_length | HTTP请求信息中content-length的字段 |
| $content_type | 请求信息中content-type字段 |
| $cookie_COOKIE | 客户端请求中COOKIE头域的值 |
| $document_root | 针对当前请求的根路径设置值 |
| $document_uri | 与$uri相同 |
| $host | 请求信息中的host头域,如果请求中没有Host行,则等于设置的服务器名 |
| $http_HEADER | HTTP请求信息里的HEADER地段 |
| $http_host | 与$host相同,但是如果请求信息中没有host行,则可能不同客户端cookie信息 |
| $http_cookie | 客户端cookie信息 |
| $http_referer | 客户端是从哪一个地址跳转过来的 |
| $http_user_agent | 客户端代理信息,也就是你客户端浏览器 |
| $http_via | 最后一个访问服务器的IP |
| $http_x_forwarded_for | X-Forwarded-For 是一个扩展头, 用来表示HTTP 请求端真实IP |
| $is_args | 如果有args的值,则等于”?”,否则为空 |
| $limit_rate | 对连接速率的限制 |
| $nginx_version | 当前Nginx的版本 |
| $pid | 当前Nginx服务器的进程的进程ID |
| $query_string | 与$args相同 |
| $remote_addr | 客户端IP地址 |
| $remote_port | 客户端的端口 |
| $remote_user | 客户端的用户名,用于 auth basic module验证 |
| $request | 客户端请求 |
| $request_body | 客户端发送的报文体 |
| $request_body_file | 发送后端服务器的本地临时缓存文件的名称 |
| $request_filename | 当前请求的文件路径名,由root或alias指令与URI请求生成 |
| $request_method | 请求后端数据的方法,例如”GET”,”POST” |
| $request_uri | 请求的URI,带参数,不包含主机名 |
| $scheme | 所用的协议,如http或者HTTPS,比如rewrite^(.+)$$scheme://mysite.name$redirect |
| $sent_http_cache_control | 对应http请求头中的Cache-Control,需要打开chrome浏览器,右键检查,选中network,点中其中一个请求的资源 |
| $sent_http_connection | 对应http请求中的Connection |
| $sent_http_content_type | 对应http请求中的Content-Type |
| $sent_last_modified | 对应请求中的Last-Modified |
| $server_addr | 服务端的地址 |
| $server_name | 请求到达的服务器名 |
| $server_port | 请求到达服务器端口号 |
| $server_protocol | 请求协议的版本号,HTTP1.0/HTTP1.1 |
| $uri | 请求的不带请求参数的URI,可能和最初的值有不同,比如经过重定向之类的 |
| $request_time | nginx接收到请求到发送给client端数据包耗时 |
| $upstream_response_time | nginx后端服务器的响应时间 |
| $http_user_agent | 用户客户端UA标识 |
下面开始做nginx日志切割的配置了,对于nginx 的tcp和http协议都适用。
2. 全局通用配置,http或者stream模块里设置
首先通过nginx关键字map和内置变量time_iso8601拿到年月日,当然还可以按小时分钟甚至是按秒切割,按日志大小和需求情况来定
1http | stream { 2 #..... 3 #设置变量 year month day hour minutes seconds 4 map $time_iso8601 $year { 5 default '0000'; 6 "~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $1; 7 } 8 map $time_iso8601 $month { 9 default '00'; 10 "~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $2; 11 } 12 map $time_iso8601 $day { 13 default '00'; 14 "~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $3; 15 } 16 map $time_iso8601 $hour { 17 default '00'; 18 "~^^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $4; 19 } 20 map $time_iso8601 $minutes { 21 default '00'; 22 "~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $5; 23 } 24 map $time_iso8601 $seconds { 25 default '00'; 26 "~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $6; 27 } 28}
定义日志格式,nginx日志json化,日志格式化后续方便处理,比如入kafka或storm等流式处理
1http | stream { 2# 日志格式 3log_format logjsonv1 escape=json '{' 4 '"version": "logjsonv1", ' 5 '"hostname": "${hostname}", ' 6 '"remote_addr": "${remote_addr}", ' 7 '"remote_port": "${remote_port}", ' 8 '"proxy_protocol_addr": "${proxy_protocol_addr}", ' 9 '"http_x_forwarded_for": "${http_x_forwarded_for}", ' 10 '"upstream_addr": "${upstream_addr}", ' 11 '"time_iso8601": "${time_iso8601}", ' 12 '"request_method": "${request_method}", ' 13 '"scheme": "${scheme}", ' 14 '"server_name": "${host}", ' 15 '"server_port": "${server_port}", ' 16 '"uri": "${uri}", ' 17 '"args": "${args}", ' 18 '"status": "$status", ' 19 '"http_referer": "${http_referer}", ' 20 '"body_bytes_sent": "${body_bytes_sent}", ' 21 '"request_time": "${request_time}", ' 22 '"http_cookie": "${http_cookie}", ' 23 '"upstream_response_time": "${upstream_response_time}", ' 24 '"http_user_agent": "${http_user_agent}", ' 25 '"sent_http_location": "${sent_http_location}", ' 26 '"sent_http_content_type": "${sent_http_content_type}" ' 27'}'; 28 29 #记录日志,按天切割,也可以根据需要按小时切割 30 access_log logs/access_$year$month$day.log logjsonv1; 31 32 #..... 33}
我们还可以简单的map以下比如:
1 map $time_iso8601 $formatted_date { 2 default 'date-not-found'; 3 '~^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})' $year-$month-$day; 4 } 5access_log logs/access_$formatted_date.log logjsonv1;
注意:可能还有人说还可以用if啊,如下:
1 if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") { 2 set $year $1; 3 set $month $2; 4 set $day $3; 5 set $hour $4; 6 }
用if在http模块里是没有问题的,但在stream模块里不支持if语句,此方法就没有map方式通用。
以上就是本文重点tcp和http协议下怎样用nginx内置变量和命令做日志的自动切换,下面是map的简单介绍和三方工具logrotate实现的日志切割,感兴趣可以学习一下。
map介绍和三方工具logrotate实现日志切割
1. map用法
map 指令是由 ngx_http_map_module 或 ngx_stream_map_module模块提供的,默认情况下安装 nginx 都会安装该模块。
map 的主要作用是创建自定义变量,通过使用 nginx 的内置变量,去匹配某些特定规则,如果匹配成功则设置某个值给自定义变量。 而这个自定义变量便可作于它用,比如日志切割。
map语法
1http下: 2Syntax: map string $variable { ... } 3Default: — 4Context: http 5 6 7stream下: 8Syntax: map string $variable { ... } 9Default: — 10Context: stream 11
例子:
默认mobile 变量默认值是0,当http_user_agent匹配上Opera Mini后其值被赋值为1
1map $http_user_agent $mobile { 2 default 0; 3 "~Opera Mini" 1; 4}
2. logrotate 切割nginx日志
安装logrotate
yum install logrotate
安装完成后,自动在/etc/cron.daily/下生成个logrotate脚本文件。
1 2cd /etc/logrotate.d/ 3 4vim nginx 内容如下: 5 6/path/nginx/logs/*log { 7 su root www 8 create 600 www www 9 daily 10 dateext 11 rotate 10 12 missingok 13 notifempty 14 compress 15 sharedscripts 16 olddir /path/nginx/logs/oldlogs # 这个目录要事先创建好,并给相关权限 17 postrotate 18 kill -USR1 `cat /path/nginx/logs/nginx.pid` 19 endscript 20} 21
#设置定时任务
1crontab -e 2 30 0 * * * /usr/sbin/logrotate -f /etc/logrotate.d/nginx 4
除了三方工具我们还可以写脚本实现日志切割,脚本方式大家可以尝试一下。
nginx内置工具实现有一个弊端没有压缩和日志保留时长的处理,这个大家也可以思考一下怎么处理,大家可以在评论区留言讨论。
