将日志无重复、无缺失地存储到Elasticsearch的提案

首先

如果你有Elasticsearch,你可能想要将日志记录完整地存储下来。
由于可以稍后进行解析,所以只需将其存储起来即可。
然而,我们希望在不损失信息量的前提下记录日志。
由于日志数量可能具有意义,所以我们希望避免重复,并且不希望丢失任何日志。

为了实现这一目标,似乎可以为每个个体的日志指定一个唯一的ID。

如果从日志发送源(Logstash等)或者发送目标(Elasticsearch)发生异常并需要重新发送,只要有ID,就可以无重复地存储,因此可以充分进行重新发送,以补充任何缺失的部分。

    • ちなみに、もし重複を許すなら、途中まで読んでいたログファイルをもう1度読み込みなおせばよい。

 

    • なお、もし再送を十分な回数できるなら、ID が(Elasticsearch の自動生成 ID など)ログに対して一意でなくとも

 

    • 何度もインデックスを消して送りなおせば重複なく格納することはできなくはないが、

 

    ログの量が多くなると時間がかかりすぎる恐れがある。

在这里,我们将讨论如何通过指定ID将日志存储到Elasticsearch中。

顺便提一下,在使用 Elasticsearch 时不要使用自动生成的 ID,这样会降低索引创建的性能(参考:Elasticsearch 参考资料)。

在Elasticsearch中存储日志的方法是使用ID参数指定。

关于ID的设计原则将在后文作为参考,但通常情况下,我们可以通过文件路径和日志位置(≒行号)来创建一个ID就能解决问题,所以我将对这种方法进行解释。

由于Logstash无法获取日志的位置,因此需要使用Filebeat来读取文件。
在Filebeat中,文件路径被记录在源字段中,日志位置被记录在偏移字段中。
然而,Filebeat本身无法从这两个字段生成ID,因此需要依赖Logstash的过滤器插件或Elasticsearch的Ingest Node来实现。

当使用 Logstash 的 Filter 插件生成 ID 时,

    1. 将Filebeat的输出目标设置为Logstash,

 

    1. 通过Logstash Filter插件生成ID,

 

    通过Logstash Elasticsearch Output插件指定ID。

以下是一個設定示例。

filebeat.prospectors:
- input_type: log
  paths:
  - ${LOG_PATH}
output.logstash:
  hosts:
  - ${LOGSTASH_HOST}:${LOGSTASH_PORT}
input {
  beats {
    port => 5044
  }
}
filter {
  mutate {
    # 元のsourceは残しておきたいため一時退避。
    # 退避先はメタデータにしておけば Elasticsearch には格納されないため、煩わしくない。
    copy => {'source' => '[@metadata][source]'}

    # 退避したメタデータフィールドを使って、ID 用に変換。
    # この例では、ファイルパスのスラッシュをアンダースコアに変えている。
    gsub => ['[@metadata][source]', '/', '_']
  }
}
output {
  elasticsearch {
    hosts => '${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT}'

    # ID を生成。
    # この例では、ファイルパスにログ位置をアンダースコアで連結している。
    document_id => '%{[@metadata][source]}_%{offset}'
  }
}

如果使用Ingest Node生成ID的话,-如果使用Ingest Node生成ID的话,

    1. 在Elasticsearch中创建一个用于生成ID的Pipeline,

 

    并将Elasticsearch和Pipeline指定为Filebeat的输出目标。

下面是一个设定示例。

{
  "generate-id" : {
    "description" : "Generate ID with source and offset",
    "processors" : [
      {
        "set" : {
          "field" : "_id",
          "value" : "{{source}}_{{offset}}"
        }
      }
    ]
  }
}
filebeat.prospectors:
- input_type: log
  paths:
  - ${LOG_PATH}
output.elasticsearch:
  hosts:
  - ${ELASTICSEARCH_HOST}:${ELASTICSEARCH_PORT}
  pipeline: generate-id

(参考)身份证的设计方针

首先,考虑一个文件路径指向特定日志实体的情况。
也就是说,该文件不会被后续内容覆盖,只会进行追加,
例如,在不进行日志轮转的情况下。

在这种情况下,可以根据文件路径和日志位置(≒行号)创建一个ID。

/var/log/foo/2017-11-10.log  ->  var_log_foo_2017-11-10_log_123456

如果有可能在另一个主机上出现相同的文件路径,就添加主机名或IP地址。

host01:/var/log/foo/2017-11-10.log  ->  host01_var_log_foo_2017-11-10_log_123456
host12:/var/log/foo/2017-11-10.log  ->  host12_var_log_foo_2017-11-10_log_123456

如果假设主机根据时间而增减,并且有可能出现相同的主机名和相同的IP地址多次,那么在某个时间段内是唯一的,所以要在增减上加上足够短的时间(比如日期)来区分。

2017/11/10; host23:/var/log/foo/app.log  ->  2017-11-10-host23_var_log_foo_app_log_123456
2017/11/13; host23:/var/log/foo/app.log  ->  2017-11-13-host23_var_log_foo_app_log_123456

接下来,让我们考虑一种情况,即在相同的文件路径下放置不同的日志实体。与之前的情况相反,例如,当进行日志轮转时。

即使在这种情况下,也可以通过为每个时间间隔添加足够小的时段标识来确保唯一性。

2017/11/10; host23:/var/log/messages  ->  2017-11-10-host23_var_log_messages_123456
2017/11/13; host23:/var/log/messages  ->  2017-11-13-host23_var_log_messages_123456

由於ID的長度有限制(參考:Elasticsearch論壇),所以可能需要縮短文件路徑或主機名等,以確保唯一性不受影響。

(参考)需要注意哈希值的碰撞率的ID。

由于哈希值可能存在冲突(对于不同的输入产生相同的输出)的可能性,所以在将其用作ID时,必须考虑到可能会导致日志丢失的情况。
如果每个索引中的日志数量很少,可能可以将其视为可以忽略的概率;但是,由于哈希不发生冲突的概率意外地很大,
如果绝对不允许日志丢失的情况,就不应该使用哈希值。

逆而言之,即使具备设计ID所需的完整信息,
如果将其哈希化而不使用ID,导致碰撞,那就太可惜了。
哈希可以在有一定日志丢失的情况下使用,适用于ID设计复杂的情况。

bannerAds