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

NXLog CE 記憶體暴增案例

Jason Cheng 發布
NXLog CE 記憶體暴增案例
本文目錄
  1. 環境
  2. 從 nxlog.log 找線索
  3. 問題設定
  4. 超長行的由來
  5. 修正方式
  6. 順便整理設定檔
  7. 結論
  8. 參考資料

這是一個真實案例:一台 Windows 主機上的 NXLog CE 記憶體一直往上漲,最後把主機吃到很慢,重新啟動服務後過一陣子又來一次。設定檔看起來沒什麼特別,就是讀幾個應用程式的記錄檔、用 GELF 送到 Graylog。

最後查出來的原因是:應用程式的記錄裡有超長的單行,一行就有好幾 MB,NXLog 在處理字串時一直複製、串接,記憶體就爆了。這篇記錄追查過程與修正方式。

環境

項目內容
作業系統Windows Server
收集工具NXLog CE 3.2
來源Windows 事件記錄+5 個應用程式的文字記錄檔
傳送GELF over UDP 到 Graylog

從 nxlog.log 找線索

NXLog 自己的記錄在 C:\Program Files\nxlog\data\nxlog.log,打開就看到一大串警告:

2025-06-23 12:17:31 WARNING truncating oversized string (1557567) to StringLimit (1048576)
2025-06-23 12:17:31 WARNING truncating oversized string (2597567) to StringLimit (1048576)
2025-06-23 12:17:31 WARNING truncating oversized string (3702567) to StringLimit (1048576)
...
2025-06-23 12:17:31 WARNING truncating oversized string (11047567) to StringLimit (1048576)
2025-06-23 12:17:31 WARNING truncating oversized string (11161216) to StringLimit (1048576) in nx_string_clone()
2025-06-23 12:17:31 ERROR assignment failed at line 59, character 90 in C:\Program Files\nxlog\conf\nxlog.conf.
  statement execution has been aborted; binary operation failed at line 59, character 90 in C:\Program Files\nxlog\conf\nxlog.conf.
  expression evaluation has been aborted; oversized string (1048587), limit is 1048576 bytes

幾個重點:

  • StringLimit (1048576)
    NXLog 單一字串的上限是 1 MiB,超過就會被截斷
  • 字串大小從 1.5 MB 一路長到 11 MB
    同一秒內連續出現,代表有一行超長的內容正在被處理
  • line 59, character 90 的 assignment failed
    設定檔第 59 行的運算式,結果超過上限而失敗

問題設定

第 59 行是應用程式記錄的 Input:

<Input in_app_order>
    Module       im_file
    File         "D:\\logs\\OrderAPI\\*.log"
    SavePos      TRUE
    ReadFromLast TRUE
    InputType    LineBased
    Exec $ShortMessage = substr($raw_event, 0, 250); $FullMessage = "AppAPI|" + $raw_event;
</Input>

$FullMessage = "AppAPI|" + $raw_event 會產生一個「前綴+整行內容」的新字串。$raw_event 本身已經接近或超過 1 MiB 時,串接後的結果就超過 StringLimit,運算失敗。

而且處理過程中,NXLog 要同時保留原始的 $raw_event、截斷的副本、串接的結果、GELF 轉換時的 JSON 字串,一行 10 MB 的記錄可能在記憶體裡變成好幾十 MB。這種超長行一多,記憶體就撐不住。

超長行的由來

回頭檢查應用程式的記錄檔,發現有些行是整個物件序列化成一行 JSON,或是把整份 HTTP 回應內容直接寫進記錄,單行就有數 MB。可以用 PowerShell 找出記錄檔裡最長的幾行:

Get-ChildItem D:\logs\OrderAPI\*.log | ForEach-Object {
    $f = $_.FullName
    Get-Content $f -ReadCount 1000 | ForEach-Object { $_ } |
        Where-Object { $_.Length -gt 100000 } |
        ForEach-Object { [pscustomobject]@{ File = $f; Length = $_.Length } }
} | Sort-Object Length -Descending | Select-Object -First 10

修正方式

先截斷再處理

在每個 Input 的最前面,先把超過一定長度的 $raw_event 截斷,之後的運算就不會超過上限:

<Input in_app_order>
    Module       im_file
    File         "D:\\logs\\OrderAPI\\*.log"
    SavePos      TRUE
    ReadFromLast TRUE
    InputType    LineBased
    Exec if size($raw_event) > 262144 { $raw_event = substr($raw_event, 0, 262144); }
    Exec $ShortMessage = substr($raw_event, 0, 250);
    Exec $FullMessage  = "AppAPI|" + $raw_event;
</Input>
  • size()
    取得字串的位元組數
  • substr(字串, 起點, 終點)
    取出指定範圍
  • 拆成多行 Exec
    比塞在同一行好讀,出錯時 nxlog.log 的行號也比較好對

每個 Input 都要改,而且前綴要換成各自應用程式的名稱,不要整份複製貼上後全部都變成同一個前綴。

截斷長度的選擇

我選 256 KB(262144),原因是:

  • 遠低於 StringLimit 的 1 MiB,串接前綴、轉成 GELF 都不會超過
  • GELF over UDP 的單筆訊息有大小限制:GELF 的 UDP 分塊最多 128 塊,太大的訊息 Graylog 收不完整會直接丟掉;就算沒有截斷問題,超大訊息走 UDP 也送不到
  • 真正有意義的記錄內容,256 KB 一般都夠了

如果確實需要完整保留超大的記錄,建議改走 TCP(Graylog 開 GELF TCP input),而不是單純把 StringLimit 調大。

StringLimit 能不能調大

NXLog 有全域設定 StringLimit 可以調整上限,但調大只是讓更大的字串進到記憶體,記憶體問題只會更嚴重。除非很確定記錄大小有上限,否則不建議。

從源頭修正

最根本的還是請應用程式的開發人員調整:不要把整個物件或回應內容寫進記錄,需要的話只寫摘要或識別碼,完整內容另外存檔。記錄檔是給人追查問題用的,一行 10 MB 也沒有人看得完。

順便整理設定檔

原本的設定檔還有一些可以順便整理的地方:

<Extension _gelf>
    Module xm_gelf
</Extension>

<Input in_eventlog>
    Module im_msvistalog
</Input>

<Input in_app_order>
    Module       im_file
    File         "D:\\logs\\OrderAPI\\*.log"
    SavePos      TRUE
    ReadFromLast TRUE
    Exec if size($raw_event) > 262144 { $raw_event = substr($raw_event, 0, 262144); }
    Exec $ShortMessage = substr($raw_event, 0, 250);
    Exec $FullMessage  = "OrderAPI|" + $raw_event;
</Input>

<Output out_graylog>
    Module     om_udp
    Host       192.168.1.10
    Port       12201
    OutputType GELF
</Output>

<Route r_all>
    Path in_eventlog, in_app_order => out_graylog
</Route>
  • 原本有兩個 Output(out、out_graylog)送到同一台 Graylog 的同一個通訊埠,合併成一個就好
  • 每個應用程式用自己的前綴,到 Graylog 才分得出來源;更好的做法是另外設一個欄位,例如 $app = "OrderAPI";,用欄位分類比解析字串前綴可靠
  • 測試用的 Input(例如讀暫存目錄的那個)上線前記得拿掉

改完之後重新啟動服務,觀察記憶體與 nxlog.log:

Restart-Service nxlog
Get-Process nxlog | Select-Object Name, @{n='MemMB';e={[math]::Round($_.WorkingSet64/1MB,1)}}
Get-Content 'C:\Program Files\nxlog\data\nxlog.log' -Tail 20

結論

NXLog 記憶體暴增,第一步就是看它自己的 nxlog.log,truncating oversized string 這類警告很明確地指出問題。處理原則是在 Input 的最前面就截斷,不要讓超大的字串進到後面的運算;長期來說,還是要請應用程式不要寫出這種記錄。

收記錄的工具本身也要監控,NXLog 吃爆記憶體、或靜靜停止送資料,Graylog 那邊只會看到「這台最近很安靜」,很容易被忽略。

參考資料

Jason Cheng

Jason Cheng

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

關於作者 →

延伸閱讀