這是一個真實案例:一台 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 那邊只會看到「這台最近很安靜」,很容易被忽略。
參考資料
- NXLog Community Edition 文件
https://docs.nxlog.co/ce/current/index.html - Graylog 文件:GELF
https://go2docs.graylog.org/current/getting_in_log_data/gelf.html



