TFDMemTableでApacheのログを解析する ~Delphiソースコード集
TFDMemTableはインメモリデータセットだそうです。
メモリ上で表(テーブル)を扱えます。これを使ってApacheのログを簡易に解析してみます。
プロジェクトの作成と画面設計
プロジェクトを新規作成(VCLアプリケーション)します。
フォーム(Form1)上に、TPanelを1個配置してプロパティ[Align]の値を「alTop」に設定し、TDBGridを1個を配置してプロパティ[Align]の値を「alClient」に設定します。
配置したPanel1上に、TButtonを2個、TLabelを1個、TCheckBoxを1個、TMemoを1個を配置します。
TOpenDialog、TFDMemTable、TDataSource、TFDStanStorageJsonLinkを1個を配置します。
ソースコードの記述
Form1.OnCreate、Form1.OnClose、DBGrid1.OnTextTitleClick、CheckBox1.OnClick、Button1.OnClick、Button2.OnClickなどのプロパティなど以下ソースコードを記述する。
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, Vcl.StdCtrls, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, FireDAC.Stan.StorageJSON, Vcl.Grids, Vcl.DBGrids, Vcl.ExtCtrls,FireDac.Stan.ExprFuncs; type TForm1 = class(TForm) FDMemTable1: TFDMemTable; OpenDialog1: TOpenDialog; FDStanStorageJSONLink1: TFDStanStorageJSONLink; DataSource1: TDataSource; DBGrid1: TDBGrid; Panel1: TPanel; Button1: TButton; Button2: TButton; Memo1: TMemo; Label1: TLabel; CheckBox1: TCheckBox; procedure Button1Click(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); procedure DBGrid1TitleClick(Column: TColumn); procedure CheckBox1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} function getYMD(d:String):TDate; const MonthArr:array[0..11] of string= ('Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'); var v:TArray<string>; m:integer; i:integer; begin // '01/Jan/2023'のような文字列を 日付型に変換する v:=d.Split(['/']); m:=1; for i := Low(MonthArr) to High(MonthArr) do begin if MonthArr[i]=v[1] then begin m:=i+1; break; end; end; Result:=EncodeDate( strtoint(v[2]), m, strtoint(v[0]) ); end; procedure TForm1.Button1Click(Sender: TObject); var stl1,stl2:TStringList; st,st2,st3:string; i,n:Integer; begin //ApacheのCombined Logの読み込み st:=OpenDialog1.FileName; OpenDialog1.InitialDir:=ExtractFileDir(st); OpenDialog1.FileName:=ExtractFileName(st); if not OpenDialog1.Execute then exit; stl1:=TStringList.Create; stl2:=TStringList.Create; FDMemTable1.DisableControls; try stl2.StrictDelimiter:=True; stl2.Delimiter:=' '; stl2.QuoteChar:='"'; //選択ログファイル数でループ for n := 0 to OpenDialog1.Files.Count-1 do begin stl1.LoadFromFile(Opendialog1.Files[n]); //ログファイルの行でループ for i := 0 to stl1.Count-1 do begin stl2.DelimitedText:=stl1[i]; //ステータスコードが300未満のみ取り込む if StrToIntDef(stl2[6],200)<300 then begin //UserAgentがクローラーロボットらしきログは取り込まない if (Pos('http',LowerCase(stl2[9]))=0) and (Pos('bot',LowerCase(stl2[9]))=0) and (Pos('mediapartners-google',LowerCase(stl2[9]))=0) and (Pos('crawler',LowerCase(stl2[9]))=0) then begin //URLの拡張子の確認 st:=stl2[5].Split([' '])[1]; st2:=st.Split(['?'])[0]; st3:=LowerCase(ExtractFileExt(st2)); //解析対象拡張子を".html"又は".php"又は"/"のみとする if (st3='.html') or (st3='.php')or (st2.Substring(Length(st2)-1)='/') then begin FDMemTable1.Insert; FDMemTable1.FieldByName('IP').AsString:=stl2[0]; FDMemTable1.FieldByName('DATE').AsDateTime:= getYMD(stl2[3].Substring(1,11)); FDMemTable1.FieldByName('HOUR').AsInteger:= StrToIntDef(stl2[3].Substring(13,2),0); FDMemTable1.FieldByName('URL').AsString:=stl2[5].Split([' '])[1]; FDMemTable1.FieldByName('STATUS').AsInteger:= StrToIntDef(stl2[6],200); FDMemTable1.FieldByName('REFERER').AsString:=stl2[8]; FDMemTable1.FieldByName('AGENT').AsString:=stl2[9]; FDMemTable1.Post; end; end; end; end; end; Label1.Caption:='レコード数:'+IntToStr(FDMemTable1.RecordCount); finally stl1.Free; stl2.Free; FDMemTable1.EnableControls; end; end; procedure TForm1.Button2Click(Sender: TObject); begin //レコードを空っぽにする if MessageBox(self.Handle,'データを削除します','確認',MB_YESNO)=idYes then FDMemTable1.EmptyDataSet; end; procedure TForm1.CheckBox1Click(Sender: TObject); begin if CheckBox1.Checked then begin Memo1.Enabled:=False; FDMemTable1.Filtered:=False; FDMemTable1.Filter:=Memo1.Lines.Text; FDMemTable1.Filtered:=True; Label1.Caption:='レコード数:'+IntToStr(FDMemTable1.RecordCount); end else begin Memo1.Enabled:=True; FDMemTable1.Filtered:=False; FDMemTable1.Filter:=''; Label1.Caption:='レコード数:'+IntToStr(FDMemTable1.RecordCount); end; end; procedure TForm1.DBGrid1TitleClick(Column: TColumn); var st:String; v:TArray<String>; begin st:=FDMemTable1.IndexFieldNames; if st='' then //降順 FDMemTable1.IndexFieldNames:=Column.FieldName+':DN' else begin v:=st.Split([':']); if v[0]=Column.FieldName then begin if v[1]='DN' then //昇順 FDMemTable1.IndexFieldNames:=Column.FieldName+':AN' else //降順 FDMemTable1.IndexFieldNames:=Column.FieldName+':DN'; end else //降順 FDMemTable1.IndexFieldNames:=Column.FieldName+':DN'; end; FDMemTable1.First; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin FDMemTable1.SaveToFile('db.json'); end; procedure TForm1.FormCreate(Sender: TObject); var fd:TFieldDef; begin if FileExists('db.json') then begin //保存したデータベースファイルがある場合 FDMemTable1.LoadFromFile('db.json'); Label1.Caption:='レコード数:'+IntToStr(FDMemTable1.RecordCount); end else begin //保存したデータベースファイルがない場合 //カラム定義を設定する fd:=FDMemTable1.FieldDefs.AddFieldDef; fd.Name:='IP'; fd.DataType:=ftString; fd.Size:=20; fd:=FDMemTable1.FieldDefs.AddFieldDef; fd.Name:='DATE'; fd.DataType:=ftDate; fd:=FDMemTable1.FieldDefs.AddFieldDef; fd.Name:='HOUR'; fd.DataType:=ftInteger; fd:=FDMemTable1.FieldDefs.AddFieldDef; fd.Name:='URL'; fd.DataType:=ftString; fd.Size:=2048; fd:=FDMemTable1.FieldDefs.AddFieldDef; fd.Name:='STATUS'; fd.DataType:=ftInteger; fd:=FDMemTable1.FieldDefs.AddFieldDef; fd.Name:='REFERER'; fd.DataType:=ftString; fd.Size:=2048; fd:=FDMemTable1.FieldDefs.AddFieldDef; fd.Name:='AGENT'; fd.DataType:=ftString; fd.Size:=4096; FDMemTable1.CreateDataSet; end; DataSource1.DataSet:=FDMemTable1; DBGrid1.DataSource:=DataSource1; DBGrid1.Columns[0].Width:=-Self.Font.Height*10; DBGrid1.Columns[1].Width:=-Self.Font.Height*6; DBGrid1.Columns[2].Width:=-Self.Font.Height*2; DBGrid1.Columns[3].Width:=-Self.Font.Height*10; DBGrid1.Columns[4].Width:=-Self.Font.Height*5; DBGrid1.Columns[5].Width:=-Self.Font.Height*10; DBGrid1.Columns[6].Width:=-Self.Font.Height*10; DBGrid1.Columns[0].Title.Caption:='IP'; DBGrid1.Columns[1].Title.Caption:='日付'; DBGrid1.Columns[2].Title.Caption:='時'; DBGrid1.Columns[3].Title.Caption:='URL'; DBGrid1.Columns[4].Title.Caption:='ステータス'; DBGrid1.Columns[5].Title.Caption:='Referer'; DBGrid1.Columns[6].Title.Caption:='Agent'; FDMemTable1.FilterOptions:=[foCaseInsensitive, foCaseInsensitive]; FDMemTable1.AggregatesActive:=True; Memo1.Clear; Memo1.ScrollBars:=ssVertical; Memo1.Lines.Add('DATE between ''2023/09/19'' and ''2023/09/19'''); Memo1.Lines.Add(' and URL like ''%delphi%'''); CheckBox1.Caption:='フィルタ有効( IP DATE HOUR URL STATUS REFERER AGENT )'; CheckBox1.Width:=-Self.Font.Height*36; OpenDialog1.Filter:='Apacheログファイル(*.log)|*.log'; Button1.Caption:='データの追加'; Button2.Caption:='データの削除'; //複数ファイル選択を許可する OpenDialog1.Options:=[ofAllowMultiSelect,ofEnableSizing]; end; end.
実行する
実行ボタンを押して実行します。(デバッグ実行でもOK)
Apacheのログを取り込む
「データの追加」ボタンを押して、Apacheのログファイルを選択し、開くボタンを押します。
「データの追加」ボタンを押して、Apacheのログファイルを選択し、開くボタンを押します。
フィルタを有効にして分析する
フィルタに
DATE='2023/09/19'
と入力して「フィルタ有効」にチェックを入れると、2023/09/19のログだけを表示することができます。
フィルタに
DATE='2023/09/19' and URL like '%delphi%'
と入力して「フィルタ有効」にチェックを入れると、2023/09/19で、URLに「delphi」の文字が含まれるログだけを表示することができます。
グリッドのタイトルをクリックすると、昇順、降順に並べ替えることができます。