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」の文字が含まれるログだけを表示することができます。
グリッドのタイトルをクリックすると、昇順、降順に並べ替えることができます。
