본문 바로가기
WIKI 기술 지식 베이스

Key-value processor

원문 보기 위키 갱신

출처: 문서

본문

Key-value 프로세서

key_value 프로세서를 사용하면 지정한 필드를 키-값 쌍으로 파싱할 수 있어요. 다음 옵션들을 사용해 key_value 프로세서가 필드 정보를 파싱하는 방식을 커스터마이즈할 수 있어요. 다음 옵션 각각의 타입은 string이에요.

예제

다음 예제들은 이 프로세서에서 사용할 수 있는 여러 가지 설정을 보여줘요.

이 예제들은 보안을 사용하지 않으며 시연 목적으로만 제공돼요. 프로덕션 환경에서 이 예제들을 사용하기 전에 SSL을 설정하는 것을 강력히 권장해요. {: .warning}

키-값 파싱, 정규화, 중복 제거

다음 예제는 message 필드를 key=value 쌍으로 파싱하고, 키를 정규화 및 정리한 뒤 meta_ 접두사를 붙이며, 값을 중복 제거하고, 값이 없는 키는 버려서 parsed_kv에 담아요:

kv-basic-pipeline:
  source:
    http:
      path: /logs
      ssl: false

  processor:
    - key_value:
        # Read key=value pairs from the "message" field (default anyway)
        source: message
        # Write parsed pairs into a nested object "parsed_kv"
        destination: parsed_kv

        # Split pairs on '&' and split key vs value on '='
        field_split_characters: "&"
        value_split_characters: "="

        # Normalize keys and trim garbage whitespace around keys/values
        transform_key: lowercase
        delete_key_regex: "\\s+"          # remove spaces from keys
        delete_value_regex: "^\\s+|\\s+$" # trim leading/trailing spaces

        # Add a prefix to every key (after normalization + delete_key_regex)
        prefix: meta_

        # Keep a single unique value for duplicate keys
        skip_duplicate_values: true

        # Drop keys whose value is empty/absent (e.g., `empty=` or `novalue`)
        drop_keys_with_no_value: true

  sink:
    - opensearch:
        hosts: ["https://opensearch:9200"]
        insecure: true
        username: admin
        password: admin_password
        index_type: custom
        index: kv-basic-%{yyyy.MM.dd}

{% include copy.html %}

다음 명령어로 이 파이프라인을 테스트할 수 있어요:

curl -sS -X POST "http://localhost:2021/logs" \
  -H "Content-Type: application/json" \
  -d '[
    {"message":"key1=value1&key1=value1&Key Two = value two & empty=&novalue"},
    {"message":"ENV = prod & TEAM = core & owner = alice "}
  ]'

{% include copy.html %}

OpenSearch에 저장된 문서에는 다음 정보가 담겨 있어요:

{
  ...
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "kv-basic-2025.10.14",
        "_id": "M6d84pkB3P3jd6EROH_f",
        "_score": 1,
        "_source": {
          "message": "key1=value1&key1=value1&Key Two = value two & empty=&novalue",
          "parsed_kv": {
            "meta_key1": "value1",
            "meta_empty": "",
            "meta_keytwo": "value two"
          }
        }
      },
      {
        "_index": "kv-basic-2025.10.14",
        "_id": "NKd84pkB3P3jd6EROH_f",
        "_score": 1,
        "_source": {
          "message": "ENV = prod & TEAM = core & owner = alice ",
          "parsed_kv": {
            "meta_owner": "alice",
            "meta_team": "core",
            "meta_env": "prod"
          }
        }
      }
    ]
  }
}

그룹화된 값을 루트로 저장

다음 예제는 &&로 쌍을 구분하고 ==로 키와 값을 구분해서 payload 필드를 파싱해요. 괄호로 묶인 그룹은 하나의 값으로 유지하고, 파싱 결과는 기존 필드를 덮어쓰지 않고 이벤트 루트에 기록하며, 매칭되지 않은 토큰은 null로 기록해요:

kv-grouping-pipeline:
  source:
    http:
      path: /logs
      ssl: false

  processor:
    - key_value:
        source: "payload"
        destination: null

        field_split_characters: "&&"     # pair delimiter (OK with grouping)
        value_split_characters: null     # disable the default "="
        key_value_delimiter_regex: "=="  # exact '==' for key/value

        value_grouping: true
        remove_brackets: false
        overwrite_if_destination_exists: false
        non_match_value: null

  sink:
    - opensearch:
        hosts: ["https://opensearch:9200"]
        insecure: true
        username: admin
        password: "admin_pass"
        index_type: custom
        index: "kv-regex-%{yyyy.MM.dd}"

{% include copy.html %}

다음 명령어로 이 파이프라인을 테스트할 수 있어요:

curl -sS -X POST "http://localhost:2021/logs" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "payload":"a==1&&b==[x=y,z=w]&&c==(inner=thing)&&http==http://example.com path",
      "a":"keep-me"
    },
    {
      "payload":"good==yes&&broken-token&&url==https://opensearch.org home",
      "note":"second doc"
    }
  ]'

{% include copy.html %}

OpenSearch에 저장된 문서에는 다음 정보가 담겨 있어요:

{
  ...
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "kv-regex-2025.10.14",
        "_id": "FuCX4pkB344hN2Iu62oT",
        "_score": 1,
        "_source": {
          "payload": "a==1&&b==[x=y,z=w]&&c==(inner=thing)&&http==http://example.com path",
          "a": "keep-me",
          "b": "[x=y,z=w]",
          "c": "(inner=thing)",
          "http": "http://example.com path"
        }
      },
      {
        "_index": "kv-regex-2025.10.14",
        "_id": "F-CX4pkB344hN2Iu62oT",
        "_score": 1,
        "_source": {
          "payload": "good==yes&&broken-token&&url==https://opensearch.org home",
          "note": "second doc",
          "broken-token": null,
          "good": "yes",
          "url": "https://opensearch.org home"
        }
      }
    ]
  }
}

조건부 재귀 키-값 파싱

다음 예제는 /type == "nested"인 경우에만 body에서 괄호로 묶인 중첩 key=value 구조를 parsed.*로 파싱해요. 그룹 계층 구조를 유지하고, 엄격한 중첩 규칙을 적용하며, 기본 필드를 적용하고, 중첩되지 않은 이벤트는 그대로 유지해요:

kv-conditional-recursive-pipeline:
  source:
    http:
      path: /logs
      ssl: false

  processor:
    - key_value:
        source: "body"
        destination: "parsed"

        key_value_when: '/type == "nested"'
        recursive: true

        # Split rules (per docs; not regex)
        field_split_characters: "&"
        value_split_characters: "="

        # Grouping & quoting (per docs)
        value_grouping: true
        string_literal_character: "\""
        remove_brackets: false

        # Keep only some top-level keys; then set defaults
        include_keys: ["item1","item2","owner"]
        default_values:
          owner: "unknown"
          region: "eu-west-1"

        strict_grouping: true
        tags_on_failure: ["keyvalueprocessor_failure"]

  sink:
    - opensearch:
        hosts: ["https://opensearch:9200"]
        insecure: true
        username: admin
        password: "admin_pass"
        index_type: custom
        index: "kv-recursive-%{yyyy.MM.dd}"

{% include copy.html %}

다음 명령어로 이 파이프라인을 테스트할 수 있어요:

curl -sS -X POST "http://localhost:2021/logs" \
  -H "Content-Type: application/json" \
  -d '[
  {
    "type":"nested","body":"item1=[a=1&b=(c=3&d=<e=5>)]&item2=2&owner=alice"
  },
  {
    "type":"flat","body":"item1=[should=not&be=parsed]&item2=42"
  },
  {
    "type":"nested","body":"item1=[desc=\"a=b + c=d\"&x=1]&item2=2"
  }
]'

{% include copy.html %}

OpenSearch에 저장된 문서에는 다음 정보가 담겨 있어요:

{
  ...
  "hits": {
    "total": {
      "value": 3,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "kv-recursive-2025.10.14",
        "_id": "Q7fC4pkBc0UY8I7pZ6vZ",
        "_score": 1,
        "_source": {
          "type": "nested",
          "body": "item1=[a=1&b=(c=3&d=<e=5>)]&item2=2&owner=alice",
          "parsed": {
            "owner": "alice",
            "item2": "2",
            "item1": {
              "a": "1",
              "b": {
                "c": "3",
                "d": {
                  "e": "5"
                }
              }
            },
            "region": "eu-west-1"
          }
        }
      },
      {
        "_index": "kv-recursive-2025.10.14",
        "_id": "RLfC4pkBc0UY8I7pZ6vZ",
        "_score": 1,
        "_source": {
          "type": "flat",
          "body": "item1=[should=not&be=parsed]&item2=42"
        }
      },
      {
        "_index": "kv-recursive-2025.10.14",
        "_id": "RbfC4pkBc0UY8I7pZ6vZ",
        "_score": 1,
        "_source": {
          "type": "nested",
          "body": """item1=[desc="a=b + c=d"&x=1]&item2=2""",
          "parsed": {
            "owner": "unknown",
            "item2": "2",
            "item1": {
              "desc": "\"a=b + c=d\"",
              "x": "1"
            },
            "region": "eu-west-1"
          }
        }
      }
    ]
  }
}

설정

옵션 설명 예시
source 파싱할 메시지 필드예요. 선택 사항이며 기본값은 message예요. source가 "message1"이면 {"message1": {"key1=value1"}, "message2": {"key2=value2"}}가 {"message1": {"key1=value1"}, "message2": {"key2=value2"}, "parsed_message": {"key1": "value1"}}로 파싱돼요.
destination 파싱된 소스가 저장될 대상 필드예요. 파싱된 소스는 해당 키의 기존 데이터를 덮어써요. 선택 사항이에요. destination을 null로 설정하면 파싱된 필드가 이벤트 루트에 기록돼요. 기본값은 parsed_message예요. destination이 "parsed_data"이면 {"message": {"key1=value1"}}이 {"message": {"key1=value1"}, "parsed_data": {"key1": "value1"}}로 파싱돼요.
field_delimiter_regex 키-값 쌍을 구분하는 구분자를 지정하는 정규식이에요. [와 ] 같은 특수 정규식 문자는 \\로 이스케이프해야 해요. field_split_characters와 동시에 정의할 수 없어요. 선택 사항이며, 이 옵션이 정의되지 않으면 field_split_characters가 사용돼요. field_delimiter_regex가 "&\\{2\\}"이면 {"key1=value1&&key2=value2"}가 {"key1": "value1", "key2": "value2"}로 파싱돼요.
field_split_characters 키-값 쌍을 구분하는 구분자를 지정하는 문자열이에요. [와 ] 같은 특수 정규식 문자는 \\로 이스케이프해야 해요. field_delimiter_regex와 동시에 정의할 수 없어요. 선택 사항이며 기본값은 &예요. field_split_characters가 "&&"이면 {"key1=value1&&key2=value2"}가 {"key1": "value1", "key2": "value2"}로 파싱돼요.
key_value_delimiter_regex 키-값 쌍 내에서 키와 값을 구분하는 구분자를 지정하는 정규식이에요. [와 ] 같은 특수 정규식 문자는 \\로 이스케이프해야 해요. 이 옵션은 value_split_characters와 동시에 정의할 수 없어요. 선택 사항이며, 이 옵션이 정의되지 않으면 value_split_characters가 사용돼요. key_value_delimiter_regex가 "=\\{2\\}"이면 {"key1==value1"}이 {"key1": "value1"}로 파싱돼요.
value_split_characters 키-값 쌍 내에서 키와 값을 구분하는 구분자를 지정하는 문자열이에요. [와 ] 같은 특수 정규식 문자는 \\로 이스케이프해야 해요. key_value_delimiter_regex와 동시에 정의할 수 없어요. 선택 사항이며 기본값은 =예요. value_split_characters가 "=="이면 {"key1==value1"}이 {"key1": "value1"}로 파싱돼요.
non_match_value 키-값 쌍을 제대로 분리하지 못하면 해당 키-값 쌍이 key 필드에 담기고, 지정한 값이 value 필드에 담겨요. 선택 사항이며 기본값은 null이에요. key1value1&key2=value2는 {"key1value1": null, "key2": "value2"}로 파싱돼요.
prefix 모든 키 앞에 붙일 접두사예요. 선택 사항이며 기본값은 빈 문자열이에요. prefix가 "custom"이면 {"key1=value1"}이 {"customkey1": "value1"}로 파싱돼요.
delete_key_regex 키에서 삭제할 문자를 지정하는 정규식이에요. [와 ] 같은 특수 정규식 문자는 \\로 이스케이프해야 해요. 빈 문자열일 수 없어요. 선택 사항이며 기본값은 없어요. delete_key_regex가 "\s"이면 {"key1 =value1"}이 {"key1": "value1"}로 파싱돼요.
delete_value_regex 값에서 삭제할 문자를 지정하는 정규식이에요. [와 ] 같은 특수 정규식 문자는 \\로 이스케이프해야 해요. 빈 문자열일 수 없어요. 선택 사항이며 기본값은 없어요. delete_value_regex가 "\s"이면 {"key1=value1 "}이 {"key1": "value1"}로 파싱돼요.
include_keys 파싱 시 추가할 키를 지정하는 배열이에요. 기본적으로 모든 키가 추가돼요. include_keys가 ["key2"]이면,key1=value1&key2=value2가 {"key2": "value2"}로 파싱돼요.
exclude_keys 이벤트에 추가하지 않을 파싱된 키를 지정하는 배열이에요. 기본적으로 제외되는 키는 없어요. exclude_keys가 ["key2"]이면 key1=value1&key2=value2가 {"key1": "value1"}로 파싱돼요.
default_values 파싱하는 소스 필드에 해당 키가 없을 때 이벤트에 추가할 기본 키와 값을 지정하는 맵이에요. 기본 키가 이미 메시지에 존재하면 값은 변경되지 않아요. include_keys 필터는 default_values보다 먼저 메시지에 적용돼요. default_values가 {"defaultkey": "defaultvalue"}이면 key1=value1이 {"key1": "value1", "defaultkey": "defaultvalue"}로 파싱돼요.
default_values가 {"key1": "abc"}이면 key1=value1이 {"key1": "value1"}로 파싱돼요.
include_keys가 ["key1"]이고 default_values가 {"key2": "value2"}이면 key1=value1&key2=abc가 {"key1": "value1", "key2": "value2"}로 파싱돼요.
transform_key 키를 소문자로, 대문자로, 또는 첫 글자만 대문자로 변환할 때 사용해요. transform_key가 lowercase이면 {"Key1=value1"}이 {"key1": "value1"}로 파싱돼요.
transform_key가 uppercase이면 {"key1=value1"}이 {"KEY1": "value1"}로 파싱돼요.
transform_key가 capitalize이면 {"key1=value1"}이 {"Key1": "value1"}로 파싱돼요.
whitespace 설정된 값 분리 시퀀스 주변의 불필요한 공백을 관대하게 허용할지 엄격하게 처리할지 지정해요. 기본값은 lenient예요. whitespace가 "lenient"이면 {"key1 = value1"}이 {"key1 ": " value1"}로 파싱돼요. whitespace가 "strict"이면 {"key1 = value1"}이 {"key1": "value1"}로 파싱돼요.
skip_duplicate_values 중복된 키-값 쌍을 제거하는 불리언 옵션이에요. true로 설정하면 유일한 키-값 쌍 하나만 유지돼요. 기본값은 false예요. skip_duplicate_values가 false이면 {"key1=value1&key1=value1"}이 {"key1": ["value1", "value1"]}로 파싱돼요. skip_duplicate_values가 true이면 {"key1=value1&key1=value1"}이 {"key1": "value1"}로 파싱돼요.
remove_brackets 대괄호, 꺾쇠 괄호, 소괄호를 값에서 제거해야 하는 값 "래퍼"로 취급할지 지정해요. 기본값은 false예요. remove_brackets가 true이면 {"key1=(value1)"}이 {"key1": value1}로 파싱돼요. remove_brackets가 false이면 {"key1=(value1)"}이 {"key1": "(value1)"}로 파싱돼요.
recursive 값에서 추가 키-값 쌍을 재귀적으로 가져올지 지정해요. 추가 키-값 쌍은 루트 키의 하위 키로 저장돼요. 기본값은 false예요. 재귀 파싱의 각 레벨은 레벨마다 서로 다른 괄호로 정의해야 하며, 순서는 [], (), <>예요. 지정한 다른 설정들은 가장 바깥쪽 키에만 적용돼요.
recursive가 true일 때:
remove_brackets는 true로 설정할 수 없어요;
skip_duplicate_values는 항상 true예요;
whitespace는 항상 "strict"예요.
recursive가 true이면 {"item1=[item1-subitem1=item1-subitem1-value&item1-subitem2=(item1-subitem2-subitem2A=item1-subitem2-subitem2A-value&item1-subitem2-subitem2B=item1-subitem2-subitem2B-value)]&item2=item2-value"}가 {"item1": {"item1-subitem1": "item1-subitem1-value", "item1-subitem2" {"item1-subitem2-subitem2A": "item1-subitem2-subitem2A-value", "item1-subitem2-subitem2B": "item1-subitem2-subitem2B-value"}}}로 파싱돼요.
overwrite_if_destination_exists 파싱된 필드를 이벤트에 기록할 때 키 충돌이 있으면 기존 필드를 덮어쓸지 지정해요. 기본값은 true예요. overwrite_if_destination_exists가 true이고 destination이 null이면 {"key1": "old_value", "message": "key1=new_value"}가 {"key1": "new_value", "message": "key1=new_value"}로 파싱돼요.
tags_on_failure 프로세서 내에서 kv 작업이 런타임 예외를 일으키면 프로세서가 크래시되지 않고 작업이 안전하게 중단되며, 이벤트에 지정한 태그가 붙어요. tags_on_failure가 ["keyvalueprocessor_failure"]로 설정되어 있으면 런타임 예외 발생 시 이벤트 메타데이터에 {"tags": ["keyvalueprocessor_failure"]}가 추가돼요.
value_grouping 미리 정의된 값 그룹화 구분자인 {...}, [...]', <...>, (...), "...", '...', http://... (space), https:// (space)를 사용해 값을 그룹화할지 지정해요. 이 플래그가 활성화되면 구분자 사이의 내용은 하나의 엔티티로 간주되어 키-값 쌍으로 파싱되지 않아요. 기본값은 false예요. value_grouping이 true이면 {"key1=[a=b,c=d]&key2=value2"}가 {"key1": "[a=b,c=d]", "key2": "value2"}`로 파싱돼요.
drop_keys_with_no_value 값이 null인 키를 버릴지 지정해요. 기본값은 false예요. drop_keys_with_no_value가 true로 설정되면 {"key1=value1&key2"}가 {"key1": "value1"}로 파싱돼요.
strict_grouping value_grouping 또는 string_literal_character 옵션을 사용할 때 엄격한 그룹화를 활성화할지 지정해요. 기본값은 false예요. 활성화하면 끝 문자가 짝이 맞지 않는 그룹에서 오류가 발생해요. 오류가 로그로 기록된 후 해당 이벤트는 무시돼요.
string_literal_character 작은따옴표(') 또는 큰따옴표(")로 설정할 수 있어요. 기본값은 null이에요. 이 옵션을 사용하면 지정한 따옴표 문자로 감싸인 텍스트는 무시되고 키-값 파싱에서 제외돼요. 예를 들어 text1 "key1=value1" text2 key2=value2는 {"key2": "value2"}로 파싱돼요.
key_value_when 프로세서를 이벤트에 적용할지 판단하기 위해 평가되는 조건식을 지정할 수 있어요. 예를 들어 /some-key == "test" 같은 식이에요.

더 알아보기 (Learn more)