nginx是一個高性能的HTTP和反向代理服務器,以其輕量級、資源佔用少、併發能力強等優點被廣泛使用。
在使用過程中,我們常常希望能夠將接到的請求記錄到日誌以便統計分析或問題排查,自然少不了做日誌管理。本人近期剛剛開始學習nginx的使用,總結遇到的日誌管理方面的問題整理如下(本文使用nginx-1.12.1版本):
自定義日誌格式
在nginx 的日誌目錄logs下,默認有以下3個文件:
- access.log:記錄請求日誌
- error.log:記錄錯誤日誌,例如啓動時端口被佔用,請求的資源不存在等。
- nginx.pid:記錄啓動時nginx的進程id
nginx的日誌配置是在conf/nginx.conf文件中,初始內容如下(省略部分被註釋的內容):
#user nobody;#運行worker process的用戶
worker_processes 1;#worker 進程數據,通常可以設置爲cpu核數
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;#進程pid文件
events {
worker_connections 1024;#每個worker process能處理的最大連接數
}
http {
include mime.types;
default_type application/octet-stream;
#nginx默認access日誌格式
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#默認access日誌路徑
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;#監聽端口
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
#默認請求匹配路徑
location / {
root html;#請求的根目錄是html,即從html目錄查找資源
index index.html index.htm;#默認首頁
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
從配置文件中我們可以看出,access的默認日誌格式爲:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
各字段含義如下:
字段 | 含義 | 示例 |
---|---|---|
remote_addr | 客戶端地址 | 211.28.65.253 |
remote_user | Auth Basic Module驗證的用戶名 | test |
time_local | 訪問時間和時區 | 24/Oct/2017:19:47:17 +0800 |
request | 請求的URI和HTTP協議 | GET /index.html HTTP/1.1 |
status | HTTP請求狀態 | 200 |
body_bytes_sent | 發送給客戶端文件內容大小 | 612 |
http_referer | url跳轉來源 | https://www.baidu.com |
http_user_agent | 用戶終端瀏覽器等信息 | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36 |
http_x_forwarded_for | 簡稱XFF頭,它代表客戶端,也就是HTTP的請求端真實的IP,只有在通過了HTTP 代理或者負載均衡服務器時纔會添加該項,第一個值爲真實的ip | 10.21.74.53, 10.29.83.46 |
利用這些內置的變量,我們就可以進行日誌格式的自定義,我們甚至可以將日誌格式變爲json的,只需修改配置如下:
log_format json_log escape=json '{"timestamp": "$time_local", '
'"remote_addr": "$remote_addr", '
'"remotr_user": "$remote_user", '
'"referer": "$http_referer", '
'"request": "$request", '
'"status": $status, '
'"bytes":$body_bytes_sent, '
'"agent": "$http_user_agent", '
'"x_forwarded": "$http_x_forwarded_for", '
'"upstr_addr": "$upstream_addr",'
'"upstr_host": "$upstream_http_host", '
'"request_body": "$request_body", '
'"ups_resp_time": "$upstream_response_time"}';
注意要在配置中加上escape=json。
記錄post請求內容
默認情況下,nginx只能響應GET方式的靜態資源請求,如果發送POST方式的靜態資源請求,則會得到如下信息:
<html>
<head>
<title>405 Not Allowed</title>
</head>
<body bgcolor="white">
<center>
<h1>405 Not Allowed</h1>
</center>
<hr>
<center>nginx/1.12.1</center>
</body>
</html>
有時,我們希望能夠使用POST方式來請求靜態資源,同時發送一些參數,比如我最近就遇到了這種需求,希望通過POST請求來記錄請求日誌,包括請求中的參數。
關於405的問題,網上給出的解決方法主要是兩種:
(1)將405重定向到200
(2)修改源代碼
個人覺得這兩種方式都不是很好,最終查到了另一種更優雅的方式:nginx+Lua。
具體做法如下:
(1)下載LuaJIT並安裝,本人安裝的爲2.0.5版本,下載地址:http://luajit.org/download.html
安裝完成後,設置環境變量(編輯~/.bashrc):
export LUAJIT_LIB=/home/work/local/LuaJIT-2.0.5/usr/local/lib
export LUAJIT_INC=/home/work/local/LuaJIT-2.0.5/usr/local/include/luajit-2.0
其中變量指向安裝目錄中的相應資源。
(2)下載lua-nginx-module,並解壓,無需安裝,本人使用的是0.10.10版本。下載地址:https://github.com/openresty/lua-nginx-module/releases
(3)安裝nginx:
./configure –prefix=安裝目錄 –with-pcre=pcre源碼路徑 –add-module=lua-nginx-modul解壓後路徑
make -j2
make install
額外補充一點,pcre是安裝nginx依賴的另一個包,因爲與主題無關,因此沒有專門交代。
(4)修改nginx.conf配置文件
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$request_body" '
'"$http_user_agent" "$http_x_forwarded_for"';
location / {
lua_need_request_body on;
content_by_lua 'local s = ngx.var.request_body';
root html;
index index.html index.htm;
}
注意:我們使用到另一個內置變量:request_body
,該變量的值就是客戶發送的POST請求的請求體。
各位看官看到這裏,難免會問:既然nginx已經給我們提供了request_body
變量,那我們直接用不就行了嗎?幹嘛還要扯出Lua呢?
這個問題是這樣:nginx中讀取POST數據必須要調用ngx_http_read_client_request_body()函數,而默認情況下,這個函數是不會被調用的。(莫名其妙???so am i)
那麼我們怎麼能夠調用這個函數呢?這就要藉助於ngx_lua解決,只要在輸出log之前讀一遍request_body,就可以調用該函數,配置如上面所示。
日誌分割
nginx默認不會對日誌做分割,隨着時間的積累,access.log和error.log將會越來越大,非常不便於我們查看日誌。
在linux下進行日誌分割的一個常用工具是logrotate
。logrotate
功能非常強大,除了支持日誌按照一定規則分割,還支持對歷史日誌進行壓縮,過期日誌刪除等常見操作,可謂是日誌管理的瑞士軍刀。
logrotate
的使用非常簡單,只需寫一個配置文件,基本格式如下:
/home/work/local/nginx-1.12.1/logs/access.log {
daily #日誌分割的週期,共有daily,weekly,monthly,yearly幾種
rotate 7 #保留多少份歷史日誌,爲7表示保留1周的歷史日誌
nocompress #是否將歷史日誌進行壓縮
missingok # 如果遇到文件無法找到,繼續執行,忽略錯誤
dateext #以日期作爲歷史文件結尾
dateformat .%Y-%m-%d。#與dateext配合使用,設定後綴格式
sharedscripts #在所有的日誌文件都輪轉完畢後統一執行一次腳本
postrotate # 指定日誌輪滾完成後執行的腳本
kill -USR1 `cat /home/work/local/nginx-1.12.1/logs/nginx.pid`
endscript
}
需要注意的是:kill -USR11
的作用是給nginx進程發送一個指令,讓nginx重新加載配置,目的是更新日誌文件句柄,使新的日誌輸出到新的日誌文件中。
logrotate
只負責對日誌的管理,定時執行還要靠crontab
。有關crontab的配置和使用網上很多,這裏就不再贅述了。
在crontab定時任務中配置:
0 0 * * * /usr/sbin/logrotate -f /home/work/local/nginx-1.12.1/conf/access.cron
這樣,系統就會在每天0點0分執行日誌輪滾操作。logrotate -f
的作用是讓輪滾操作強制執行。
如果定時任務是在非root賬號下執行,在運行過程中可能會報如下錯誤:
error: error creating state file /var/lib/logrotate/status: Permission denied
這是由於logrotate默認會將執行記錄寫入到/var/lib/logrotate/status文件中,而非root賬號不具有該文件的寫權限。
解決該問題,可以通過在執行logrotate命令時指定-s
參數,重新指定一個具有權限的文件即可:
/usr/sbin/logrotate -s /home/work/local/logrotate.status
使用crontab + logrotate是linux下非常理想的日誌定時管理方式。略有遺憾的是,logrotate
不支持小時粒度的輪滾。當然,我們是可以設置定時任務每小時執行一次,但是文件後綴不支持設置小時,最多隻能設置到天級別。如果要使用logrotate
做日誌小時粒度的分割並以小時作爲日誌文件後綴,還需要結合一些shell腳本來實現(主要邏輯是對分割後的文件做一次重命名)。
參考資料:
本文已遷移至我的博客:http://ipenge.com/33504.html