IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    山寨一个 Splunk 的 source 上下文查看功能

    陈子 (rao.chenlin@gmail.com)发表于 2014-08-29 00:00:00
    love 0

    跟很多朋友在聊 elk stack 的时候,都会不知不觉的开始跟 Splunk 做对比。最常见的两个抱怨就是:Splunk 的搜索构建语法 比 Kibana 方便,以及 Splunk 搜索出来的消息可以通过点击 Source 按钮查看其原始日志中的前后几条日志。

    splunk source context

    平心而论,这个上下文查找的功能确实在排错过程中非常有用。但是在 elk 里却不那么容易实现,原因是:

    elasticsearch 是一个分布式项目,其索引的 _id 默认使用的是 UUID 方式生成的随机字符串,你没法根据 UUID 来判断数据的先后。

    LogStash::Outputs::Elasticsearch 提供了让你指定 _id 内容的选项,但是在集群环境下,你很难自己搞定一个全局自增 ID。

    相反,虽然我不知道 splunk 的数据存储的内部实现,但是就他昂贵的报价来说,基本只见过单机案例。就单机而言,自增 id 太轻松了

    所以,从原理上来说,就很难实现一个通用的 elk 版上下文查看功能。

    不过我们缩小一下使用场景,却未必不能自己山寨一个对自己可用的办法来。

    假设我们一个最常见的场景,就是从各 web 服务器上收集不同日志到中心。那么这时候,通过 %{host} 和 %{path} 的 “AND” 过滤,我们就可以把范围缩小到一个单一的文件内容里。所以,我们只需要能够搞定这个文件的自增 id 就够了!

    logstash.conf 示例

    input {
        file {
            path => ["/var/log/*.log"]
        }
    }
    filter {
        ruby {
            init => '@incr={}'
            code => "key = event['host']+event['path']
                     if @incr.has_key?(key)
                         @incr[key] += 1
                     else
                         @incr[key] = 1 
                     end
                     event['lineno'] = @incr[key]"
        }
    }
    output {
        elasticsearch {
        }
    }

    上下文查询 curl 示例

    使用上面的配置运行起来 logstash 之后,假设我们现在搜到一条 syslog 日志,其 lineno 是 20,那么查看它的前后 5 条记录的 curl 命令就是:

    curl -XPOST 'http://localhost:9200/logstash-2014.08.29/_search?pretty=1' -d '
    {
      "query":{
        "range":{
          "lineno": {
            "gt":15,
            "lte":25
          }
        }   
      },  
      "filter":{
        "term":{
          "host.raw":"raochenlindeMacBook-Air.local",
          "path.raw":"/var/log/system.log"
        }
      },
      "sort":[{"lineno":"asc"}],
      "fields":["message"],
      "size":10
    }'

    得到的结果是:

    {
      "took" : 3,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "failed" : 0
      },
      "hits" : {
        "total" : 10,
        "max_score" : null,
        "hits" : [ {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "ILkv4oZOQRGXkH5nxjPT6Q",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:34:44 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391727104]: Service [sproxy] accepted connection from 127.0.0.1:52673" ]
          },
          "sort" : [ 16 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "frRzVZUDQr-dkRog9LEypQ",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:34:44 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391727104]: s_connect: connected 50.116.12.155:65080" ]
          },
          "sort" : [ 17 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "fQ50VrbuSfy6AmhNOaHpFg",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:34:44 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391727104]: Service [sproxy] connected remote server from 192.168.0.102:52674" ]
          },
          "sort" : [ 18 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "Bpza8x6gSQi3OFRfAz3vPA",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:35:23 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391882752]: Service [sproxy] accepted connection from 127.0.0.1:52710" ]
          },
          "sort" : [ 19 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "I7SQ4o-aSr--em1WXO0y0A",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:35:24 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391882752]: s_connect: connected 50.116.12.155:65080" ]
          },
          "sort" : [ 20 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "POLq7XA_QVe6E5f9cP9V-w",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:35:24 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391882752]: Service [sproxy] connected remote server from 192.168.0.102:52711" ]
          },
          "sort" : [ 21 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "sXCLVr7URu-2uKhcOP3wjA",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:35:35 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391882752]: Connection closed: 0 byte(s) sent to SSL, 0 byte(s) sent to socket" ]
          },
          "sort" : [ 22 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "3wxxElNuS7OgyvjSm8CQfg",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:36:25 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391571456]: Connection closed: 2825 byte(s) sent to SSL, 2407 byte(s) sent to socket" ]
          },
          "sort" : [ 23 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "xdsiB1cmRpagWiMxtAjMzQ",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:36:52 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391493632]: Connection closed: 1109 byte(s) sent to SSL, 583 byte(s) sent to socket" ]
          },
          "sort" : [ 24 ]
        }, {
          "_index" : "logstash-2014.08.29",
          "_type" : "logs",
          "_id" : "mLScPMbwTzSPMz9WqOPXlw",
          "_score" : null,
          "fields" : {
            "message" : [ "Aug 29 23:36:52 raochenlindeMacBook-Air.local stunnel[304]: LOG5[4391571456]: Service [sproxy] accepted connection from 127.0.0.1:52719" ]
          },
          "sort" : [ 25 ]
        } ]
      }
    }

    没错,这就是我们想要的结果了!

    注释

    这里两个要点:

    • 自增 id 为啥不用行号,因为 LogStash::Inputs::File 实现是通过 File.seek 和 File.sysread(16394) 完成的,这种时候 File.lineno 永远都是 0。获取真的行号很困难。
    • 自增 id 为什么不指定成 _id 而是另外存字段,因为 _id 是特殊字段,要求在一个 _index/_type 里是唯一的。我们对 logstash 的使用一般情况下都是多个 host 内容存在同一个 _index/_type 下,会发生重复的(重复写入 _id 相同的数据等同于 update 操作)。

    延伸

    数据如何通过 kibana 展示,则是另外一个层面的内容。有时间可能我会也做一下。

    非 input/file 方式的其他场景,只要你能通过 event 中其他字段确定出来源唯一,都可以采用这个方式做。



沪ICP备19023445号-2号
友情链接