忍者ブログ

FortigateのログをCSV形式に変換するスクリプト

2024年06月13日
ファイアウォールといえば、Fortigateが有名!

今動いているFortigateがどんな働きをしているかログを取ってみた。
が、「タイトル=値」の形式で冗長だし、
行ごとにタイトルが違っていて扱いにくいったらない。


中身を見る前にCSV形式で眺めたいと思い、スクリプトを作ってみた。




まずはFortigateが出力するログの特徴を整理。
試行錯誤の結果、以下のことがわかった。

①タイトル=値になっている
②半角スペースで区切られている
③タイトルは行によって違うことがある
④値は、ダブルコーテーションで括られる場合とそうでない場合がある
⑤ダブルコーテーションがある場合は、その中に=やスペース、カンマが含まれる可能性がある
⑥ログファイルはUTF8形式



ここからアルゴリズムの検討。

タイトル抽出
①と②からログデータの各行を半角スペースで区切って、=より左側をタイトル扱いする。

③から、タイトルをキー、列番号を値として辞書配列に格納して、
=より右側を出力用配列とする。

④と⑤より、=の後がダブルコーテーションの場合は、次のダブルコーテーションまでは値として抽出する。
そうでない場合はスペースより左側までを抽出する。

⑥は、ADOBEオブジェクトでフォーマット指定が必要。




こうやって書いてしまうとめっちゃ簡単な内容ではあるけど、
作るのに結構苦労した。。。


タイトル文字列は、部分一致するパターンでバグが発生。
対策として、=を含めて検索したうえで値を抽出する仕様に変更した。


タイトルの抽出処理は合っていたけど、⑥のフォーマット確認を失念していて、
タイトルが正しく抽出できないバグが発生。これは見つけるの大変だった。


また、ダブルコーテーションの中にカンマが含まれるのもうっかり。
出力するCSV形式は、区切り文字に‡を採用して、ファイル開いてから手動で区切る仕様に。

ダブルコーテーションの中に=も含まれていたので、
これもタイトル抽出で失敗する原因。

タイトル抽出時にダブルコーテーションで括られた部分を空白に置換する処理を追加。


値自体を空白に置換するんだったら、入っていて困る文字列を予めゼット(絶対に使われていない)な
文字列に置換して最後に戻せば良かったなぁ。


そんな感じで以下ソース。
今回はCopilotにソースコードレビューしてもらったので、結構見やすいと思う。



Option Explicit
const inDlmt = " " '入力区切り文字(1文字)
const outDlmt = "‡" '出力区切り文字
const cnsFname = "input2.log"
'テキストファイルを読み込み
dim inputText : inputText = readText_UTF8(cnsFname)
dim inputLines : inputLines = split(inputText,chr(10))
dim i,imax : imax = ubound(inputLines)
dim Dic : set Dic = CreateObject("Scripting.Dictionary")
dim oArr() : redim oArr(imax + 1,0)
for i = 0 to imax
call upDateArr(Dic,oArr,inputLines,i)
next 'i
dim outputline,j
for i = 0 to ubound(oArr,1)
outputline = ""
for j = 0 to ubound(oArr,2)
outputline = outputline & outDlmt & oArr(i,j)
next 'j
'oArrを出力
call WriteCSV(mid(outputline ,len(outDlmt)+1,len(outputline)) )
next 'i = 0 to imax
'inputLines(i)からタイトル文字列と列番号を抽出、Dic,oArrを更新して返す
sub upDateArr(Dic,oArr,inputLines,i)
dim textLine : textLine = inputLines(i)
dim dryText : dryText = getDryText(textLine)
dim Buf : Buf = split(dryText,"=") 'textLineを=で区切ったもの
dim k, kmax : kmax = ubound(Buf)
dim strTitle
for k = 0 to kmax -1
'右から=を捜して、SPから=をタイトルとする。
strTitle = mid(Buf(k),instrRev(Buf(k),inDlmt) + 1) 'タイトル文字列(=より左側でSPより右側)
'タイトルが未出であれば、列番号を振って辞書に登録 
call addTitle(Dic,strTitle,oArr)
'左から「タイトル=」を探して、=の直後が " なら次の " まで、それ以外は SPの直前までを取り出す
oArr(i+1,dic(strTitle)) = getContents(textLine,strTitle)
next 'k = 0 to kmax - 1
end sub
'引用符の中身を除外する関数
function getDryText(textLine)
dim tBuf : tBuf = split(textLine,"""")
dim i 
for i = 0 to ubound(tBuf)
if i mod 2 > 0 then
tBuf(i)=""
end if
next 'i
getDryText = join(tBuf,"""")
end function
'タイトルを辞書に追加
sub addTitle(Dic,strTitle,oArr)
if Dic.exists(strTitle) = False then
dim cCnt : cCnt = Dic.count
Dic.Add strTitle , cCnt
redim preserve oArr(ubound(inputLines)+1,cCnt)
oArr(0,cCnt) = strTitle
end if
end sub
'ログとタイトルからコンテンツを取得
function getContents(textLine,strTitle)
dim kPos : kPos = instr(textLine,strTitle & "=" ) + len(strTitle & "=" ) 'キーワードの位置
dim ePos
if mid(textLine,kPos,1) = """" then
ePos = instr(kPos + 1 , textLine,"""") '終了位置は"まで
else
ePos = instr(kPos , textLine," ") - 1 '終了位置はSPの前まで
end if
getContents = Mid(textLine, kPos, ePos - kPos + 1)
end function
'Textファイルを読み込む関数
function readText(fname)
dim Path
if len(fname)-len(replace(fname,"\","")) = 0 then '\を含まないならファイル名と解釈
Path = left(Wscript.scriptFullname,instrrev(Wscript.scriptFullname,"\")) 
end if
    ' ファイルの内容を読み込む処理
    readText = CreateObject("Scripting.FileSystemObject").OpenTextFile(Path & fname, 1, False).ReadAll
end function
'Textファイルを読み込む関数UTF8
function readText_UTF8(fname)
dim Path
if len(fname)-len(replace(fname,"\","")) = 0 then '\を含まないならファイル名と解釈
Path = left(Wscript.scriptFullname,instrrev(Wscript.scriptFullname,"\")) 
end if
 
With CreateObject("ADODB.Stream")
.Open
.Type = 2 ' テキストモード
.Charset = "UTF-8"
.LoadFromFile Path & fname
readText_UTF8 = .ReadText ' ファイルの内容を取得
    .Close
End With
end function
' CSVファイル出力する関数
dim csvFlag '上書き用フラグ
Sub WriteCSV(text)
    ' ログファイルのパス
Dim logFilePath : logFilePath = "output.csv"
    ' ファイルシステムオブジェクトの作成
    Dim fso, logFile
    Set fso = CreateObject("Scripting.FileSystemObject")
 
    ' ログファイルが存在するかチェックし、存在しなければ作成
    If Not fso.FileExists(logFilePath) Then
        Set logFile = fso.CreateTextFile(logFilePath)
csvFlag = 1
    Else
if csvFlag = 0 then
        Set logFile = fso.OpenTextFile(logFilePath, 2) ' 8 = アペンド
csvFlag = 1
else
        Set logFile = fso.OpenTextFile(logFilePath, 8) ' 8 = アペンド
    End If
End If
 
    ' テキストをログファイルに書き込む
    logFile.WriteLine text
    logFile.Close
 
    ' オブジェクトの解放
    Set logFile = Nothing
    Set fso = Nothing
End Sub
sub errorEnd(msg)
msgbox msg
Wscript.Quit
end sub
PR
Comment
  Vodafone絵文字 i-mode絵文字 Ezweb絵文字