├── .DS_Store ├── .gitignore ├── README.md ├── SUMMARY.md ├── book.json ├── codec ├── README.md ├── json.md └── multiline.md ├── contrib_plugins ├── README.md ├── hdfs.md ├── kafka.md └── scribe.md ├── cover.jpg ├── cover_small.jpg ├── dive_into ├── README.md ├── why_jvm_and_howto_run_with_mri.md └── write_your_own.md ├── ecosystem ├── README.md ├── fluent.md ├── heka.md ├── logstash_forwarder.md ├── message_passing.md ├── nxlog.md └── rsyslog.md ├── filter ├── README.md ├── date.md ├── geoip.md ├── grok.md ├── json.md ├── kv.md ├── metrics.md ├── mutate.md ├── ruby.md ├── split.md └── useragent.md ├── get_start ├── README.md ├── daemon.md ├── full_config.md ├── hello_world.md ├── install.md └── introduction.md ├── images ├── logstash-arch.jpg └── nxlog.png ├── input ├── README.md ├── collectd.md ├── file.md ├── generator.md ├── redis.md ├── stdin.md ├── syslog.md └── tcp.md └── output ├── README.md ├── elasticsearch.md ├── email.md ├── exec.md ├── file.md ├── nagios.md ├── redis.md ├── statsd.md ├── stdout.md └── tcp.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenryn/logstash-best-practice-cn/1ecbd5e730d90a2838fb5484c056e0482262ba3a/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _book/* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 本repo已经废弃,请移步[ELK Stack 中文指南](http://kibana.logstash.es/) 2 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [简介](README.md) 4 | * [基础知识](get_start/README.md) 5 | * [介绍](get_start/introduction.md) 6 | * [安装](get_start/install.md) 7 | * [Hello World](get_start/hello_world.md) 8 | * [长期运行](get_start/daemon.md) 9 | * [配置语法](get_start/full_config.md) 10 | * [输入插件(Input)](input/README.md) 11 | * [标准输入(Stdin)](input/stdin.md) 12 | * [读取文件(File)](input/file.md) 13 | * [读取网络数据(TCP)](input/tcp.md) 14 | * [生成测试数据(Generator)](input/generator.md) 15 | * [读取 Syslog 数据](input/syslog.md) 16 | * [读取 Redis 数据](input/redis.md) 17 | * [读取 Collectd 数据](input/collectd.md) 18 | * [编码插件(Codec)](codec/README.md) 19 | * [采用 JSON 编码](codec/json.md) 20 | * [合并多行数据(Multiline)](codec/multiline.md) 21 | * [过滤器插件(Filter)](filter/README.md) 22 | * [Grok 正则捕获](filter/grok.md) 23 | * [时间处理(Date)](filter/date.md) 24 | * [数据修改(Mutate)](filter/mutate.md) 25 | * [GeoIP 查询归类](filter/geoip.md) 26 | * [JSON 编解码](filter/json.md) 27 | * [split 拆分事件](filter/split.md) 28 | * [UserAgent 匹配归类](filter/useragent.md) 29 | * [Key-Value 切分](filter/kv.md) 30 | * [随心所欲的 Ruby 处理](filter/ruby.md) 31 | * [数值统计(Metrics)](filter/metrics.md) 32 | * [输出插件(Output)](output/README.md) 33 | * [标准输出(Stdout)](output/stdout.md) 34 | * [保存成文件(File)](output/file.md) 35 | * [保存进 Elasticsearch](output/elasticsearch.md) 36 | * [输出到 Redis](output/redis.md) 37 | * [输出网络数据(TCP)](output/tcp.md) 38 | * [输出到 Statsd](output/statsd.md) 39 | * [报警到 Nagios](output/nagios.md) 40 | * [发送邮件(Email)](output/email.md) 41 | * [调用命令执行(Exec)](output/exec.md) 42 | * [尚未进入官方库的常用插件](contrib_plugins/README.md) 43 | * [Kafka](contrib_plugins/kafka.md) 44 | * [HDFS](contrib_plugins/hdfs.md) 45 | * [Scribe](contrib_plugins/scribe.md) 46 | * [深入了解](dive_into/README.md) 47 | * [自己写一个插件](dive_into/write_your_own.md) 48 | * [为什么用 JRuby? 能用 MRI 运行么?](dive_into/why_jvm_and_howto_run_with_mri.md) 49 | * [周边项目](ecosystem/README.md) 50 | * [Rsyslog](ecosystem/rsyslog.md) 51 | * [logstash-forwarder](ecosystem/logstash_forwarder.md) 52 | * [nxlog](ecosystem/nxlog.md) 53 | * [heka](ecosystem/heka.md) 54 | * [Message::Passing](ecosystem/message_passing.md) 55 | * [fluent](ecosystem/fluent.md) 56 | 57 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /codec/README.md: -------------------------------------------------------------------------------- 1 | # 编码插件(Codec) 2 | 3 | Codec 是 logstash 从 1.3.0 版开始新引入的概念(*Codec* 来自 *Co*der/*dec*oder 两个单词的首字母缩写)。 4 | 5 | 在此之前,logstash 只支持纯文本形式输入,然后以*过滤器*处理它。但现在,我们可以在*输入* 期处理不同类型的数据,这全是因为有了 **codec** 设置。 6 | 7 | 所以,这里需要纠正之前的一个概念。Logstash 不只是一个`input | filter | output` 的数据流,而是一个 `input | decode | filter | encode | output` 的数据流!*codec* 就是用来 decode、encode 事件的。 8 | 9 | codec 的引入,使得 logstash 可以更好更方便的与其他有自定义数据格式的运维产品共存,比如 graphite、fluent、netflow、collectd,以及使用 msgpack、json、edn 等通用数据格式的其他产品等。 10 | 11 | 事实上,我们在第一个 "hello world" 用例中就已经用过 *codec* 了 —— *rubydebug* 就是一种 *codec*!虽然它一般只会用在 stdout 插件中,作为配置测试或者调试的工具。 12 | 13 | *小贴士:这个五段式的流程说明源自 Perl 版的 Logstash (后来改名叫 Message::Passing 模块)的设计。本书最后会对该模块稍作介绍。* 14 | -------------------------------------------------------------------------------- /codec/json.md: -------------------------------------------------------------------------------- 1 | # 采用 JSON 编码 2 | 3 | 在早期的版本中,有一种降低 logstash 过滤器的 CPU 负载消耗的做法盛行于社区(在当时的 cookbook 上有专门的一节介绍):**直接输入预定义好的 JSON 数据,这样就可以省略掉 filter/grok 配置!** 4 | 5 | 这个建议依然有效,不过在当前版本中需要稍微做一点配置变动 —— 因为现在有专门的 *codec* 设置。 6 | 7 | ## 配置示例 8 | 9 | 社区常见的示例都是用的 Apache 的 customlog。不过我觉得 Nginx 是一个比 Apache 更常用的新型 web 服务器,所以我这里会用 nginx.conf 做示例: 10 | 11 | ``` 12 | logformat json '{"@timestamp":"$time_iso8601",' 13 | '"@version":"1",' 14 | '"host":"$server_addr",' 15 | '"client":"$remote_addr",' 16 | '"size":$body_bytes_sent,' 17 | '"responsetime":$request_time,' 18 | '"domain":"$host",' 19 | '"url":"$uri",' 20 | '"status":"$status"}'; 21 | access_log /var/log/nginx/access.log_json json; 22 | ``` 23 | 24 | *注意:在 `$request_time` 和 `$body_bytes_sent` 变量两头没有双引号 `"`,这两个数据在 JSON 里应该是数值类型!* 25 | 26 | 重启 nginx 应用,然后修改你的 input/file 区段配置成下面这样: 27 | 28 | ``` 29 | input { 30 | file { 31 | path => "/var/log/nginx/access.log_json"" 32 | codec => "json" 33 | } 34 | } 35 | ``` 36 | 37 | ## 运行结果 38 | 39 | 下面访问一下你 nginx 发布的 web 页面,然后你会看到 logstash 进程输出类似下面这样的内容: 40 | 41 | ```ruby 42 | { 43 | "@timestamp" => "2014-03-21T18:52:25.000+08:00", 44 | "@version" => "1", 45 | "host" => "raochenlindeMacBook-Air.local", 46 | "client" => "123.125.74.53", 47 | "size" => 8096, 48 | "responsetime" => 0.04, 49 | "domain" => "www.domain.com", 50 | "url" => "/path/to/file.suffix", 51 | "status" => "200" 52 | } 53 | ``` 54 | 55 | ## 小贴士 56 | 57 | 对于一个 web 服务器的访问日志,看起来已经可以很好的工作了。不过如果 Nginx 是作为一个代理服务器运行的话,访问日志里有些变量,比如说 `$upstream_response_time`,可能不会一直是数字,它也可能是一个 `"-"` 字符串!这会直接导致 logstash 对输入数据验证报异常。 58 | 59 | 有两个办法解决这个问题: 60 | 61 | 1. 用 `sed` 在输入之前先替换 `-` 成 `0`。 62 | 63 | 运行 logstash 进程时不再读取文件而是标准输入,这样命令就成了下面这个样子: 64 | 65 | ``` 66 | tail -F /var/log/nginx/proxy_access.log_json \ 67 | | sed 's/upstreamtime":-/upstreamtime":0/' \ 68 | | /usr/local/logstash/bin/logstash -f /usr/local/logstash/etc/proxylog.conf 69 | ``` 70 | 71 | 2. 日志格式中统一记录为字符串格式(即都带上双引号 `"`),然后再在 logstash 中用 `filter/mutate` 插件来变更应该是数值类型的字符字段的值类型。 72 | 73 | 有关 `LogStash::Filters::Mutate` 的内容,本书稍后会有介绍。 74 | -------------------------------------------------------------------------------- /codec/multiline.md: -------------------------------------------------------------------------------- 1 | # 合并多行数据(Multiline) 2 | 3 | 有些时候,应用程序调试日志会包含非常丰富的内容,为一个事件打印出很多行内容。这种日志通常都很难通过命令行解析的方式做分析。 4 | 5 | 而 logstash 正为此准备好了 *codec/multiline* 插件! 6 | 7 | *小贴士:multiline 插件也可以用于其他类似的堆栈式信息,比如 linux 的内核日志。* 8 | 9 | ## 配置示例 10 | 11 | ``` 12 | input { 13 | stdin { 14 | codec => multiline { 15 | pattern => "^\[" 16 | negate => true 17 | what => "previous" 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ## 运行结果 24 | 25 | 运行 logstash 进程,然后在等待输入的终端中输入如下几行数据: 26 | 27 | ``` 28 | [Aug/08/08 14:54:03] hello world 29 | [Aug/08/09 14:54:04] hello logstash 30 | hello best practice 31 | hello raochenlin 32 | [Aug/08/10 14:54:05] the end 33 | ``` 34 | 35 | 你会发现 logstash 输出下面这样的返回: 36 | 37 | ```ruby 38 | { 39 | "@timestamp" => "2014-08-09T13:32:03.368Z", 40 | "message" => "[Aug/08/08 14:54:03] hello world\n", 41 | "@version" => "1", 42 | "host" => "raochenlindeMacBook-Air.local" 43 | } 44 | { 45 | "@timestamp" => "2014-08-09T13:32:24.359Z", 46 | "message" => "[Aug/08/09 14:54:04] hello logstash\n\n hello best practice\n\n hello raochenlin\n", 47 | "@version" => "1", 48 | "tags" => [ 49 | [0] "multiline" 50 | ], 51 | "host" => "raochenlindeMacBook-Air.local" 52 | } 53 | ``` 54 | 55 | 你看,后面这个事件,在 "message" 字段里存储了三行数据! 56 | 57 | *小贴士:你可能注意到输出的事件中都没有最后的"the end"字符串。这是因为你最后输入的回车符 `\n` 并不匹配设定的 `^\[` 正则表达式,logstash 还得等下一行数据直到匹配成功后才会输出这个事件。* 58 | 59 | ## 解释 60 | 61 | 其实这个插件的原理很简单,就是把当前行的数据添加到前面一行后面,,直到新进的当前行匹配 `^\[` 正则为止。 62 | 63 | 这个正则还可以用 grok 表达式,稍后你就会学习这方面的内容。 64 | 65 | 66 | ## Log4J 的另一种方案 67 | 68 | 说到应用程序日志,log4j 肯定是第一个被大家想到的。使用 `codec/multiline` 也确实是一个办法。 69 | 70 | 不过,如果你本事就是开发人员,或者可以推动程序修改变更的话,logstash 还提供了另一种处理 log4j 的方式:[input/log4j](http://logstash.net/docs/1.4.2/inputs/log4j)。与 `codec/multiline` 不同,这个插件是直接调用了 `org.apache.log4j.spi.LoggingEvent` 处理 TCP 端口接收的数据。 71 | 72 | ## 推荐阅读 73 | 74 | 75 | -------------------------------------------------------------------------------- /contrib_plugins/README.md: -------------------------------------------------------------------------------- 1 | # Contrib Plugins 2 | -------------------------------------------------------------------------------- /contrib_plugins/hdfs.md: -------------------------------------------------------------------------------- 1 | # HDFS 2 | 3 | 4 | 5 | * 6 | 7 | This plugin based on WebHDFS api of Hadoop, it just POST data to WebHDFS port. So, it's a native Ruby code. 8 | 9 | ``` 10 | output { 11 | hadoop_webhdfs { 12 | workers => 2 13 | server => "your.nameno.de:14000" 14 | user => "flume" 15 | path => "/user/flume/logstash/dt=%{+Y}-%{+M}-%{+d}/logstash-%{+H}.log" 16 | flush_size => 500 17 | compress => "snappy" 18 | idle_flush_time => 10 19 | retry_interval => 0.5 20 | } 21 | } 22 | ``` 23 | 24 | 25 | * 26 | 27 | This plugin based on HDFS api of Hadoop, it import java classes like `org.apache.hadoop.fs.FileSystem` etc. 28 | 29 | ### Configuration 30 | 31 | ``` 32 | output { 33 | hdfs { 34 | path => "/path/to/output_file.log" 35 | enable_append => true 36 | } 37 | } 38 | ``` 39 | 40 | ### Howto run 41 | 42 | ``` 43 | CLASSPATH=$(find /path/to/hadoop -name '*.jar' | tr '\n' ':'):/etc/hadoop/conf:/path/to/logstash-1.1.7-monolithic.jar java logstash.runner agent -f conf/hdfs-output.conf -p /path/to/cloned/logstash-hdfs 44 | ``` 45 | -------------------------------------------------------------------------------- /contrib_plugins/kafka.md: -------------------------------------------------------------------------------- 1 | # Kafka 2 | 3 | 4 | 5 | 插件已经正式合并进官方仓库,以下使用介绍基于**logstash 1.4相关版本**,1.5及以后版本的使用后续依照官方文档持续更新。 6 | 7 | 插件本身内容非常简单,其主要依赖同一作者写的 [jruby-kafka](https://github.com/joekiller/jruby-kafka) 模块。需要注意的是:**该模块仅支持 Kafka-0.8 版本。如果是使用 0.7 版本 kafka 的,将无法直接使 jruby-kafka 该模块和 logstash-kafka 插件。** 8 | 9 | ## 安装 10 | 11 | * 安装按照官方文档完全自动化的安装.或是可以通过以下方式手动自己安装插件,不过重点注意的是 **kafka 的版本**,上面已经指出了。 12 | 13 | > 1. 下载 logstash 并解压重命名为 `./logstash-1.4.0` 文件目录。 14 | 15 | > 2. 下载 kafka 相关组件,以下示例选的为 [kafka_2.8.0-0.8.1.1-src](https://www.apache.org/dyn/closer.cgi?path=/kafka/0.8.1.1/kafka-0.8.1.1-src.tgz),并解压重命名为 `./kafka_2.8.0-0.8.1.1`。 16 | 17 | > 3. 下载 logstash-kafka v0.4.2 从 [releases](https://github.com/joekiller/logstash-kafka/releases),并解压重命名为 `./logstash-kafka-0.4.2`。 18 | 19 | > 4. 从 `./kafka_2.8.0-0.8.1.1/libs` 目录下复制所有的 jar 文件拷贝到 `./logstash-1.4.0/vendor/jar/kafka_2.8.0-0.8.1.1/libs` 下,其中你需要创建 `kafka_2.8.0-0.8.1.1/libs` 相关文件夹及目录。 20 | 21 | > 5. 分别复制 `./logstash-kafka-0.4.2/logstash` 里的 `inputs` 和 `outputs` 下的 `kafka.rb`,拷贝到对应的 `./logstash-1.4.0/lib/logstash` 里的 `inputs` 和 `outputs` 对应目录下。 22 | 23 | > 6. 切换到 `./logstash-1.4.0` 目录下,现在需要运行 logstash-kafka 的 gembag.rb 脚本去安装 jruby-kafka 库,执行以下命令: `GEM_HOME=vendor/bundle/jruby/1.9 GEM_PATH= java -jar vendor/jar/jruby-complete-1.7.11.jar --1.9 ../logstash-kafka-0.4.2/gembag.rb ../logstash-kafka-0.4.2/logstash-kafka.gemspec`。 24 | 25 | > 7. 现在可以使用 logstash-kafka 插件运行 logstash 了。例如:`bin/logstash agent -f logstash.conf`。 26 | 27 | ## Input 配置示例 28 | 29 | 以下配置可以实现对 kafka 读取端(consumer)的基本使用。 30 | 31 | 消费端更多详细的配置请查看 kafka 官方文档的消费者部分配置文档。 32 | 33 | ``` 34 | input { 35 | kafka { 36 | zk_connect => "localhost:2181" 37 | group_id => "logstash" 38 | topic_id => "test" 39 | reset_beginning => false # boolean (optional), default: false 40 | consumer_threads => 5 # number (optional), default: 1 41 | decorate_events => true # boolean (optional), default: false 42 | } 43 | } 44 | ``` 45 | 46 | ## Input 解释 47 | 48 | 消费端的一些比较有用的配置项: 49 | 50 | * group_id 51 | 52 | 消费者分组,可以通过组 ID 去指定,不同的组之间消费是相互不受影响的,相互隔离。 53 | 54 | * topic_id 55 | 56 | 指定消费话题,也是必填项目,指定消费某个 `topic` ,这个其实就是订阅某个主题,然后去消费。 57 | 58 | * reset_beginning 59 | 60 | logstash 启动后从什么位置开始读取数据,默认是结束位置,也就是说 logstash 进程会以从上次读取结束时的偏移量开始继续读取,如果之前没有消费过,那么就开始从头读取.如果你是要导入原有数据,把这个设定改成 "true", logstash 进程就从头开始读取.有点类似 `cat` ,但是读到最后一行不会终止,而是变成 `tail -F` ,继续监听相应数据。 61 | 62 | * decorate_events 63 | 64 | 在输出消息的时候会输出自身的信息包括:消费消息的大小, topic 来源以及 consumer 的 group 信息。 65 | 66 | * rebalance\_max\_retries 67 | 68 | 当有新的 consumer(logstash) 加入到同一 group 时,将会 `reblance` ,此后将会有 `partitions` 的消费端迁移到新的 `consumer` 上,如果一个 `consumer` 获得了某个 `partition` 的消费权限,那么它将会向 `zookeeper` 注册, `Partition Owner registry` 节点信息,但是有可能此时旧的 `consumer` 尚没有释放此节点,此值用于控制,注册节点的重试次数。 69 | 70 | * consumer\_timeout\_ms 71 | 72 | 指定时间内没有消息到达就抛出异常,一般不需要改。 73 | 74 | 以上是相对重要参数的使用示例,更多参数可以选项可以跟据 查看 input 默认参数。 75 | 76 | ## 注意 77 | 78 | 1.想要使用多个 logstash 端协同消费同一个 `topic` 的话,那么需要把两个或是多个 logstash 消费端配置成相同的 `group_id` 和 `topic_id`, 但是前提是要把**相应的 topic 分多个 partitions (区)**,多个消费者消费是无法保证消息的消费顺序性的。 79 | 80 | > 这里解释下,为什么要分多个 **partitions(区)**, kafka 的消息模型是对 topic 分区以达到分布式效果。每个 `topic` 下的不同的 **partitions (区)**只能有一个 **Owner** 去消费。所以只有多个分区后才能启动多个消费者,对应不同的区去消费。其中协调消费部分是由 server 端协调而成。不必使用者考虑太多。只是**消息的消费则是无序的**。 81 | 82 | 总结:保证消息的顺序,那就用一个 **partition**。 **kafka 的每个 partition 只能同时被同一个 group 中的一个 consumer 消费**。 83 | 84 | ## Output 配置 85 | 86 | 以下配置可以实现对 kafka 写入端 (producer) 的基本使用。 87 | 88 | 生产端更多详细的配置请查看 kafka 官方文档的生产者部分配置文档。 89 | 90 | ``` 91 | output { 92 | kafka { 93 | broker_list => "localhost:9092" 94 | topic_id => "test" 95 | compression_codec => "snappy" # string (optional), one of ["none", "gzip", "snappy"], default: "none" 96 | } 97 | } 98 | ``` 99 | 100 | ## Output 解释 101 | 102 | 生产的可设置性还是很多的,设置其实更多,以下是更多的设置: 103 | 104 | * compression_codec 105 | 106 | 消息的压缩模式,默认是 none,可以有 gzip 和 snappy (暂时还未测试开启压缩与不开启的性能,数据传输大小等对比)。 107 | 108 | * compressed_topics 109 | 110 | 可以针对特定的 topic 进行压缩,设置这个参数为 `topic` ,表示此 `topic` 进行压缩。 111 | 112 | * request\_required\_acks 113 | 114 | 消息的确认模式: 115 | 116 | > 可以设置为 0: 生产者不等待 broker 的回应,只管发送.会有最低能的延迟和最差的保证性(在服务器失败后会导致信息丢失) 117 | > 118 | > 可以设置为 1: 生产者会收到 leader 的回应在 leader 写入之后.(在当前 leader 服务器为复制前失败可能会导致信息丢失) 119 | > 120 | > 可以设置为 -1: 生产者会收到 leader 的回应在全部拷贝完成之后。 121 | 122 | * partitioner_class 123 | 124 | 分区的策略,默认是 hash 取模 125 | 126 | * send\_buffer\_bytes 127 | 128 | socket 的缓存大小设置,其实就是缓冲区的大小 129 | 130 | #### 消息模式相关 131 | 132 | * serializer_class 133 | 134 | 消息体的系列化处理类,转化为字节流进行传输,**请注意 encoder 必须和下面的 `key_serializer_class` 使用相同的类型**。 135 | 136 | * key\_serializer\_class 137 | 138 | 默认的是与 `serializer_class` 相同 139 | 140 | * producer_type 141 | 142 | 生产者的类型 `async` 异步执行消息的发送 `sync` 同步执行消息的发送 143 | 144 | * queue\_buffering\_max\_ms 145 | 146 | **异步模式下**,那么就会在设置的时间缓存消息,并一次性发送 147 | 148 | * queue\_buffering\_max\_messages 149 | 150 | **异步的模式下**,最长等待的消息数 151 | 152 | * queue\_enqueue\_timeout\_ms 153 | 154 | **异步模式下**,进入队列的等待时间,若是设置为0,那么要么进入队列,要么直接抛弃 155 | 156 | * batch\_num\_messages 157 | 158 | **异步模式下**,每次发送的最大消息数,前提是触发了 `queue_buffering_max_messages` 或是 `queue_enqueue_timeout_ms` 的限制 159 | 160 | 以上是相对重要参数的使用示例,更多参数可以选项可以跟据 查看 output 默认参数。 161 | 162 | ### 小贴士 163 | 164 | 默认情况下,插件是使用 json 编码来输入和输出相应的消息,消息传递过程中 logstash 默认会为消息编码内加入相应的时间戳和 hostname 等信息。如果不想要以上信息(一般做消息转发的情况下),可以使用以下配置,例如: 165 | 166 | ``` 167 | output { 168 | kafka { 169 | codec => plain { 170 | format => "%{message}" 171 | } 172 | } 173 | } 174 | ``` 175 | -------------------------------------------------------------------------------- /contrib_plugins/scribe.md: -------------------------------------------------------------------------------- 1 | # scribe 2 | 3 | 4 | 5 | 6 | ``` 7 | input { 8 | scribe { 9 | host => "localhost" 10 | port => 8000 11 | } 12 | } 13 | ``` 14 | 15 | 16 | ``` 17 | java -Xmx400M -server \ 18 | -cp scribe_server.jar:logstash-1.2.1-flatjar.jar \ 19 | logstash.runner agent \ 20 | -p /where/did/i/put/this/downloaded/plugin \ 21 | -f logstash.conf 22 | ``` 23 | -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenryn/logstash-best-practice-cn/1ecbd5e730d90a2838fb5484c056e0482262ba3a/cover.jpg -------------------------------------------------------------------------------- /cover_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenryn/logstash-best-practice-cn/1ecbd5e730d90a2838fb5484c056e0482262ba3a/cover_small.jpg -------------------------------------------------------------------------------- /dive_into/README.md: -------------------------------------------------------------------------------- 1 | # 深入了解 2 | 3 | logstash 已经拥有数以百计的插件,并提供了一站式的部署方式,极大的方便了新手入门。但在实际运用上,我们终究会碰上其他人还没碰到过,或者碰到过但没公布出来完整解决方案的问题。可能是某些环境适配,可能是某个环节的性能不佳,可能是某处硬编码设置不合理,等等等等。这时候,了解一些 logstash 的代码逻辑,了解 logstash 之所以做出当前选择的缘由。是有助于解决实际问题的。 4 | 5 | 此外,logstash 作为一个框架式的项目,并不排斥,甚至欢迎与其他类似软件进行混搭式的运行。本书最后也会介绍一些其他日志处理框架以及如何和 logstash 共存的方式( 《logstashbook》也同样有类似内容)。希望大家各取所长,做好最适合自己的日志处理系统。 6 | -------------------------------------------------------------------------------- /dive_into/why_jvm_and_howto_run_with_mri.md: -------------------------------------------------------------------------------- 1 | # 为什么用 JRuby?能用 MRI 运行么? 2 | 3 | 对日志处理框架有一些了解的都知道,大多数框架都是用 Java 写的,毕竟做大规模系统 Java 有天生优势。而另一个新生代 fluentd 则是标准的 Ruby 产品(即 Matz's Ruby Interpreter)。logstash 选用 JRuby 来实现,似乎有点两头不讨好啊? 4 | 5 | 乔丹西塞曾经多次著文聊过这个问题。为了避凑字数的嫌,这里罗列他的 gist 地址: 6 | 7 | * [Time sucks](https://gist.github.com/jordansissel/2929216) 一文是关于 Time 对象的性能测试,最快的生成方法是 `sprintf` 方法,MRI 性能为 82600 call/sec,JRuby1.6.7 为 131000 call/sec,而 JRuby1.7.0 为 215000 call/sec。 8 | * [Comparing egexp patterns speeds](https://gist.github.com/jordansissel/1491302) 9 | 一文是关于正则表达式的性能测试,使用的正则统一为 `(?-mix:('(?:[^\\']+|(?:\\.)+)*'))`,结果 MRI1.9.2 为 530000 matches/sec,而 JRuby1.6.5 为 690000 matches/sec。 10 | * [Logstash performance under ruby](https://gist.github.com/jordansissel/4171039)一文是关于 logstash 本身数据流转性能的测试,使用 *inputs/generator* 插件生成数据,*outputs/stdout* 到 pv 工具记点统计。结果 MRI1.9.3 为 4000 events/sec,而 JRuby1.7.0 为 25000 events/sec。 11 | 12 | 可能你已经运行着 logstash 并发现自己的线上数据远超过这个测试——这是因为乔丹西塞在2013年之前,一直是业余时间开发 logstash,而且从未用在自己线上过。所以当时的很多测试是在他自己电脑上完成的。 13 | 14 | 在 logstash 得到大家强烈关注后,作者发表了《[logstash needs full time love](https://gist.github.com/jordansissel/3088552)》,表明了这点并求一份可以让自己全职开发 logstash 的工作,同时列出了1.1.0 版本以后的 roadmap。(不过事实证明当时作者列出来的这些需求其实不紧急,因为大多数,或者说除了 kibana 以外,至今依然没有==!) 15 | 16 | 时间轴继续向前推,到 2011 年,你会发现 logstash 原先其实也是用 MRI1.8.7 写的!在 [grok 模块从 C 扩展改写成 FFI 扩展后](https://code.google.com/p/logstash/issues/detail?id=37),才正式改用 JRuby。 17 | 18 | 切换语言的当时,乔丹西塞发表了《[logstash, why jruby?](https://gist.github.com/jordansissel/978956)》大家可以一读。 19 | 20 | 事实上,时至今日,多种 Ruby 实现的痕迹(到处都有 RUBY_ENGINE 变量判断)依然遍布 logstash 代码各处,作者也力图保证尽可能多的代码能在 MRI 上运行。 21 | 22 | 作为简单的指示,在和插件无关的核心代码中,只有 LogStash::Event 里生成 `@timestamp`字段时用了 Java 的 joda 库为 JRuby 仅有的。稍微修改成 Ruby 自带的 Time 库,即可在 MRI 上运行起来。而主要插件中,也只有 filters/date 和 outputs/elasticsearch 是 Java 相关的。 23 | -------------------------------------------------------------------------------- /dive_into/write_your_own.md: -------------------------------------------------------------------------------- 1 | # 自己写一个插件 2 | 3 | 前面已经提过在运行 logstash 的时候,可以通过 `--pluginpath` 参数来加载自己写的插件。那么,插件又该怎么写呢? 4 | 5 | ## 插件格式 6 | 7 | 一个标准的 logstash 输入插件格式如下: 8 | 9 | ```ruby 10 | require 'logstash/namespace' 11 | require 'logstash/inputs/base' 12 | class LogStash::Inputs::MyPlugin < LogStash::Inputs::Base 13 | config_name 'myplugin' 14 | milestone 1 15 | config :myoption_key, :validate => :string, :default => 'myoption_value' 16 | public def register 17 | end 18 | public def run(queue) 19 | end 20 | end 21 | ``` 22 | 23 | 其中大多数语句在过滤器和输出阶段是共有的。 24 | 25 | * config_name 用来定义该插件写在 logstash 配置文件里的名字; 26 | * milestone 标记该插件的开发里程碑,一般为1,2,3,如果不再维护的,标记为 0; 27 | * config 可以定义很多个,即该插件在 logstash 配置文件中的可配置参数。logstash 很温馨的提供了验证方法,确保接收的数据是你期望的数据类型; 28 | * register logstash 在启动的时候运行的函数,一些需要常驻内存的数据,可以在这一步先完成。比如对象初始化,*filters/ruby* 插件中的 `init` 语句等。 29 | 30 | *小贴士* 31 | 32 | milestone 级别在 3 以下的,logstash 默认为不足够稳定,会在启动阶段,读取到该插件的时候,输出类似下面这样的一行提示信息,日志级别是 warn。**这不代表运行出错**!只是提示如果用户碰到 bug,欢迎提供线索。 33 | 34 | > {:timestamp=>"2015-02-06T10:37:26.312000+0800", :message=>"Using milestone 2 input plugin 'file'. This plugin should be stable, but if you see strange behavior, please let us know! For more information on plugin milestones, see http://logstash.net/docs/1.4.2-modified/plugin-milestones", :level=>:warn} 35 | 36 | ## 插件的关键方法 37 | 38 | 输入插件独有的是 run 方法。在 run 方法中,必须实现一个长期运行的程序(最简单的就是 loop 指令)。然后在每次收到数据并处理成 `event` 之后,一定要调用 `queue << event` 语句。一个输入流程就算是完成了。 39 | 40 | 而如果是过滤器插件,对应修改成: 41 | 42 | ```ruby 43 | require 'logstash/filters/base' 44 | class LogStash::Filters::MyPlugin < LogStash::Filters::Base 45 | public def filter(event) 46 | end 47 | end 48 | ``` 49 | 50 | 输出插件则是: 51 | 52 | ```ruby 53 | require 'logstash/outputs/base' 54 | class LogStash::Outputs::MyPlugin < LogStash::Outputs::Base 55 | public def receive(event) 56 | end 57 | end 58 | ``` 59 | 60 | 另外,为了在终止进程的时候不遗失数据,建议都实现如下这个方法,只要实现了,logstash 在 shutdown 的时候就会自动调用: 61 | 62 | ```ruby 63 | public def teardown 64 | end 65 | ``` 66 | 67 | ## 推荐阅读 68 | 69 | * [Extending logstash](http://logstash.net/docs/1.4.2/extending/) 70 | * [Plugin Milestones](http://logstash.net/docs/1.4.2/plugin-milestones) 71 | -------------------------------------------------------------------------------- /ecosystem/README.md: -------------------------------------------------------------------------------- 1 | # 周边项目 2 | -------------------------------------------------------------------------------- /ecosystem/fluent.md: -------------------------------------------------------------------------------- 1 | # fluent 2 | -------------------------------------------------------------------------------- /ecosystem/heka.md: -------------------------------------------------------------------------------- 1 | # Heka 2 | 3 | heka 是 Mozilla 公司仿造 logstash 设计,用 Golang 重写的一个开源项目。同样采用了input -> decoder -> filter -> encoder -> output 的流程概念。其特点在于,在中间的 decoder/filter/encoder 部分,设计了 sandbox 概念,可以采用内嵌 lua 脚本做这一部分的工作,降低了全程使用静态 Golang 编写的难度。此外,其 filter 阶段还提供了一些监控和统计报警功能。 4 | 5 | 官网地址见: 6 | 7 | 下面是同样的处理逻辑,通过 syslog 接收 nginx 访问日志,解析并存储进 Elasticsearch,heka 配置文件如下: 8 | 9 | ```ini 10 | [hekad] 11 | maxprocs = 48 12 | 13 | [TcpInput] 14 | address = ":514" 15 | parser_type = "token" 16 | decoder = "shipped-nginx-decoder" 17 | 18 | [shipped-nginx-decoder] 19 | type = "MultiDecoder" 20 | subs = ['RsyslogDecoder', 'nginx-access-decoder'] 21 | cascade_strategy = "all" 22 | log_sub_errors = true 23 | 24 | [RsyslogDecoder] 25 | type = "SandboxDecoder" 26 | filename = "lua_decoders/rsyslog.lua" 27 | [RsyslogDecoder.config] 28 | type = "nginx.access" 29 | template = '<%pri%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n' 30 | tz = "Asia/Shanghai" 31 | 32 | [nginx-access-decoder] 33 | type = "SandboxDecoder" 34 | filename = "lua_decoders/nginx_access.lua" 35 | 36 | [nginx-access-decoder.config] 37 | type = "combined" 38 | user_agent_transform = true 39 | log_format = '[$time_local]`$http_x_up_calling_line_id`"$request"`"$http_user_agent"`$staTus`[$remote_addr]`$http_x_log_uid`"$http_referer"`$request_time`$body_bytes_sent`$http_x_forwarded_proto`$http_x_forwarded_for`$request_uid`$http_host`$http_cookie`$upstream_response_time' 40 | 41 | [ESLogstashV0Encoder] 42 | es_index_from_timestamp = true 43 | fields = ["Timestamp", "Payload", "Hostname", "Fields"] 44 | type_name = "%{Type}" 45 | 46 | [ElasticSearchOutput] 47 | message_matcher = "Type == 'nginx.access'" 48 | server = "http://eshost.example.com:9200" 49 | encoder = "ESLogstashV0Encoder" 50 | flush_interval = 50 51 | flush_count = 5000 52 | ``` 53 | 54 | heka 目前仿造的还是旧版本的 logstash schema 设计,所有切分字段都存储在 `@fields` 下。 55 | 56 | 经测试,其处理性能跟开启了多线程 filters 的 logstash 进程类似,都在每秒 30000 条。 57 | -------------------------------------------------------------------------------- /ecosystem/logstash_forwarder.md: -------------------------------------------------------------------------------- 1 | # Logstash Forwarder 2 | 3 | Redis 已经帮我们解决了很多的问题,而且也很轻量,为什么我们还需要 logstash-forwarder 呢? 4 | 5 | > Redis provides simple authentication but no transport-layer encryption or authorization. This is perfectly fine in trusted environments. However, if you're connecting to Redis between datacenters you will probably want to use encryption. 6 | 7 | 简而言之他很好,但是他不 secure。 8 | 9 | 现在看看我们如何来配置 logstash-forwarder。 10 | 11 | ## indexer 端配置 12 | 13 | 在 logstash 作为 indexer server 角色的这端,我们首先需要生成证书: 14 | 15 | cd /etc/pki/tls 16 | sudo openssl req -x509 -batch -nodes -days 3650 -newkey rsa:2048 -keyout private/logstash-forwarder.key -out certs/logstash-forwarder.crt 17 | 18 | 然后把证书发送到准备运行 logstash-forwarder 的 shipper 端服务器上去: 19 | 20 | scp private/logstash-forwarder.key root@target_server_ip:/etc/pki/tls/private 21 | scp certs/logstash-forwarder.crt root@target_server_ip:/etc/pki/tls/certs 22 | 23 | 然后创建 logstash 的配置文件。监听部分 /etc/logstash/conf.d/02-lumberjack-input.conf ,内容如下: 24 | 25 | ``` 26 | input { 27 | lumberjack { 28 | port => 5000 29 | type => "anything" 30 | ssl_certificate => "/etc/pki/tls/certs/logstash-forwarder.crt" 31 | ssl_key => "/etc/pki/tls/private/logstash-forwarder.key" 32 | } 33 | } 34 | ``` 35 | 36 | 以上,我们在 logstash 这端已经配置完成。运行 `logstash -f /etc/logstash/conf.d/` 即可。 37 | 38 | *小知识:lumberjack 是 logstash-forwarder 还没用 Golang 重写之前的名字* 39 | 40 | ## shipper 端配置 41 | 42 | 我们现在登入到我们需要传送 log 的机器上,我们已在之前的步骤中发送了 logstash 的 crt 过来。 43 | 44 | ### logstash-forwarder 安装 45 | 46 | 首先,我们需要安装 logstash-forwarder 软件。官方都已经提供了软件仓库可用。在 Redhat 机器上只需要添加一个 */etc/yum.repos.d/logstash-forwarder.repo*,内容如下: 47 | 48 | ```ini 49 | [logstash-forwarder] 50 | name=logstash-forwarder 51 | baseurl=http://packages.elasticsearch.org/logstash-forwarder/centos 52 | gpgcheck=1 53 | gpgkey=http://packages.elasticsearch.org/GPG-KEY-elasticsearch 54 | enabled=1 55 | ``` 56 | 57 | 然后运行安装命令即可: 58 | 59 | sudo yum install -y logstash-forwarder 60 | 61 | 你可以从我提供的 gist 中下载已经更改的 init script 或者使用 rpm 中提供的脚本 [logstash-forwader](https://gist.github.com/ae30a4c1a1f342df1274.git). 62 | 63 | ### logstash-forwarder 配置 64 | 65 | logstash-forwarder 的配置文件是纯 JSON 格式。因为其轻量级的设计目的,所以可配置项很少。下面是一个 */etc/logstash-forwarder* 配置示例: 66 | 67 | ```json 68 | { 69 | "network": { 70 | "servers": [ "10.18.10.2:5000" ], 71 | "timeout": 15, 72 | "ssl ca" : "/etc/pki/tls/certs/logstash-forwarder.crt" 73 | "ssl key": "/etc/pki/tls/private/logstash-forwarder.key" 74 | }, 75 | "files": [ 76 | { 77 | "paths": [ 78 | "/var/log/message", 79 | "/var/log/secure" 80 | ], 81 | "fields": { "type": "syslog" } 82 | } 83 | ] 84 | } 85 | ``` 86 | 87 | 我们已完成了配置,当 `sudo service logstash-forwarder start` 之后,你就可以在 kibana 上看到你的日志了 88 | 89 | ### logstash-forwarder 配置说明 90 | 91 | 配置中,主要包括下面几个可用配置项: 92 | 93 | * network.servers: 用来指定远端(即 logstash indexer 角色)服务器的 IP 地址和端口。这里可以写数组,但是 logstash-forwarder 只会随机选一台作为对端发送数据,一直到对端故障,才会重选其他服务器。 94 | * network.ssl\*: 网络交互中使用的 SSL 证书路径。 95 | * files.\*.paths: 读取的文件路径。 96 | logstash-forwarder 只支持两种输入,一种就是示例中用的文件方式,和 logstash 一样也支持 glob 路径,即 `"/var/log/*.log"` 这样的写法;一种是标准输入,写法为 `"paths": [ "-" ]` 97 | * files.\*.fields: 给每条日志添加的固定字段,相当于 logstash 里的 `add_field` 参数。 98 | 注意示例中添加的是 **type** 字段。在 logstash-forwarder 里添加的字段是优先于 LogStash::Inputs::Lumberjack 配置里定义的字段的。所以,在本例中,即便你在 indexer 上定义 type 为 "anything"。事件的实际 type 依然是这里添加的 "syslog"。这也意味着,你在 indexer 上如果做后续判断,应该是这样: 99 | 100 | ``` 101 | filter { 102 | if [type] == "syslog" { 103 | grok { 104 | match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" } 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | ## 安全性提示 111 | 112 | 虽然 ssl 是可信任的,但是当 hacker 得到你一台机器上的证书后,他可以畅通无阻,建议对每台机器都签发单独的证书,如果你忙的过来的话:) 113 | 114 | ## Logstash-forwarder-java的使用 115 | 116 | 在AIX环境下(IBM Power小型机的一种操作系统),你无法使用logstash(因为IBM JDK没有实现相关方法),也无法使用logstash-forwarder,github上有个Logstash-forwarder再实现的项目就是解决这个问题的: 117 | https://github.com/didfet/logstash-forwarder-java 118 | 119 | 配置和Logstash-forwarder基本一致,但是注意有一个坑是需要关注的,作者也在他的github上提到了,就是: 120 | ``` 121 | the ssl ca parameter points to a java keystore containing the root certificate of the server, not a PEM file 122 | ``` 123 | 124 | 不熟悉证书相关体系的读者可能不太清楚这个意思,换句话说,如果你还按照logstash-forwarder的配置方法配置shipper端,那么你将会得到一个诡异的java.io.IOException: Invalid keystore format 异常。 125 | 126 | 首先介绍下背景知识,摘录一段知乎上的讲解: @刘长元 from http://www.zhihu.com/question/29620953 127 | 128 | ``` 129 | 把SSL系统比喻为工商局系统。 130 | 首先有SSL就有CA,certificate authority。证书局,用于制作、认证证书的第三方机构,我们假设营业执照非常难制作,就像身份证一样,需要有制证公司来提供,并且提供技术帮助工商局验证执照的真伪。然后CA是可以有多个的,也就是可以有多个制证公司,但工商局就只有一个,它来说那个制证公司是可信的,那些是假的,需要打击。在SSL的世界中,微软、Google和Mozilla扮演了一部分这个角色。也就是说,IE、Chrome、Firefox中内置有一些CA,经过这些CA颁发,验证过的证书都是可以信的,否则就会提示你不安全。 131 | 这也是为什么前几天Chrome决定屏蔽CNNIC的CA时,CNNIC那么遗憾了。 132 | 也因为内置的CA是相对固定的,所以当你决定要新建网站时,就需要购买这些内置CA颁发的证书来让用户看到你的域名前面是绿色的,而不是红色。而这个最大的卖证书的公司就是VeriSign如果你听说过的话,当然它被卖给了Symantec,这家伙不只出Ghost,还是个卖证书的公司。 133 | 134 | 要开店的老板去申请营业执照的时候是需要交他的身份证的,然后办出来的营业执照上也会有他的照片和名字。身份证相当于私钥,营业执照就是证书,Ceritficate,.cer文件。 135 | 136 | 然后关于私钥和公钥如何解释我没想好,而它们在数据加密层面,数据的流向是这样的。 137 | 138 | 消息-->[公钥]-->加密后的信息-->[私钥]-->消息 139 | 140 | 公钥是可以随便扔给谁的,他把消息加了密传给我。对了,可以这样理解,我有一个箱子,一把锁和一把钥匙,我把箱子和开着的锁给别人,他写了信放箱子里,锁上,然后传递回我手上的途中谁都是打不开箱子的,只有我可以用原来的钥匙打开,这就是SSL,公钥,私钥传递加密消息的方式。这里的密钥就是key文件。 141 | 142 | 于是我们就有了.cer和.key文件。接下来说keystore 143 | 144 | 不同的语言、工具序列SSL相关文件的格式和扩展名是不一样的。 145 | 其中Java系喜欢用keystore, truststore来干活,你看它的名字,Store,仓库,它里面存放着key和信任的CA,key和CA可以有多个。 146 | 这里的truststore就像你自己电脑的证书管理器一样,如果你打开Chrome的设置,找到HTTP SSL,就可以看到里面有很多CA,truststore就是干这个活儿的,它也里面也是存一个或多个CA让Tomcat或Java程序来调用。 147 | 而keystore就是用来存密钥文件的,可以存放多个。 148 | 149 | 然后是PEM,它是由RFC1421至1424定义的一种数据格式。其实前面的.cert和.key文件都是PEM格式的,只不过在有些系统中(比如Windows)会根据扩展名不同而做不同的事。所以当你看到.pem文件时,它里面的内容可能是certificate也可能是key,也可能两个都有,要看具体情况。可以通过openssl查看。 150 | 151 | ``` 152 | 看到这儿你就应该懂了,按照logstash-forwarder-java的作者设计,此时你的shipper端ssl ca这个域配置的应该是keystore,而不是PEM,因此需要从你生成的crt中创建出keystore(jks)文件,方法为: 153 | 154 | ``` 155 | keytool -importcert -trustcacerts -file logstash-forwarder.crt -alias ca -keystore keystore.jks 156 | ``` 157 | 158 | 一个示例的shipper.conf为: 159 | { 160 | "network": { 161 | "servers": [ "192.168.1.1:5043"], 162 | "ssl ca": "/mnt/disk12/logger/logstash/config/keystore.jks" 163 | }, 164 | "files": [ 165 | { 166 | "paths": [ "/mnt/disk12/logger/logstash/config/2.txt" ], 167 | "fields": { "type": "sadb" } 168 | } 169 | ] 170 | } 171 | *注意:server可以配置多个,这样agent如果一个logstash连接不上可以连接另外的。* 172 | 173 | 其余配置信息,请参考logstash-forwarder,它完全兼容。 174 | 175 | 配置好以后启动它即可:nohup java -jar logstash-forwarder-java-0.2.3-SNAPSHOT.jar -quiet -config logforwarder.conf > logforwarder.log & 176 | 177 | 178 | *-quiet 参数可以大大减少不必要的日志量,如果遇到错误请打开-debug和-trace选项,得到相关信息后查证,未果时请转向该项目github的issue区,作者很热心* 179 | 180 | 测试通过环境: 181 | ``` 182 | AIX版本 183 | 6100-04-06-1034 184 | 185 | Java版本 186 | java version "1.6.0" 187 | Java(TM) SE Runtime Environment (build pap6460sr14-20130705_01(SR14)) 188 | IBM J9 VM (build 2.4, JRE 1.6.0 IBM J9 2.4 AIX ppc64-64 jvmap6460sr14-20130704_155156 (JIT enabled, AOT enabled) 189 | J9VM - 20130704_155156 190 | JIT - r9_20130517_38390 191 | GC - GA24_Java6_SR14_20130704_1138_B155156) 192 | JCL - 20130618_01 193 | ``` 194 | -------------------------------------------------------------------------------- /ecosystem/message_passing.md: -------------------------------------------------------------------------------- 1 | # Message::Passing 2 | 3 | [Message::Passing](https://metacpan.org/pod/Message::Passing) 是一个仿造 Logstash 写的 Perl5 项目。项目早期甚至就直接照原样也叫 "Logstash" 的名字。 4 | 5 | 但实际上,Message::Passing 内部原理设计还是有所偏差的。这个项目整个基于 AnyEvent 事件驱动开发框架(即著名的 libev 库)完成,也要求所有插件不要采取阻塞式编程。所以,虽然项目开发不太活跃,插件接口不甚完善,但是性能方面却非常棒。这也是我从多个 Perl 日志处理框架中选择介绍这个的原因。 6 | 7 | Message::Passing 有比较全的 input 和 output 插件,这意味着它可以通过多种协议跟 logstash 混跑,不过 filter 插件比较缺乏。对等于 grok 的插件叫 `Message::Passing::Filter::Regexp`( 我写的,嘿嘿)。下面是一个完整的配置示例: 8 | 9 | ```perl 10 | use Message::Passing::DSL; 11 | run_message_server message_chain { 12 | output stdout => ( 13 | class => 'STDOUT', 14 | ); 15 | output elasticsearch => ( 16 | class => 'ElasticSearch', 17 | elasticsearch_servers => ['127.0.0.1:9200'], 18 | ); 19 | encoder("encoder", 20 | class => 'JSON', 21 | output_to => 'stdout', 22 | output_to => 'es', 23 | ); 24 | filter regexp => ( 25 | class => 'Regexp', 26 | format => ':nginxaccesslog', 27 | capture => [qw( ts status remotehost url oh responsetime upstreamtime bytes )] 28 | output_to => 'encoder', 29 | ); 30 | filter logstash => ( 31 | class => 'ToLogstash', 32 | output_to => 'regexp', 33 | ); 34 | decoder decoder => ( 35 | class => 'JSON', 36 | output_to => 'logstash', 37 | ); 38 | input file => ( 39 | class => 'FileTail', 40 | output_to => 'decoder', 41 | ); 42 | }; 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /ecosystem/nxlog.md: -------------------------------------------------------------------------------- 1 | # nxlog 2 | 3 | nxlog 是用 C 语言写的一个跨平台日志收集处理软件。其内部支持使用 Perl 正则和语法来进行数据结构化和逻辑判断操作。不过,其最常用的场景。是在 windows 服务器上,作为 logstash 的替代品运行。 4 | 5 | nxlog 的 windows 安装文件下载 url 见: 6 | 7 | 8 | ## 配置 9 | 10 | Nxlog默认配置文件位置在:`C:\Program Files (x86)\nxlog`。 11 | 12 | 配置文件中有3个关键设置,分别是:input(日志输入端)、output(日志输出端)、route(绑定某输入到具体某输出)。。 13 | 14 | ## 例子 15 | 16 | 假设我们有两台服务器,收集其中的 windows 事务日志: 17 | 18 | * logstash服务器ip地址:192.168.1.100 19 | * windows测试服务器ip地址:192.168.1.101 20 | 21 | 收集流程: 22 | 23 | 1. nxlog 使用模块 im_file 收集日志文件,开启位置记录功能 24 | 2. nxlog 使用模块tcp输出日志 25 | 3. logstash 使用input/tcp ,收集日志,输出至es 26 | 27 | ### Logstash配置文件 28 | 29 | ``` 30 | input { 31 | tcp { 32 | port => 514 33 | } 34 | } 35 | output{ 36 | elasticsearch { 37 | host => "127.0.0.1" 38 | port => "9200" 39 | protocol => "http" 40 | } 41 | } 42 | ``` 43 | 44 | ### Nxlog配置文件 45 | 46 | ``` 47 | 48 | Module im_file 49 | File "C:\\test\\\*.log" 50 | SavePos TRUE 51 | 52 | 53 | 54 | Module om_tcp 55 | Host 192.168.1.100 56 | Port 514 57 | 58 | 59 | 60 | Path testfile => out 61 | 62 | ``` 63 | 64 | 配置文件修改完毕后,重启服务即可: 65 | 66 | ![](../images/nxlog.png) 67 | -------------------------------------------------------------------------------- /ecosystem/rsyslog.md: -------------------------------------------------------------------------------- 1 | # Rsyslog 2 | 3 | Rsyslog 是 RHEL6 开始的默认系统 syslog 应用软件(当然,RHEL 自带的版本较低,实际官方稳定版本已经到 v8 了)。官网地址: 4 | 5 | 目前 Rsyslog 本身也支持多种输入输出方式,内部逻辑判断和模板处理。 6 | 7 | ![](http://www.rsyslog.com/common/images/rsyslog-features-imagemap.png) 8 | 9 | ## 常用模块介绍 10 | 11 | 不同模块插件在 rsyslog 流程中发挥作用的原理,可以阅读: 12 | 13 | 流程中可以使用 [mmnormalize](http://www.rsyslog.com/doc/master/configuration/modules/mmnormalize.html) 组件来完成数据的切分(相当于 logstash 的 *filters/grok* 功能)。 14 | 15 | rsyslog 从 v7 版本开始带有 *omelasticsearch* 插件可以直接写入数据到 elasticsearch 集群,配合 mmnormalize 的使用示例见: 16 | 17 | 而 normalize 语法说明见: 18 | 19 | 类似的还有 mmjsonparse 组件。 20 | 21 | 使用示例见: 22 | 23 | ## rsyslog 与 logstash 合作 24 | 25 | 虽然 Rsyslog 很早就支持直接输出数据给 elasticsearch,但**如果你使用的是 v8.4 以下的版本**,我们这里并不推荐这种方式。因为normalize 语法还是比较简单,只支持时间,字符串,数字,ip 地址等几种。在复杂条件下远比不上完整的正则引擎。 26 | 27 | 那么,怎么使用 rsyslog 作为日志收集和传输组件,来配合 logstash 工作呢? 28 | 29 | 如果只是简单的 syslog 数据,直接单个 logstash 运行即可,配置方式见本书 2.4 章节。 30 | 31 | 如果你运行着一个高负荷运行的 rsyslog 系统,每秒传输的数据远大过单个 logstash 能处理的能力,你可以运行多个 logstash 在多个端口,然后让 rsyslog 做轮训转发(事实上,单个 omfwd 本身的转发能力也有限,所以推荐这种做法): 32 | 33 | ``` 34 | Ruleset( name="forwardRuleSet" ) { 35 | Action ( type="mmsequence" mode="instance" from="0" to="4" var="$.seq" ) 36 | if $.seq == "0" then { 37 | action (type="omfwd" Target="127.0.0.1" Port="5140" Protocol="tcp" queue.size="150000" queue.dequeuebatchsize="2000" ) 38 | } 39 | if $.seq == "1" then { 40 | action (type="omfwd" Target="127.0.0.1" Port="5141" Protocol="tcp" queue.size="150000" queue.dequeuebatchsize="2000" ) 41 | } 42 | if $.seq == "2" then { 43 | action (type="omfwd" Target="127.0.0.1" Port="5142" Protocol="tcp" queue.size="150000" queue.dequeuebatchsize="2000" ) 44 | } 45 | if $.seq == "3" then { 46 | action (type="omfwd" Target="127.0.0.1" Port="5143" Protocol="tcp" queue.size="150000" queue.dequeuebatchsize="2000" ) 47 | } 48 | } 49 | ``` 50 | 51 | ## rsyslog v8 版的 omelasticsearch 52 | 53 | 如果你使用的是最新的 v8.4 版 rsyslog,其中有一个新加入的 mmexternal 模块。该模块是在 v7 的 omprog 模块基础上发展出来的,可以让你使用任意脚本,接收标准输入,自行处理以后再输出回来,而 rsyslog 接收到这个输出再进行下一步处理,这就解决了前面提到的 “normalize 语法太简单”的问题! 54 | 55 | 下面是使用 rsyslog 的 mmexternal 和 omelasticsearch 完成 Nginx 访问日志直接解析存储的配置。 56 | 57 | rsyslog 配置如下: 58 | 59 | ``` 60 | module(load="imuxsock" SysSock.RateLimit.Interval="0") 61 | module(load="mmexternal") 62 | module(load="omelasticsearch") 63 | template(name="logstash-index" type="list") { 64 | constant(value="logstash-") 65 | property(name="timereported" dateFormat="rfc3339" position.from="1" position.to="4") 66 | constant(value=".") 67 | property(name="timereported" dateFormat="rfc3339" position.from="6" position.to="7") 68 | constant(value=".") 69 | property(name="timereported" dateFormat="rfc3339" position.from="9" position.to="10") 70 | } 71 | template( name="nginx-log" type="string" string="%msg%\n" ) 72 | if ( $syslogfacility-text == 'local6' and $programname startswith 'wb-www-access-' and not ($msg contains '/2/remind/unread_count' or $msg contains '/2/remind/group_unread') ) then 73 | { 74 | action( type="mmexternal" binary="/usr/local/bin/rsyslog-nginx-elasticsearch.py" interface.input="fulljson" ) 75 | action( type="omelasticsearch" 76 | template="nginx-log" 77 | server="eshost.example.com" 78 | searchIndex="logstash-index" 79 | searchType="nginxaccess" 80 | asyncrepl="on" 81 | bulkmode="on" 82 | queue.type="linkedlist" 83 | queue.size="10000" 84 | queue.dequeuebatchsize="2000" 85 | dynSearchIndex="on" 86 | ) 87 | stop 88 | } 89 | ``` 90 | 91 | 其中调用的 python 脚本示例如下: 92 | 93 | ```python 94 | #! /usr/bin/python 95 | import sys 96 | import json 97 | import datetime 98 | 99 | def nginxLog(data): 100 | hostname = data['hostname'] 101 | logline = data['msg'] 102 | time_local, http_x_up_calling_line_id, request, http_user_agent, staTus, remote_addr, http_x_log_uid, http_referer, request_time, body_bytes_sent, http_x_forwarded_proto, http_x_forwarded_for, request_uid, http_host, http_cookie, upstream_response_time = logline.split('`') 103 | try: 104 | upstream_response_time = float(upstream_response_time) 105 | except: 106 | upstream_response_time = None 107 | 108 | method, uri, verb = request.split(' ') 109 | arg = {} 110 | try: 111 | url_path, url_args = uri.split('?') 112 | for args in url_args.split('&'): 113 | k, v = args.split('=') 114 | arg[k] = v 115 | except: 116 | url_path = uri 117 | 118 | # Why %z do not implement? 119 | ret = { 120 | "@timestamp": datetime.datetime.strptime(time_local, ' [%d/%b/%Y:%H:%M:%S +0800]').strftime('%FT%T+0800'), 121 | "host": hostname, 122 | "method": method.lstrip('"'), 123 | "url_path": url_path, 124 | "url_args": arg, 125 | "verb": verb.rstrip('"'), 126 | "http_x_up_calling_line_id": http_x_up_calling_line_id, 127 | "http_user_agent": http_user_agent, 128 | "status": int(staTus), 129 | "remote_addr": remote_addr.strip('[]'), 130 | "http_x_log_uid": http_x_log_uid, 131 | "http_referer": http_referer, 132 | "request_time": float(request_time), 133 | "body_bytes_sent": int(body_bytes_sent), 134 | "http_x_forwarded_proto": http_x_forwarded_proto, 135 | "http_x_forwarded_for": http_x_forwarded_for, 136 | "request_uid": request_uid, 137 | "http_host": http_host, 138 | "http_cookie": http_cookie, 139 | "upstream_response_time": upstream_response_time 140 | } 141 | return ret 142 | 143 | def onInit(): 144 | """ Do everything that is needed to initialize processing 145 | """ 146 | 147 | def onReceive(msg): 148 | data = json.loads(msg) 149 | ret = nginxLog(data) 150 | print json.dumps({'msg': ret}) 151 | 152 | def onExit(): 153 | """ Do everything that is needed to finish processing. This is being called immediately before exiting. 154 | """ 155 | # most often, nothing to do here 156 | 157 | onInit() 158 | keepRunning = 1 159 | while keepRunning == 1: 160 | msg = sys.stdin.readline() 161 | if msg: 162 | msg = msg[:len(msg)-1] 163 | onReceive(msg) 164 | sys.stdout.flush() 165 | else: 166 | keepRunning = 0 167 | onExit() 168 | sys.stdout.flush() 169 | ``` 170 | 171 | 注意输出的时候,顶层的 key 是不能变的,msg 还得叫 msg,如果是 hostname 还得叫 hostname ,等等。否则,rsyslog 会当做处理无效,直接传递原有数据内容给下一步。 172 | 173 | **慎用提示** 174 | 175 | 从实际运行效果看,rsyslog 对 mmexternal 的程序没有最大并发数限制!所以如果你发送的数据量较大的事情,rsyslog 并不会像普通的转发模式那样缓冲在磁盘队列上,而是**持续 fork 出新的 mmexternal 程序**,几千个进程后,你的服务器就挂了!! 176 | -------------------------------------------------------------------------------- /filter/README.md: -------------------------------------------------------------------------------- 1 | # 过滤器插件(Filter) 2 | 3 | 丰富的过滤器插件的存在是 logstash 威力如此强大的重要因素。名为过滤器,其实提供的不单单是过滤的功能。在本章我们就会重点介绍几个插件,它们扩展了进入过滤器的原始数据,进行复杂的逻辑处理,甚至可以无中生有的添加新的 logstash 事件到后续的流程中去! 4 | -------------------------------------------------------------------------------- /filter/date.md: -------------------------------------------------------------------------------- 1 | # 时间处理(Date) 2 | 3 | 之前章节已经提过,*filters/date* 插件可以用来转换你的日志记录中的时间字符串,变成 `LogStash::Timestamp` 对象,然后转存到 `@timestamp` 字段里。 4 | 5 | **注意:因为在稍后的 outputs/elasticsearch 中常用的 `%{+YYYY.MM.dd}` 这种写法必须读取 `@timestamp` 数据,所以一定不要直接删掉这个字段保留自己的字段,而是应该用 filters/date 转换后删除自己的字段!** 6 | 7 | 这在导入旧数据的时候固然非常有用,而在实时数据处理的时候同样有效,因为一般情况下数据流程中我们都会有缓冲区,导致最终的实际处理时间跟事件产生时间略有偏差。 8 | 9 | *小贴士:个人强烈建议打开 Nginx 的 access_log 配置项的 buffer 参数,对极限响应性能有极大提升!* 10 | 11 | ## 配置示例 12 | 13 | *filters/date* 插件支持五种时间格式: 14 | 15 | ### ISO8601 16 | 17 | 类似 "2011-04-19T03:44:01.103Z" 这样的格式。具体Z后面可以有 "08:00"也可以没有,".103"这个也可以没有。常用场景里来说,Nginx 的 *log_format* 配置里就可以使用 `$time_iso8601` 变量来记录请求时间成这种格式。 18 | 19 | ### UNIX 20 | 21 | UNIX 时间戳格式,记录的是从 1970 年起始至今的总秒数。Squid 的默认日志格式中就使用了这种格式。 22 | 23 | ### UNIX_MS 24 | 25 | 这个时间戳则是从 1970 年起始至今的总毫秒数。据我所知,JavaScript 里经常使用这个时间格式。 26 | 27 | ### TAI64N 28 | 29 | TAI64N 格式比较少见,是这个样子的:`@4000000052f88ea32489532c`。我目前只知道常见应用中, qmail 会用这个格式。 30 | 31 | ### Joda-Time 库 32 | 33 | Logstash 内部使用了 Java 的 Joda 时间库来作时间处理。所以我们可以使用 Joda 库所支持的时间格式来作具体定义。Joda 时间格式定义见下表: 34 | 35 | #### 时间格式 36 | 37 | |Symbol |Meaning |Presentation |Examples| 38 | |--------|-----------------------------|--------------|-------| 39 | |G |era |text |AD| 40 | |C |century of era (>=0) |number |20| 41 | |Y |year of era (>=0) |year |1996| 42 | |x |weekyear |year |1996| 43 | |w |week of weekyear |number |27| 44 | |e |day of week |number |2| 45 | |E |day of week |text |Tuesday; Tue| 46 | |y |year |year |1996| 47 | |D |day of year |number |189| 48 | |M |month of year |month |July; Jul; 07| 49 | |d |day of month |number |10| 50 | |a |halfday of day |text |PM| 51 | |K |hour of halfday (0~11) |number |0| 52 | |h |clockhour of halfday (1~12) |number |12| 53 | |H |hour of day (0~23) |number |0| 54 | |k |clockhour of day (1~24) |number |24| 55 | |m |minute of hour |number |30| 56 | |s |second of minute |number |55| 57 | |S |fraction of second |number |978| 58 | |z |time zone |text |Pacific Standard Time; PST| 59 | |Z |time zone offset/id |zone |-0800; -08:00; America/Los_Angeles| 60 | |' |escape for text |delimiter || 61 | |'' |single quote |literal |'| 62 | 63 | 64 | 65 | 下面我们写一个 Joda 时间格式的配置作为示例: 66 | 67 | ``` 68 | filter { 69 | grok { 70 | match => ["message", "%{HTTPDATE:logdate}"] 71 | } 72 | date { 73 | match => ["logdate", "dd/MMM/yyyy:HH:mm:ss Z"] 74 | } 75 | } 76 | ``` 77 | 78 | **注意:时区偏移量只需要用一个字母 `Z` 即可。** 79 | 80 | ## 时区问题的解释 81 | 82 | 很多中国用户经常提一个问题:为什么 @timestamp 比我们早了 8 个小时?怎么修改成北京时间? 83 | 84 | 其实,Elasticsearch 内部,对时间类型字段,是**统一采用 UTC 时间,存成 long 长整形数据的**!对日志统一采用 UTC 时间存储,是国际安全/运维界的一个通识——欧美公司的服务器普遍广泛分布在多个时区里——不像中国,地域横跨五个时区却只用北京时间。 85 | 86 | 对于页面查看,ELK 的解决方案是在 Kibana 上,读取浏览器的当前时区,然后在页面上转换时间内容的**显示**。 87 | 88 | 所以,建议大家接受这种设定。否则,即便你用 `.getLocalTime` 修改,也还要面临在 Kibana 上反过去修改,以及 Elasticsearch 原有的 `["now-1h" TO "now"]` 这种方便的搜索语句无法正常使用的尴尬。 89 | 90 | 以上,请读者自行斟酌。 91 | -------------------------------------------------------------------------------- /filter/geoip.md: -------------------------------------------------------------------------------- 1 | # GeoIP 地址查询归类 2 | 3 | GeoIP 是最常见的免费 IP 地址归类查询库,同时也有收费版可以采购。GeoIP 库可以根据 IP 地址提供对应的地域信息,包括国别,省市,经纬度等,对于可视化地图和区域统计非常有用。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | filter { 9 | geoip { 10 | source => "message" 11 | } 12 | } 13 | ``` 14 | 15 | 16 | ## 运行结果 17 | 18 | ```ruby 19 | { 20 | "message" => "183.60.92.253", 21 | "@version" => "1", 22 | "@timestamp" => "2014-08-07T10:32:55.610Z", 23 | "host" => "raochenlindeMacBook-Air.local", 24 | "geoip" => { 25 | "ip" => "183.60.92.253", 26 | "country_code2" => "CN", 27 | "country_code3" => "CHN", 28 | "country_name" => "China", 29 | "continent_code" => "AS", 30 | "region_name" => "30", 31 | "city_name" => "Guangzhou", 32 | "latitude" => 23.11670000000001, 33 | "longitude" => 113.25, 34 | "timezone" => "Asia/Chongqing", 35 | "real_region_name" => "Guangdong", 36 | "location" => [ 37 | [0] 113.25, 38 | [1] 23.11670000000001 39 | ] 40 | } 41 | } 42 | ``` 43 | 44 | ## 配置说明 45 | 46 | GeoIP 库数据较多,如果你不需要这么多内容,可以通过 `fields` 选项指定自己所需要的。下例为全部可选内容: 47 | 48 | ``` 49 | filter { 50 | geoip { 51 | fields => ["city_name", "continent_code", "country_code2", "country_code3", "country_name", "dma_code", "ip", "latitude", "longitude", "postal_code", "region_name", "timezone"] 52 | } 53 | } 54 | ``` 55 | 56 | 需要注意的是:`geoip.location` 是 logstash 通过 `latitude` 和 `longitude` 额外生成的数据。所以,如果你是想要经纬度又不想重复数据的话,应该像下面这样做: 57 | 58 | filter { 59 | geoip { 60 | fields => ["city_name", "country_code2", "country_name", "latitude", "longitude", "region_name"] 61 | remove_field => ["[geoip][latitude]", "[geoip][longitude]"] 62 | } 63 | } 64 | ``` 65 | 66 | ## 小贴士 67 | 68 | geoip 插件的 "source" 字段可以是任一处理后的字段,比如 "client_ip",但是字段内容却需要小心!geoip 库内只存有公共网络上的 IP 信息,查询不到结果的,会直接返回 null,而 logstash 的 geoip 插件对 null 结果的处理是:**不生成对应的 geoip.字段。** 69 | 70 | 所以读者在测试时,如果使用了诸如 127.0.0.1, 172.16.0.1, 182.168.0.1, 10.0.0.1 等内网地址,会发现没有对应输出! 71 | -------------------------------------------------------------------------------- /filter/grok.md: -------------------------------------------------------------------------------- 1 | # Grok 正则捕获 2 | 3 | Grok 是 Logstash 最重要的插件。你可以在 grok 里预定义好命名正则表达式,在稍后(grok参数或者其他正则表达式里)引用它。 4 | 5 | ## 正则表达式语法 6 | 7 | 运维工程师多多少少都会一点正则。你可以在 grok 里写标准的正则,像下面这样: 8 | 9 | ``` 10 | \s+(?\d+(?:\.\d+)?)\s+ 11 | ``` 12 | 13 | *小贴士:这个正则表达式写法对于 Perl 或者 Ruby 程序员应该很熟悉了,Python 程序员可能更习惯写 `(?Ppattern)`,没办法,适应一下吧。* 14 | 15 | 现在给我们的配置文件添加第一个过滤器区段配置。配置要添加在输入和输出区段之间(logstash 执行区段的时候并不依赖于次序,不过为了自己看得方便,还是按次序书写吧): 16 | 17 | ``` 18 | input {stdin{}} 19 | filter { 20 | grok { 21 | match => { 22 | "message" => "\s+(?\d+(?:\.\d+)?)\s+" 23 | } 24 | } 25 | } 26 | output {stdout{}} 27 | ``` 28 | 29 | 运行 logstash 进程然后输入 "begin 123.456 end",你会看到类似下面这样的输出: 30 | 31 | ``` 32 | { 33 | "message" => "begin 123.456 end", 34 | "@version" => "1", 35 | "@timestamp" => "2014-08-09T11:55:38.186Z", 36 | "host" => "raochenlindeMacBook-Air.local", 37 | "request_time" => "123.456" 38 | } 39 | ``` 40 | 41 | 漂亮!不过数据类型好像不太满意……*request_time* 应该是数值而不是字符串。 42 | 43 | 我们已经提过稍后会学习用 `LogStash::Filters::Mutate` 来转换字段值类型,不过在 grok 里,其实有自己的魔法来实现这个功能! 44 | 45 | ## Grok 表达式语法 46 | 47 | Grok 支持把预定义的 *grok 表达式* 写入到文件中,官方提供的预定义 grok 表达式见:。 48 | 49 | **注意:在新版本的logstash里面,pattern目录已经为空,最后一个commit提示core patterns将会由logstash-patterns-core gem来提供,该目录可供用户存放自定义patterns** 50 | 51 | 下面是从官方文件中摘抄的最简单但是足够说明用法的示例: 52 | 53 | ``` 54 | USERNAME [a-zA-Z0-9._-]+ 55 | USER %{USERNAME} 56 | ``` 57 | 58 | **第一行,用普通的正则表达式来定义一个 grok 表达式;第二行,通过打印赋值格式,用前面定义好的 grok 表达式来定义另一个 grok 表达式。** 59 | 60 | grok 表达式的打印复制格式的完整语法是下面这样的: 61 | 62 | ``` 63 | %{PATTERN_NAME:capture_name:data_type} 64 | ``` 65 | 66 | *小贴士:data_type 目前只支持两个值:`int` 和 `float`。* 67 | 68 | 所以我们可以改进我们的配置成下面这样: 69 | 70 | ``` 71 | filter { 72 | grok { 73 | match => { 74 | "message" => "%{WORD} %{NUMBER:request_time:float} %{WORD}" 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | 重新运行进程然后可以得到如下结果: 81 | 82 | ``` 83 | { 84 | "message" => "begin 123.456 end", 85 | "@version" => "1", 86 | "@timestamp" => "2014-08-09T12:23:36.634Z", 87 | "host" => "raochenlindeMacBook-Air.local", 88 | "request_time" => 123.456 89 | } 90 | ``` 91 | 92 | 这次 *request_time* 变成数值类型了。 93 | 94 | ## 最佳实践 95 | 96 | 实际运用中,我们需要处理各种各样的日志文件,如果你都是在配置文件里各自写一行自己的表达式,就完全不可管理了。所以,我们建议是把所有的 grok 表达式统一写入到一个地方。然后用 *filter/grok* 的 `patterns_dir` 选项来指明。 97 | 98 | 如果你把 "message" 里所有的信息都 grok 到不同的字段了,数据实质上就相当于是重复存储了两份。所以你可以用 `remove_field` 参数来删除掉 *message* 字段,或者用 `overwrite` 参数来重写默认的 *message* 字段,只保留最重要的部分。 99 | 100 | 重写参数的示例如下: 101 | 102 | ``` 103 | filter { 104 | grok { 105 | patterns_dir => "/path/to/your/own/patterns" 106 | match => { 107 | "message" => "%{SYSLOGBASE} %{DATA:message}" 108 | } 109 | overwrite => ["message"] 110 | } 111 | } 112 | ``` 113 | 114 | ## 小贴士 115 | 116 | ### 多行匹配 117 | 118 | 在和 *codec/multiline* 搭配使用的时候,需要注意一个问题,grok 正则和普通正则一样,默认是不支持匹配回车换行的。就像你需要 `=~ //m` 一样也需要单独指定,具体写法是在表达式开始位置加 `(?m)` 标记。如下所示: 119 | 120 | ``` 121 | match => { 122 | "message" => "(?m)\s+(?\d+(?:\.\d+)?)\s+" 123 | } 124 | ``` 125 | 126 | ### 多项选择 127 | 128 | 有时候我们会碰上一个日志有多种可能格式的情况。这时候要写成单一正则就比较困难,或者全用 `|` 隔开又比较丑陋。这时候,logstash 的语法提供给我们一个有趣的解决方式。 129 | 130 | 文档中,都说明 logstash/filters/grok 插件的 `match` 参数应该接受的是一个 Hash 值。但是因为早期的 logstash 语法中 Hash 值也是用 `[]` 这种方式书写的,所以其实现在传递 Array 值给 `match` 参数也完全没问题。所以,我们这里其实可以传递多个正则来匹配同一个字段: 131 | 132 | ``` 133 | match => [ 134 | "message", "(?\d+(?:\.\d+)?)", 135 | "message", "%{SYSLOGBASE} %{DATA:message}", 136 | "message", "(?m)%{WORD}" 137 | ] 138 | ``` 139 | 140 | logstash 会按照这个定义次序依次尝试匹配,到匹配成功为止。虽说效果跟用 `|` 分割写个大大的正则是一样的,但是可阅读性好了很多。 141 | 142 | **最后也是最关键的,我强烈建议每个人都要使用 [Grok Debugger](http://grokdebug.herokuapp.com) 来调试自己的 grok 表达式。** 143 | 144 | ![grokdebugger](http://www.elasticsearch.org/content/uploads/2014/10/Screen-Shot-2014-10-22-at-00.37.37.png) 145 | -------------------------------------------------------------------------------- /filter/json.md: -------------------------------------------------------------------------------- 1 | # JSON 编解码 2 | 3 | 在上一章,已经讲过在 codec 中使用 JSON 编码。但是,有些日志可能是一种复合的数据结构,其中只是一部分记录是 JSON 格式的。这时候,我们依然需要在 filter 阶段,单独启用 JSON 解码插件。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | filter { 9 | json { 10 | source => "message" 11 | target => "jsoncontent" 12 | } 13 | } 14 | ``` 15 | 16 | ## 运行结果 17 | 18 | ``` 19 | { 20 | "@version": "1", 21 | "@timestamp": "2014-11-18T08:11:33.000Z", 22 | "host": "web121.mweibo.tc.sinanode.com", 23 | "message": "{\"uid\":3081609001,\"type\":\"signal\"}", 24 | "jsoncontent": { 25 | "uid": 3081609001, 26 | "type": "signal" 27 | } 28 | } 29 | ``` 30 | 31 | ## 小贴士 32 | 33 | 如果不打算使用多层结构的话,删掉 `target` 配置即可。新的结果如下: 34 | 35 | ``` 36 | { 37 | "@version": "1", 38 | "@timestamp": "2014-11-18T08:11:33.000Z", 39 | "host": "web121.mweibo.tc.sinanode.com", 40 | "message": "{\"uid\":3081609001,\"type\":\"signal\"}", 41 | "uid": 3081609001, 42 | "type": "signal" 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /filter/kv.md: -------------------------------------------------------------------------------- 1 | # Key-Value 切分 2 | 3 | 在很多情况下,日志内容本身都是一个类似于 key-value 的格式,但是格式具体的样式却是多种多样的。logstash 提供 `filters/kv` 插件,帮助处理不同样式的 key-value 日志,变成实际的 LogStash::Event 数据。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | filter { 9 | ruby { 10 | init => "@kname = ['method','uri','verb']" 11 | code => "event.append(Hash[@kname.zip(event['request'].split(' '))])" 12 | } 13 | if [uri] { 14 | ruby { 15 | init => "@kname = ['url_path','url_args']" 16 | code => "event.append(Hash[@kname.zip(event['uri'].split('?'))])" 17 | } 18 | kv { 19 | prefix => "url_" 20 | source => "url_args" 21 | field_split => "&" 22 | remove_field => [ "url_args", "uri", "request" ] 23 | } 24 | } 25 | } 26 | ``` 27 | 28 | ## 解释 29 | 30 | Nginx 访问日志中的 `$request`,通过这段配置,可以详细切分成 `method`, `url_path`, `verb`, `url_a`, `url_b` ... 31 | 32 | 33 | -------------------------------------------------------------------------------- /filter/metrics.md: -------------------------------------------------------------------------------- 1 | # 数值统计(Metrics) 2 | 3 | *filters/metrics* 插件是使用 Ruby 的 *Metriks* 模块来实现在内存里实时的计数和采样分析。该模块支持两个类型的数值分析:meter 和 timer。下面分别举例说明: 4 | 5 | ## Meter 示例(速率阈值检测) 6 | 7 | web 访问日志的异常状态码频率是运维人员会非常关心的一个数据。通常我们的做法,是通过 logstash 或者其他日志分析脚本,把计数发送到 rrdtool 或者 graphite 里面。然后再通过 check_graphite 脚本之类的东西来检查异常并报警。 8 | 9 | 事实上这个事情可以直接在 logstash 内部就完成。比如如果最近一分钟 504 请求的个数超过 100 个就报警: 10 | 11 | ``` 12 | filter { 13 | metrics { 14 | meter => "error.%{status}" 15 | add_tag => "metric" 16 | ignore_older_than => 10 17 | } 18 | if "metric" in [tags] { 19 | ruby { 20 | code => "event.cancel if event['error.504.rate_1m'] * 60 < 100" 21 | } 22 | } 23 | } 24 | output { 25 | if "metric" in [tags] { 26 | exec { 27 | command => "echo \"Out of threshold: %{error.504.rate_1m}\"" 28 | } 29 | } 30 | } 31 | ``` 32 | 33 | 这里需要注意 `*60` 的含义。 34 | 35 | metriks 模块生成的 *rate_1m/5m/15m* 意思是:最近 1,5,15 分钟的**每秒**速率! 36 | 37 | 38 | ## Timer 示例(box and whisker 异常检测) 39 | 40 | 官版的 *filters/metrics* 插件只适用于 metric 事件的检查。由插件生成的新事件内部不存有来自 input 区段的实际数据信息。所以,要完成我们的百分比分布箱体检测,需要首先对代码稍微做几行变动,即在 metric 的 timer 事件里加一个属性,存储最近一个实际事件的数值: 41 | 42 | 然后我们就可以用如下配置来探测异常数据了: 43 | 44 | ``` 45 | filter { 46 | metrics { 47 | timer => {"rt" => "%{request_time}"} 48 | percentiles => [25, 75] 49 | add_tag => "percentile" 50 | } 51 | if "percentile" in [tags] { 52 | ruby { 53 | code => "l=event['rt.p75']-event['rt.p25'];event['rt.low']=event['rt.p25']-l;event['rt.high']=event['rt.p75']+l" 54 | } 55 | } 56 | } 57 | output { 58 | if "percentile" in [tags] and ([rt.last] > [rt.high] or [rt.last] < [rt.low]) { 59 | exec { 60 | command => "echo \"Anomaly: %{rt.last}\"" 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | *小贴士:有关 box and shisker plot 内容和重要性,参见《数据之魅》一书。* 67 | -------------------------------------------------------------------------------- /filter/mutate.md: -------------------------------------------------------------------------------- 1 | # 数据修改(Mutate) 2 | 3 | *filters/mutate* 插件是 Logstash 另一个重要插件。它提供了丰富的基础类型数据处理能力。包括类型转换,字符串处理和字段处理等。 4 | 5 | ## 类型转换 6 | 7 | 类型转换是 *filters/mutate* 插件最初诞生时的唯一功能。其应用场景在之前 [Codec/JSON](../codec/json.md) 小节已经提到。 8 | 9 | 可以设置的转换类型包括:"integer","float" 和 "string"。示例如下: 10 | 11 | ``` 12 | filter { 13 | mutate { 14 | convert => ["request_time", "float"] 15 | } 16 | } 17 | ``` 18 | 19 | **注意:mutate 除了转换简单的字符值,还支持对数组类型的字段进行转换,即将 `["1","2"]` 转换成 `[1,2]`。但不支持对哈希类型的字段做类似处理。有这方面需求的可以采用稍后讲述的 filters/ruby 插件完成。** 20 | 21 | ## 字符串处理 22 | 23 | * gsub 24 | 25 | 仅对字符串类型字段有效 26 | 27 | ``` 28 | gsub => ["urlparams", "[\\?#]", "_"] 29 | ``` 30 | 31 | * split 32 | 33 | ``` 34 | filter { 35 | mutate { 36 | split => ["message", "|"] 37 | } 38 | } 39 | ``` 40 | 41 | 随意输入一串以`|`分割的字符,比如 "123|321|adfd|dfjld*=123",可以看到如下输出: 42 | 43 | ```ruby 44 | { 45 | "message" => [ 46 | [0] "123", 47 | [1] "321", 48 | [2] "adfd", 49 | [3] "dfjld*=123" 50 | ], 51 | "@version" => "1", 52 | "@timestamp" => "2014-08-20T15:58:23.120Z", 53 | "host" => "raochenlindeMacBook-Air.local" 54 | } 55 | ``` 56 | 57 | * join 58 | 59 | 仅对数组类型字段有效 60 | 61 | 我们在之前已经用 `split` 割切的基础再 `join` 回去。配置改成: 62 | 63 | ``` 64 | filter { 65 | mutate { 66 | split => ["message", "|"] 67 | } 68 | mutate { 69 | join => ["message", ","] 70 | } 71 | } 72 | ``` 73 | 74 | filter 区段之内,是顺序执行的。所以我们最后看到的输出结果是: 75 | 76 | ```ruby 77 | { 78 | "message" => "123,321,adfd,dfjld*=123", 79 | "@version" => "1", 80 | "@timestamp" => "2014-08-20T16:01:33.972Z", 81 | "host" => "raochenlindeMacBook-Air.local" 82 | } 83 | ``` 84 | 85 | * merge 86 | 87 | 合并两个数组或者哈希字段。依然在之前 split 的基础上继续: 88 | 89 | ``` 90 | filter { 91 | mutate { 92 | split => ["message", "|"] 93 | } 94 | mutate { 95 | merge => ["message", "message"] 96 | } 97 | } 98 | ``` 99 | 100 | 我们会看到输出: 101 | 102 | ```ruby 103 | { 104 | "message" => [ 105 | [0] "123", 106 | [1] "321", 107 | [2] "adfd", 108 | [3] "dfjld*=123", 109 | [4] "123", 110 | [5] "321", 111 | [6] "adfd", 112 | [7] "dfjld*=123" 113 | ], 114 | "@version" => "1", 115 | "@timestamp" => "2014-08-20T16:05:53.711Z", 116 | "host" => "raochenlindeMacBook-Air.local" 117 | } 118 | ``` 119 | 120 | 如果 src 字段是字符串,会自动先转换成一个单元素的数组再合并。把上一示例中的来源字段改成 "host": 121 | 122 | ``` 123 | filter { 124 | mutate { 125 | split => ["message", "|"] 126 | } 127 | mutate { 128 | merge => ["message", "host"] 129 | } 130 | } 131 | ``` 132 | 133 | 结果变成: 134 | 135 | ```ruby 136 | { 137 | "message" => [ 138 | [0] "123", 139 | [1] "321", 140 | [2] "adfd", 141 | [3] "dfjld*=123", 142 | [4] "raochenlindeMacBook-Air.local" 143 | ], 144 | "@version" => "1", 145 | "@timestamp" => "2014-08-20T16:07:53.533Z", 146 | "host" => [ 147 | [0] "raochenlindeMacBook-Air.local" 148 | ] 149 | } 150 | ``` 151 | 152 | 看,目的字段 "message" 确实多了一个元素,但是来源字段 "host" 本身也由字符串类型变成数组类型了! 153 | 154 | 下面你猜,如果来源位置写的不是字段名而是直接一个字符串,会产生什么奇特的效果呢? 155 | 156 | * strip 157 | * lowercase 158 | * uppercase 159 | 160 | 161 | ## 字段处理 162 | 163 | * rename 164 | 165 | 重命名某个字段,如果目的字段已经存在,会被覆盖掉: 166 | 167 | ``` 168 | filter { 169 | mutate { 170 | rename => ["syslog_host", "host"] 171 | } 172 | } 173 | ``` 174 | 175 | * update 176 | 177 | 更新某个字段的内容。如果字段不存在,不会新建。 178 | 179 | 180 | * replace 181 | 182 | 作用和 update 类似,但是当字段不存在的时候,它会起到 `add_field` 参数一样的效果,自动添加新的字段。 183 | 184 | 185 | ## 执行次序 186 | 187 | 需要注意的是,filter/mutate 内部是有执行次序的。其次序如下: 188 | 189 | ``` 190 | rename(event) if @rename 191 | update(event) if @update 192 | replace(event) if @replace 193 | convert(event) if @convert 194 | gsub(event) if @gsub 195 | uppercase(event) if @uppercase 196 | lowercase(event) if @lowercase 197 | strip(event) if @strip 198 | remove(event) if @remove 199 | split(event) if @split 200 | join(event) if @join 201 | merge(event) if @merge 202 | 203 | filter_matched(event) 204 | ``` 205 | 206 | 而 `filter_matched` 这个 filters/base.rb 里继承的方法也是有次序的。 207 | 208 | ``` 209 | @add_field.each do |field, value| 210 | end 211 | @remove_field.each do |field| 212 | end 213 | @add_tag.each do |tag| 214 | end 215 | @remove_tag.each do |tag| 216 | end 217 | ``` 218 | -------------------------------------------------------------------------------- /filter/ruby.md: -------------------------------------------------------------------------------- 1 | # 随心所欲的 Ruby 处理 2 | 3 | 如果你稍微懂那么一点点 Ruby 语法的话,*filters/ruby* 插件将会是一个非常有用的工具。 4 | 5 | 比如你需要稍微修改一下 `LogStash::Event` 对象,但是又不打算为此写一个完整的插件,用 *filters/ruby* 插件绝对感觉良好。 6 | 7 | ## 配置示例 8 | 9 | ``` 10 | filter { 11 | ruby { 12 | init => "@kname = ['client','servername','url','status','time','size','upstream','upstreamstatus','upstreamtime','referer','xff','useragent']" 13 | code => "event.append(Hash[@kname.zip(event['message'].split('|'))])" 14 | } 15 | } 16 | ``` 17 | 18 | 官网示例是一个比较有趣但是没啥大用的做法 —— 随机取消 90% 的事件。 19 | 20 | 所以上面我们给出了一个有用而且强大的实例。 21 | 22 | ## 解释 23 | 24 | 通常我们都是用 *filters/grok* 插件来捕获字段的,但是正则耗费大量的 CPU 资源,很容易成为 Logstash 进程的瓶颈。 25 | 26 | 而实际上,很多流经 Logstash 的数据都是有自己预定义的特殊分隔符的,我们可以很简单的直接切割成多个字段。 27 | 28 | *filters/mutate* 插件里的 "split" 选项只能切成数组,后续很不方便使用和识别。而在 *filters/ruby* 里,我们可以通过 "init" 参数预定义好由每个新字段的名字组成的数组,然后在 "code" 参数指定的 Ruby 语句里通过两个数组的 zip 操作生成一个哈希并添加进数组里。短短一行 Ruby 代码,可以减少 50% 以上的 CPU 使用率。 29 | 30 | *filters/ruby* 插件用途远不止这一点,下一节你还会继续见到它的身影。 31 | 32 | ## 更多实例 33 | 34 | *2014 年 09 年 23 日新增* 35 | 36 | ``` 37 | filter{ 38 | date { 39 | match => ["datetime" , "UNIX"] 40 | } 41 | ruby { 42 | code => "event.cancel if 5 * 24 * 3600 < (event['@timestamp']-::Time.now).abs" 43 | } 44 | } 45 | ``` 46 | 47 | 在实际运用中,我们几乎肯定会碰到出乎意料的输入数据。这都有可能导致 Elasticsearch 集群出现问题。 48 | 49 | 当数据格式发生变化,比如 UNIX 时间格式变成 UNIX_MS 时间格式,会导致 logstash 疯狂创建新索引,集群崩溃。 50 | 51 | 或者误输入过老的数据时,因为一般我们会 close 几天之前的索引以节省内存,必要时再打开。而直接尝试把数据写入被关闭的索引会导致内存问题。 52 | 53 | 这时候我们就需要提前校验数据的合法性。上面配置,就是用于过滤掉时间范围与当前时间差距太大的非法数据的。 54 | -------------------------------------------------------------------------------- /filter/split.md: -------------------------------------------------------------------------------- 1 | # split 拆分事件 2 | 3 | 上一章我们通过 multiline 插件将多行数据合并进一个事件里,那么反过来,也可以把一行数据,拆分成多个事件。这就是 split 插件。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | filter { 9 | split { 10 | field => "message" 11 | terminator => "#" 12 | } 13 | } 14 | ``` 15 | 16 | ## 运行结果 17 | 18 | 这个测试中,我们在 intputs/stdin 的终端中输入一行数据:"test1#test2",结果看到输出两个事件: 19 | 20 | ``` 21 | { 22 | "@version": "1", 23 | "@timestamp": "2014-11-18T08:11:33.000Z", 24 | "host": "web121.mweibo.tc.sinanode.com", 25 | "message": "test1" 26 | } 27 | { 28 | "@version": "1", 29 | "@timestamp": "2014-11-18T08:11:33.000Z", 30 | "host": "web121.mweibo.tc.sinanode.com", 31 | "message": "test2" 32 | } 33 | ``` 34 | 35 | ## 重要提示 36 | 37 | split 插件中使用的是 yield 功能,其结果是 split 出来的新事件,会直接结束其在 filter 阶段的历程,也就是说写在 split 后面的其他 filter 插件都不起作用,进入到 output 阶段。所以,一定要保证 **split 配置写在全部 filter 配置的最后**。 38 | 39 | 使用了类似功能的还有 clone 插件。 40 | 41 | *注:从 logstash-1.5.0beta1 版本以后修复该问题。* 42 | -------------------------------------------------------------------------------- /filter/useragent.md: -------------------------------------------------------------------------------- 1 | # UserAgent 匹配归类 2 | 3 | ## 配置示例 4 | 5 | ``` 6 | filter { 7 | useragent { 8 | target => "ua" 9 | source => "useragent" 10 | } 11 | } 12 | ``` 13 | -------------------------------------------------------------------------------- /get_start/README.md: -------------------------------------------------------------------------------- 1 | # 基础知识 2 | 3 | *什么是 Logstash?为什么要用 Logstash?怎么用 Logstash?* 4 | 5 | 本章正是来回答这个问题,或许不完整,但是足够讲述一些基础概念。跟着我们安装章节一步步来,你就可以成功的运行起来自己的第一个 logstash 了。 6 | 7 | 我可能不会立刻来展示 logstash 配置细节或者运用场景。我认为基础原理和语法的介绍应该更加重要,这些知识未来对你的帮助绝对更大! 8 | 9 | 所以,认真阅读他们吧! 10 | 11 | -------------------------------------------------------------------------------- /get_start/daemon.md: -------------------------------------------------------------------------------- 1 | # 长期运行 2 | 3 | 完成上一节的初次运行后,你肯定会发现一点:一旦你按下 Ctrl+C,停下标准输入输出,logstash 进程也就随之停止了。作为一个肯定要长期运行的程序,应该怎么处理呢? 4 | 5 | *本章节问题对于一个运维来说应该属于基础知识,鉴于 ELK 用户很多其实不是运维,添加这段内容。* 6 | 7 | 办法有很多种,下面介绍四种最常用的办法: 8 | 9 | ## 标准的 service 方式 10 | 11 | 采用 RPM、DEB 发行包安装的读者,推荐采用这种方式。发行包内,都自带有 sysV 或者 systemd 风格的启动程序/配置,你只需要直接使用即可。 12 | 13 | 以 RPM 为例,`/etc/init.d/logstash` 脚本中,会加载 `/etc/init.d/functions` 库文件,利用其中的 `daemon` 函数,将 logstash 进程作为后台程序运行。 14 | 15 | 所以,你只需把自己写好的配置文件,统一放在 `/etc/logstash/` 目录下(注意目录下所有配置文件都应该是 **.conf** 结尾,且不能有其他文本文件存在。因为 logstash agent 启动的时候是读取全文件夹的),然后运行 `service logstash start` 命令即可。 16 | 17 | ## 最基础的 nohup 方式 18 | 19 | 这是最简单的方式,也是 linux 新手们很容易搞混淆的一个经典问题: 20 | 21 | ``` 22 | command 23 | command > /dev/null 24 | command > /dev/null 2>&1 25 | command & 26 | command > /dev/null & 27 | command > /dev/null 2>&1 & 28 | command &> /dev/null 29 | nohup command &> /dev/null 30 | ``` 31 | 32 | 请回答以上命令的异同…… 33 | 34 | 具体不一一解释了。直接说答案,想要维持一个长期后台运行的 logstash,你需要同时在命令前面加 `nohup`,后面加 `&`。 35 | 36 | ## 更优雅的 SCREEN 方式 37 | 38 | screen 算是 linux 运维一个中高级技巧。通过 screen 命令创建的环境下运行的终端命令,其父进程不是 sshd 登录会话,而是 screen 。这样就可以即避免用户退出进程消失的问题,又随时能重新接管回终端继续操作。 39 | 40 | 创建独立的 screen 命令如下: 41 | 42 | ``` 43 | screen -dmS elkscreen_1 44 | ``` 45 | 46 | 接管连入创建的 `elkscreen_1` 命令如下: 47 | 48 | ``` 49 | screen -r elkscreen_1 50 | ``` 51 | 52 | 然后你可以看到一个一模一样的终端,运行 logstash 之后,不要按 Ctrl+C,而是按 Ctrl+A+D 键,断开环境。想重新接管,依然 `screen -r elkscreen_1` 即可。 53 | 54 | 如果创建了多个 screen,查看列表命令如下: 55 | 56 | ``` 57 | screen -list 58 | ``` 59 | 60 | ## 最推荐的 daemontools 方式 61 | 62 | 不管是 nohup 还是 screen,都不是可以很方便管理的方式,在运维管理一个 ELK 集群的时候,必须寻找一种尽可能简洁的办法。所以,对于需要长期后台运行的大量程序(注意大量,如果就一个进程,还是学习一下怎么写 init 脚本吧),推荐大家使用一款 daemontools 工具。 63 | 64 | daemontools 是一个软件名称,不过配置略复杂。所以这里我其实是用其名称来指代整个同类产品,包括但不限于 python 实现的 supervisord,perl 实现的 ubic,ruby 实现的 god 等。 65 | 66 | 以 supervisord 为例,因为这个出来的比较早,可以直接通过 EPEL 仓库安装。 67 | 68 | ``` 69 | yum -y install supervisord --enablerepo=epel 70 | ``` 71 | 72 | 在 `/etc/supervisord.conf` 配置文件里添加内容,定义你要启动的程序: 73 | 74 | ``` 75 | [program:elkpro_1] 76 | environment=LS_HEAP_SIZE=5000m 77 | directory=/opt/logstash 78 | command=/opt/logstash/bin/logstash -f /etc/logstash/pro1.conf --pluginpath /opt/logstash/plugins/ -w 10 -l /var/log/logstash/pro1.log 79 | [program:elkpro_2] 80 | environment=LS_HEAP_SIZE=5000m 81 | directory=/opt/logstash 82 | command=/opt/logstash/bin/logstash -f /etc/logstash/pro2.conf --pluginpath /opt/logstash/plugins/ -w 10 -l /var/log/logstash/pro2.log 83 | ``` 84 | 85 | 然后启动 `service supervisord start` 即可。 86 | 87 | logstash 会以 supervisord 子进程的身份运行,你还可以使用 `supervisorctl` 命令,单独控制一系列 logstash 子进程中某一个进程的启停操作: 88 | 89 | `supervisorctl stop elkpro_2` 90 | 91 | -------------------------------------------------------------------------------- /get_start/full_config.md: -------------------------------------------------------------------------------- 1 | # 配置语法 2 | 3 | Logstash 社区通常习惯用 *shipper*,*broker* 和 *indexer* 来描述数据流中不同进程各自的角色。如下图: 4 | 5 | ![logstash arch](../images/logstash-arch.jpg) 6 | 7 | 不过我见过很多运用场景里都没有用 logstash 作为 *shipper*,或者说没有用 elasticsearch 作为数据存储也就是说也没有 *indexer*。所以,我们其实不需要这些概念。只需要学好怎么使用和配置 logstash 进程,然后把它运用到你的日志管理架构中最合适它的位置就够了。 8 | 9 | ## 语法 10 | 11 | Logstash 设计了自己的 DSL —— 有点像 Puppet 的 DSL,或许因为都是用 Ruby 语言写的吧 —— 包括有区域,注释,数据类型(布尔值,字符串,数值,数组,哈希),条件判断,字段引用等。 12 | 13 | ### 区段(section) 14 | 15 | Logstash 用 `{}` 来定义区域。区域内可以包括插件区域定义,你可以在一个区域内定义多个插件。插件区域内则可以定义键值对设置。示例如下: 16 | 17 | ``` 18 | input { 19 | stdin {} 20 | syslog {} 21 | } 22 | ``` 23 | 24 | ### 数据类型 25 | 26 | Logstash 支持少量的数据值类型: 27 | 28 | * bool 29 | 30 | ``` 31 | debug => true 32 | ``` 33 | 34 | * string 35 | 36 | ``` 37 | host => "hostname" 38 | ``` 39 | 40 | * number 41 | 42 | ``` 43 | port => 514 44 | ``` 45 | 46 | * array 47 | 48 | ``` 49 | match => ["datetime", "UNIX", "ISO8601"] 50 | ``` 51 | 52 | * hash 53 | 54 | ``` 55 | options => { 56 | key1 => "value1", 57 | key2 => "value2" 58 | } 59 | ``` 60 | 61 | **注意**:如果你用的版本低于 1.2.0,*哈希*的语法跟*数组*是一样的,像下面这样写: 62 | 63 | ``` 64 | match => [ "field1", "pattern1", "field2", "pattern2" ] 65 | ``` 66 | 67 | ### 字段引用(field reference) 68 | 69 | 字段是 `Logstash::Event` 对象的属性。我们之前提过事件就像一个哈希一样,所以你可以想象字段就像一个键值对。 70 | 71 | *小贴士:我们叫它字段,因为 Elasticsearch 里是这么叫的。* 72 | 73 | 如果你想在 Logstash 配置中使用字段的值,只需要把字段的名字写在中括号 `[]` 里就行了,这就叫**字段引用**。 74 | 75 | 对于 **嵌套字段**(也就是多维哈希表,或者叫哈希的哈希),每层的字段名都写在 `[]` 里就可以了。比如,你可以从 geoip 里这样获取 *longitude* 值(是的,这是个笨办法,实际上有单独的字段专门存这个数据的): 76 | 77 | ``` 78 | [geoip][location][0] 79 | ``` 80 | 81 | *小贴士:logstash 的数组也支持倒序下标,即 `[geoip][location][-1]` 可以获取数组最后一个元素的值。* 82 | 83 | Logstash 还支持变量内插,在字符串里使用字段引用的方法是这样: 84 | 85 | ``` 86 | "the longitude is %{[geoip][location][0]}" 87 | ``` 88 | 89 | ### 条件判断(condition) 90 | 91 | Logstash从 1.3.0 版开始支持条件判断和表达式。 92 | 93 | 表达式支持下面这些操作符: 94 | 95 | * equality, etc: ==, !=, <, >, <=, >= 96 | * regexp: =~, !~ 97 | * inclusion: in, not in 98 | * boolean: and, or, nand, xor 99 | * unary: !() 100 | 101 | 通常来说,你都会在表达式里用到字段引用。比如: 102 | 103 | ``` 104 | if "_grokparsefailure" not in [tags] { 105 | } else if [status] !~ /^2\d\d/ and [url] == "/noc.gif" { 106 | } else { 107 | } 108 | ``` 109 | 110 | ## 命令行参数 111 | 112 | Logstash 提供了一个 shell 脚本叫 `logstash` 方便快速运行。它支持一下参数: 113 | 114 | * -e 115 | 116 | 意即*执行*。我们在 "Hello World" 的时候已经用过这个参数了。事实上你可以不写任何具体配置,直接运行 `bin/logstash -e ''` 达到相同效果。这个参数的默认值是下面这样: 117 | 118 | ``` 119 | input { 120 | stdin { } 121 | } 122 | output { 123 | stdout { } 124 | } 125 | ``` 126 | 127 | * --config 或 -f 128 | 129 | 意即*文件*。真实运用中,我们会写很长的配置,甚至可能超过 shell 所能支持的 1024 个字符长度。所以我们必把配置固化到文件里,然后通过 `bin/logstash -f agent.conf` 这样的形式来运行。 130 | 131 | 此外,logstash 还提供一个方便我们规划和书写配置的小功能。你可以直接用 `bin/logstash -f /etc/logstash.d/` 来运行。logstash 会自动读取 `/etc/logstash.d/` 目录下所有的文本文件,然后在自己内存里拼接成一个完整的大配置文件,再去执行。 132 | 133 | * --configtest 或 -t 134 | 135 | 意即*测试*。用来测试 Logstash 读取到的配置文件语法是否能正常解析。Logstash 配置语法是用 grammar.treetop 定义的。尤其是使用了上一条提到的读取目录方式的读者,尤其要提前测试。 136 | 137 | * --log 或 -l 138 | 139 | 意即*日志*。Logstash 默认输出日志到标准错误。生产环境下你可以通过 `bin/logstash -l logs/logstash.log` 命令来统一存储日志。 140 | 141 | * --filterworkers 或 -w 142 | 143 | 意即*工作线程*。Logstash 会运行多个线程。你可以用 `bin/logstash -w 5` 这样的方式强制 Logstash 为**过滤**插件运行 5 个线程。 144 | 145 | *注意:Logstash目前还不支持输入插件的多线程。而输出插件的多线程需要在配置内部设置,这个命令行参数只是用来设置过滤插件的!* 146 | 147 | **提示:Logstash 目前不支持对过滤器线程的监测管理。如果 filterworker 挂掉,Logstash 会处于一个无 filter 的僵死状态。这种情况在使用 filter/ruby 自己写代码时非常需要注意,很容易碰上 `NoMethodError: undefined method '*' for nil:NilClass` 错误。需要妥善处理,提前判断。** 148 | 149 | * --pluginpath 或 -P 150 | 151 | 可以写自己的插件,然后用 `bin/logstash --pluginpath /path/to/own/plugins` 加载它们。 152 | 153 | * --verbose 154 | 155 | 输出一定的调试日志。 156 | 157 | *小贴士:如果你使用的 Logstash 版本低于 1.3.0,你只能用 `bin/logstash -v` 来代替。* 158 | 159 | * --debug 160 | 161 | 输出更多的调试日志。 162 | 163 | *小贴士:如果你使用的 Logstash 版本低于 1.3.0,你只能用 `bin/logstash -vv` 来代替。* 164 | -------------------------------------------------------------------------------- /get_start/hello_world.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | 和绝大多数 IT 技术介绍一样,我们以一个输出 "hello world" 的形式开始我们的 logstash 学习。 4 | 5 | ## 运行 6 | 7 | 在终端中,像下面这样运行命令来启动 Logstash 进程: 8 | 9 | ``` 10 | # bin/logstash -e 'input{stdin{}}output{stdout{codec=>rubydebug}}' 11 | ``` 12 | 13 | 然后你会发现终端在等待你的输入。没问题,敲入 **Hello World**,回车,然后看看会返回什么结果! 14 | 15 | ## 结果 16 | 17 | ```ruby 18 | { 19 | "message" => "Hello World", 20 | "@version" => "1", 21 | "@timestamp" => "2014-08-07T10:30:59.937Z", 22 | "host" => "raochenlindeMacBook-Air.local", 23 | } 24 | ``` 25 | 26 | 没错!你搞定了!这就是全部你要做的。 27 | 28 | ## 解释 29 | 30 | 每位系统管理员都肯定写过很多类似这样的命令:`cat randdata | awk '{print $2}' | sort | uniq -c | tee sortdata`。这个管道符 `|` 可以算是 Linux 世界最伟大的发明之一(另一个是“一切皆文件”)。 31 | 32 | Logstash 就像管道符一样! 33 | 34 | 你**输入**(就像命令行的 `cat` )数据,然后处理**过滤**(就像 `awk` 或者 `uniq` 之类)数据,最后**输出**(就像 `tee` )到其他地方。 35 | 36 | *当然实际上,Logstash 是用不同的线程来实现这些的。如果你运行 `top` 命令然后按下 `H` 键,你就可以看到下面这样的输出:* 37 | 38 | ``` 39 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 40 | 21401 root 16 0 1249m 303m 10m S 18.6 0.2 866:25.46 |worker 41 | 21467 root 15 0 1249m 303m 10m S 3.7 0.2 129:25.59 >elasticsearch. 42 | 21468 root 15 0 1249m 303m 10m S 3.7 0.2 128:53.39 >elasticsearch. 43 | 21400 root 15 0 1249m 303m 10m S 2.7 0.2 108:35.80 output 45 | 21470 root 15 0 1249m 303m 10m S 1.0 0.2 56:24.24 >elasticsearch. 46 | ``` 47 | 48 | *小贴士:logstash 很温馨的给每个线程都取了名字,输入的叫xx,过滤的叫|xx* 49 | 50 | 数据在线程之间以 **事件** 的形式流传。不要叫*行*,因为 logstash 可以处理[多行](../codec/multiline.md)事件。 51 | 52 | Logstash 会给事件添加一些额外信息。最重要的就是 **@timestamp**,用来标记事件的发生时间。因为这个字段涉及到 Logstash 的内部流转,所以必须是一个 joda 对象,如果你尝试自己给一个字符串字段重命名为 `@timestamp` 的话,Logstash 会直接报错。所以,**请使用 [filters/date 插件](../filter/date.md) 来管理这个特殊字段**。 53 | 54 | 此外,大多数时候,还可以见到另外几个: 55 | 56 | 1. **host** 标记事件发生在哪里。 57 | 2. **type** 标记事件的唯一类型。 58 | 3. **tags** 标记事件的某方面属性。这是一个数组,一个事件可以有多个标签。 59 | 60 | 你可以随意给事件添加字段或者从事件里删除字段。事实上事件就是一个 Ruby 对象,或者更简单的理解为就是一个哈希也行。 61 | 62 | *小贴士:每个 logstash 过滤插件,都会有四个方法叫 `add_tag`, `remove_tag`, `add_field` 和 `remove_field`。它们在插件过滤匹配成功时生效。* 63 | 64 | ## 推荐阅读 65 | 66 | * [《the life of an event》官网文档](http://logstash.net/docs/1.4.2/life-of-an-event) 67 | * [《life of a logstash event》Elastic{ON} 上的演讲](https://speakerdeck.com/elastic/life-of-a-logstash-event) 68 | -------------------------------------------------------------------------------- /get_start/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | ## 下载 4 | 5 | 目前,Logstash 分为两个包:核心包和社区贡献包。你可以从 下载这两个包的源代码或者二进制版本。 6 | 7 | * 源代码方式 8 | 9 | ``` 10 | wget https://download.elasticsearch.org/logstash/logstash/logstash-1.4.2.tar.gz 11 | wget https://download.elasticsearch.org/logstash/logstash/logstash-contrib-1.4.2.tar.gz 12 | ``` 13 | 14 | * Debian 平台 15 | 16 | ``` 17 | wget https://download.elasticsearch.org/logstash/logstash/packages/debian/logstash_1.4.2-1-2c0f5a1_all.deb 18 | wget https://download.elasticsearch.org/logstash/logstash/packages/debian/logstash-contrib_1.4.2-1-efd53ef_all.deb 19 | ``` 20 | 21 | * Redhat 平台 22 | 23 | ``` 24 | wget https://download.elasticsearch.org/logstash/logstash/packages/centos/logstash-1.4.2-1_2c0f5a1.noarch.rpm 25 | https://download.elasticsearch.org/logstash/logstash/packages/centos/logstash-contrib-1.4.2-1_efd53ef.noarch.rpm 26 | ``` 27 | 28 | ## 安装 29 | 30 | 上面这些包,你可能更偏向使用 `rpm`,`dpkg` 等软件包管理工具来安装 Logstash,开发者在软件包里预定义了一些依赖。比如,`logstash-1.4.2-1_2c0f5a.narch` 就依赖于 `jre` 包。 31 | 32 | 另外,软件包里还包含有一些很有用的脚本程序,比如 `/etc/init.d/logstash`。 33 | 34 | 如果你必须得在一些很老的操作系统上运行 Logstash,那你只能用源代码包部署了,记住要自己提前安装好 Java: 35 | 36 | ``` 37 | yum install openjdk-jre 38 | export JAVA_HOME=/usr/java 39 | tar zxvf logstash-1.4.2.tar.gz 40 | ``` 41 | 42 | ## 最佳实践 43 | 44 | 但是真正的建议是:如果可以,请用 Elasticsearch 官方仓库来直接安装 Logstash! 45 | 46 | ### Debian 平台 47 | 48 | ```bash 49 | wget -O - http://packages.elasticsearch.org/GPG-KEY-elasticsearch | apt-key add - 50 | cat >> /etc/apt/sources.list < /etc/yum.repos.d/logstash.repo 52 | Interface "eth0" 53 | IgnoreSelected false 54 | 55 | 56 | ## logstash 的 IP 地址和 collectd 的数据接收端口号 57 | 58 | 59 | ``` 60 | 61 | ##logstash的配置 62 | 63 | 以下配置实现通过 logstash 监听 `25826` 端口,接收从 collectd 发送过来的各项检测数据: 64 | 65 | ### 示例一: 66 | 67 | ``` 68 | input { 69 | collectd { 70 | port => 25826 ## 端口号与发送端对应 71 | type => collectd 72 | } 73 | ``` 74 | 75 | ### 示例二:(推荐) 76 | 77 | ``` 78 | udp { 79 | port => 25826 80 | buffer_size => 1452 81 | workers => 3 # Default is 2 82 | queue_size => 30000 # Default is 2000 83 | codec => collectd { } 84 | type => "collectd" 85 | } 86 | ``` 87 | 88 | ## 运行结果 89 | 90 | 下面是简单的一个输出结果: 91 | 92 | ``` 93 | { 94 | "_index": "logstash-2014.12.11", 95 | "_type": "collectd", 96 | "_id": "dS6vVz4aRtK5xS86kwjZnw", 97 | "_score": null, 98 | "_source": { 99 | "host": "host.example.com", 100 | "@timestamp": "2014-12-11T06:28:52.118Z", 101 | "plugin": "interface", 102 | "plugin_instance": "eth0", 103 | "collectd_type": "if_packets", 104 | "rx": 19147144, 105 | "tx": 3608629, 106 | "@version": "1", 107 | "type": "collectd", 108 | "tags": [ 109 | "_grokparsefailure" 110 | ] 111 | }, 112 | "sort": [ 113 | 1418279332118 114 | ] 115 | } 116 | ``` 117 | 118 | 119 | ##参考资料 120 | 121 | * collectd支持收集的数据类型: 122 | 123 | 124 | * collectd收集各数据类型的配置参考资料: 125 | 126 | 127 | * collectd简单配置文件示例: 128 | 129 | -------------------------------------------------------------------------------- /input/file.md: -------------------------------------------------------------------------------- 1 | # 读取文件(File) 2 | 3 | 分析网站访问日志应该是一个运维工程师最常见的工作了。所以我们先学习一下怎么用 logstash 来处理日志文件。 4 | 5 | Logstash 使用一个名叫 *FileWatch* 的 Ruby Gem 库来监听文件变化。这个库支持 glob 展开文件路径,而且会记录一个叫 *.sincedb* 的数据库文件来跟踪被监听的日志文件的当前读取位置。所以,不要担心 logstash 会漏过你的数据。 6 | 7 | *sincedb 文件中记录了每个被监听的文件的 inode, major number, minor number 和 pos。* 8 | 9 | ## 配置示例 10 | 11 | ``` 12 | input 13 | file { 14 | path => ["/var/log/*.log", "/var/log/message"] 15 | type => "system" 16 | start_position => "beginning" 17 | } 18 | } 19 | ``` 20 | 21 | ## 解释 22 | 23 | 有一些比较有用的配置项,可以用来指定 *FileWatch* 库的行为: 24 | 25 | * discover_interval 26 | 27 | logstash 每隔多久去检查一次被监听的 `path` 下是否有新文件。默认值是 15 秒。 28 | 29 | * exclude 30 | 31 | 不想被监听的文件可以排除出去,这里跟 `path` 一样支持 glob 展开。 32 | 33 | * sincedb_path 34 | 35 | 如果你不想用默认的 `$HOME/.sincedb`(Windows 平台上在 `C:\Windows\System32\config\systemprofile\.sincedb`),可以通过这个配置定义 sincedb 文件到其他位置。 36 | 37 | * sincedb_write_interval 38 | 39 | logstash 每隔多久写一次 sincedb 文件,默认是 15 秒。 40 | 41 | * stat_interval 42 | 43 | logstash 每隔多久检查一次被监听文件状态(是否有更新),默认是 1 秒。 44 | 45 | * start_position 46 | 47 | logstash 从什么位置开始读取文件数据,默认是结束位置,也就是说 logstash 进程会以类似 `tail -F` 的形式运行。如果你是要导入原有数据,把这个设定改成 "beginning",logstash 进程就从头开始读取,有点类似 `cat`,但是读到最后一行不会终止,而是继续变成 `tail -F`。 48 | 49 | ## 注意 50 | 51 | 1. 通常你要导入原有数据进 Elasticsearch 的话,你还需要 [filter/date](../filter/date.md) 插件来修改默认的"@timestamp" 字段值。稍后会学习这方面的知识。 52 | 2. *FileWatch* 只支持文件的**绝对路径**,而且会不自动递归目录。所以有需要的话,请用数组方式都写明具体哪些文件。 53 | 3. *LogStash::Inputs::File* 只是在进程运行的注册阶段初始化一个 *FileWatch* 对象。所以它不能支持类似 fluentd 那样的 `path => "/path/to/%{+yyyy/MM/dd/hh}.log"` 写法。达到相同目的,你只能写成 `path => "/path/to/*/*/*/*.log"`。 54 | 4. `start_position` 仅在该文件从未被监听过的时候起作用。如果 sincedb 文件中已经有这个文件的 inode 记录了,那么 logstash 依然会从记录过的 pos 开始读取数据。所以重复测试的时候每回需要删除 sincedb 文件。 55 | 5. 因为 windows 平台上没有 inode 的概念,Logstash 某些版本在 windows 平台上监听文件不是很靠谱。windows 平台上,推荐考虑使用 nxlog 作为收集端,参阅本书[稍后](../ecosystem/nxlog.md)章节。 56 | -------------------------------------------------------------------------------- /input/generator.md: -------------------------------------------------------------------------------- 1 | # 生成测试数据(Generator) 2 | 3 | 实际运行的时候这个插件是派不上用途的,但这个插件依然是非常重要的插件之一。因为每一个使用 ELK stack 的运维人员都应该清楚一个道理:数据是支持操作的唯一真理(否则你也用不着 ELK)。所以在上线之前,你一定会需要在自己的实际环境中,测试 Logstash 和 Elasticsearch 的性能状况。这时候,这个用来生成测试数据的插件就有用了! 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | input { 9 | generator { 10 | count => 10000000 11 | message => '{"key1":"value1","key2":[1,2],"key3":{"subkey1":"subvalue1"}}' 12 | codec => json 13 | } 14 | } 15 | ``` 16 | 17 | 插件的默认生成数据,message 内容是 "hello world"。你可以根据自己的实际需要这里来写其他内容。 18 | 19 | ## 使用方式 20 | 21 | 做测试有两种主要方式: 22 | 23 | * 配合 LogStash::Outputs::Null 24 | 25 | inputs/generator 是无中生有,output/null 则是锯嘴葫芦。事件流转到这里直接就略过,什么操作都不做。相当于只测试 Logstash 的 pipe 和 filter 效率。测试过程非常简单: 26 | 27 | $ time ./bin/logstash -f generator_null.conf 28 | real 3m0.864s 29 | user 3m39.031s 30 | sys 0m51.621s 31 | 32 | * 使用 pv 命令配合 LogStash::Outputs::Stdout 和 LogStash::Codecs::Dots 33 | 34 | 上面的这种方式虽然想法挺好,不过有个小漏洞:logstash 是在 JVM 上运行的,有一个明显的启动时间,运行也有一段事件的预热后才算稳定运行。所以,要想更真实的反应 logstash 在长期运行时候的效率,还有另一种方法: 35 | 36 | ``` 37 | output { 38 | stdout { 39 | codec => dots 40 | } 41 | } 42 | ``` 43 | 44 | LogStash::Codecs::Dots 也是一个另类的 codec 插件,他的作用是:把每个 event 都变成一个点(`.`)。这样,在输出的时候,就变成了一个一个的 `.` 在屏幕上。显然这也是一个为了测试而存在的插件。 45 | 46 | 下面就要介绍 pv 命令了。这个命令的作用,就是作实时的标准输入、标准输出监控。我们这里就用它来监控标准输出: 47 | 48 | $ ./bin/logstash -f generator_dots.conf | pv -abt > /dev/null 49 | 2.2MiB 0:03:00 [12.5kiB/s] 50 | 51 | 可以很明显的看到在前几秒中,速度是 0 B/s,因为 JVM 还没启动起来呢。开始运行的时候,速度依然不快。慢慢增长到比较稳定的状态,这时候的才是你需要的数据。 52 | 53 | 这里单位是 B/s,但是因为一个 event 就输出一个 `.`,也就是 1B。所以 12.5kiB/s 就相当于是 12.5k event/s。 54 | 55 | *注:如果你在 CentOS 上通过 yum 安装的 pv 命令,版本较低,可能还不支持 -a 参数。单纯靠 -bt 参数看起来还是有点累的。* 56 | 57 | ## 额外的话 58 | 59 | 既然单独花这么一节来说测试,这里想额外谈谈一个很常见的话题:* ELK 的性能怎么样?* 60 | 61 | **其实这压根就是一个不正确的提问**。ELK 并不是一个软件而是一个并不耦合的套件。所以,我们需要分拆开讨论这三个软件的性能如何?怎么优化? 62 | 63 | * LogStash 的性能,是最让新人迷惑的地方。因为 LogStash 本身并不维护队列,所以整个日志流转中任意环节的问题,都可能看起来像是 LogStash 的问题。这里,需要熟练使用本节说的测试方法,针对自己的每一段配置,都确定其性能。另一方面,就是本书之前提到过的,LogStash 给自己的线程都设置了单独的线程名称,你可以在 `top -H` 结果中查看具体线程的负载情况。 64 | 65 | * Elasticsearch 的性能。这里最需要强调的是:Elasticsearch 是一个分布式系统。从来没有分布式系统要跟人比较单机处理能力的说法。所以,更需要关注的是:在确定的单机处理能力的前提下,性能是否能做到线性扩展。当然,这不意味着说提高处理能力只能靠加机器了——有效利用 mapping API 是非常重要的。不过暂时就在这里讲述了。 66 | 67 | * Kibana 的性能。通常来说,Kibana 只是一个单页 Web 应用,只需要 nginx 发布静态文件即可,没什么性能问题。页面加载缓慢,基本上是因为 Elasticsearch 的请求响应时间本身不够快导致的。不过一定要细究的话,也能找出点 Kibana 本身性能相关的话题:因为 Kibana3 默认是连接固定的一个 ES 节点的 IP 端口的,所以这里会涉及一个浏览器的同一 IP 并发连接数的限制。其次,就是 Kibana 用的 AngularJS 使用了 Promise.then 的方式来处理 HTTP 请求响应。这是异步的。 68 | -------------------------------------------------------------------------------- /input/redis.md: -------------------------------------------------------------------------------- 1 | # 读取 Redis 数据 2 | 3 | Redis 服务器是 logstash 官方推荐的 broker 选择。Broker 角色也就意味着会同时存在输入和输出俩个插件。这里我们先学习输入插件。 4 | 5 | `LogStash::Inputs::Redis` 支持三种 *data_type*(实际上是*redis_type*),不同的数据类型会导致实际采用不同的 Redis 命令操作: 6 | 7 | * list => BLPOP 8 | * channel => SUBSCRIBE 9 | * pattern_channel => PSUBSCRIBE 10 | 11 | 注意到了么?**这里面没有 GET 命令!** 12 | 13 | Redis 服务器通常都是用作 NoSQL 数据库,不过 logstash 只是用来做消息队列。所以不要担心 logstash 里的 Redis 会撑爆你的内存和磁盘。 14 | 15 | ## 配置示例 16 | 17 | ``` 18 | input { 19 | redis { 20 | data_type => "pattern_channel" 21 | key => "logstash-*" 22 | host => "192.168.0.2" 23 | port => 6379 24 | threads => 5 25 | } 26 | } 27 | ``` 28 | 29 | ## 使用方式 30 | 31 | ### 基本方法 32 | 33 | 首先确认你设置的 host 服务器上已经运行了 redis-server 服务,然后打开终端运行 logstash 进程等待输入数据,然后打开另一个终端,输入 `redis-cli` 命令(先安装好 redis 软件包),在交互式提示符后面输入`PUBLISH logstash-demochan "hello world"`: 34 | 35 | ``` 36 | # redis-cli 37 | 127.0.0.1:6379> PUBLISH logstash-demochan "hello world" 38 | ``` 39 | 40 | 你会在第一个终端里看到 logstash 进程输出类似下面这样的内容: 41 | 42 | ```ruby 43 | { 44 | "message" => "hello world", 45 | "@version" => "1", 46 | "@timestamp" => "2014-08-08T16:26:29.399Z" 47 | } 48 | ``` 49 | 50 | 注意:这个事件里没有 **host** 字段!(或许这算是 bug……) 51 | 52 | ### 输入 JSON 数据 53 | 54 | 如果你想通过 redis 的频道给 logstash 事件添加更多字段,直接向频道发布 JSON 字符串就可以了。 `LogStash::Inputs::Redis` 会直接把 JSON 转换成事件。 55 | 56 | 继续在第二个终端的交互式提示符下输入如下内容: 57 | 58 | ``` 59 | 127.0.0.1:6379> PUBLISH logstash-chan '{"message":"hello world","@version":"1","@timestamp":"2014-08-08T16:34:21.865Z","host":"raochenlindeMacBook-Air.local","key1":"value1"}' 60 | ``` 61 | 62 | 你会看到第一个终端里的 logstash 进程随即也返回新的内容,如下所示: 63 | 64 | ```ruby 65 | { 66 | "message" => "hello world", 67 | "@version" => "1", 68 | "@timestamp" => "2014-08-09T00:34:21.865+08:00", 69 | "host" => "raochenlindeMacBook-Air.local", 70 | "key1" => "value1" 71 | } 72 | ``` 73 | 74 | 看,新的字段出现了!现在,你可以要求开发工程师直接向你的 redis 频道发送信息好了,一切自动搞定。 75 | 76 | ### 小贴士 77 | 78 | 这里我们建议的是使用 *pattern_channel* 作为输入插件的 *data_type* 设置值。因为实际使用中,你的 redis 频道可能有很多不同的 *keys*,一般命名成 *logstash-chan-%{type}* 这样的形式。这时候 *pattern_channel* 类型就可以帮助你一次订阅全部 logstash 相关频道! 79 | 80 | ## 扩展方式 81 | 82 | 如上段"小贴士"提到的,之前两个使用场景采用了同样的配置,即数据类型为频道发布订阅方式。这种方式在需要扩展 logstash 成多节点集群的时候,会出现一个问题:**通过频道发布的一条信息,会被所有订阅了该频道的 logstash 进程同时接收到,然后输出重复内容!** 83 | 84 | *你可以尝试再做一次上面的实验,这次在两个终端同时启动 `logstash -f redis-input.conf` 进程,结果会是两个终端都输出消息。* 85 | 86 | 这种时候,就需要用 *list* 类型。在这种类型下,数据输入到 redis 服务器上暂存,logstash 则连上 redis 服务器取走 (`BLPOP` 命令,所以只要 logstash 不堵塞,redis 服务器上也不会有数据堆积占用空间)数据。 87 | 88 | ### 配置示例 89 | 90 | ``` 91 | input { 92 | redis { 93 | batch_count => 1 94 | data_type => "list" 95 | key => "logstash-list" 96 | host => "192.168.0.2" 97 | port => 6379 98 | threads => 5 99 | } 100 | } 101 | ``` 102 | 103 | ### 使用方式 104 | 105 | 这次我们同时在两个终端运行 `logstash -f redis-input-list.conf` 进程。然后在第三个终端里启动 redis-cli 命令交互: 106 | 107 | ``` 108 | $ redis-cli 109 | 127.0.0.1:6379> RPUSH logstash-list "hello world" 110 | (integer) 1 111 | ``` 112 | 113 | 这时候你可以看到,只有一个终端输出了结果。 114 | 115 | 连续 `RPUSH` 几次,可以看到两个终端近乎各自输出一半条目。 116 | 117 | ### 小贴士 118 | 119 | RPUSH 支持 batch 方式,修改 logstash 配置中的 `batch_count` 值,作为示例这里只改到 2,实际运用中可以更大(事实上 `LogStash::Outputs::Redis` 对应这点的 `batch_event` 配置默认值就是 50)。 120 | 121 | 重启 logstash 进程后,redis-cli 命令中改成如下发送: 122 | 123 | ``` 124 | 127.0.0.1:6379> RPUSH logstash-list "hello world" "hello world" "hello world" "hello world" "hello world" "hello world" 125 | (integer) 3 126 | ``` 127 | 128 | 可以看到,两个终端也各自输出一部分结果。而你只用了一次 RPUSH 命令。 129 | 130 | ## 推荐阅读 131 | 132 | * 133 | -------------------------------------------------------------------------------- /input/stdin.md: -------------------------------------------------------------------------------- 1 | # 标准输入(Stdin) 2 | 3 | 我们已经见过好几个示例使用 `stdin` 了。这也应该是 logstash 里最简单和基础的插件了。 4 | 5 | 所以,在这段中,我们可以学到一些未来每个插件都会有的一些方法。 6 | 7 | ## 配置示例 8 | 9 | ``` 10 | input { 11 | stdin { 12 | add_field => {"key" => "value"} 13 | codec => "plain" 14 | tags => ["add"] 15 | type => "std" 16 | } 17 | } 18 | ``` 19 | 20 | ## 运行结果 21 | 22 | 用上面的新 `stdin` 设置重新运行一次最开始的 hello world 示例。我建议大家把整段配置都写入一个文本文件,然后运行命令:`bin/logstash -f stdin.conf`。输入 "hello world" 并回车后,你会在终端看到如下输出: 23 | 24 | ```ruby 25 | { 26 | "message" => "hello world", 27 | "@version" => "1", 28 | "@timestamp" => "2014-08-08T06:48:47.789Z", 29 | "type" => "std", 30 | "tags" => [ 31 | [0] "add" 32 | ], 33 | "key" => "value", 34 | "host" => "raochenlindeMacBook-Air.local" 35 | } 36 | ``` 37 | 38 | ## 解释 39 | 40 | *type* 和 *tags* 是 logstash 事件中两个特殊的字段。通常来说我们会在*输入区段*中通过 *type* 来标记事件类型 —— 我们肯定是提前能知道这个事件属于什么类型的。而 *tags* 则是在数据处理过程中,由具体的插件来添加或者删除的。 41 | 42 | 最常见的用法是像下面这样: 43 | 44 | ``` 45 | input { 46 | stdin { 47 | type => "web" 48 | } 49 | } 50 | filter { 51 | if [type] == "web" { 52 | grok { 53 | match => ["message", %{COMBINEDAPACHELOG}] 54 | } 55 | } 56 | } 57 | output { 58 | if "_grokparsefailure" in [tags] { 59 | nagios_nsca { 60 | nagios_status => "1" 61 | } 62 | } else { 63 | elasticsearch { 64 | } 65 | } 66 | } 67 | ``` 68 | 69 | 看起来蛮复杂的,对吧? 70 | 71 | 继续学习,你也可以写出来的。 72 | -------------------------------------------------------------------------------- /input/syslog.md: -------------------------------------------------------------------------------- 1 | # 读取 Syslog 数据 2 | 3 | syslog 可能是运维领域最流行的数据传输协议了。当你想从设备上收集系统日志的时候,syslog 应该会是你的第一选择。尤其是网络设备,比如思科 —— syslog 几乎是唯一可行的办法。 4 | 5 | 我们这里不解释如何配置你的 `syslog.conf`, `rsyslog.conf` 或者 `syslog-ng.conf` 来发送数据,而只讲如何把 logstash 配置成一个 syslog 服务器来接收数据。 6 | 7 | 有关 `rsyslog` 的用法,稍后的[类型项目](../dive_into/similar_projects.md)一节中,会有更详细的介绍。 8 | 9 | ## 配置示例 10 | 11 | ``` 12 | input { 13 | syslog { 14 | port => "514" 15 | } 16 | } 17 | ``` 18 | 19 | ## 运行结果 20 | 21 | 作为最简单的测试,我们先暂停一下本机的 `syslogd` (或 `rsyslogd` )进程,然后启动 logstash 进程(这样就不会有端口冲突问题)。现在,本机的 syslog 就会默认发送到 logstash 里了。我们可以用自带的 `logger` 命令行工具发送一条 "Hello World"信息到 syslog 里(即 logstash 里)。看到的 logstash 输出像下面这样: 22 | 23 | ```ruby 24 | { 25 | "message" => "Hello World", 26 | "@version" => "1", 27 | "@timestamp" => "2014-08-08T09:01:15.911Z", 28 | "host" => "127.0.0.1", 29 | "priority" => 31, 30 | "timestamp" => "Aug 8 17:01:15", 31 | "logsource" => "raochenlindeMacBook-Air.local", 32 | "program" => "com.apple.metadata.mdflagwriter", 33 | "pid" => "381", 34 | "severity" => 7, 35 | "facility" => 3, 36 | "facility_label" => "system", 37 | "severity_label" => "Debug" 38 | } 39 | ``` 40 | 41 | ## 解释 42 | 43 | Logstash 是用 `UDPSocket`, `TCPServer` 和 `LogStash::Filters::Grok` 来实现 `LogStash::Inputs::Syslog` 的。所以你其实可以直接用 logstash 配置实现一样的效果: 44 | 45 | ``` 46 | input { 47 | tcp { 48 | port => "8514" 49 | } 50 | } 51 | filter { 52 | grok { 53 | match => ["message", %{SYSLOGLINE} ] 54 | } 55 | syslog_pri { } 56 | } 57 | ``` 58 | 59 | ## 最佳实践 60 | 61 | **建议在使用 `LogStash::Inputs::Syslog` 的时候走 TCP 协议来传输数据。** 62 | 63 | 因为具体实现中,UDP 监听器只用了一个线程,而 TCP 监听器会在接收每个连接的时候都启动新的线程来处理后续步骤。 64 | 65 | 如果你已经在使用 UDP 监听器收集日志,用下行命令检查你的 UDP 接收队列大小: 66 | 67 | ``` 68 | # netstat -plnu | awk 'NR==1 || $4~/:514$/{print $2}' 69 | Recv-Q 70 | 228096 71 | ``` 72 | 73 | 228096 是 UDP 接收队列的默认最大大小,这时候 linux 内核开始丢弃数据包了! 74 | 75 | **强烈建议使用`LogStash::Inputs::TCP`和 `LogStash::Filters::Grok` 配合实现同样的 syslog 功能!** 76 | 77 | 虽然 LogStash::Inputs::Syslog 在使用 TCPServer 的时候可以采用多线程处理数据的接收,但是在同一个客户端数据的处理中,其 grok 和 date 是一直在该线程中完成的,这会导致总体上的处理性能几何级的下降 —— 经过测试,TCPServer 每秒可以接收 50000 条数据,而在同一线程中启用 grok 后每秒只能处理 5000 条,再加上 date 只能达到 500 条! 78 | 79 | 才将这两步拆分到 filters 阶段后,logstash 支持对该阶段插件单独设置多线程运行,大大提高了总体处理性能。在相同环境下, `logstash -f tcp.conf -w 20` 的测试中,总体处理性能可以达到每秒 30000 条数据! 80 | 81 | *注:测试采用 logstash 作者提供的 `yes "<44>May 19 18:30:17 snack jls: foo bar 32" | nc localhost 3000` 命令。出处见:* 82 | 83 | ### 小贴士 84 | 85 | 如果你实在没法切换到 TCP 协议,你可以自己写程序,或者使用其他基于异步 IO 框架(比如 libev )的项目。下面是一个简单的异步 IO 实现 UDP 监听数据输入 Elasticsearch 的示例: 86 | 87 | 88 | -------------------------------------------------------------------------------- /input/tcp.md: -------------------------------------------------------------------------------- 1 | # 读取网络数据(TCP) 2 | 3 | 未来你可能会用 Redis 服务器或者其他的消息队列系统来作为 logstash broker 的角色。不过 Logstash 其实也有自己的 TCP/UDP 插件,在临时任务的时候,也算能用,尤其是测试环境。 4 | 5 | *小贴士:虽然 `LogStash::Inputs::TCP` 用 Ruby 的 `Socket` 和 `OpenSSL` 库实现了高级的 SSL 功能,但 Logstash 本身只能在 `SizedQueue` 中缓存 20 个事件。这就是我们建议在生产环境中换用其他消息队列的原因。* 6 | 7 | ## 配置示例 8 | 9 | ``` 10 | input { 11 | tcp { 12 | port => 8888 13 | mode => "server" 14 | ssl_enable => false 15 | } 16 | } 17 | ``` 18 | 19 | ## 常见场景 20 | 21 | 目前来看,`LogStash::Inputs::TCP` 最常见的用法就是配合 `nc` 命令导入旧数据。在启动 logstash 进程后,在另一个终端运行如下命令即可导入数据: 22 | 23 | ``` 24 | # nc 127.0.0.1 8888 < olddata 25 | ``` 26 | 27 | 这种做法比用 `LogStash::Inputs::File` 好,因为当 nc 命令结束,我们就知道数据导入完毕了。而用 input/file 方式,logstash 进程还会一直等待新数据输入被监听的文件,不能直接看出是否任务完成了。 28 | -------------------------------------------------------------------------------- /output/README.md: -------------------------------------------------------------------------------- 1 | # 输出插件(Output) 2 | -------------------------------------------------------------------------------- /output/elasticsearch.md: -------------------------------------------------------------------------------- 1 | # 保存进 Elasticsearch 2 | 3 | Logstash 早期有三个不同的 elasticsearch 插件。到 1.4.0 版本的时候,开发者彻底重写了 `LogStash::Outputs::Elasticsearch` 插件。从此,我们只需要用这一个插件,就能任意切换使用 Elasticsearch 集群支持的各种不同协议了。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | output { 9 | elasticsearch { 10 | host => "192.168.0.2" 11 | protocol => "http" 12 | index => "logstash-%{type}-%{+YYYY.MM.dd}" 13 | index_type => "%{type}" 14 | workers => 5 15 | template_overwrite => true 16 | } 17 | } 18 | ``` 19 | 20 | ## 解释 21 | 22 | ### 协议 23 | 24 | 现在,新插件支持三种协议: *node*,*http* 和 *transport*。 25 | 26 | 一个小集群里,使用 *node* 协议最方便了。Logstash 以 elasticsearch 的 client 节点身份(即不存数据不参加选举)运行。如果你运行下面这行命令,你就可以看到自己的 logstash 进程名,对应的 `node.role` 值是 **c**: 27 | 28 | ``` 29 | # curl 127.0.0.1:9200/_cat/nodes?v 30 | host ip heap.percent ram.percent load node.role master name 31 | local 192.168.0.102 7 c - logstash-local-1036-2012 32 | local 192.168.0.2 7 d * Sunstreak 33 | 34 | ``` 35 | 36 | 特别的,作为一个快速运行示例的需要,你还可以在 logstash 进程内部运行一个**内嵌**的 elasticsearch 服务器。内嵌服务器默认会在 `$PWD/data` 目录里存储索引。如果你想变更这些配置,在 `$PWD/elasticsearch.yml` 文件里写自定义配置即可,logstash 会尝试自动加载这个文件。 37 | 38 | 对于拥有很多索引的大集群,你可以用 *transport* 协议。logstash 进程会转发所有数据到你指定的某台主机上。这种协议跟上面的 *node* 协议是不同的。*node* 协议下的进程是可以接收到整个 Elasticsearch 集群状态信息的,当进程收到一个事件时,它就知道这个事件应该存在集群内哪个机器的分片里,所以它就会直接连接该机器发送这条数据。而 *transport* 协议下的进程不会保存这个信息,在集群状态更新(节点变化,索引变化都会发送全量更新)时,就不会对所有的 logstash 进程也发送这种信息。更多 Elasticsearch 集群状态的细节,参阅。 39 | 40 | 如果你已经有现成的 Elasticsearch 集群,但是版本跟 logstash 自带的又不太一样,建议你使用 *http* 协议。Logstash 会使用 POST 方式发送数据。 41 | 42 | #### 小贴士 43 | 44 | * Logstash 1.4.2 在 transport 和 http 协议的情况下是固定连接指定 host 发送数据。从 1.5.0 开始,host 可以设置数组,它会从节点列表中选取不同的节点发送数据,达到 Round-Robin 负载均衡的效果。 45 | * Kibana4 强制要求 ES 全集群所有 node 版本在 1.4 以上,所以采用 node 方式发送数据的 logstash-1.4(携带的 Elasticsearch.jar 库是 1.1.1 版本) 会导致 Kibana4 无法运行,采用 Kibana4 的读者务必改用 http 方式。 46 | * 开发者在 IRC freenode#logstash 频道里表示:"高于 1.0 版本的 Elasticsearch 应该都能跟最新版 logstash 的 node 协议一起正常工作"。此信息仅供参考,请认真测试后再上线。 47 | * index 48 | 注意index中不能有大写字母,否则es在日志中会报InvalidIndexNameException,但是logstash不会报错,这个错误比较隐晦,也容易掉进这个坑中。 49 | 50 | #### 性能问题 51 | 52 | Logstash 1.4.2 在 http 协议下默认使用作者自己的 ftw 库,随同分发的是 0.0.39 版。该版本有[内存泄露问题](https://github.com/elasticsearch/logstash/issues/1604),长期运行下输出性能越来越差! 53 | 54 | 解决办法: 55 | 56 | 1. 对性能要求不高的,可以在启动 logstash 进程时,配置环境变量ENV["BULK"],强制采用 elasticsearch 官方 Ruby 库。命令如下: 57 | 58 | export BULK="esruby" 59 | 60 | 2. 对性能要求高的,可以尝试采用 logstash-1.5.0RC2 。新版的 outputs/elasticsearch 放弃了 ftw 库,改用了一个 JRuby 平台专有的 [Manticore 库](https://github.com/cheald/manticore/wiki/Performance)。根据测试,性能跟 ftw 比[相当接近](https://github.com/elasticsearch/logstash/pull/1777)。 61 | 3. 对性能要求极高的,可以手动更新 ftw 库版本,目前最新版是 0.0.42 版,据称内存问题在 0.0.40 版即解决。 62 | 63 | ### 模板 64 | 65 | Elasticsearch 支持给索引预定义设置和 mapping(前提是你用的 elasticsearch 版本支持这个 API,不过估计应该都支持)。Logstash 自带有一个优化好的模板,内容如下: 66 | 67 | ```json 68 | { 69 | "template" : "logstash-*", 70 | "settings" : { 71 | "index.refresh_interval" : "5s" 72 | }, 73 | "mappings" : { 74 | "_default_" : { 75 | "_all" : {"enabled" : true}, 76 | "dynamic_templates" : [ { 77 | "string_fields" : { 78 | "match" : "*", 79 | "match_mapping_type" : "string", 80 | "mapping" : { 81 | "type" : "string", "index" : "analyzed", "omit_norms" : true, 82 | "fields" : { 83 | "raw" : {"type": "string", "index" : "not_analyzed", "ignore_above" : 256} 84 | } 85 | } 86 | } 87 | } ], 88 | "properties" : { 89 | "@version": { "type": "string", "index": "not_analyzed" }, 90 | "geoip" : { 91 | "type" : "object", 92 | "dynamic": true, 93 | "path": "full", 94 | "properties" : { 95 | "location" : { "type" : "geo_point" } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | 这其中的关键设置包括: 105 | 106 | * template for index-pattern 107 | 108 | 只有匹配 `logstash-*` 的索引才会应用这个模板。有时候我们会变更 Logstash 的默认索引名称,记住你也得通过 PUT 方法上传可以匹配你自定义索引名的模板。当然,我更建议的做法是,把你自定义的名字放在 "logstash-" 后面,变成 `index => "logstash-custom-%{+yyyy.MM.dd}"` 这样。 109 | 110 | * refresh_interval for indexing 111 | 112 | Elasticsearch 是一个*近*实时搜索引擎。它实际上是每 1 秒钟刷新一次数据。对于日志分析应用,我们用不着这么实时,所以 logstash 自带的模板修改成了 5 秒钟。你还可以根据需要继续放大这个刷新间隔以提高数据写入性能。 113 | 114 | * multi-field with not_analyzed 115 | 116 | Elasticsearch 会自动使用自己的默认分词器(空格,点,斜线等分割)来分析字段。分词器对于搜索和评分是非常重要的,但是大大降低了索引写入和聚合请求的性能。所以 logstash 模板定义了一种叫"多字段"(multi-field)类型的字段。这种类型会自动添加一个 ".raw" 结尾的字段,并给这个字段设置为不启用分词器。简单说,你想获取 url 字段的聚合结果的时候,不要直接用 "url" ,而是用 "url.raw" 作为字段名。 117 | 118 | * geo_point 119 | 120 | Elasticsearch 支持 *geo_point* 类型, *geo distance* 聚合等等。比如说,你可以请求某个 *geo_point* 点方圆 10 千米内数据点的总数。在 Kibana 的 bettermap 类型面板里,就会用到这个类型的数据。 121 | 122 | ### 其他模板配置建议 123 | 124 | * doc_values 125 | 126 | doc_values 是 Elasticsearch 1.3 版本引入的新特性。启用该特性的字段,索引写入的时候会在磁盘上构建 fielddata。而过去,fielddata 是固定只能使用内存的。在请求范围加大的时候,很容易触发 OOM 报错: 127 | 128 | > ElasticsearchException[org.elasticsearch.common.breaker.CircuitBreakingException: Data too large, data for field [@timestamp] would be larger than limit of [639015321/609.4mb]] 129 | 130 | doc_values 只能给不分词(对于字符串字段就是设置了 `"index":"not_analyzed"`,数值和时间字段默认就没有分词) 的字段配置生效。 131 | 132 | doc_values 虽然用的是磁盘,但是系统本身也有自带 VFS 的 cache 效果并不会太差。据官方测试,经过 1.4 的优化后,只比使用内存的 fielddata 慢 15% 。所以,在数据量较大的情况下,**强烈建议开启**该配置: 133 | 134 | ```json 135 | { 136 | "template" : "logstash-*", 137 | "settings" : { 138 | "index.refresh_interval" : "5s" 139 | }, 140 | "mappings" : { 141 | "_default_" : { 142 | "_all" : {"enabled" : true}, 143 | "dynamic_templates" : [ { 144 | "string_fields" : { 145 | "match" : "*", 146 | "match_mapping_type" : "string", 147 | "mapping" : { 148 | "type" : "string", "index" : "analyzed", "omit_norms" : true, 149 | "fields" : { 150 | "raw" : { "type": "string", "index" : "not_analyzed", "ignore_above" : 256, "doc_values": true } 151 | } 152 | } 153 | } 154 | } ], 155 | "properties" : { 156 | "@version": { "type": "string", "index": "not_analyzed" }, 157 | "@timestamp": { "type": "date", "index": "not_analyzed", "doc_values": true, "format": "dateOptionalTime" }, 158 | "geoip" : { 159 | "type" : "object", 160 | "dynamic": true, 161 | "path": "full", 162 | "properties" : { 163 | "location" : { "type" : "geo_point" } 164 | } 165 | } 166 | } 167 | } 168 | } 169 | } 170 | ``` 171 | 172 | * order 173 | 174 | 如果你有自己单独定制 template 的想法,很好。这时候有几种选择: 175 | 176 | 1. 在 logstash/outputs/elasticsearch 配置中开启 `manage_template => false` 选项,然后一切自己动手; 177 | 2. 在 logstash/outputs/elasticsearch 配置中开启 `template => "/path/to/your/tmpl.json"` 选项,让 logstash 来发送你自己写的 template 文件; 178 | 3. 避免变更 logstash 里的配置,而是另外发送一个 template ,利用 elasticsearch 的 templates order 功能。 179 | 180 | 这个 order 功能,就是 elasticsearch 在创建一个索引的时候,如果发现这个索引同时匹配上了多个 template ,那么就会先应用 order 数值小的 template 设置,然后再应用一遍 order 数值高的作为覆盖,最终达到一个 merge 的效果。 181 | 182 | 比如,对上面这个模板已经很满意,只想修改一下 `refresh_interval` ,那么只需要新写一个: 183 | 184 | ```json 185 | { 186 | "order" : 1, 187 | "template" : "logstash-*", 188 | "settings" : { 189 | "index.refresh_interval" : "20s" 190 | } 191 | } 192 | ``` 193 | 194 | 然后运行 `curl -XPUT http://localhost:9200/_template/template_newid -d '@/path/to/your/tmpl.json'` 即可。 195 | 196 | logstash 默认的模板, order 是 0,id 是 logstash,通过 logstash/outputs/elasticsearch 的配置选项 `template_name` 修改。你的新模板就不要跟这个名字冲突了。 197 | 198 | ## 推荐阅读 199 | 200 | * 201 | -------------------------------------------------------------------------------- /output/email.md: -------------------------------------------------------------------------------- 1 | # 发送邮件(Email) 2 | 3 | ## 配置示例 4 | 5 | ``` 6 | output { 7 | email { 8 | to => "admin@website.com,root@website.com" 9 | cc => "other@website.com" 10 | via => "smtp" 11 | subject => "Warning: %{title}" 12 | options => { 13 | smtpIporHost => "localhost", 14 | port => 25, 15 | domain => 'localhost.localdomain', 16 | userName => nil, 17 | password => nil, 18 | authenticationType => nil, # (plain, login and cram_md5) 19 | starttls => true 20 | } 21 | htmlbody => "" 22 | body => "" 23 | attachments => ["/path/to/filename"] 24 | } 25 | } 26 | ``` 27 | 28 | ## 解释 29 | 30 | *outputs/email* 插件支持 SMTP 协议和 sendmail 两种方式,通过 `via` 参数设置。SMTP 方式有较多的 options 参数可配置。sendmail 只能利用本机上的 sendmail 服务来完成 —— 文档上描述了 Mail 库支持的 sendmail 配置参数,但实际代码中没有相关处理,不要被迷惑了。。。 31 | 32 | -------------------------------------------------------------------------------- /output/exec.md: -------------------------------------------------------------------------------- 1 | # 调用命令执行(Exec) 2 | 3 | *outputs/exec* 插件的运用也非常简单,如下所示,将 logstash 切割成的内容作为参数传递给命令。这样,在每个事件到达该插件的时候,都会触发这个命令的执行。 4 | 5 | ``` 6 | output { 7 | exec { 8 | command => "sendsms.pl \"%{message}\" -t %{user}" 9 | } 10 | } 11 | ``` 12 | 13 | 需要注意的是。这种方式是每次都重新开始执行一次命令并退出。本身是比较慢速的处理方式(程序加载,网络建联等都有一定的时间消耗)。最好只用于少量的信息处理场景,比如不适用 nagios 的其他报警方式。示例就是通过短信发送消息。 14 | -------------------------------------------------------------------------------- /output/file.md: -------------------------------------------------------------------------------- 1 | # 保存成文件(File) 2 | 3 | 通过日志收集系统将分散在数百台服务器上的数据集中存储在某中心服务器上,这是运维最原始的需求。早年的 scribed ,甚至直接就把输出的语法命名为 ``。Logstash 当然也能做到这点。 4 | 5 | 和 `LogStash::Inputs::File` 不同, `LogStash::Outputs::File` 里可以使用 sprintf format 格式来自动定义输出到带日期命名的路径。 6 | 7 | ## 配置示例 8 | 9 | ``` 10 | output { 11 | file { 12 | path => "/path/to/%{+yyyy/MM/dd/HH}/%{host}.log.gz" 13 | message_format => "%{message}" 14 | gzip => true 15 | } 16 | } 17 | ``` 18 | 19 | ## 解释 20 | 21 | 使用 *output/file* 插件首先需要注意的就是 `message_format` 参数。插件默认是输出整个 event 的 JSON 形式数据的。这可能跟大多数情况下使用者的期望不符。大家可能只是希望按照日志的原始格式保存就好了。所以需要定义为 `%{message}`,当然,前提是在之前的 *filter* 插件中,你没有使用 `remove_field` 或者 `update` 等参数删除或修改 `%{message}` 字段的内容。 22 | 23 | 另一个非常有用的参数是 gzip。gzip 格式是一个非常奇特而友好的格式。其格式包括有: 24 | 25 | * 10字节的头,包含幻数、版本号以及时间戳 26 | * 可选的扩展头,如原文件名 27 | * 文件体,包括DEFLATE压缩的数据 28 | * 8字节的尾注,包括CRC-32校验和以及未压缩的原始数据长度 29 | 30 | 这样 gzip 就可以一段一段的识别出来数据 —— **反过来说,也就是可以一段一段压缩了添加在后面!** 31 | 32 | 这对于我们流式添加数据简直太棒了! 33 | 34 | *小贴士:你或许见过网络流传的 parallel 命令行工具并发处理数据的神奇文档,但在自己用的时候总见不到效果。实际上就是因为:文档中处理的 gzip 文件,可以分开处理然后再合并的。* 35 | 36 | 37 | -------------------------------------------------------------------------------- /output/nagios.md: -------------------------------------------------------------------------------- 1 | # 报警到 Nagios 2 | 3 | Logstash 中有两个 output 插件是 nagios 有关的。*outputs/nagios* 插件发送数据给本机的 `nagios.cmd` 管道命令文件,*outputs/nagios_nsca* 插件则是 调用 `send_nsca` 命令以 NSCA 协议格式把数据发送给 nagios 服务器(远端或者本地皆可)。 4 | 5 | ## Nagios.Cmd 6 | 7 | nagios.cmd 是 nagios 服务器的核心组件。nagios 事件处理和内外交互都是通过这个管道文件来完成的。 8 | 9 | 使用 CMD 方式,需要自己保证发送的 Logstash 事件符合 nagios 事件的格式。即必须在 *filter* 阶段预先准备好 `nagios_host` 和 `nagios_service` 字段;此外,如果在 *filter* 阶段也准备好 `nagios_annotation` 和 `nagios_level` 字段,这里也会自动转换成 nagios 事件信息。 10 | 11 | ``` 12 | filter { 13 | if [message] =~ /err/ { 14 | mutate { 15 | add_tag => "nagios" 16 | rename => ["host", "nagios_host"] 17 | replace => ["nagios_service", "logstash_check_%{type}"] 18 | } 19 | } 20 | } 21 | output { 22 | if "nagios" in [tags] { 23 | nagios { } 24 | } 25 | } 26 | ``` 27 | 28 | 如果不打算在 *filter* 阶段提供 `nagios_level` ,那么也可以在该插件中通过参数配置。 29 | 30 | 所谓 `nagios_level`,即我们通过 nagios plugin 检查数据时的返回值。其取值范围和含义如下: 31 | 32 | * "0",代表 "OK",服务正常; 33 | * "1",代表 "WARNNING",服务警告,一般 nagios plugin 命令中使用 `-w` 参数设置该阈值; 34 | * "2",代表 "CRITICAL",服务危急,一般 nagios plugin 命令中使用 `-c` 参数设置该阈值; 35 | * "3",代表 "UNKNOWN",未知状态,一般会在 timeout 等情况下出现。 36 | 37 | 默认情况下,该插件会以 "CRITICAL" 等级发送报警给 Nagios 服务器。 38 | 39 | nagios.cmd 文件的具体位置,可以使用 `command_file` 参数设置。默认位置是 "/var/lib/nagios3/rw/nagios.cmd"。 40 | 41 | 关于和 nagios.cmd 交互的具体协议说明,有兴趣的读者请阅读 [Using external commands in Nagios](http://archive09.linux.com/feature/153285) 一文,这是《Learning Nagios 3.0》书中内容节选。 42 | 43 | ## NSCA 44 | 45 | NSCA 是一种标准的 nagios 分布式扩展协议。分布在各机器上的 `send_nsca` 进程主动将监控数据推送给远端 nagios 服务器的 NSCA 进程。 46 | 47 | 当 Logstash 跟 nagios 服务器没有在同一个主机上运行的时候,就只能通过 NSCA 方式来发送报警了 —— 当然也必须在 Logstash 服务器上安装 `send_nsca` 命令。 48 | 49 | nagios 事件所需要的几个属性在上一段中已经有过描述。不过在使用这个插件的时候,不要求提前准备好,而是可以在该插件内部定义参数: 50 | 51 | ``` 52 | output { 53 | nagios_nsca { 54 | nagios_host => "%{host}" 55 | nagios_service => "logstash_check_%{type}" 56 | nagios_status => "2" 57 | message_format => "%{@timestamp}: %{message}" 58 | host => "nagiosserver.domain.com" 59 | } 60 | } 61 | ``` 62 | 63 | 这里请注意,`host` 和 `nagios_host` 两个参数,分别是用来设置 nagios 服务器的地址,和报警信息中有问题的服务器地址。 64 | 65 | 关于 NSCA 原理,架构和配置说明,还不了解的读者请阅读官方网站 [Using NSClient++ from nagios with NSCA](http://nsclient.org/nscp/wiki/doc/usage/nagios/nsca) 一节。 66 | 67 | ## 推荐阅读 68 | 69 | 除了 nagios 以外,logstash 同样可以发送信息给其他常见监控系统。方式和 nagios 大同小异: 70 | 71 | * *outputs/ganglia* 插件通过 UDP 协议,发送 gmetric 型数据给本机/远端的 `gmond` 或者 `gmetad` 72 | * *outputs/zabbix* 插件调用本机的 `zabbix_sender` 命令发送 73 | -------------------------------------------------------------------------------- /output/redis.md: -------------------------------------------------------------------------------- 1 | # 输出到 Redis 2 | 3 | ## 配置示例 4 | 5 | ``` 6 | input { stdin {} } 7 | output { 8 | redis { 9 | data_type => "channel" 10 | key => "logstash-chan-%{+yyyy.MM.dd}" 11 | } 12 | } 13 | ``` 14 | 15 | ## Usage 16 | 17 | 我们还是继续先用 `redis-cli` 命令行来演示 *outputs/redis* 插件的实质。 18 | 19 | ### basical use case 20 | 21 | 运行 logstash 进程,然后另一个终端启动 redis-cli 命令。输入订阅指定频道的 Redis 命令 ("SUBSCRIBE logstash-chan-2014.08.08") 后,首先会看到一个订阅成功的返回信息。如下所示: 22 | 23 | ``` 24 | # redis-cli 25 | 127.0.0.1:6379> SUBSCRIBE logstash-chan-2014.08.08 26 | Reading messages... (press Ctrl-C to quit) 27 | 1) "subscribe" 28 | 2) "logstash-chan-2014.08.08" 29 | 3) (integer) 1 30 | ``` 31 | 32 | 好,在运行 logstash 的终端里输入 "hello world" 字符串。切换回 redis-cli 的终端,你发现已经自动输出了一条信息: 33 | 34 | ``` 35 | 1) "message" 36 | 2) "logstash-chan-2014.08.08" 37 | 3) "{\"message\":\"hello world\",\"@version\":\"1\",\"@timestamp\":\"2014-08-08T16:34:21.865Z\",\"host\":\"raochenlindeMacBook-Air.local\"}" 38 | ``` 39 | 40 | 看起来是不是非常眼熟?这一串字符其实就是我们在 inputs/redis 一节中使用的那段数据。 41 | 42 | 看,这样就把 *outputs/redis* 和 *inputs/redis* 串联起来了吧! 43 | 44 | 事实上,这就是我们使用 redis 服务器作为 logstassh 架构中 broker 角色的原理。 45 | 46 | 让我们把这两节中不同配置的 logstash 进程分别在两个终端运行起来,这次不再要运行 redis-cli 命令了。在配有 *outputs/redis* 这端输入 "hello world",配有 "inputs/redis" 的终端上,就自动输出数据了! 47 | 48 | ### notification use case 49 | 50 | 我们还可以用其他程序来订阅 redis 频道,程序里就可以随意写其他逻辑了。你可以看看 [output/juggernaut](http://logstash.net/docs/1.4.2/outputs/juggernaut) 插件的原理。这个 Juggernaut 就是基于 redis 服务器和 socket.io 框架构建的。利用它,logstash 可以直接向 webkit 等支持 socket.io 的浏览器推送告警信息。 51 | 52 | ## 扩展方式 53 | 54 | 和 `LogStash::Inputs::Redis` 一样,这里也有设置成 **list** 的方式。使用 `RPUSH` 命令发送给 redis 服务器,效果和之前展示的完全一致。包括可以调整的参数 `batch_event`,也在之前章节中讲过。这里不再重复举例。 55 | 56 | -------------------------------------------------------------------------------- /output/statsd.md: -------------------------------------------------------------------------------- 1 | # 输出到 Statsd 2 | 3 | Statsd 最早是 2008 年 Flickr 公司用 Perl 写的针对 graphite、datadog 等监控数据后端存储开发的前端网络应用,2011 年 Etsy 公司用 nodejs 重构。用于接收、写入、读取和聚合时间序列数据,包括即时值和累积值等。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | output { 9 | statsd { 10 | host => "statsdserver.domain.com" 11 | namespace => "logstash" 12 | sender => "%{host}" 13 | increment => ["httpd.response.%{status}"] 14 | } 15 | } 16 | ``` 17 | 18 | ## 解释 19 | 20 | Graphite 以树状结构存储监控数据,所以 statsd 也是如此。所以发送给 statsd 的数据的 key 也一定得是 "first.second.tree.four" 这样的形式。而在 *outputs/statsd* 插件中,就会以三个配置参数来拼接成这种形式: 21 | 22 | ``` 23 | namespace.sender.metric 24 | ``` 25 | 26 | 其中 namespace 和 sender 都是直接设置的,而 metric 又分为好几个不同的参数可以分别设置。statsd 支持的 metric 类型如下: 27 | 28 | ### metric 类型 29 | 30 | * increment 31 | 32 | 示例语法:`increment => ["nginx.status.%{status}"]` 33 | 34 | * decrement 35 | 36 | 语法同 increment。 37 | 38 | * count 39 | 40 | 示例语法:`count => {"nginx.bytes" => "%{bytes}"}` 41 | 42 | * gauge 43 | 44 | 语法同 count。 45 | 46 | * set 47 | 48 | 语法同 count。 49 | 50 | * timing 51 | 52 | 语法同 count。 53 | 54 | 关于这些 metric 类型的详细说明,请阅读 statsd 文档:。 55 | 56 | ## 推荐阅读 57 | 58 | * Etsy 发布 nodejs 版本 statsd 的博客:[Measure Anything, Measure Everything](http://codeascraft.etsy.com/2011/02/15/measure-anything-measure-everything/) 59 | * Flickr 发布 statsd 的博客:[Counting & Timing](http://code.flickr.net/2008/10/27/counting-timing/) 60 | -------------------------------------------------------------------------------- /output/stdout.md: -------------------------------------------------------------------------------- 1 | # 标准输出(Stdout) 2 | 3 | 和之前 *inputs/stdin* 插件一样,*outputs/stdout* 插件也是最基础和简单的输出插件。同样在这里简单介绍一下,作为输出插件的一个共性了解。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | output { 9 | stdout { 10 | codec => rubydebug 11 | workers => 2 12 | } 13 | } 14 | ``` 15 | 16 | ## 解释 17 | 18 | 输出插件统一具有一个参数是 `workers`。Logstash 为输出做了多线程的准备。 19 | 20 | 其次是 codec 设置。codec 的作用在之前已经讲过。可能除了 `codecs/multiline` ,其他 codec 插件本身并没有太多的设置项。所以一般省略掉后面的配置区段。换句话说。上面配置示例的完全写法应该是: 21 | 22 | ``` 23 | output { 24 | stdout { 25 | codec => rubydebug { 26 | } 27 | workers => 2 28 | } 29 | } 30 | ``` 31 | 32 | 单就 *outputs/stdout* 插件来说,其最重要和常见的用途就是调试。所以在不太有效的时候,加上命令行参数 `-vv` 运行,查看更多详细调试信息。 33 | -------------------------------------------------------------------------------- /output/tcp.md: -------------------------------------------------------------------------------- 1 | # 发送网络数据(TCP) 2 | 3 | 虽然之前我们已经提到过不建议直接使用 LogStash::Inputs::TCP 和 LogStash::Outputs::TCP 做转发工作,不过在实际交流中,发现确实有不少朋友觉得这种简单配置足够使用,因而不愿意多加一层消息队列的。所以,还是把 Logstash 如何直接发送 TCP 数据也稍微提点一下。 4 | 5 | ## 配置示例 6 | 7 | ``` 8 | output { 9 | tcp { 10 | host => "192.168.0.2" 11 | port => 8888 12 | codec => json_lines 13 | } 14 | } 15 | ``` 16 | 17 | ## 配置说明 18 | 19 | 在收集端采用 tcp 方式发送给远端的 tcp 端口。这里需要注意的是,默认的 codec 选项是 **json**。而远端的 LogStash::Inputs::TCP 的默认 codec 选项却是 **plain** !所以不指定各自的 codec ,对接肯定是失败的。 20 | 21 | 另外,由于IO BUFFER 的原因,即使是两端共同约定为 **json** 依然无法正常运行,接收端会认为一行数据没结束,一直等待直至自己 OutOfMemory ! 22 | 23 | 所以,正确的做法是,发送端指定 codec 为 **json_lines** ,这样每条数据后面会加上一个回车,接收端指定 codec 为 **json_lines** 或者 **json** 均可,这样才能正常处理。包括在收集端已经切割好的字段,也可以直接带入收集端使用了。 24 | --------------------------------------------------------------------------------