파서 작성 시 팁 및 문제 해결

다음에서 지원:

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

파서 코드를 작성할 때 안내 파싱이 예상대로 작동하지 않으면 오류가 발생할 수 있습니다. 오류가 발생할 수 있는 상황은 다음과 같습니다.

  • 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라는 불리언 중간 변수로 성공적으로 포착합니다. 조건문(예: if)에서 product 변수를 사용할 수는 없습니다. 다음은 그 예시입니다.

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" }
    }
  }

이렇게 하면 지정된 로그 유형에 맞지 않는 형식으로 로그가 처리되더라도 파싱이 실패하지 않습니다.

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

  • 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>[^\\\\]+)\\\"\\.$