本文档从多方面介绍如何使用不同方案提取字符串键值对,来解决用户需求。

以下是一个url的数据,下面使用两种方案来解析这条日志内容:
request:  https://yz.m.sm.cn/s?ver=3.2.3&app_type=supplier&os=Android8.1.0
需求:
  • 对日志解析出protodomainparam等内容。
  • param中的键值对做展开操作。
原始日志:
__source__:  10.43.xx.xx
__tag__:__client_ip__:  12.120.xx.xx
__tag__:__receive_time__:  1563517113
__topic__:  
request:  https://yz.m.sm.cn/video/getlist/s?ver=3.2.3&app_type=supplier&os=Android8.1.0

LOG DSL编排

  1. 使用GROK模式对字段request进行解析。也可以使用正则解析,请参见GROK函数GROK模式参考
    e_regex('request',grok("%{URIPROTO:uri_proto}://(?:%{USER:user}(?::[^@]*)?@)?(?:%{URIHOST:uri_domain})?(?:%{URIPATHPARAM:uri_param})?"))
    预览处理日志:
    uri_domain:  yz.m.sm.cn
    uri_param:  /video/getlist/s?ver=3.2.3&app_type=supplier&os=Android8.1.0
    uri_proto:  https
  2. 使用GROK模式对字段uri_param进行解析。
    e_regex('uri_param',grok("%{GREEDYDATA:uri_path}\?%{GREEDYDATA:uri_query}"))
    预览处理日志:
    uri_path:  /video/getlist/s
    uri_query:  ver=3.2.3&app_type=supplier&os=Android8.1.0
  3. 对uri_query进行字段提取,具体操作如下:
    e_kv("uri_query")
    预览处理后日志:
    app_type:  supplier
    os:  Android8.1.0
    ver:  3.2.3
  4. 综上LOG DSL规则可以如以下形式:
    # 初步处理解析request内容
    e_regex('request',grok("%{URIPROTO:uri_proto}://(?:%{USER:user}(?::[^@]*)?@)?(?:%{URIHOST:uri_domain})?(?:%{URIPATHPARAM:uri_param})?"))
    # 其次处理解析uri_param
    e_regex('uri_param',grok("%{GREEDYDATA:uri_path}\?%{GREEDYDATA:uri_query}"))
    # 展开kv形式
    e_kv("uri_query")
    预览处理后日志:
    __source__:  10.43.xx.xx
    __tag__:__client_ip__:  12.120.xx.xx
    __tag__:__receive_time__:  1563517113
    __topic__:  
    request:  https://yz.m.sm.cn/video/getlist/s?ver=3.2.3&app_type=supplier&os=Android8.1.0
    uri_domain:  yz.m.sm.cn
    uri_path:  /video/getlist/s
    uri_proto:  https
    uri_query:  ver=3.2.3&app_type=supplier&os=Android8.1.0
    app_type:  supplier
    os:  Android8.1.0
    ver:  3.2.3
    假如只有第二个需求,可以直接对字段request使用e_kv函数。例如:
    e_kv("request")
    预览处理后日志:
    __source__:  10.43.xx.xx
    __tag__:__client_ip__:  12.120.xx.xx
    __tag__:__receive_time__:  1563517113
    __topic__:  
    request:  https://yz.m.sm.cn/video/getlist/s?ver=3.2.3&app_type=supplier&os=Android8.1.0
    app_type:  supplier
    os:  Android8.1.0
    ver:  3.2.3

其他方案

url: https://yz.m.sm.cn/video/getlist/s?ver=3.2.3&app_type=supplier&os=Android8.1.0为例,如果要提取其中动态字段verapp_typeos等,还有如下多种方案。
  • 使用正则
    e_regex("url", r"\b(\w+)=([^=&]+)", {r"\1": r"\2"})
  • 使用e_kv_delmit函数
    e_kv_delimit("url", pair_sep=r"?&")
对于大部分url函数形式,都可使用上面几种方式进行解析。但是针对以上url形式,使用e_kv函数已经足够,清晰明了而且形式简单。

方案比较

方案 关键字提取 值提取 关键字加工 值加工
e_kv 使用特定正则 支持默认的字符集+特定分隔符或者带"分隔 支持前后缀 支持文本escape
e_kv_delimit 使用特定正则 使用分隔符 支持前后缀 默认没有
e_regex 自定义正则+默认字符集过滤 完全自定义 自由设置 自由设置
  • 关键字提取
    e_kve_kv_delimite_regex在使用关键字提取的时候都遵循字段名提取约束
    • 案例1
      k1: q=asd&a=1&b=2&__1__=3为例,如果要对以上日志格式做关键字和值提取的话,三种方案如下:
      # 默认以特定字符集提取关键字
      e_kv("k1")
      
      # 以&分隔键值后, 用&分隔提取出关键字
      e_kv_delimit("k1", pair_sep=r"&")
      
      # 自行指定字符集提取关键字和值
      e_regex("k1",r"(\w+)=([a-zA-Z0-9]+)",{r"\1": r"\2"})
      经过以上DSL编排过后的日志格式为:
      k1: q=asd&a=1&b=2
      q: asd
      a: 1
      b: 2
      说明 没有提取出关键字__1__是因为其不符合字段名提取约束
    • 案例2
      content:k1=v1&k2=v2?k3:v3为例,需要特定正则提取关键字,三种方案如下:
      e_kv("content",sep="(?:=|:)")
      e_kv_delimit("content",pair_sep=r"&?",kv_sep="(?:=|:)")
      e_regex("content",r"([a-zA-Z0-9]+)[=|:]([a-zA-Z0-9]+)",{r"\1": r"\2"})
      说明 给参数pari_sepkv_sep或者sep传递字符集的时候,需要使用正则的不捕获分组,形式如(?:字符集)
      经过DSL编排之后的日志格式:
      content:k1=v1&k2=v2?k3:v3
      k1: v1
      k2: v2
      k3: v3
    • 案例3
      以下格式的字符串比较复杂,使用e_regex提取更方便。
      content :"ak_id:"LTAiscW,"ak_key:"rsd7r8f
      如果要提取字符串的关键字前有",需要使用e_regex来提取。
      e_regex("str",r'(\w+):(\"\w+)',{r"\1":r"\2"})
      经过DSL编排之后的日志格式:
      content :"ak_id:"LTAiscW,"ak_key:"rsd7r8f
      ak_id: LTAiscW
      ak_key: rsd7r8f
  • 值提取
    动态键值对之间以及关键字与值之间有明确标识如:
    • 日志格式为a=ba="cxxx"形式的推荐用e_kv函数:
      content1:  k="helloworld",the change world, k2="good"
      这种情况下使用e_kv函数就可以,提取内容不包括the change world这几个词:
      e_kv("content1")
      # e_kv_delimit函数写法,特别注意k2前有空格,所以e_kv_delimit函数的pair_sep参数需要使用`,\s`才能正常解析,否则解析不出来k2
      e_kv_delimit("content1",kv_sep="=", pair_sep=",\s")
      # e_regex函数写法
      e_regex("str",r"(\w+)=(\"\w+)",{r"\1": r"\2"})
      提取后的日志为:
      content1:  k="helloworld",the change world, k2="good"
      k1: helloworld
      k2: good
    • "的日志格式content:k1="v1=1"&k2=v2?k3=v3,如果使用e_kv函数提取会比较容易,例如:
      e_kv("content",sep="=", quote="'")
      处理后日志为:
      content: k1='v1=1'&k2=v2?k3=v3
      k1: v1=1
      k2:v2
      k3:v3

      而如果使用e_kv_delimit函数做提取,规则为e_kv_delimit("ctx", pair_sep=r"&?", kv_sep="="),只能解析出k2: v2k3: v3,因为其中第一个提取的键值对中关键字是k1="v1,不符合字段名提取约束会被丢弃。

    • 分隔符的键值对中,值包含了特殊字符但没有用特定字符括起来。例如:
      content:  rats eat rice, oil|chicks eat bugs, rice|kittens eat fish, mice|
      使用e_kv_delimit函数比较合适。
      e_kv_delimit("content", pair_sep="|", kv_sep=" eat ")
      处理后日志为:
      content:  rats eat rice, oil|chicks eat bugs, rice|kittens eat fish, mice|
      kittens:  fish, mice
      chicks:  bugs, rice
      rats:  rice, oil
      而使用e_kv无法解析完整。
      e_kv("f1", sep="eat")
      处理后日志为
      content:  rats eat rice, oil|chicks eat bugs, rice|kittens eat fish, mice|
      kittens:  fish
      chicks:  bugs
      rats:  rice
  • 关键字加工
    • e_kve_kv_delimit函数都可以通过prefix="", suffix=""对关键字和值进行加工。
      原始日志:
      k1: q=asd&a=1&b=2
      加工编排:
      e_kv("k1", sep="=", quote='"', prefix="start_", suffix="_end")
      e_kv_delimit("k1", pair_sep=r"&", kv_sep="=", prefix="start_", suffix="_end")
      e_regex("k1",r"(\w+)=([a-zA-Z0-9]+)",{r"start_\1_end": r"\2"})
      加工后的数据都是关键字加工形式,如下:
      k1: q=asd&a=1&b=2
      start_q_end: asd
      start_a_end: 1
      start_b_end: 2
      e_regex对关键字加工的能力更强,例如:
      e_regex("k1",r"(\w+)=([a-zA-Z0-9]+)",{r"\1_\1": r"\2"})
      加工后的数据都是关键字加工形式,如下:
      k1: q=asd&a=1&b=2
      q_q: asd
      a_a: 1
      a_a: 2
    • e_regex对关键字加工的能力更强,例如:
      e_regex("k1",r"(\w+)=([a-zA-Z0-9]+)",{r"\1_\1": r"\2"})
      加工后的数据都是关键字加工形式,如下:
      k1: q=asd&a=1&b=2
      q_q: asd
      a_a: 1
      a_a: 2
  • 值加工
    • 日志格式为k1:"v1\"abc",值内容有双引号的情况,只有e_kv可以正常提取出,其他两种比较难实现。
      """
      这里的\只是普通的符号,不是转义符
      """
      content2:  k1:"v1\"abc", k2:"v2", k3: "v3"
      使用e_kv规则为:
      e_kv("content2",sep=":", quote='"')
      提取后的日志为:
      content2:  k1:"v1\"abc", k2:"v2", k3: "v3"
      k1: v1\
      k2: v2
      k3: v3
      e_kv通过参数escape支持对\字符转义。例如:
      e_kv("content2",sep=":", quote='"',escape=True)
      提取后的日志为:
      content2:  k1:"v1\"abc", k2:"v2", k3: "v3"
      k1: v1"abc
      k2: v2
      k3: v3
    • 日志格式为a='k1=k2\';k2=k3'形式的日志,只有e_kv可以正常提取出,其他两种比较难以实现。
      data: i=c10 a='k1=k2\';k2=k3'
      默认情况下e_kv函数的escape=False,结果为:
      e_kv("data", quote="'")
      提取后的日志为:
      a:  k1=k2\
      i:  c10
      k2:  k3
      e_kv通过参数escape支持对\字符转义。例如:
      e_kv("data", quote="'", escape=True)
      提取后的日志为:
      data: i=c10 a='k1=k2\';k2=k3'
      i: c10
      a: k1=k2';k2=k3
    • 键值的复杂加工。
      日志示例为:
      content:  rats eat rice|chicks eat bugs|kittens eat fish|
      使用e_regex函数进行加工。
      e_regex("content", r"\b(\w+) eat ([^\|]+)", {r"\1": r"\2 by \1"})
      处理后日志为:
      content:  rats eat rice|chicks eat bugs|kittens eat fish|
      kittens:  fish by kittens
      chicks:  bugs by chicks
      rats:  rice by rats

结论

大部分键值对的提取使用e_kv并配置特定参数就可以很好的满足,尤其是带括字符和反斜杠需要提取并转义时。其他复杂或高级的场景可以用e_regex来提取。某些特定场景下的键值对使用e_kv_delemit会更简单。