2019/02/15

時間の範囲を指定してログを抽出する

syslog や apache のログファイルなどから時間の範囲を指定してログを抽出したい場合、 ログファイルの時間表記をそのまま比較するのは面倒なので一度 epoch に置き換えて比較すると楽だ。

現在時刻から epoch は date (1) のフォーマット指定 '+%s' を利用する事で取得できるが、 指定した時刻から epoch を取得する汎用的な手法は現状では存在しないので今回は GNU date の '-d' オプションを利用する。

例として combined 形式の apache のログの時間範囲を抽出してみる。
combined 形式の apache のログは以下のフォーマットとなっているので、時刻は空白区切りの4番目のフィールドに格納されている。 そこで、ログの行毎に4番目フィールドの時刻を epoch に変換して比較する事で指定された時間内のログを抽出できる。

192.168.15.134 - - [14/Feb/2019:03:35:54 +0900] "GET / HTTP/1.1" 200 1920 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
192.168.99.23 - - [14/Feb/2019:07:20:35 +0900] "GET / HTTP/1.1" 200 1920 "https://www.bsdhack.org/" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36; 360Spider"
192.168.157.241 - - [14/Feb/2019:09:28:45 +0900] "GET / HTTP/1.1" 200 1920 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
192.168.15.143 - - [14/Feb/2019:09:58:44 +0900] "GET /bsdhack.css HTTP/1.1" 200 3246 "-" "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
192.168.93.5 - - [14/Feb/2019:12:03:47 +0900] "GET / HTTP/1.1" 200 1920 "-" "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1"
    

ただし、標準的な apache のログに格納されている時刻情報はそのままでは date (1) の '-d' オプションでは変換できない。

$ date -d "14/Feb/2019:09:28:45"
date: invalid date `14/Feb/2019:09:28:45'
    
そこで、 まずは date (1) で変換できる様な形式に変換してから変換する。 そのためには日付けの区切り文字 '/' と、日付け部と時間部を分割している ':' をスペースに置換すれば良い。
$ date -d "14 Feb 2019 09:28:45"
Thu Feb 14 09:28:45 JST 2019
    
全体の流れとしてはログファイルから1行ずつ読み込み、時刻フィールドを epoch に変換して基準時刻と比較する処理を行う。 今回は全ての処理を awk (1) を利用して実装してみる。

  1#!/bin/sh
  2#   $1: 開始時間 -- date(1) が認識できる形式
  3#   $2: 終了時間 -- date(1) が認識できる形式
  4#   $3: ログファイル
  5
  6# 開始時間
  7start=$(date '+%s' -d "${1}")
  8# 終了時間
  9end=$(date '+%s' -d "${2}")
 10# ログ抽出
 11awk -v "start=${start}" -v "end=${end}" '{
 12    # 時刻フィールドを date(1) が認識できる形式に変換
 13    gsub(/[][/]/, " ", $4)
 14    sub(/:/, " ", $4)
 15    # ログの各行について時刻を epoch に変換する
 16    # $4 にはスペースが含まれているので全体をクォートする
 17    cmd = sprintf("date +%%s -d '"'%s'"'", $4);
 18    # date(1) を実行して epoch を変数 s に取得
 19    cmd | getline s;
 20    # コマンド実行でオープンされたディスクリプタをクローズ
 21    close(cmd);
 22    # 行が範囲内なら出力する
 23    if(start <= s && end >= s)
 24        print $0
 25}' ${3}
    

これで開始時間から終了時間の間のログを抽出できる。
行毎に時間の変換処理を実行するので実行に時間はかかってしまうのが現状の問題点。


Copyright © Mitzyuki IMAIZUMI 2008,2009. All rights reserved.