awk를 사용하여 로그 파일에서 특정 항목 가져오기

awk를 사용하여 로그 파일에서 특정 항목 가져오기

나는 현재 큰 로그 파일에서 특정 기준과 일치하는 항목을 가져오기 위해 awk를 사용하려고 합니다. 기본적으로 명령에 포함된 정보(일반적으로 명령의 다른 위치에 있을 수 있음)를 기반으로 트랜잭션 ID로 태그된 전체 명령을 추출할 수 있어야 합니다. 아래 샘플 로그(고농축). 전송된 명령은 한 줄일 수도 있고 여러 줄(예: 00001 및 00002)에 걸쳐 있을 수도 있으며 명령이 반드시 함께 그룹화될 필요는 없으며 명령 사이에 다른 ID가 삽입될 수 있습니다.

(NAME, 486, 00001) <xml><command:name>target</command:name></xml>
(NAME, 486, 00001)   <response>
(NAME, 486, 00001)     <result code="200">
(NAME, 486, 00001)       <msg>Command failed</msg>
(NAME, 486, 00001)     </result>
(NAME, 486, 00001)  </response>
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002)   <response>
(FOO, 486, 00002)     <result code="400">
(FOO, 486, 00002)       <msg>Command completed successfully</msg>
(FOO, 486, 00002)     </result>
(FOO, 486, 00002)  </response>
(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

기본적으로 응답(괄호 안의 5자리 숫자는 트랜잭션 ID)을 포함하여 전체 명령인 이름을 반환하고 싶지만 성공하는 경우(결과 코드="400")만 반환하고 싶습니다.

이것이 내가 지금까지 가지고 있는 것입니다:

BEGIN { FS="[(,)]"; }
$4 ~ "<command:name" { id[$3] = $3 }

{ for (i in id) {
        if ($3 == i) {
                if ($5 ~ "Command completed success")
                        success[i] = i;
                }
        }
}

$4 in success { print $0 }

하지만 분명히 이건 돌아가지 않을 거야위로검색이 성공하면 항목의 나머지 부분을 얻을 수 있습니다. 다음만 반환됩니다.

(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

BEGIN 문 안에 루프를 넣으려고 했지만 시간이 오래 걸리고 해당 크기의 배열을 사용하려고 하면 메모리 문제가 발생합니다(이 파일은 1GB가 넘습니다).

내가 반환하고 싶은 것은 다음과 같습니다.

(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>
(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

내가 시도하는 것이 awk에서 가능한지 궁금합니다. 나는 한동안 이 작업에 어떤 도구를 사용할지 알아내려고 노력해 왔으며, 내가 아는 한 awk가 단연 최고입니다(Python을 사용해야 하는 것 외에). 속도가 나의 주요 관심사입니다. 오늘의 파일만 일반 텍스트로 사용할 수 있지만(그래서 충분히 빠릅니다), 나머지는 gzip으로 압축되어 있습니다(그래서 그렇게 하고 있습니다 zcat filename | awk -f test.awk) - 파일을 여러 번 읽는 것을 피하려고 노력하고 있습니다 , 그리고 너무 커서 메모리에 저장할 수 없습니다.

답변1

</response>이를 레코드 끝 표시 로 사용할 수 있습니다 . 예를 들어:

$ awk -F'[ )]' '{record[$3] = record[$3] "\n" $0};

                /<\/response>/ {
                  if (record[$3] ~ /completed successfully/) {
                    # optional: remove leading newline if you don't want
                    # a blank line before each output record:
                    # sub(/\n/,"",record[$3])

                    print record[$3]
                  };
                  delete record[$3]
                }' input.log 

(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002)   <response>
(FOO, 486, 00002)     <result code="400">
(FOO, 486, 00002)       <msg>Command completed successfully</msg>
(FOO, 486, 00002)     </result>
(FOO, 486, 00002)  </response>

(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>

(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

이는 아래의 sed+perl 및 sed+awk 버전과 유사하지만 이라는 배열의 적절한 요소(즉, id 번호)에 각 행(앞에 개행 문자가 붙음)을 추가하여 각 레코드 자체를 구성합니다 record. 줄이 표시되면 </response>"성공적으로 완료됨"과 일치하면 요소를 인쇄한 다음 요소를 삭제합니다.

이는 sed + awk 또는 sed + perl 버전보다 약간 느립니다(왜냐하면각 입력 행을 배열 요소에 추가합니다.sed- 빈 행을 자주 삽입하는 것보다 더 많은 CPU 리소스를 사용하고 더 많은 메모리를 사용하지만( </response>행이 있을 때까지 각 레코드가 메모리에 유지되기 때문에) 지나치게 많지는 않습니다. 각 레코드를 메모리에 유지합니다. 필요한 만큼만 삭제한 다음 삭제하세요.

그러나 이 버전은 특정 ID에 대한 레코드가 다른 ID에 대한 레코드와 인터리브되는 경우에도 작동합니다.

다음은 Perl과 동등한 것입니다:

perl -F'[\h)]' -e '
  $record{$F[2]} .= $_;

  if (/<\/response>/) {
    if ($record{$F[2]} =~ /completed successfully/) {
      # print blank line between records
      print "\n" if $not_first_record++;

      print $record{$F[2]}
    }
    delete $record{$F[2]};
  }' input.log

내 테스트(100,000개의 샘플 데이터 복사본이 포함된 120MB 입력 파일 사용)에서는 awk 버전이 거의 두 배 빠른 것으로 나타났습니다. 내 테스트 시스템(고대 AMD Phenom II 1090T)에서 awk 버전은 약 4.6초 만에 실행된 반면, Perl 버전은 약 7.4초가 걸렸습니다.

고쳐 쓰다

최적화된 Perl 버전은 다음과 같습니다.

가로 공백이나 닫는 대괄호( )를 필드 구분 기호로 [\h)]사용하는 정규식을 사용하는 대신 Perl의 기본 공백 구분 기호를 사용합니다. 세 번째 필드에서 각 레코드의 키를 추출한 다음 마지막 문자( ))를 자릅니다.

이 버전은 약 3.9초 만에 실행되며 이는 거의 두 배 빠른 속도입니다. 이는 자동 분할 모드 에 정규식을 사용할 -F때 엄청난 성능 저하를 보여줍니다.

그런데, 레코드에 연관 배열 대신 인덱스 배열을 사용해 보았지만(즉, 문자열 키 @record대신 숫자 인덱스 사용 %record) 성능에 눈에 띄는 차이는 없었습니다. 또한 index()정규식 일치 대신( index($record{$key},"completed successfully")대신 ) 해당 함수를 사용해 보았지만 $record{$F[2]} =~ /completed successfully/눈에 띄는 성능 차이는 발생하지 않았습니다.

perl -ane '
  chop($key = $F[2]);
  $record{$key} .= $_;

  if (/<\/response>/) {
    if ($record{$key} =~ /completed successfully/) {
      print "\n" if $not_first_record++;
      print $key, $record{$key};
    }
    delete $record{$key}
  }' input.log

동일한 최적화는 극적으로는 아니지만 awk의 성능도 향상시킵니다.

chop()awk에는 기능이 없지만 substr()동일한 작업을 수행하는 데 사용할 수 있습니다.

awk '{
       key = substr($3, 1, length($3)-1);
       record[key] = record[key] "\n" $0
     };

     /<\/response>/ {
       if (record[key] ~ /completed successfully/) {
         sub(/^\n/,"",record[key])
         print record[key]
       };
       delete record[key]
     }' input.log

이 버전은 약 3.5초 만에 실행됩니다(이전 awk 버전의 4.6초보다 약 30% 빠릅니다).

전반적으로 업데이트된 awk 및 perl 버전은 성능이 훨씬 더 비슷하지만 awk는 여전히 약 12% 더 빠릅니다.

코드를 조금만 변경하면 성능이 크게 달라질 수 있습니다.


또는:

로그 항목은 항상 이와 같은 ID로 깔끔하게 구분되어 있습니까, 아니면 다른 ID와 인터리브되어 있습니까?

깔끔하게 구분되어 있는 경우 가장 쉬운 방법 중 하나는 sed빈 줄을 삽입하여 "단락"으로 나누는 것입니다(예: 하나 이상의 빈 줄로 구분).앞으로<xml>라인.

sed그런 다음 출력은 "단락 모드"에서 로그 로 파이프 awk되거나 읽혀집니다. perlawk의 경우 RS=""BEGIN 블록에서 설정하거나 -v옵션을 사용하고, Perl의 경우 -00명령줄 옵션을 사용합니다. 그런 다음 awk 또는 perl 스크립트는 레코드에 "성공적으로 완료됨"이 포함되어 있는지 확인하면 됩니다. 그렇다면 기록을 인쇄하십시오.

위의 awk 전용 버전보다 훨씬 더 빠르게 실행되고 더 적은 메모리를 사용하지만 로깅할 때만 올바르게 작동합니다.아니요다른 레코드와 인터리브되었습니다.

$ sed '/) <xml>/i\\n' input.log |
    perl -00 -ne 'print if /completed successfully/m'
(FOO, 486, 00002) <xml>
(FOO, 486, 00002) <differentCommand:name>This is another sent command</differentCommand:name></xml>
(FOO, 486, 00002) </xml>
(FOO, 486, 00002)   <response>
(FOO, 486, 00002)     <result code="400">
(FOO, 486, 00002)       <msg>Command completed successfully</msg>
(FOO, 486, 00002)     </result>
(FOO, 486, 00002)  </response>

(ANOTHERNAME, 486, 00003) <xml><command:name>target</command:name></xml>
(ANOTHERNAME, 486, 00003)   <response>
(ANOTHERNAME, 486, 00003)     <result code="400">
(ANOTHERNAME, 486, 00003)       <msg>Command completed successfully</msg>
(ANOTHERNAME, 486, 00003)     </result>
(ANOTHERNAME, 486, 00003)   </response>

(FOO, 486, 00004) <xml>
(FOO, 486, 00004) <command:name>This is another sent command</command:name></xml>
(FOO, 486, 00004) </xml>
(FOO, 486, 00004)   <response>
(FOO, 486, 00004)     <result code="400">
(FOO, 486, 00004)       <msg>Command completed successfully</msg>
(FOO, 486, 00004)     </result>
(FOO, 486, 00004)  </response>

아니면 awk를 사용하세요:

sed '/) <xml>/i\\n' input.log | awk -v RS='' '/completed successfully/'

이 버전의 출력은 거의 동일하지만 각 출력 레코드 사이에 빈 줄이 없습니다.

개인적으로 각 출력 레코드 사이에 빈 줄이 있으면 이미 "단락 모드"에 있으므로 필요한 경우 출력을 더 쉽게 처리할 수 있으므로 유용하다고 생각합니다. 물론 이것은 주관적인 선호일 뿐이다.


관련 정보