파서 작성 시 팁 및 문제 해결

이 문서에서는 파서 코드를 작성할 때 발생할 수 있는 문제에 대해 설명합니다.

파서 코드를 작성할 때 파싱 명령어가 예상대로 작동하지 않을 때 오류가 발생할 수 있습니다. 오류가 발생할 수 있는 상황은 다음과 같습니다.

  • Grok 패턴 실패
  • rename 또는 replace 작업 실패
  • 파서 코드의 구문 오류

파서 코드의 일반적인 권장사항

다음 섹션에서는 문제 해결에 도움이 되는 권장사항, 팁, 솔루션을 설명합니다.

변수 이름에 점 또는 하이픈 사용하지 않기

변수 이름에 하이픈과 점을 사용하면 UDM 필드에 값을 저장하기 위해 merge 작업을 수행할 때 종종 예기치 않은 동작이 발생할 수 있습니다. 간헐적 파싱 문제가 발생할 수도 있습니다.

예를 들어 다음 변수 이름을 사용하지 마세요.

  • my.variable.result
  • my-variable-result

대신 my_variable_result 변수 이름을 사용하세요.

변수 이름으로 특별한 의미를 갖는 용어 사용하지 않기

eventtimestamp와 같은 특정 단어는 파서 코드에서 특별한 의미를 가질 수 있습니다.

event 문자열은 단일 UDM 레코드를 나타내는 데 주로 사용되며 @output 문에 사용됩니다. 로그 메시지에 event라는 필드가 포함되거나 event라는 중간 변수를 정의한 경우 파서 코드에서 @output 문에 event 단어를 사용하면 이름 충돌에 대한 오류 메시지가 표시됩니다.

중간 변수의 이름을 다른 이름으로 변경하거나 event1이라는 용어를 UDM 필드 이름과 @output 문에서 프리픽스로 사용합니다.

timestamp라는 단어는 원본 원시 로그의 생성된 타임스탬프를 나타냅니다. 이 중간 변수에 설정된 값은 metadata.event_timestamp UDM 필드에 저장됩니다. @timestamp라는 용어는 원시 로그가 UDM 레코드를 만들기 위해 파싱된 날짜와 시간을 나타냅니다.

다음 예시에서는 metadata.event_timestamp UDM 필드를 원시 로그가 파싱된 날짜 및 시간으로 설정합니다.

 # Save the log parse date and time to the timestamp variable
  mutate {
     rename => {
       "@timestamp" => "timestamp"
     }
   }

다음 예시에서는 metadata.event_timestamp UDM 필드를 원래 원시 로그에서 추출되어 when 중간 변수에 저장된 날짜 및 시간으로 설정합니다.

   # Save the event timestamp to timestamp variable
   mutate {
     rename => {
       "when" => "timestamp"
     }
   }

다음 용어를 변수로 사용하지 마세요.

  • collectiontimestamp
  • createtimestamp
  • event
  • filename
  • 메시지
  • 네임스페이스
  • 출력
  • onerrorcount
  • timestamp
  • timezone

각 데이터 값을 별도의 UDM 필드에 저장

여러 필드를 구분 기호로 연결하여 단일 UDM 필드에 여러 필드를 저장하지 않습니다. 다음은 그 예시입니다.

"principal.user.first_name" => "first:%{first_name},last:%{last_name}"

대신 각 값을 별도의 UDM 필드에 저장합니다.

"principal.user.first_name" => "%{first_name}"
"principal.user.last_name" => "%{last_name}"

코드에서 Tab 대신 공백 사용

파서 코드에 Tab을 사용하지 마세요. 공백만 사용하고 한 번에 두 칸을 들여 쓰세요.

단일 작업에서 여러 병합 작업 수행하지 않기

단일 작업에서 여러 필드를 병합하면 일관성 없는 결과가 발생할 수 있습니다. 대신 merge 문을 별도의 작업으로 배치합니다.

예를 들어 다음 예시를 바꿉니다.

mutate {
  merge => {
      "security_result.category_details" => "category_details"
      "security_result.category_details" => "super_category_details"
  }
}

다음과 같이 바꿉니다.

mutate {
  merge => {
    "security_result.category_details" => "category_details"
  }
}

mutate {
  merge => {
    "security_result.category_details" => "super_category_details"
  }
}

ifif else 조건식 비교 선택

테스트하는 조건부 값이 단일 일치만 가질 수 있으면 if else 조건문을 사용합니다. 이 방법은 약간 더 효율적입니다. 그러나 테스트된 값이 두 번 이상 일치할 수 있는 시나리오가 있으면 여러 고유한 if 문을 사용하여 가장 일반적인 사례에서 가장 구체적인 사례로 문을 정렬합니다.

파서 변경사항을 테스트할 대표 로그 파일 세트 선택

권장사항은 다양한 형식의 원시 로그 샘플을 사용하여 파서 코드를 테스트하는 것입니다. 이렇게 하면 파서가 처리해야 하는 고유한 로그 또는 특이 사례를 찾을 수 있습니다.

파서 코드에 설명이 포함된 주석 추가

문이 하는 역할보다는 문이 중요한 이유를 설명하는 주석을 파서 코드에 추가합니다. 주석은 파서를 유지관리하는 모든 사용자가 흐름을 파악할 수 있도록 지원합니다. 다음은 그 예시입니다.

# only assign a Namespace if the source address is RFC 1918 or Loopback IP address
if [jsonPayload][id][orig_h] =~ /^(127(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{3\}$)|(10(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{3\}$)|(192\.168(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{2\}$)|(172\.(?:1[6-9]|2\d|3[0-1])(?:\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\{2\}$)/ {
  mutate {
    replace => {
      "event1.idm.read_only_udm.principal.namespace" => "%{resource.labels.project_id}"
    }
  }
}

중간 변수를 조기에 초기화

원본 원시 로그에서 값을 추출하기 전에 테스트 값을 저장하는 데 사용할 중간 변수를 초기화합니다.

이렇게 하면 중간 변수가 존재하지 않음을 나타내는 오류가 반환되지 않습니다.

다음 문은 product 변수의 값을 metadata.product_name UDM 필드에 할당합니다.

mutate{
  replace => {
    "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
  }
}

product 변수가 없으면 다음 오류가 발생합니다.

"generic::invalid_argument: pipeline failed: filter mutate (4) failed: replace failure: field \"event1.idm.read_only_udm.metadata.product_name\": source field \"product\": field not set"

on_error 문을 추가하여 오류를 포착할 수 있습니다. 다음은 그 예시입니다.

mutate{
  replace => {
    "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
    }
  on_error => "_error_does_not_exist"
  }

위 예시 문은 파싱 오류를 _error_does_not_exist라는 불리언 중간 변수로 성공적으로 포착합니다. 조건문에서는 product 변수를 사용할 수 없습니다(예: if). 다음은 그 예시입니다.

if [product] != "" {
  mutate{
    replace => {
      "event1.idm.read_only_udm.metadata.product_name" => "%{product}"
    }
  }
  on_error => "_error_does_not_exist"
}

if 조건부 절은 on_error 문을 지원하지 않으므로 위 예시는 다음과 같은 오류를 반환합니다.

"generic::invalid_argument: pipeline failed: filter conditional (4) failed: failed to evaluate expression: generic::invalid_argument: "product" not found in state data"

이 문제를 해결하려면 추출 필터(json, csv, xml, kv, grok) 문을 실행하기 전에 중간 변수를 초기화하는 별도의 문 블록을 추가합니다. 다음은 그 예시입니다.

filter {
  # Initialize intermediate variables for any field you will use for a conditional check
  mutate {
    replace => {
      "timestamp" => ""
      "does_not_exist" => ""
    }
  }

  # load the logs fields from the message field
  json {
    source         => "message"
    array_function => "split_columns"
    on_error       => "_not_json"
  }
}

파서 코드의 업데이트된 스니펫은 조건문을 사용하여 필드가 있는지 확인하는 여러 시나리오를 처리합니다. 또한 on_error 문은 발생할 수 있는 오류를 처리합니다.

SHA-256을 base64로 변환

다음 예시에서는 SHA-256 값을 추출하여 base64로 인코딩하고 인코딩된 데이터를 16진수 문자열로 변환한 후 특정 필드를 추출 및 처리된 값으로 바꿉니다.

if [Sha256] != ""
{
  base64
  {
  encoding => "RawStandard"
  source => "Sha256"
  target => "base64_sha256"
  on_error => "base64_message_error"
  }
  mutate
  {
    convert =>
    {
      "base64_sha256" => "bytestohex"
    }
    on_error => "already_a_string"
  }
  mutate
  {
    replace =>
  {
     "event.idm.read_only_udm.network.tls.client.certificate.sha256" => "%{base64_sha256}"
     "event.idm.read_only_udm.target.resource.name" => "%{Sha256}"
  }
  }
}

파서 문의 오류 처리

수신 로그가 예기치 않은 로그 형식이거나 잘못된 형식의 데이터를 포함하는 경우가 많습니다.

파서를 빌드하여 이러한 오류를 처리할 수 있습니다. 추출 필터에 on_error 핸들러를 추가한 후 파서 로직의 다음 세그먼트로 진행하기 전에 중간 변수를 테스트하는 것이 좋습니다.

다음 예시에서는 on_error 문이 있는 json 추출 필터를 사용하여 _not_json 불리언 변수를 설정합니다. _not_jsontrue로 설정되면 수신 로그 항목이 유효한 JSON 형식이 아니며 로그 항목이 성공적으로 파싱되지 않은 것입니다. _not_json 변수가 false이면 수신 로그 항목이 유효한 JSON 형식인 것입니다.

 # load the incoming log from the default message field
  json {
    source         => "message"
    array_function => "split_columns"
    on_error       => "_not_json"
  }

필드의 형식이 올바른지 테스트할 수도 있습니다. 다음 예시에서는 로그가 예상한 형식이 아님을 나타내는 _not_jsontrue로 설정되었는지 확인합니다.

 # Test that the received log matches the expected format
  if [_not_json] {
    drop { tag => "TAG_MALFORMED_MESSAGE" }
  } else {
    # timestamp is always expected
    if [timestamp] != "" {

      # ...additional parser logic goes here …

    } else {

      # if the timestamp field does not exist, it's not a log source
      drop { tag => "TAG_UNSUPPORTED" }
    }
  }

이렇게 하면 지정된 로그 유형의 잘못된 형식으로 로그가 수집될 때 파싱이 실패하지 않습니다.

BigQuery의 수집 측정항목 테이블에 조건이 캡처되도록 tag 변수와 함께 drop 필터를 사용합니다.

  • TAG_UNSUPPORTED
  • TAG_MALFORMED_ENCODING
  • TAG_MALFORMED_MESSAGE
  • TAG_NO_SECURITY_VALUE

drop 필터는 파서가 원시 로그를 처리하고 필드를 정규화하고 UDM 레코드를 만들지 못하도록 중지합니다. 원본 원시 로그는 계속 Google Security Operations에 수집되고 Google Security Operations에서 원시 로그 검색을 통해 검색될 수 있습니다.

tag 변수에 전달된 값은 수집 측정항목 테이블의 drop_reason_code 필드에 저장됩니다. 다음과 유사한 테이블에 대해 임시 쿼리를 실행할 수 있습니다.

SELECT
  log_type,
  drop_reason_code,
  COUNT(drop_reason_code) AS count
FROM `datalake.ingestion_metrics`
GROUP BY 1,2
ORDER BY 1 ASC

검증 오류 문제 해결

파서를 빌드할 때 검증과 관련된 오류가 발생할 수 있습니다. 예를 들어 UDM 레코드에 필수 필드가 설정되지 않았습니다. 이 오류는 다음과 비슷하게 표시됩니다.

Error: generic::unknown: invalid event 0: LOG_PARSING_GENERATED_INVALID_EVENT: "generic::invalid_argument: udm validation failed: target field is not set"

파서 코드가 성공적으로 실행되지만 생성된 UDM 레코드에는 metadata.event_type으로 설정된 값으로 정의된 모든 필수 UDM 필드가 포함되지 않습니다. 다음은 이 오류를 발생시킬 수 있는 추가 예시입니다.

  • metadata.event_typeUSER_LOGIN이고 target.user value UDM 필드가 설정되지 않은 경우
  • metadata.event_typeNETWORK_CONNECTION이고 target.hostnameUDM 필드가 설정되지 않은 경우

metadata.event_type UDM 필드 및 필수 필드에 대한 자세한 내용은 UDM 사용 가이드를 참조하세요.

이 유형의 오류 문제를 해결하는 한 가지 옵션은 정적 값을 UDM 필드로 설정하는 것입니다. 필요한 모든 UDM 필드를 정의한 후 원본 원시 로그를 검사하여 파싱하고 UDM 레코드에 저장할 값을 확인합니다. 원본 원시 로그에 특정 필드가 포함되어 있지 않으면 기본값을 설정해야 할 수 있습니다.

다음은 이 방법을 보여주는 USER_LOGIN 이벤트 유형의 예시 템플릿입니다.

다음 사항에 유의하세요.

  • 템플릿은 중간 변수를 초기화하고 각 변수를 정적 문자열로 설정합니다.
  • 필드 할당 섹션의 코드는 중간 변수의 값을 UDM 필드로 설정합니다.

추가 중간 변수와 UDM 필드를 추가하여 이 코드를 확장할 수 있습니다. 채워야 하는 모든 UDM 필드를 식별한 후 다음을 수행합니다.

  • 입력 구성 섹션에서 원본 원시 로그에서 필드를 추출하고 값을 중간 변수로 설정하는 코드를 추가합니다.

  • 날짜 추출 섹션에서 원본 원시 로그에서 이벤트 타임스탬프를 추출하여 변환하고 중간 변수로 설정하는 코드를 추가합니다.

  • 필요에 따라 각 중간 변수에 설정된 초기화된 값을 빈 문자열로 바꿉니다.

filter {
 mutate {
   replace => {
     # UDM > Metadata
     "metadata_event_timestamp"    => ""
     "metadata_vendor_name"        => "Example"
     "metadata_product_name"       => "Example SSO"
     "metadata_product_version"    => "1.0"
     "metadata_product_event_type" => "login"
     "metadata_product_log_id"     => "12345678"
     "metadata_description"        => "A user logged in."
     "metadata_event_type"         => "USER_LOGIN"

     # UDM > Principal
     "principal_ip"       => "192.168.2.10"

     # UDM > Target
     "target_application"            => "Example Connect"
     "target_user_user_display_name" => "Mary Smith"
     "target_user_userid"            => "mary@example.com"

     # UDM > Extensions
     "auth_type"          => "SSO"
     "auth_mechanism"     => "USERNAME_PASSWORD"

     # UDM > Security Results
     "securityResult_action"         => "ALLOW"
     "security_result.severity"       => "LOW"

   }
 }

 # ------------ Input Configuration  --------------
  # Extract values from the message using one of the extraction filters: json, kv, grok

 # ------------ Date Extract  --------------
 # If the  date {} function is not used, the default is the normalization process time

  # ------------ Field Assignment  --------------
  # UDM Metadata
  mutate {
    replace => {
      "event1.idm.read_only_udm.metadata.vendor_name"        =>  "%{metadata_vendor_name}"
      "event1.idm.read_only_udm.metadata.product_name"       =>  "%{metadata_product_name}"
      "event1.idm.read_only_udm.metadata.product_version"    =>  "%{metadata_product_version}"
      "event1.idm.read_only_udm.metadata.product_event_type" =>  "%{metadata_product_event_type}"
      "event1.idm.read_only_udm.metadata.product_log_id"     =>  "%{metadata_product_log_id}"
      "event1.idm.read_only_udm.metadata.description"        =>  "%{metadata_description}"
      "event1.idm.read_only_udm.metadata.event_type"         =>  "%{metadata_event_type}"
    }
  }

  # Set the UDM > auth fields
  mutate {
    replace => {
      "event1.idm.read_only_udm.extensions.auth.type"        => "%{auth_type}"
    }
    merge => {
      "event1.idm.read_only_udm.extensions.auth.mechanism"   => "auth_mechanism"
    }
  }

  # Set the UDM > principal fields
  mutate {
    merge => {
      "event1.idm.read_only_udm.principal.ip"                => "principal_ip"
    }
  }

  # Set the UDM > target fields
  mutate {
    replace => {
      "event1.idm.read_only_udm.target.user.userid"             =>  "%{target_user_userid}"
      "event1.idm.read_only_udm.target.user.user_display_name"  =>  "%{target_user_user_display_name}"
      "event1.idm.read_only_udm.target.application"             =>  "%{target_application}"
    }
  }

  # Set the UDM > security_results fields
  mutate {
    merge => {
      "security_result.action" => "securityResult_action"
    }
  }

  # Set the security result
  mutate {
    merge => {
      "event1.idm.read_only_udm.security_result" => "security_result"
    }
  }

 # ------------ Output the event  --------------
  mutate {
    merge => {
      "@output" => "event1"
    }
  }

}

Grok 함수를 사용하여 구조화되지 않은 텍스트 파싱

Grok 함수를 사용하여 구조화되지 않은 텍스트에서 값을 추출할 때는 사전 정의된 Grok 패턴과 정규 표현식 문을 사용할 수 있습니다. Grok 패턴을 사용하면 코드를 더 쉽게 읽을 수 있습니다. 정규 표현식에 약식 문자(예: \w, \s)가 포함되지 않은 경우 문을 복사하여 파서 코드에 직접 붙여넣을 수 있습니다.

Grok 패턴은 문에서 추가 추상화 레이어이므로 오류가 발생하면 문제 해결이 더 복잡해질 수 있습니다. 다음은 사전 정의된 Grok 패턴과 정규 표현식을 모두 포함하는 Grok 함수의 예시입니다.

grok {
  match => {
    "message" => [
      "%{NUMBER:when}\\s+\\d+\\s%{SYSLOGHOST:srcip} %{WORD:action}\\/%{NUMBER:returnCode} %{NUMBER:size} %{WORD:method} (?P<url>\\S+) (?P<username>.*?) %{WORD}\\/(?P<tgtip>\\S+).*"
    ]
  }
}

Grok 패턴이 없는 추출 문은 더 높은 성능을 발휘할 수 있습니다. 예를 들어 다음 예시는 일치시키기 위한 처리 단계의 절반 미만이 걸립니다. 잠재적으로 볼륨이 큰 로그 소스의 경우 이는 중요한 고려사항입니다.

RE2 정규 표현식과 PCRE 정규 표현식 간의 차이점 이해

Google Security Operations 파서는 RE2를 정규 표현식 엔진으로 사용합니다. PCRE 구문에 익숙하다면 차이점을 발견할 수 있습니다. 다음은 그 예시입니다.

(?<_custom_field>\w+)\s는 PCRE 문입니다.

(?P<_custom_field>\\w+)\\s는 파서 코드의 RE2 문입니다.

이스케이프 문자를 이스케이프 처리해야 함

Google Security Operations는 수신되는 원시 로그 데이터를 JSON 인코딩 형식으로 저장합니다. 이는 정규 표현식 약식으로 보이는 문자열이 리터럴 문자열로 해석되도록 하기 위함입니다. 예를 들어 \t는 Tab 문자가 아닌 리터럴 문자열로 해석됩니다.

다음 예시는 원본 원시 로그와 JSON으로 인코딩된 형식 로그입니다. entry라는 용어를 묶는 각 백슬래시 문자 앞에 이스케이프 문자가 추가되었습니다.

다음은 원본 원시 로그입니다.

field=\entry\

다음은 JSON 인코딩 형식으로 변환된 로그입니다.

field=\\entry\\

파서 코드에서 정규 표현식을 사용할 때 값만 추출하려면 이스케이프 문자를 추가해야 합니다. 원본 원시 로그의 백슬래시와 일치시키려면 추출 문에 백슬래시 4개를 사용합니다.

다음은 파서 코드의 정규 표현식입니다.

^field=\\\\(?P<_value>.*)\\\\$

다음은 생성된 결과입니다. 이름이 지정된 _value 그룹은 entry라는 용어를 저장합니다.

"_value": "entry"

표준 정규 표현식 문을 파서 코드로 이동할 때 추출 문에서 정규 표현식 단축 문자를 이스케이프합니다. 예를 들어 \s\\s로 변경합니다.

추출 문에서 이중 이스케이프 처리된 경우 정규 표현식 특수 문자를 변경하지 않은 상태로 둡니다. 예를 들어 \\\\로 변경되지 않습니다.

다음은 표준 정규 표현식입니다.

^.*?\\\"(?P<_user>[^\\]+)\\\"\s(?:(logged\son|logged\soff))\s.*?\\\"(?P<_device>[^\\]+)\\\"\.$

다음 정규 표현식은 파서 코드 내에서 작동하도록 수정됩니다.

^.*?\\\"(?P<_user>[^\\\\]+)\\\"\\s(?:(logged\\son|logged\\soff))\\s.*?\\\"(?P<_device>[^\\\\]+)\\\"\\.$

다음 표에는 표준 정규 표현식이 파서 코드에 포함되기 전에 추가 이스케이프 문자를 포함해야 하는 경우가 요약되어 있습니다.

정규 표현식 파서 코드에 대한 수정된 정규 표현식 변경사항 설명

\s

\\s
단축 문자는 이스케이프 처리되어야 합니다.

\.

\\.
예약된 문자는 이스케이프 처리되어야 합니다.

\\"

\\\"
예약된 문자는 이스케이프 처리되어야 합니다.

\]

\\]
예약된 문자는 이스케이프 처리되어야 합니다.

\|

\\|
예약된 문자는 이스케이프 처리되어야 합니다.

[^\\]+

[^\\\\]+
문자 클래스 그룹 내 특수문자는 이스케이프 처리되어야 합니다.

\\\\

\\\\
문자 클래스 그룹 이외의 특수문자 또는 단축 문자는 추가 이스케이프가 필요하지 않습니다.

정규 표현식에는 이름이 지정된 캡처 그룹이 포함되어야 함

"^.*$"와 같은 정규 표현식은 유효한 RE2 구문입니다. 하지만 파서 코드에서 다음 오류와 함께 실패합니다.

"ParseLogEntry failed: pipeline failed: filter grok (0) failed: failed to parse data with all match
patterns"

표현식에 유효한 캡처 그룹을 추가해야 합니다. Grok 패턴을 사용하는 경우 기본적으로 이름이 지정된 캡처 그룹이 포함됩니다. 정규 표현식 재정의를 사용할 때는 이름이 지정된 그룹을 포함해야 합니다.

다음은 파서 코드의 정규 표현식 예시입니다.

"^(?P<_catchall>.*$)"

다음은 이름이 지정된 _catchall 그룹에 할당된 텍스트를 보여주는 결과입니다.

"_catchall": "User \"BOB\" logged on to workstation \"DESKTOP-01\"."

표현식을 만들 때 이름이 지정된 포괄 그룹을 사용하여 시작하기

추출 문을 작성할 때는 원하는 것보다 많은 것을 포착하는 표현식으로 시작하세요. 그런 다음 표현식을 한 번에 한 필드씩 펼칩니다.

다음 예시에서는 전체 메시지와 일치하는 이름이 지정된 그룹(_catchall)을 사용하여 시작합니다. 그런 다음 텍스트의 추가 부분을 일치시켜 표현식을 단계별로 빌드합니다. 각 단계마다 이름이 지정된 _catchall 그룹에는 원본 텍스트가 적게 포함됩니다. 계속 진행하며 한 번에 한 단계씩 반복하여 이름이 지정된 _catchall 그룹이 더 이상 필요하지 않을 때까지 메시지를 일치시킵니다.

단계 파서 코드의 정규 표현식 이름이 지정된 _catchall 캡처 그룹의 출력
1

"^(?P<_catchall>.*$)"

User \"BOB\" logged on to workstation \"DESKTOP-01\".
2

^User\s\\\"(?P<_catchall>.*$)

BOB\" logged on to workstation \"DESKTOP-01\".
3

^User\s\\\"(?P<_user>.*?)\\\"\s(?P<_catchall>.*$)

logged on to workstation \"DESKTOP-01\".
표현식이 전체 텍스트 문자열과 일치할 때까지 계속 진행합니다.

정규 표현식에서 단축 문자 이스케이프 처리

파서 코드에서 표현식을 사용할 때는 정규 표현식 단축 문자를 이스케이프 처리해야 합니다. 다음은 예시 텍스트 문자열과 첫 번째 단어 This를 추출하는 표준 정규 표현식입니다.

  This is a sample log.

다음 표준 정규 표현식은 첫 번째 단어 This를 추출합니다. 하지만 파서 코드에서 이 표현식을 실행하면 결과에 문자 s가 누락됩니다.

표준 정규 표현식. 이름이 지정된 _firstWord 캡처 그룹의 출력
"^(?P<_firstWord>[^\s]+)\s.*$" "_firstWord": "Thi",

파서 코드의 정규 표현식에 단축 문자에 추가된 이스케이프 문자가 필요하기 때문입니다. 앞의 예시에서 \s\\s로 변경해야 합니다.

파서 코드의 수정된 정규 표현식. 이름이 지정된 _firstWord 캡처 그룹의 출력
"^(?P<_firstWord>[^\\s]+)\\s.*$" "_firstWord": "This",

이는 \s, \r, \t과 같은 단축 문자에만 적용됩니다. ``와 같은 다른 문자는 더 이스케이프 처리할 필요가 없습니다.

전체 예시

이 섹션에서는 이전의 규칙을 엔드 투 엔드 예시로 설명합니다. 다음은 구조화되지 않은 텍스트 문자열과 문자열 파싱을 위해 작성된 표준 정규 표현식입니다. 마지막으로 파서 코드에서 작동하는 수정된 정규 표현식이 포함됩니다.

다음은 원본 텍스트 문자열입니다.

User "BOB" logged on to workstation "DESKTOP-01".

다음은 텍스트 문자열을 파싱하는 표준 RE2 정규 표현식입니다.

^.*?\\\"(?P<_user>[^\\]+)\\\"\s(?:(logged\son|logged\soff))\s.*?\\\"(?P<_device>[^\\]+)\\\"\.$

이 표현식은 다음 필드를 추출합니다.

일치 그룹 문자 위치 텍스트 문자열
전체 일치 0-53

User \"BOB\" logged on to workstation \"DESKTOP-01\".
`_user` 그룹 7-10

BOB
그룹 2 13-22

logged on
`_device` 그룹 40-50

DESKTOP-01

수정된 표현식입니다. 표준 RE2 정규 표현식이 파서 코드에서 작동하도록 수정되었습니다.

^.*?\\\"(?P<_user>[^\\\\]+)\\\"\\s(?:(logged\\son|logged\\soff))\\s.*?\\\"(?P<_device>[^\\\\]+)\\\"\\.$