上一講中,咱們介紹瞭如何用SPL將一行日誌結構化爲一條記錄,今天則要說一下多行日誌對應一條記錄的狀況,咱們稱之爲不定行日誌。node
事實上,集算器本身的輸出日誌就是這種不定行日誌,咱們來看一下集算器節點機下的一個日誌文件rqlog. log,一樣摘錄兩段日誌:函數
[2018-05-14 09:20:20]性能
DEBUG: 臨時文件過時時間爲:12小時。fetch
[2018-05-14 09:20:20]線程
DEBUG: Temporary file directory is:日誌
D:\temp\esProc\nodes\127.0.0.1_8281\temp.blog
Files in temporary directory will be deleted on every 12 hours.內存
簡單分析一下這兩段日誌,每一段第一行都是方括號中的時間,緊接着第二行是用冒號分隔的日誌類型和日誌內容,隨後各行都是日誌內容的補充,直至遇到下一個方括號中的時間行。由此能夠肯定,結構化後的字段應該有三個:日誌時間、日誌類型和日誌內容。get
對於這種不定行的日誌,最直接的結構化思路就是逐行加載,並根據條件拼接成相應的字段。但這裏有個麻煩,因爲每條記錄對應的行數不肯定,因此須要另外經過條件判斷來識別每條記錄的起始行。另外一個問題就是當使用遊標分析比較大的文件時,因爲每次fetch的行數不可能老是恰好斷在一條記錄的末尾,因此在fetch的塊與塊之間還要考慮銜接問題。table
因此,對於這種類型的日誌,最理想的方法,是應該能把每一塊日誌與其它塊自動分開。這樣,就能夠直接將每一塊日誌解析到一條記錄,而不須要考慮讀取的行應該屬於上一條記錄仍是下一條記錄。
SPL中就有這樣的分組方式,形如A.group@i(exp),只不過其中的分組表達式exp不能返回枚舉值,而應該返回布爾值。而i選項則表示根據相鄰行計算exp,若是返回true就表示創建一個新組,其後面連續返回false的行都歸於這個組。
有了這樣的分組方式,咱們只要寫出能解析記錄第一行的exp就能夠了。本例中,記錄第一行是以方括號「[」開始的一個日期時間,若是可以肯定後面的內容中不會有以「[」開始的行,那麼分組表達式只需判斷每一行的第一個字符是否爲‘[’就能夠了。
下面是實現的腳本文件convertlog.dfx:
A |
B |
C |
D |
|
1 |
=create(日誌時間, 日誌類型, 日誌內容 ) |
=now() |
||
2 |
=file@s(fileName:"UTF-8") |
=A2.read@n() |
=B2.select(~!="") |
=C2.group@i(pos(~,"[")==1) |
3 |
=A2.name() |
=filename@d(A3) |
=filename@n(A3) |
|
4 |
=outFile=B3+"\\"+C3+".btx" |
=file(outFile) |
>movefile(B4) |
|
5 |
||||
6 |
for D2 |
=0 |
||
7 |
for A6 |
>B6=B6+1 |
||
8 |
if B6==1 |
=mid(B7,2,19) |
||
9 |
>A5=datetime(D8,"yyyy-MM-dd HHss") |
|||
10 |
next |
|||
11 |
if B6==2 |
>B5=substr@l(B7,":") |
||
12 |
>C5=C5|substr(B7,":") |
|||
13 |
next |
|||
14 |
>C5=C5|B7 |
|||
15 |
>A1.record([A5:C5]) |
>C5=[] |
||
16 |
if A1.len()>99999 |
|||
17 |
>B4.export@ab(A1) |
|||
18 |
>D4=D4+A1.len() |
>output("轉換完成:"/D4) |
||
19 |
>A1.reset() |
|||
20 |
>D4=D4+A1.len() |
>B4.export@ab(A1) |
>output("轉換完成:"/D4) |
|
21 |
=now() |
=interval@ms(B1,A21) |
Return |
|
22 |
>output("轉換爲 "+outFile+" 成功。耗時 "/B21+" 毫秒。") |
表(1)
腳本仍然是一次性將文件所有讀入,只是在分析過程當中,每湊夠十萬行就將其追加到輸出文件。下面是重點代碼的解析:
1) C2過濾空行,日誌的塊與塊之間可能有不少空行,此處用select函數選出非空行。
2) D2是本例的重點,按照每一行是否由‘[’開頭來進行分組,分組後的結果是每一塊對應一個序列,再由這些塊序列組成一個大的序列。
3) 第5行空了出來,實際上是預留A5到C5來容納解析後的3個字段。
4) 因爲D2返回的序列已是按照記錄對應的塊作好分組了,因此只需在A6中對D2序列循環便可。
5) B7到C14將每一塊日誌結構化到各個字段。
6) A20到C20將最後一段不夠十萬行的數據,寫到文件。
下面是當前代碼的執行結果:
圖(1)
上面這個例子介紹瞭如何用分組來結構化不定行的日誌。不過,分析前仍然導入了整個文件,佔用的內存較大。爲了減小內存佔用,咱們可使用遊標來處理。和上面這個例子很是相似,從文件獲得遊標後,緊接着的是和序列幾乎同樣的過濾、分組方法。細微的區別是因爲遊標中返回的分組是序表,而再也不是序列,所以須要用字段名‘_1’來引用各行的值。另外,使用遊標後,每次fetch獲得的是一個序表的序列,所以相對於全文導入,要多一層循環處理。
下面是實現的腳本文件convertLogCursor.dfx:
A |
B |
C |
D |
E |
|
1 |
=create(日誌時間, 日誌類型, 日誌內容 ) |
=now() |
|||
2 |
=file@s(fileName:"UTF-8") |
=A2.cursor@s() |
=B2.select(_1!="") |
=C2.group@i(pos(_1,"[")==1) |
|
3 |
=A2.name() |
=filename@d(A3) |
=filename@n(A3) |
||
4 |
=outFile=B3+"\\"+C3+".btx" |
=file(outFile) |
>movefile(B4) |
||
5 |
|||||
6 |
for C2,10000 |
for A6 |
=0 |
||
7 |
for B6 |
>C6=C6+1 |
|||
8 |
if C6==1 |
=mid(C7._1,2,19) |
|||
9 |
>A5=datetime(E8,"yyyy-MM-dd HHss") |
||||
10 |
next |
||||
11 |
if C6==2 |
>B5=substr@l(C7._1,":") |
|||
12 |
>C5=C5|substr(C7._1,":") |
||||
13 |
next |
||||
14 |
>C5=C5|C7._1 |
||||
15 |
>A1.record([A5:C5]) |
>C5=[] |
|||
16 |
if A1.len()>99999 |
||||
17 |
>B4.export@ab(A1) |
||||
18 |
>D4=D4+A1.len() |
>output("轉換完成:"/D4) |
|||
19 |
>A1.reset() |
||||
20 |
>D4=D4+A1.len() |
>B4.export@ab(A1) |
>output("轉換完成:"/D4) |
||
21 |
=now() |
=interval@ms(B1,A21) |
|||
22 |
>output("轉換爲 "+outFile+" 成功。耗時 "/B21+" 毫秒。") |
表(2)
能夠看到,除了用遊標時,表達式中須要使用缺省字段名‘_1’,以及多了一層循環取數,這個腳本和前面所有讀入文件的方式徹底一致。執行後能夠看到結果也跟convertLog.dfx徹底同樣。
看到這裏,細心的讀者可能會想到,是否是也能夠像上一講同樣,使用多路遊標來提高性能?答案是確定的,不過須要調整一下方法。若是隻是簡單使用cursor@m返回多路遊標的話,因爲線程遊標是由SPL自動建立的,每一個遊標會被自動分配一段屬於本身的數據,這就有可能會將原本屬於同一條記錄的數據塊,分配給了兩個線程,從而形成線程內部難以處理數據缺失的部分。若是不介意部分數據不完整,那麼能夠稍稍調整一下腳本,直接跳過殘缺的記錄。而若是對數據的完整性要求很嚴格的話,就須要將不完整的記錄頭和記錄尾返回給主程序,讓主程序來集中處理了。但要注意的是多路遊標的各線程遊標是自動建立的,線程之間的順序很差肯定,因此主程序拿到這些不完整的記錄頭和記錄尾,也會由於次序無法肯定而沒法正確銜接。所以,這時就須要改用 fork to(n)語句來主動建立順序線程了。fork語句所在的格子值就是當前線程的序號,而後在代碼段中建立局部遊標,並加載相應的數據段就能夠了。 不過,不管如何都會麻煩一些,因不是很常見,這裏也就再也不舉例了,有興趣的同窗能夠將它做爲一個練習題。
最後總結一下,不定行日誌的處理關鍵是肯定幾行日誌能夠「湊」成一條記錄。而利用集算器SPL中group函數的@i選項,就能夠定義日誌塊的分隔條件,從而簡潔方便地「庖丁解牛」了。
更多精彩內容,詳情請閱讀原文