跳到主要內容
節省工具箱Jason Tools
經驗分享

NXLog 以記錄時間覆寫 timestamp

Jason Cheng 發布
NXLog 以記錄時間覆寫 timestamp
本文目錄
  1. 時間是怎麼決定的
  2. 範例記錄
  3. 做法一:在 NXLog 解析時間
  4. 做法二:在 Graylog 用 Pipeline 覆寫
  5. 兩種做法比較
  6. 注意事項
  7. 結論
  8. 參考資料

用 NXLog 收應用程式的文字記錄檔送到 Graylog 時,常會發現 Graylog 上的時間和記錄裡寫的時間對不起來。原因是 NXLog 預設用「讀到這一行的時間」當作訊息時間,而不是記錄本身發生的時間。

平常讀取即時寫入的檔案,誤差只有幾秒,看不太出來;但只要 NXLog 重新啟動、補讀一批舊檔案,或是網路中斷一段時間,所有訊息的時間就會擠在同一刻,在 Graylog 依時間查詢、做告警就全部失準。這篇整理兩種把記錄裡的時間(以下稱 LogEventTime)蓋回 timestamp 的做法。

時間是怎麼決定的

我去看了 NXLog CE 的原始碼,xm_gelf 產生 GELF 的 timestamp 時,依序取:

順序欄位說明
1$EventTime必須是 datetime 型別
2$EventReceivedTimeNXLog 讀到這筆的時間
3當下時間都沒有的話

用 im_file 讀文字檔時,NXLog 不會自己解析每一行的時間,所以 $EventTime 是空的,最後送出去的就是 $EventReceivedTime。

另外有一個細節:NXLog CE 送出的 GELF timestamp 只到秒,原始碼裡會把微秒除掉。如果記錄的時間有毫秒,而你需要依毫秒排序,就要用後面 Graylog 端的做法。

範例記錄

以下面這種常見格式為例:

2026-01-19 10:15:32.1234 INFO  [OrderService] order created, id=10023
2026-01-19 10:15:32.5678 WARN  [OrderService] payment timeout, retry=1

做法一:在 NXLog 解析時間

在 NXLog 端用正規表示式取出時間,轉成 datetime 指給 $EventTime,GELF 的 timestamp 就會直接用它:

<Input in_app>
    Module       im_file
    File         "D:\\logs\\app\\*.log"
    SavePos      TRUE
    ReadFromLast TRUE
    Exec if $raw_event =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\.(\d+)/ \
         { \
             $LogEventTime = $1 + "." + $2; \
             $EventTime = strptime($1, "%Y-%m-%d %H:%M:%S"); \
         }
    Exec $ShortMessage = substr($raw_event, 0, 250);
</Input>
  • strptime()
    依指定格式把字串轉成 datetime,NXLog CE 內建
  • $LogEventTime
    保留含毫秒的原始時間字串,一起送到 Graylog 備查
  • 時區
    strptime() 以 NXLog 主機的本地時區解讀,主機時區要設對

優點是 Graylog 不用另外設定;缺點是只到秒,而且每種記錄格式都要在 NXLog 寫一次。

做法二:在 Graylog 用 Pipeline 覆寫

NXLog 只負責把時間字串取出來放進 LogEventTime 欄位(做法一的 $LogEventTime = ... 那行即可,不用設 $EventTime),由 Graylog 的 Pipeline Rule 轉成 timestamp:

rule "LogEventTime_Override_Timestamp"
when
  has_field("LogEventTime")
then
  let ts = parse_date(
    value:    to_string($message.LogEventTime),
    pattern:  "yyyy-MM-dd HH:mm:ss.SSSS",
    locale:   "en",
    timezone: "Asia/Taipei"
  );
  set_field("received_timestamp", $message.timestamp);
  set_field("timestamp", ts);
end
  • pattern
    Joda 格式,SSSS 對應 4 位數的小數秒,要和記錄的格式一致
  • timezone
    記錄裡的時間是哪個時區就填哪個,台灣是 Asia/Taipei;沒填會被當成 UTC,時間會差 8 小時
  • received_timestamp
    覆寫前先把原本的時間存起來,日後要查「延遲了多久才收到」時也可以使用

這個做法保留了毫秒,也可以集中管理,所有 NXLog 只要負責送字串就好。

格式不符的保護

記錄裡偶爾會有不是時間開頭的行(例如例外的堆疊追蹤),parse_date() 解析失敗會讓這筆訊息處理出錯。可以在條件裡先用正規表示式確認格式:

rule "LogEventTime_Override_Timestamp"
when
  has_field("LogEventTime")
  && regex("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{4}$", to_string($message.LogEventTime)).matches == true
then
  let ts = parse_date(to_string($message.LogEventTime), "yyyy-MM-dd HH:mm:ss.SSSS", "en", "Asia/Taipei");
  set_field("received_timestamp", $message.timestamp);
  set_field("timestamp", ts);
end

套用到串流

Pipeline 建立後要連結到這些記錄會進入的串流,並在「System → Configurations → Message Processors」確認 Pipeline Processor 有啟用。設定前可以用 Pipeline 頁面的「Simulate processing」貼一筆訊息試看看結果。

兩種做法比較

項目NXLog 端(EventTime)Graylog 端(Pipeline)
精確度秒毫秒
設定位置每台 NXLogGraylog 集中管理
時區依 NXLog 主機規則裡明確指定
解析失敗不設 EventTime,退回接收時間要自己加條件保護
Graylog 負擔無每筆多一次解析

我自己的習慣是用做法二:NXLog 的設定越簡單越好,格式、時區這些規則集中在 Graylog,改起來也方便。

注意事項

  • 時間差太多的訊息
    覆寫成很久以前的時間後,訊息仍然寫進目前的 index,但依時間範圍查詢時只會看到落在範圍內的。補讀大量舊記錄時,查詢要記得把時間範圍拉大
  • 伺服器時間要同步
    產生記錄的主機時間不準,覆寫後反而更亂,所有主機都要設好 NTP
  • Windows 事件記錄不用這樣做
    im_msvistalog 讀到的事件本身就帶有 EventTime,NXLog 會直接使用

結論

記錄的時間是事後追查的基準,用「收到的時間」代替「發生的時間」,平常看不出差別,真的出事要對時間軸時才會發現對不上。用 NXLog 收文字記錄檔的話,建議一開始就把時間解析做好。

參考資料

Jason Cheng

Jason Cheng

企業開源技術顧問,專注架構規劃與實務導入

關於作者 →

延伸閱讀