来源: 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
拿到年月日,当然还可以按小时分钟甚至是按秒切割,按日志大小和需求情况来定
http | stream {
#.....
#设置变量 year month day hour minutes seconds
map $time_iso8601 $year {
default '0000';
"~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $1;
}
map $time_iso8601 $month {
default '00';
"~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $2;
}
map $time_iso8601 $day {
default '00';
"~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $3;
}
map $time_iso8601 $hour {
default '00';
"~^^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $4;
}
map $time_iso8601 $minutes {
default '00';
"~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $5;
}
map $time_iso8601 $seconds {
default '00';
"~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" $6;
}
}
定义日志格式,nginx
日志json
化,日志格式化后续方便处理,比如入kafka
或storm
等流式处理
http | stream {
# 日志格式
log_format logjsonv1 escape=json '{'
'"version": "logjsonv1", '
'"hostname": "${hostname}", '
'"remote_addr": "${remote_addr}", '
'"remote_port": "${remote_port}", '
'"proxy_protocol_addr": "${proxy_protocol_addr}", '
'"http_x_forwarded_for": "${http_x_forwarded_for}", '
'"upstream_addr": "${upstream_addr}", '
'"time_iso8601": "${time_iso8601}", '
'"request_method": "${request_method}", '
'"scheme": "${scheme}", '
'"server_name": "${host}", '
'"server_port": "${server_port}", '
'"uri": "${uri}", '
'"args": "${args}", '
'"status": "$status", '
'"http_referer": "${http_referer}", '
'"body_bytes_sent": "${body_bytes_sent}", '
'"request_time": "${request_time}", '
'"http_cookie": "${http_cookie}", '
'"upstream_response_time": "${upstream_response_time}", '
'"http_user_agent": "${http_user_agent}", '
'"sent_http_location": "${sent_http_location}", '
'"sent_http_content_type": "${sent_http_content_type}" '
'}';
#记录日志,按天切割,也可以根据需要按小时切割
access_log logs/access_$year$month$day.log logjsonv1;
#.....
}
我们还可以简单的map以下比如:
map $time_iso8601 $formatted_date {
default 'date-not-found';
'~^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})' $year-$month-$day;
}
access_log logs/access_$formatted_date.log logjsonv1;
注意:可能还有人说还可以用if啊,如下:
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
set $year $1;
set $month $2;
set $day $3;
set $hour $4;
}
用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语法
http下:
Syntax: map string $variable { ... }
Default: —
Context: http
stream下:
Syntax: map string $variable { ... }
Default: —
Context: stream
例子:
默认mobile
变量默认值是0,当http_user_agen
t匹配上Opera Mini
后其值被赋值为1
map $http_user_agent $mobile {
default 0;
"~Opera Mini" 1;
}
2. logrotate
切割nginx
日志
安装logrotate
yum install logrotate
安装完成后,自动在/etc/cron.daily/
下生成个logrotate
脚本文件。
cd /etc/logrotate.d/
vim nginx 内容如下:
/path/nginx/logs/*log {
su root www
create 600 www www
daily
dateext
rotate 10
missingok
notifempty
compress
sharedscripts
olddir /path/nginx/logs/oldlogs # 这个目录要事先创建好,并给相关权限
postrotate
kill -USR1 `cat /path/nginx/logs/nginx.pid`
endscript
}
#设置定时任务
crontab -e
0 0 * * * /usr/sbin/logrotate -f /etc/logrotate.d/nginx
除了三方工具我们还可以写脚本实现日志切割,脚本方式大家可以尝试一下。
nginx
内置工具实现有一个弊端没有压缩和日志保留时长的处理,这个大家也可以思考一下怎么处理,大家可以在评论区留言讨论。