トップへ(mam-mam.net/)

TFDMemTableでApacheのログを解析する ~Delphiソースコード集

検索:

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個を配置します。

Delphi IDEで画面設計

ソースコードの記述

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)

TFDMemTableでApacheのログを解析する

Apacheのログを取り込む

「データの追加」ボタンを押して、Apacheのログファイルを選択し、開くボタンを押します。

TFDMemTableでApacheのログを解析する

「データの追加」ボタンを押して、Apacheのログファイルを選択し、開くボタンを押します。

TFDMemTableでApacheのログを解析する

フィルタを有効にして分析する

フィルタに
DATE='2023/09/19'
と入力して「フィルタ有効」にチェックを入れると、2023/09/19のログだけを表示することができます。

TFDMemTableでApacheのログを解析する

フィルタに
DATE='2023/09/19' and URL like '%delphi%'
と入力して「フィルタ有効」にチェックを入れると、2023/09/19で、URLに「delphi」の文字が含まれるログだけを表示することができます。

TFDMemTableでApacheのログを解析する

グリッドのタイトルをクリックすると、昇順、降順に並べ替えることができます。

TFDMemTableでApacheのログを解析する