Como extrair logs entre dois carimbos de data / hora

22

Eu quero extrair todos os logs entre dois timestamps. Algumas linhas podem não ter o timestamp, mas também quero essas linhas. Em suma, eu quero cada linha que cai sob dois carimbos de tempo. Minha estrutura de log se parece com:

[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall

Suponha que eu queira extrair tudo entre 2014-04-07 23:00 e 2014-04-08 02:00 .

Por favor, note que o carimbo de hora de início ou o carimbo de hora de término pode não estar lá no log, mas eu quero cada linha entre esses dois carimbos de hora.

    
por Amit 09.04.2014 / 22:03

4 respostas

18

Você pode usar awk para isso:

$ awk -F'[]]|[[]' \
  '$0 ~ /^\[/ && $2 >= "2014-04-07 23:00" { p=1 }
   $0 ~ /^\[/ && $2 >= "2014-04-08 02:00" { p=0 }
                                        p { print $0 }' log

Onde:

  • -F especifica os caracteres [ e ] como separadores de campo usando uma expressão regular
  • $0 faz referência a uma linha completa
  • $2 faz referência ao campo de data
  • p é usado como variável booleana que protege a impressão real
  • $0 ~ /regex/ é verdadeiro se a regex corresponder a $0
  • >= é usado para comparar lexicograficamente string (equivalente a, por exemplo, strcmp() )

Variações

A linha de comando acima implementa a correspondência intervalo de tempo correto . Para obter a semântica do intervalo fechado, basta incrementar sua data correta, por exemplo:

$ awk -F'[]]|[[]' \
  '$0 ~ /^\[/ && $2 >= "2014-04-07 23:00"    { p=1 }
   $0 ~ /^\[/ && $2 >= "2014-04-08 02:00:01" { p=0 }
                                           p { print $0 }' log

Caso você deseje corresponder registros de data e hora em outro formato, é necessário modificar a sub-expressão $0 ~ /^\[/ . Observe que é usado para ignorar linhas sem nenhum registro de data e hora da lógica de ativação / desativação da impressão.

Por exemplo, para um formato de registro de data e hora como YYYY-MM-DD HH24:MI:SS (sem [] chaves), você poderia modificar o comando da seguinte maneira:

$ awk \
  '$0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/
      {
        if ($1" "$2 >= "2014-04-07 23:00")     p=1;
        if ($1" "$2 >= "2014-04-08 02:00:01")  p=0;
      }
    p { print $0 }' log

(note que também o separador de campo é alterado - para transição em branco / não em branco, o padrão)

    
por 09.04.2014 / 22:27
10

Confira dategrep no link

Descrição:

dategrep searches the named input files for lines matching a date range and prints them to stdout.

If dategrep works on a seekable file, it can do a binary search to find the first and last line to print pretty efficiently. dategrep can also read from stdin if one the filename arguments is just a hyphen, but in this case it has to parse every single line which will be slower.

Exemplos de uso:

dategrep --start "12:00" --end "12:15" --format "%b %d %H:%M:%S" syslog
dategrep --end "12:15" --format "%b %d %H:%M:%S" syslog
dategrep --last-minutes 5 --format "%b %d %H:%M:%S" syslog
dategrep --last-minutes 5 --format rsyslog syslog
cat syslog | dategrep --end "12:15" -

Embora essa limitação possa torná-la inadequada para sua pergunta exata:

At the moment dategrep will die as soon as it finds a line that is not parsable. In a future version this will be configurable.

    
por 09.04.2014 / 22:48
3

Uma alternativa para awk ou uma ferramenta não-padrão é usar o GNU grep para seus greps contextuais. O grep do GNU permitirá que você especifique o número de linhas após uma correspondência positiva para imprimir com -A e as linhas precedentes para imprimir com -B Por exemplo:

[davisja5@xxxxxxlp01 ~]$ cat test.txt
Ignore this line, please.
This one too while you're at it...
[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall
we don't
want these lines.


[davisja5@xxxxxxlp01 ~]$ egrep "^\[2014-04-07 23:59:58\]" test.txt -A 10000 | egrep "^\[2014-04-08 00:00:03\]" -B 10000
[2014-04-07 23:59:58] CheckForCallAction [ERROR] Exception caught in +CheckForCallAction :: null
--Checking user--
Post
[2014-04-08 00:00:03] MobileAppRequestFilter [DEBUG] Action requested checkforcall

O texto acima diz basicamente grep para imprimir as 10.000 linhas que seguem a linha que corresponde ao padrão que você deseja iniciar, efetivamente fazendo com que sua saída comece onde você está querendo e vá até o final (esperamos que ) enquanto o segundo egrep no pipeline diz para imprimir apenas a linha com o delimitador final e as 10.000 linhas anteriores. O resultado final desses dois está começando onde você está querendo e não vai passar onde você disse para parar.

10.000 é apenas um número que eu criei, sinta-se à vontade para mudá-lo para um milhão se achar que sua saída será muito longa.

    
por 09.04.2014 / 22:58
0

Usando sed:

#!/bin/bash

E_BADARGS=23

if [ $# -ne "3" ]
then
  echo "Usage: 'basename $0' \"<start_date>\" \"<end_date>\" file"
  echo "NOTE:Make sure to put dates in between double quotes"
  exit $E_BADARGS
fi 

isDatePresent(){
        #check if given date exists in file.
        local date=$1
        local file=$2
        grep -q "$date" "$file"
        return $?

}

convertToEpoch(){
    #converts to epoch time
    local _date=$1
    local epoch_date='date --date="$_date" +%s'
    echo $epoch_date
}

convertFromEpoch(){
    #converts to date/time format from epoch
    local epoch_date=$1
    local _date='date  --date="@$epoch_date" +"%F %T"'
    echo $_date

}

getDates(){
        # collects all dates at beginning of lines in a file, converts them to epoch and returns a sequence of numbers
        local file="$1"
        local state="$2"
        local i=0
        local date_array=( )
        if [[ "$state" -eq "S" ]];then
            datelist='cat "$file" | sed -r -e "s/^\[([^\[]+)\].*//" | egrep  "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}"'
        elif [[ "$state" -eq "E" ]];then
            datelist='tac "$file" | sed -r -e "s/^\[([^\[]+)\].*//" | egrep  "^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}"'

        else
            echo "Something went wrong while getting dates..." 1>&2
            exit 500
        fi

        while read _date
            do
                epoch_date='convertToEpoch "$_date"'
                date_array[$i]=$epoch_date
                #echo "$_date" "$epoch_date" 1>&2

            (( i++ ))
            done<<<"$datelist"
        echo ${date_array[@]}   


}

findneighbours(){
    # search next best date if date is not in the file using recursivity
    IFS="$old_IFS"
    local elt=$1
    shift
    local state="$1"
    shift
    local -a array=( "$@" ) 

    index_pivot='expr ${#array[@]} / 2'
    echo "#array="${#array[@]} ";array="${array[@]} ";index_pivot="$index_pivot 1>&2
    if [ "$index_pivot" -eq 1 -a ${#array[@]} -eq 2 ];then

        if [ "$state" == "E" ];then
            echo ${array[0]}
        elif [ "$state" == "S" ];then
            echo ${array[(( ${#array[@]} - 1 ))]} 
        else
            echo "State" $state "undefined" 1>&2
            exit 100
        fi

    else
        echo "elt with index_pivot="$index_pivot":"${array[$index_pivot]} 1>&2
        if [ $elt -lt ${array[$index_pivot]} ];then
            echo "elt is smaller than pivot" 1>&2
            array=( ${array[@]:0:(($index_pivot + 1)) } )
        else
            echo "elt is bigger than pivot" 1>&2
            array=( ${array[@]:$index_pivot:(( ${#array[@]} - 1 ))} ) 
        fi
        findneighbours "$elt" "$state" "${array[@]}"
    fi
}



findFirstDate(){
    local file="$1"
    echo "Looking for first date in file" 1>&2
    while read line
        do 
            echo "$line" | egrep -q "^\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]" &>/dev/null
            if [ "$?" -eq "0" ]
            then
                #echo "line=" "$line" 1>&2
                firstdate='echo "$line" | sed -r -e "s/^\[([^\[]+)\].*//"'
                echo "$firstdate"
                break
            else
                echo $? 1>&2
            fi
        done< <( cat "$file" )



}

findLastDate(){
    local file="$1"
    echo "Looking for last date in file" 1>&2
    while read line
        do 
            echo "$line" | egrep -q "^\[[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]" &>/dev/null
            if [ "$?" -eq "0" ]
            then
                #echo "line=" "$line" 1>&2
                lastdate='echo "$line" | sed -r -e "s/^\[([^\[]+)\].*//"'
                echo "$lastdate"
                break
            else
                echo $? 1>&2
            fi
        done< <( tac "$file" )


}

findBestDate(){

        IFS="$old_IFS"
        local initdate="$1"
        local file="$2"
        local state="$3"
        local first_elts="$4"
        local last_elts="$5"
        local date_array=( )
        local initdate_epoch='convertToEpoch "$initdate"'   

        if [[ $initdate_epoch -lt $first_elt ]];then
            echo 'convertFromEpoch "$first_elt"'
        elif [[ $initdate_epoch -gt $last_elt ]];then
            echo 'convertFromEpoch "$last_elt"' 

        else
            date_array=( 'getDates "$file" "$state"' )
            echo "date_array="${date_array[@]} 1>&2
            #first_elt=${date_array[0]}
            #last_elt=${date_array[(( ${#date_array[@]} - 1 ))]}

            echo 'convertFromEpoch $(findneighbours "$initdate_epoch" "$state" "${date_array[@]}")'

        fi

}


main(){
    init_date_start="$1"
    init_date_end="$2"
    filename="$3"
    echo "problem start.." 1>&2
    date_array=( "$init_date_start","$init_date_end"  )
    flag_array=( 0 0 )
    i=0
    #echo "$IFS" | cat -vte
    old_IFS="$IFS"
    #changing separator to avoid whitespace issue in date/time format
    IFS=,
    for _date in ${date_array[@]}
    do
        #IFS="$old_IFS"
        #echo "$IFS" | cat -vte
        if isDatePresent "$_date" "$filename";then
            if [ "$i" -eq 0 ];then 
                echo "Starting date exists" 1>&2
                #echo "date_start=""$_date" 1>&2
                date_start="$_date"
            else
                echo "Ending date exists" 1>&2
                #echo "date_end=""$_date" 1>&2
                date_end="$_date"
            fi

        else
            if [ "$i" -eq 0 ];then 
                echo "start date $_date not found" 1>&2
            else
                echo "end date $_date not found" 1>&2
            fi
            flag_array[$i]=1
        fi
        #IFS=,
        (( i++ ))
    done

    IFS="$old_IFS"
    if [ ${flag_array[0]} -eq 1 -o ${flag_array[1]} -eq 1 ];then

        first_elt='convertToEpoch "$(findFirstDate "$filename")"'
        last_elt='convertToEpoch "$(findLastDate "$filename")"'
        border_dates_array=( "$first_elt","$last_elt" )

        #echo "first_elt=" $first_elt "last_elt=" $last_elt 1>&2
        i=0
        IFS=,
        for _date in ${date_array[@]}
        do
            if [ $i -eq 0 -a ${flag_array[$i]} -eq 1 ];then
                date_start='findBestDate "$_date" "$filename" "S" "${border_dates_array[@]}"'
            elif [ $i -eq 1 -a ${flag_array[$i]} -eq 1 ];then
                date_end='findBestDate "$_date" "$filename" "E" "${border_dates_array[@]}"'
            fi

            (( i++ ))
        done
    fi


    sed -r -n "/^\[${date_start}\]/,/^\[${date_end}\]/p" "$filename"

}


main "$1" "$2" "$3"

Copie isso em um arquivo. Se você não deseja ver informações de depuração, a depuração é enviada para o stderr, portanto basta adicionar "2 > / dev / null"

    
por 09.04.2014 / 23:38