Nginx map 實現時間格式轉換

2023-09-14 15:00:21

哈嘍大家好,我是鹹魚

最近我們需要把 Nginx 的紀錄檔接入到自研的紀錄檔採集平臺上,但是這個平臺只支援 JSON 格式,所以需要把 Nginx 紀錄檔格式改成 JSON 格式

例如下面這樣的效果

剛開始在主組態檔 nginx.conf 中定義了一個名叫 json 的紀錄檔格式欄位

驗證的時候其他內容沒啥問題,但是時間是2023-09-12T13:54:22+08:00 這樣子的,不太符合預期


鹹魚想著把 $time_iso8601 變數中的年月日時分秒分別提取出來然後用變數去接受它,如下所示:

我自定義了一個時間格式 $year-$month-$day $hour:$minutes:$seconds:000,然後接著用了一個 if 語句用於檢查請求的時間是否匹配 ISO8601 時間格式(例如:2023-09-12T13:54:22+08:00

如果匹配,它將執行其中的程式碼塊。在程式碼塊中,使用正規表示式提取時間的年、月、日、小時、分鐘和秒,並將它們賦值給變數 $year$month$day$hour$minutes$seconds

    log_format json '{  "SYS_NO":"my_nginx",
                        "OS_TIME":"$year-$month-$day $hour:$minutes:$seconds",
                        "LOG_TYPE":"",
                        "RESULT":"$status",
                        "SPENT_TIME":"$request_time",
                        "CLIENT_IP":"$remote_addr",
                        "SERVER_IP":"$server_addr",
                        "O_CHANNEL":"$http_user_agent",
                        "UID":"$http_host",
                        "VALUE1":$body_bytes_sent,
                        "INFO1":"$http_referer",
					}';
	  if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})") {
                        set $year $1;
                        set $month $2;
                        set $day $3;
                        set $hour $4;
                        set $minutes $5;
                        set $seconds $6;
                        }

但是 nginx -t 檢測的時候我發現 if 語句不能夠放在 http 塊內,否則會報錯

在 Nginx 中,if 語句主要用於在 serverlocation 塊內設定條件,以便根據請求的屬性來執行不同的設定。if 語句通常應該包含在 serverlocation 塊內,而不是直接放在 http 塊中

如果將 if 語句放在一個一個 server 塊中,這不得累死我(有很多個 server 塊),而且後期維護也不方便

所以如何將自定義變數在全域性設定中生效則成為了一個問題

map

map 指令是由 ngx_http_map_module 模組提供的,是 Nginx 組態檔中的一種用於建立變數對映的指令

模組連結:Module ngx_http_map_module (nginx.org)

它允許我們將一個或多個輸入值對映到一個輸出值,類似於字典或雜湊表的概念。map 指令通常用於根據特定條件為請求設定自定義變數,或者執行基於請求屬性的條件控制

比較常見的 map 用法是通過 map 來實現允許多個域名跨域存取的問題

語法如下:

map $variable $new_variable {
    value1   result1;
    value2   result2;
    ...
    default  default_result;
}

其中:

  • $variable 輸入變數,通常是 nginx 的內建變數。
  • $new_variable 輸出變數,它將根據輸入值的對映設定為特定的結果。
  • value1, value2, ... 輸入值,可以列出多個值。
  • result1, result2, ... 與相應輸入值相關聯的輸出結果。
  • default 一個可選項,表示如果沒有匹配的輸入值,將使用預設結果

需要注意的是,map 只能放在 http 塊中

有了 map,我們就可以輕易的把 $time_iso8601 變數中的 ISO 8601 格式的時間戳轉換為指定的時間格式,然後儲存到自定義變數 $log_time

# nginx.conf
	map $time_iso8601 $log_time {
        default "";
        "~^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T(?<hour>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})" "${year}-${month}-${day} ${hour}:${minutes}:${seconds}";
    }

我們可以看到:

  • 輸入變數為 $time_iso8601,輸出變數為 $log_time
  • default "":如果沒有匹配任何條件,將使用空字串作為 $log_time 的值
  • "~^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T(?<hour>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})":這是一個正規表示式條件,它用於匹配 ISO 8601 格式的時間戳。該正規表示式包含了多個捕獲組(使用 ?<name> 語法),用於從時間戳中提取年、月、日、小時、分鐘和秒的值
  • "${year}-${month}-${day} ${hour}:${minutes}:${seconds}":這是與正規表示式條件匹配時設定的輸出值。當正規表示式條件匹配時,它會將捕獲組中提取的年、月、日、小時、分鐘和秒的值組合成一個自定義的時間格式,然後將該值設定為 $log_time 變數的值

例如,如果 $time_iso8601 的值是 "2023-09-09T14:30:00",則 $log_time 將被設定為 "2023-09-09 14:30:00"

然後我們再把 $log_time 放進我們自定義的紀錄檔格式裡面,完整設定如下

    log_format json '{  "SYS_NO":"my_nginx",
                        "OS_TIME":"$log_time",
                        "LOG_TYPE":"",
                        "RESULT":"$status",
                        "SPENT_TIME":"$request_time",
                        "CLIENT_IP":"$remote_addr",
                        "SERVER_IP":"$server_addr",
                        "O_CHANNEL":"$http_user_agent",
                        "UID":"$http_host",
                        "VALUE1":$body_bytes_sent,
                        "INFO1":"$http_referer",
		}';
					
	map $time_iso8601 $log_time {
    	default "";
        "~^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T(?<hour>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})" "${year}-${month}-${day} ${hour}:${minutes}:${seconds}";
    }

最後我們驗證一下