경험에 의하면 정규 표현식은 개발자의 툴박스에서 만능 스위스 칼의 역할을 합니다. 그리고 작업을 하다보면 항상 더 나은 정규 표현식이 있다는 것을 알게됩니다. 우수한 정규 표현식을 개발하는 작업은 반복적인 과정이며, 그 품질과 신뢰성은 엣지 사례들을 포함해 새롭고 흥미로운 데이터를 더 많이 공급하면 할수록 증가합니다.

정규 표현식은 작동만 하면, 그것으로 충분할 수 있기 때문에, 데이터가 예측이 가능하다면 정규 표현식을 최적화하는 것은 불필요한 작업이 될 수 있습니다. 그러나 정규 표현식을 더 넓은 시스템이나 규모에서, 또는 신뢰할 수 없는 데이터 세트의 일부로 사용하기 시작하면, 표현식의 안정성과 복원력, 성능을 확인해봐야 합니다.

정규 표현식은 언뜻보면 복잡해 보일 수 있지만, 이해를 하면 구조가 논리적이고 예측 가능합니다. 그러나 복잡한 정규 표현식을 역엔지니어링하는 것은 그리 신나는 일은 아닙니다.

이 글은 중요한 사용 사례에서 정규 표현식을 구성하는 방법을 설명합니다. 로그 줄에서 이름-값 쌍을 추출하는 것은 로그 관리에서 중요한 부분입니다. 로그는 강력한 정규 표현식을 사용해야 하는 경우에 해당되는 좋은 예입니다. 일반적으로 로그는 더 넓은 시스템의 일부이고(이상적으로는 전체 스택에 대한 로그가 존재), 애플리케이션에 따라 확장되어야 하며, 일관성이 없는 경우가 많기 때문입니다. 이제 몇 가지 정규 표현식을 살펴보겠습니다. 이를 통해, 지금 작업하고 있는 다른 정규 표현식을 강화하는 방법을 배우게 되시길 바랍니다.

정규 표현식을 사용한 로그 줄 구문 분석

이 사용 사례는 뉴렐릭에서 로그의 구문 분석을 수행하는 고객을 지원하는 데 사용되었던 실제 요구 사항을 기반으로 합니다. 뉴렐릭에는 원시 로그 데이터를 인제스트하고 의미 있는 개별 열로 구문 분석할 수 있는 강력한 데이터 구문 분석 메커니즘이 있습니다.

실제 사용 사례에서의 요구 사항은 다음과 같습니다.

  • 로그 데이터에는 여러 개의 이름-값 쌍과 기타 데이터가 포함됩니다.
  • 쌍은 (attr=value) 형식으로 나타납니다 .
  • 값에는 공백이 포함될 수 있습니다.
  • 모든 이름-값 쌍을 수집할 필요는 없습니다.
  • 일부 쌍은 모든 로그 줄에 있을 수 있지만, 일부는 그렇지 않을 수 있습니다.
  • 쌍은 순서에 관계없이 나타날 수 있습니다.

다음은 일반적인 로그 줄의 예입니다.

내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드 장소=런던 이름=제임스 뷰캐넌

이 예시의 경우, 데이터에서 피자, 음료이름 필드를 추출한다고 가정하겠습니다. 그러나 장소 데이터나 로그 줄의 다른 데이터는 추출하지 않습니다. 더 복잡하게 만들어볼까요. 많은 로그 줄에서 이 데이터를 수집해야 하는데 데이터가 항상 일관되게 표시되지 않습니다. 이 경우 어떤 정규 표현식으로 이러한 값들을 캡처할 수 있을까요?

TL;DR 정규 표현식

Google에서 검색을 하다 이곳에 오게 되어 규칙을 복사해 붙여 넣고 잘 작동하는지 확인만 하길 원하실 수도 있을 겁니다. 이름-값 쌍을 추출하기 위한 정규 표현식은 다음과 같습니다. = 기호로 구분됩니다.

(?:^|\s+)(?=.*?attrname=(?<attrname>[^=]+?(?=(?:\s+\b\w+\b=|\s*?$))))?

Grok 버전은 다음과 같습니다.

(?:^|\s+)(?=%{DATA}attrname=(?<attrname>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

이 규칙의 경우:

  • 모든 키-값 쌍이 있어야 하는 것은 아닙니다. 이 규칙은 키-값 쌍 중 일부가 줄에 없어도 중단되지 않고 존재하는 키-값 쌍에 대해 작동합니다.
  • 키-값 쌍의 순서는 중요하지 않습니다.
  • 값 내에 공백이 허용됩니다.

규칙이 어떻게 작동하는지 자세히 설명해드리겠습니다.

Grok을 사용한 구문 분석

Grok 버전이 좀 더 깔끔하니, Grok 버전 규칙을 중심으로 설명하려고 합니다. 또한 뉴렐릭의 구문 분석 규칙은 Grok에서 작성되므로, 기존에 명명된 Grok 패턴을 사용할 수 있습니다. Grok은 정규 표현식을 기반으로 하기 때문에, 모든 유효한 정규 표현식은 유효한 Grok 표현식이기도 합니다. Grok을 사용하지 않는 경우, 이전 섹션에서 제공된 표준 정규 표현식 버전을 사용하시면 됩니다.

깨지기 쉬운 구문 분석 규칙으로 시작

정규 표현식을 테스트하려면 먼저 몇 가지 데이터를 살펴봐야 합니다. 저는 맥주와 피자를 모두 좋아하고, 나무 화덕도 가지고 있습니다. 피자를 테마로 한 데이터 세트는 다음과 같습니다.

1: 내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드 이름=제임스 뷰캐넌

2: 내가 가장 좋아하는 음료=라임과 레모네이드 이름=제임스 뷰캐넌 피자=햄과 파인애플

3: 내가 가장 좋아하는 이름= 제임스 뷰캐넌 피자=햄과 파인애플 드링크=라임과 레모네이드

4: 내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드

5: 내가 가장 좋아하는 이름= 제임스 뷰캐넌 피자=햄과 파인애플 드링크=라임과 레모네이드

6: 내가 가장 좋아하는 음료=라임과 레모네이드

이 데이터 세트는 키-값 쌍이 서로 다른 순서, 다양한 공백, 심지어 서로 다른 수의 키-값 쌍으로 구성되어 있다는 것을 알 수 있습니다.

이 예시 데이터에서 각 줄의 키 값 쌍은 drink=coke 같이 등호 = 기호로 구분됩니다. 피자(pizza), 음료(drink)이름(name)의 세 가지 값을 추출하려는 경우를 가정해 보겠습니다.

데이터가 항상 1번째 줄처럼 나타나면, 다음과 같이 각 값을 추출하는 Grok 구문 분석 규칙을 작성할 수 있습니다.

pizza=(?<pizza>%{DATA})drink=(?<drink>%{DATA})name=(?<name>%{GREEDYDATA})

이 규칙은 작동은 하지만 깨지기 쉽습니다. 값이 항상 같은 순서로 되어 있어야 하기 때문니다. 값이 누락되었거나 추가 데이터가 있으면 전체 규칙이 실패합니다. 데이터가 정확히 매치하지 않아 데이터가 누락되는 일이 생기면 안됩니다. 데이터가 일관적이라고 해도 100% 확신할 수 있을까요?

Logs > Parsing > Create parsing rule로 이동하면 뉴렐릭에 포함된 로그 구문 분석 테스트 툴을 사용해 직접 확인해볼 수 있습니다. 규칙과 함께 예시 로그 줄을 붙여 넣으면 출력을 볼 수 있습니다. 또는 이 Grok 툴을 사용해 Grok 규칙을 시도해보시기 바랍니다.

Lookahead(전방탐색) 규칙 사용

이 구문 분석 규칙을 어떻게 더 견고하게 만들 수 있을까요? 여기서 lookahead 기능을 사용하면 도움이 됩니다. 단일 키-값 쌍을 대상으로 하려면 매치 시작 시기와 종료 시기, 이 두 가지 사항을 알아야 합니다. 이 과정을 단계별로 살펴보겠습니다.

값 쌍 찾기

피자 값 쌍을 예로 들어 보겠습니다. 이 쌍은 항상 pizza=으로 시작합니다. 패턴이 일관되므로 다음과 같이 전방탐색을 하고 텍스트를 캡처할 수 있습니다. 

(?=%{DATA}pizza=(?<pizza>.*))

그러면 다음 결과가 반환됩니다.

pizza: ham and pineapple drink=lime and lemonade name=james buchanan

DATA.*? 표현식과 동일합니다. 여기에서 Grok 패턴의 유용한 목록을 확인하실 수 있습니다. 이 lookahead 규칙은 pizza= 뒤에 있는 항목을 찾아 pizza라는 필드에 캡처합니다. 이렇게 하면 표현식이 작동은 하지만 음료와 이름 값도 캡처됩니다. 따라서 다음 이름-값 쌍까지만 문자와 공백을 캡처하도록 규칙을 제한해야 합니다.

필요한 속성만 캡처

pizza 값만 캡처하려면, 다른 lookahead 문자를 사용할 수 있습니다. 다음 규칙은 등호가 아닌 모든 문자를 캡처합니다. Non-greedy 문자를 사용해야 합니다. ?[^=]+ 패턴에 추가되어야 한다는 의미입니다. 그 뒤에는 공백 문자, 단어, 등호가 차례로 붙습니다. 규칙은 다음과 같습니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=))))

이렇게 하면 1번째 줄에 대해 pizza:ham and pineapple이 반환됩니다.✅

그러나 2번째 줄에 대해 no match!가 반환됩니다. ❌

훨씬 더 낫습니다. 그런데, 2번째 줄이 피자를 매치시키지 못했습니다. 왜 그럴까요?

이 패턴은 데이터 다음에 다른 이름-값 쌍이 옵니다. 그렇지만 이 경우 규칙이 전체 줄을 검색했고 추가적인 이름-값 쌍이 없습니다. 캡처는 다른 이름-값 쌍이 오거나 $로 표시되는 줄 끝부분으로 확장되어야 합니다. 또한 후행 공백을 고려하는 것이 중요합니다. 이 공백은 non-greedy 연산자 %{SPACE}?로 삭제할 수 있습니다.

업데이트된 패턴은 다음과 같습니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))

1번째 줄에 대한 반환 값: pizza:ham and pineapple

2번째 줄에 대한 반환 값: pizza:ham and pineapple

이 표현식이 훨씬 더 낫고 안정적입니다. 하나의 필드만 캡처하려는 경우, 작업은 완료되었습니다. 그러나 로그에서 여러 필드를 캡처해야 하는 경우가 많습니다.

로그에 여러 필드 캡처

동일한 식을 반복하고 필요에 따라 값 이름을 변경해 여러 식을 연결하면 다른 값들을 캡처할 수 있습니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))(?=%{DATA}drink=(?<drink>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))(?=%{DATA}name=(?<name>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))

그러면 다음 결과가 반환됩니다.

1번째 줄: pizza:ham and pineapple, name:james buchanan and drink:lime and lemonade

2번째: 1번째 줄과 동일합니다 ✅

3번째: 1번째 줄과 동일합니다 ✅

4번째: no match! ❌

이는 예시 데이터의 1-3번째 줄에 적용됩니다. 이제 키-값 쌍의 순서에 관계없이 규칙이 매치 항목을 반환합니다. 안타깝게도, 입력의 줄4에 대해서는 실패합니다.

4: 내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드

4번째 줄에 이름 키가 누락되어 있을 수 있습니다. 정규 표현식 규칙에는 이름이 있어야 하고 그렇지 않으면 전체 패턴이 실패합니다. 이러한 오류는 데이터 세트에 정규 표현식을 사용할 때 종종 눈에 간과하는 일반적인 오류입니다. 이러한 종류의 문제는 규칙이 제대로 작동하는 것처럼 보이지만 중요한 정보를 수집하지 않기 때문에 다루기가 매우 까다롭습니다. 각 패턴을 선택적으로 만들어 이 문제를 해결할 수 있습니다. 이를 위해서는 ?를 각 표현식의 끝부분에 추가합니다.

다음은 각 키-값 쌍의 일반화된 패턴입니다.

(?=%{DATA}attrname=(?<attrname>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

데이터에 이 정규 표현식을 사용해 보겠습니다. 다음 표현식에는 캡처해야 하는 각 속성(name, pizzadrink)에 대해 한 번씩, 패턴이 총 세 번 포함됩니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}drink=(?<drink>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}name=(?<name>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

그러면 다음 결과가 반환됩니다.

1번째 줄: pizza:ham and pineapple, name:james buchanan and drink:lime and lemonade

2번째: 1번째 줄과 동일합니다 ✅

3번째: 1번째 줄과 동일합니다 ✅

4번째 줄: pizza:ham and pineapple, drink:lime and lemonade

5번째 줄: 1번째 줄과 동일합니다.✅

6번째 줄: drink:lime and lemonade

규칙은 순서에 관계없이 모든 테스트 입력 데이터에 정확하게 매치하며, 누락된 필드에 대해서도 계속 작동합니다.

정규 표현식의 성능 전방탐색

Lookaheads에는 추가적인 성능 오버헤드가 있으므로, 데이터가 신뢰할 수 있을정도로 일관성이 있다면, Lookaheads가 없는 보다 간단하고 성능이 나은 규칙을 사용할 수 있습니다. 규칙 시작 부분에 접두사 (?:^|\s+)를 추가하면 이 규칙을 더욱 효율적으로 실행할 수 있습니다.

(?:^|\s+)(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}drink=(?<drink>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}name=(?<name>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

이렇게 조금 변경을 하면 lookaheads는 모든 문자가 아니라 줄의 시작 부분이나 공백이 있을 때만 실행되며, 필요하지 않은 곳에서는 규칙이 lookaheads를 사용하지 않습니다.

결론

이 규칙이 작동하는 방식을 더 잘 이해하고 규칙을 더 안정적으로 만들기 위해 지속적으로 개선하는 데 조금이라도 도움이 되었길 바랍니다. 생각을 조금만 더하면 항상 더 나은 정규 표현식이 있습니다. 지금 사용 작업하는 사용 사례에서 훨씬 더 효과적인 정규 표현식을 찾으시길 바랍니다!

Capabilities
New relic virtuo customer story
Discover the power of New Relic log management
Manage your logs Manage your logs

경험에 의하면 정규 표현식은 개발자의 툴박스에서 만능 스위스 칼의 역할을 합니다. 그리고 작업을 하다보면 항상 더 나은 정규 표현식이 있다는 것을 알게됩니다. 우수한 정규 표현식을 개발하는 작업은 반복적인 과정이며, 그 품질과 신뢰성은 엣지 사례들을 포함해 새롭고 흥미로운 데이터를 더 많이 공급하면 할수록 증가합니다.

정규 표현식은 작동만 하면, 그것으로 충분할 수 있기 때문에, 데이터가 예측이 가능하다면 정규 표현식을 최적화하는 것은 불필요한 작업이 될 수 있습니다. 그러나 정규 표현식을 더 넓은 시스템이나 규모에서, 또는 신뢰할 수 없는 데이터 세트의 일부로 사용하기 시작하면, 표현식의 안정성과 복원력, 성능을 확인해봐야 합니다.

정규 표현식은 언뜻보면 복잡해 보일 수 있지만, 이해를 하면 구조가 논리적이고 예측 가능합니다. 그러나 복잡한 정규 표현식을 역엔지니어링하는 것은 그리 신나는 일은 아닙니다.

이 글은 중요한 사용 사례에서 정규 표현식을 구성하는 방법을 설명합니다. 로그 줄에서 이름-값 쌍을 추출하는 것은 로그 관리에서 중요한 부분입니다. 로그는 강력한 정규 표현식을 사용해야 하는 경우에 해당되는 좋은 예입니다. 일반적으로 로그는 더 넓은 시스템의 일부이고(이상적으로는 전체 스택에 대한 로그가 존재), 애플리케이션에 따라 확장되어야 하며, 일관성이 없는 경우가 많기 때문입니다. 이제 몇 가지 정규 표현식을 살펴보겠습니다. 이를 통해, 지금 작업하고 있는 다른 정규 표현식을 강화하는 방법을 배우게 되시길 바랍니다.

정규 표현식을 사용한 로그 줄 구문 분석

이 사용 사례는 뉴렐릭에서 로그의 구문 분석을 수행하는 고객을 지원하는 데 사용되었던 실제 요구 사항을 기반으로 합니다. 뉴렐릭에는 원시 로그 데이터를 인제스트하고 의미 있는 개별 열로 구문 분석할 수 있는 강력한 데이터 구문 분석 메커니즘이 있습니다.

실제 사용 사례에서의 요구 사항은 다음과 같습니다.

  • 로그 데이터에는 여러 개의 이름-값 쌍과 기타 데이터가 포함됩니다.
  • 쌍은 (attr=value) 형식으로 나타납니다 .
  • 값에는 공백이 포함될 수 있습니다.
  • 모든 이름-값 쌍을 수집할 필요는 없습니다.
  • 일부 쌍은 모든 로그 줄에 있을 수 있지만, 일부는 그렇지 않을 수 있습니다.
  • 쌍은 순서에 관계없이 나타날 수 있습니다.

다음은 일반적인 로그 줄의 예입니다.

내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드 장소=런던 이름=제임스 뷰캐넌

이 예시의 경우, 데이터에서 피자, 음료이름 필드를 추출한다고 가정하겠습니다. 그러나 장소 데이터나 로그 줄의 다른 데이터는 추출하지 않습니다. 더 복잡하게 만들어볼까요. 많은 로그 줄에서 이 데이터를 수집해야 하는데 데이터가 항상 일관되게 표시되지 않습니다. 이 경우 어떤 정규 표현식으로 이러한 값들을 캡처할 수 있을까요?

TL;DR 정규 표현식

Google에서 검색을 하다 이곳에 오게 되어 규칙을 복사해 붙여 넣고 잘 작동하는지 확인만 하길 원하실 수도 있을 겁니다. 이름-값 쌍을 추출하기 위한 정규 표현식은 다음과 같습니다. = 기호로 구분됩니다.

(?:^|\s+)(?=.*?attrname=(?<attrname>[^=]+?(?=(?:\s+\b\w+\b=|\s*?$))))?

Grok 버전은 다음과 같습니다.

(?:^|\s+)(?=%{DATA}attrname=(?<attrname>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

이 규칙의 경우:

  • 모든 키-값 쌍이 있어야 하는 것은 아닙니다. 이 규칙은 키-값 쌍 중 일부가 줄에 없어도 중단되지 않고 존재하는 키-값 쌍에 대해 작동합니다.
  • 키-값 쌍의 순서는 중요하지 않습니다.
  • 값 내에 공백이 허용됩니다.

규칙이 어떻게 작동하는지 자세히 설명해드리겠습니다.

Grok을 사용한 구문 분석

Grok 버전이 좀 더 깔끔하니, Grok 버전 규칙을 중심으로 설명하려고 합니다. 또한 뉴렐릭의 구문 분석 규칙은 Grok에서 작성되므로, 기존에 명명된 Grok 패턴을 사용할 수 있습니다. Grok은 정규 표현식을 기반으로 하기 때문에, 모든 유효한 정규 표현식은 유효한 Grok 표현식이기도 합니다. Grok을 사용하지 않는 경우, 이전 섹션에서 제공된 표준 정규 표현식 버전을 사용하시면 됩니다.

깨지기 쉬운 구문 분석 규칙으로 시작

정규 표현식을 테스트하려면 먼저 몇 가지 데이터를 살펴봐야 합니다. 저는 맥주와 피자를 모두 좋아하고, 나무 화덕도 가지고 있습니다. 피자를 테마로 한 데이터 세트는 다음과 같습니다.

1: 내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드 이름=제임스 뷰캐넌

2: 내가 가장 좋아하는 음료=라임과 레모네이드 이름=제임스 뷰캐넌 피자=햄과 파인애플

3: 내가 가장 좋아하는 이름= 제임스 뷰캐넌 피자=햄과 파인애플 드링크=라임과 레모네이드

4: 내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드

5: 내가 가장 좋아하는 이름= 제임스 뷰캐넌 피자=햄과 파인애플 드링크=라임과 레모네이드

6: 내가 가장 좋아하는 음료=라임과 레모네이드

이 데이터 세트는 키-값 쌍이 서로 다른 순서, 다양한 공백, 심지어 서로 다른 수의 키-값 쌍으로 구성되어 있다는 것을 알 수 있습니다.

이 예시 데이터에서 각 줄의 키 값 쌍은 drink=coke 같이 등호 = 기호로 구분됩니다. 피자(pizza), 음료(drink)이름(name)의 세 가지 값을 추출하려는 경우를 가정해 보겠습니다.

데이터가 항상 1번째 줄처럼 나타나면, 다음과 같이 각 값을 추출하는 Grok 구문 분석 규칙을 작성할 수 있습니다.

pizza=(?<pizza>%{DATA})drink=(?<drink>%{DATA})name=(?<name>%{GREEDYDATA})

이 규칙은 작동은 하지만 깨지기 쉽습니다. 값이 항상 같은 순서로 되어 있어야 하기 때문니다. 값이 누락되었거나 추가 데이터가 있으면 전체 규칙이 실패합니다. 데이터가 정확히 매치하지 않아 데이터가 누락되는 일이 생기면 안됩니다. 데이터가 일관적이라고 해도 100% 확신할 수 있을까요?

Logs > Parsing > Create parsing rule로 이동하면 뉴렐릭에 포함된 로그 구문 분석 테스트 툴을 사용해 직접 확인해볼 수 있습니다. 규칙과 함께 예시 로그 줄을 붙여 넣으면 출력을 볼 수 있습니다. 또는 이 Grok 툴을 사용해 Grok 규칙을 시도해보시기 바랍니다.

Lookahead(전방탐색) 규칙 사용

이 구문 분석 규칙을 어떻게 더 견고하게 만들 수 있을까요? 여기서 lookahead 기능을 사용하면 도움이 됩니다. 단일 키-값 쌍을 대상으로 하려면 매치 시작 시기와 종료 시기, 이 두 가지 사항을 알아야 합니다. 이 과정을 단계별로 살펴보겠습니다.

값 쌍 찾기

피자 값 쌍을 예로 들어 보겠습니다. 이 쌍은 항상 pizza=으로 시작합니다. 패턴이 일관되므로 다음과 같이 전방탐색을 하고 텍스트를 캡처할 수 있습니다. 

(?=%{DATA}pizza=(?<pizza>.*))

그러면 다음 결과가 반환됩니다.

pizza: ham and pineapple drink=lime and lemonade name=james buchanan

DATA.*? 표현식과 동일합니다. 여기에서 Grok 패턴의 유용한 목록을 확인하실 수 있습니다. 이 lookahead 규칙은 pizza= 뒤에 있는 항목을 찾아 pizza라는 필드에 캡처합니다. 이렇게 하면 표현식이 작동은 하지만 음료와 이름 값도 캡처됩니다. 따라서 다음 이름-값 쌍까지만 문자와 공백을 캡처하도록 규칙을 제한해야 합니다.

필요한 속성만 캡처

pizza 값만 캡처하려면, 다른 lookahead 문자를 사용할 수 있습니다. 다음 규칙은 등호가 아닌 모든 문자를 캡처합니다. Non-greedy 문자를 사용해야 합니다. ?[^=]+ 패턴에 추가되어야 한다는 의미입니다. 그 뒤에는 공백 문자, 단어, 등호가 차례로 붙습니다. 규칙은 다음과 같습니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=))))

이렇게 하면 1번째 줄에 대해 pizza:ham and pineapple이 반환됩니다.✅

그러나 2번째 줄에 대해 no match!가 반환됩니다. ❌

훨씬 더 낫습니다. 그런데, 2번째 줄이 피자를 매치시키지 못했습니다. 왜 그럴까요?

이 패턴은 데이터 다음에 다른 이름-값 쌍이 옵니다. 그렇지만 이 경우 규칙이 전체 줄을 검색했고 추가적인 이름-값 쌍이 없습니다. 캡처는 다른 이름-값 쌍이 오거나 $로 표시되는 줄 끝부분으로 확장되어야 합니다. 또한 후행 공백을 고려하는 것이 중요합니다. 이 공백은 non-greedy 연산자 %{SPACE}?로 삭제할 수 있습니다.

업데이트된 패턴은 다음과 같습니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))

1번째 줄에 대한 반환 값: pizza:ham and pineapple

2번째 줄에 대한 반환 값: pizza:ham and pineapple

이 표현식이 훨씬 더 낫고 안정적입니다. 하나의 필드만 캡처하려는 경우, 작업은 완료되었습니다. 그러나 로그에서 여러 필드를 캡처해야 하는 경우가 많습니다.

로그에 여러 필드 캡처

동일한 식을 반복하고 필요에 따라 값 이름을 변경해 여러 식을 연결하면 다른 값들을 캡처할 수 있습니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))(?=%{DATA}drink=(?<drink>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))(?=%{DATA}name=(?<name>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))

그러면 다음 결과가 반환됩니다.

1번째 줄: pizza:ham and pineapple, name:james buchanan and drink:lime and lemonade

2번째: 1번째 줄과 동일합니다 ✅

3번째: 1번째 줄과 동일합니다 ✅

4번째: no match! ❌

이는 예시 데이터의 1-3번째 줄에 적용됩니다. 이제 키-값 쌍의 순서에 관계없이 규칙이 매치 항목을 반환합니다. 안타깝게도, 입력의 줄4에 대해서는 실패합니다.

4: 내가 가장 좋아하는 피자=햄과 파인애플 드링크=라임과 레모네이드

4번째 줄에 이름 키가 누락되어 있을 수 있습니다. 정규 표현식 규칙에는 이름이 있어야 하고 그렇지 않으면 전체 패턴이 실패합니다. 이러한 오류는 데이터 세트에 정규 표현식을 사용할 때 종종 눈에 간과하는 일반적인 오류입니다. 이러한 종류의 문제는 규칙이 제대로 작동하는 것처럼 보이지만 중요한 정보를 수집하지 않기 때문에 다루기가 매우 까다롭습니다. 각 패턴을 선택적으로 만들어 이 문제를 해결할 수 있습니다. 이를 위해서는 ?를 각 표현식의 끝부분에 추가합니다.

다음은 각 키-값 쌍의 일반화된 패턴입니다.

(?=%{DATA}attrname=(?<attrname>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

데이터에 이 정규 표현식을 사용해 보겠습니다. 다음 표현식에는 캡처해야 하는 각 속성(name, pizzadrink)에 대해 한 번씩, 패턴이 총 세 번 포함됩니다.

(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}drink=(?<drink>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}name=(?<name>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

그러면 다음 결과가 반환됩니다.

1번째 줄: pizza:ham and pineapple, name:james buchanan and drink:lime and lemonade

2번째: 1번째 줄과 동일합니다 ✅

3번째: 1번째 줄과 동일합니다 ✅

4번째 줄: pizza:ham and pineapple, drink:lime and lemonade

5번째 줄: 1번째 줄과 동일합니다.✅

6번째 줄: drink:lime and lemonade

규칙은 순서에 관계없이 모든 테스트 입력 데이터에 정확하게 매치하며, 누락된 필드에 대해서도 계속 작동합니다.

정규 표현식의 성능 전방탐색

Lookaheads에는 추가적인 성능 오버헤드가 있으므로, 데이터가 신뢰할 수 있을정도로 일관성이 있다면, Lookaheads가 없는 보다 간단하고 성능이 나은 규칙을 사용할 수 있습니다. 규칙 시작 부분에 접두사 (?:^|\s+)를 추가하면 이 규칙을 더욱 효율적으로 실행할 수 있습니다.

(?:^|\s+)(?=%{DATA}pizza=(?<pizza>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}drink=(?<drink>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?(?=%{DATA}name=(?<name>[^=]+?(?=(?:\s+%{WORD}=|%{SPACE}?$))))?

이렇게 조금 변경을 하면 lookaheads는 모든 문자가 아니라 줄의 시작 부분이나 공백이 있을 때만 실행되며, 필요하지 않은 곳에서는 규칙이 lookaheads를 사용하지 않습니다.

결론

이 규칙이 작동하는 방식을 더 잘 이해하고 규칙을 더 안정적으로 만들기 위해 지속적으로 개선하는 데 조금이라도 도움이 되었길 바랍니다. 생각을 조금만 더하면 항상 더 나은 정규 표현식이 있습니다. 지금 사용 작업하는 사용 사례에서 훨씬 더 효과적인 정규 표현식을 찾으시길 바랍니다!

Regex parsing FAQs

1. What is regex and why should I use it for log parsing?

Regex, short for regular expression, is a powerful tool for pattern matching and extracting specific information from text. It's particularly useful for log parsing because logs often follow specific patterns. Regex allows you to define these patterns and extract meaningful data from log entries.

2. What are some common regex patterns for log parsing?

Common regex patterns for log parsing include:

  • Extracting IP addresses: \b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b
  • Parsing dates: \b\d{4}-\d{2}-\d{2}\b
  • Extracting URLs: (https?|ftp):\/\/[^\s/$.?#].[^\s]*

3. How can I optimize my regex parsing for performance?

  • Use specific patterns instead of generic ones to avoid unnecessary backtracking.
  • Utilize non-capturing groups (?:...) when you don't need to extract the matched content.
  • Be mindful of greedy vs. lazy quantifiers (e.g., * vs. *?) to avoid excessive matching.
  • Test your regex with a variety of input data to ensure it performs well under different conditions.

4. How do I handle multiline logs with regex?

Use the re.OTALL flag or (?s) modifier at the start of your regex pattern to make . match newline characters. Alternatively, you can use \n to explicitly match newline characters in your regex pattern.

5. How can I debug complex regex patterns?

Break down your regex pattern into smaller parts and test each part individually. Use comments within your pattern to annotate each section, making it easier to understand. Additionally, regex visualizers can help you visualize how your pattern matches input data.

6. Are there any common regex pitfalls I should be aware of?

  • Greediness: Greedy quantifiers can match more than intended. Use lazy quantifiers (*?, +?) when appropriate.
  • Overlooking special characters: Special characters like . or * need to be escaped (\. or \*) if you want to match them literally.
  • Not handling edge cases: Consider edge cases in your data, like empty fields or special characters, and adjust your regex pattern accordingly.