fluent-plugin-elasticsearch 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb97386663d594fba5f8370521ec652d8421be24
4
- data.tar.gz: ee4c8b7df8eedab7a49928a8501d125acee07d81
3
+ metadata.gz: 52e6ea7f1fcf3e37be32d06f5d81d5364702cbe5
4
+ data.tar.gz: 66ce94029829ccf119262929ca50e9e21c4d73ea
5
5
  SHA512:
6
- metadata.gz: 1e18d1b311ac77e067fead2acd4e4dcbfafad8e1930f60d041195024da6aa8f1993e068af25ad2ce58f95a6c87f746baf254aadbc48a8612c82c43fdf747f7c3
7
- data.tar.gz: 226358388cf4cdb2916bbba8c7272e019f257bcb8b964d1e1b839f7a6be4aa6aee9688a917ae20b2f02982863ac43b0a1b9125ce1879baa8bda30a0db4e09e40
6
+ metadata.gz: b9922d58ba0999c3c03671144b1f4446da3154731e9e5b9dc4bc7dc921d154f382259b2b8b972a21bbcafc35f12265921dadbcba4f592d4fc0f1ebba16566ff5
7
+ data.tar.gz: d0ec86f473deb9e1a70cca98f3d8194a72f82d08f389246840ae1e25c0dcad9a2c7832b3d54acb6e774652e6c26f50858a9945bca069760f8e972ee01b666515
data/History.md CHANGED
@@ -3,9 +3,16 @@
3
3
  ### Future
4
4
 
5
5
 
6
+ ### 0.5.0
7
+
8
+ - add full connection URI support (#65)
9
+ - use `@timestamp` for index (#41)
10
+ - add support for elasticsearch gem version 1 (#71)
11
+ - fix connection reset & retry when connection is lost (#67)
12
+
6
13
  ### 0.4.0
7
14
 
8
- - add `request_timeout` config
15
+ - add `request_timeout` config (#59)
9
16
  - fix lockup when non-hash values are sent (#52)
10
17
 
11
18
  ### 0.3.1
@@ -40,7 +47,6 @@
40
47
 
41
48
  - fix timezone in logstash key
42
49
 
43
-
44
50
  ### 0.1.0
45
51
 
46
52
  - Initial gem release.
data/README.md CHANGED
@@ -25,18 +25,38 @@ index_name fluentd
25
25
  type_name fluentd
26
26
  ```
27
27
 
28
+ **Index templates**
29
+
30
+ This plugin creates ElasticSearch indices by merely writing to them. Consider using [Index Templates](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-templates.html) to gain control of what get indexed and how. See [this example](https://github.com/uken/fluent-plugin-elasticsearch/issues/33#issuecomment-38693282) for a good starting point.
31
+
28
32
  **More options:**
29
33
 
30
34
  ```
31
35
  hosts host1:port1,host2:port2,host3:port3
32
36
  ```
33
37
 
38
+ or
39
+
40
+ ```
41
+ hosts https://customhost.com:443/path,https://username:password@host-failover.com:443
42
+ ```
43
+
34
44
  You can specify multiple elasticsearch hosts with separator ",".
35
45
 
36
- If you specify multiple hosts, plugin writes to elasticsearch with load balanced. (it's elasticsearch-ruby's feature, default is round-robin.)
46
+ If you specify multiple hosts, this plugin will load balance updates to elasticsearch. This is an [elasticsearch-ruby](https://github.com/elasticsearch/elasticsearch-ruby) feature, the default strategy is round-robin.
37
47
 
38
48
  If you specify this option, host and port options are ignored.
39
49
 
50
+ ```
51
+ user demo
52
+ password secret
53
+ path /elastic_search/
54
+ scheme https
55
+ ```
56
+
57
+ You can specify user and password for HTTP basic auth. If used in conjunction with a hosts list, then these options will be used by default i.e. if you do not provide any of these options within the hosts listed.
58
+
59
+
40
60
  ```
41
61
  logstash_format true # defaults to false
42
62
  ```
@@ -53,7 +73,7 @@ By default, the records inserted into index `logstash-YYMMDD`. This option allow
53
73
  logstash_dateformat %Y.%m. # defaults to "%Y.%m.%d"
54
74
  ```
55
75
 
56
- By default, when inserting records in logstash format, @timestamp is dynamically created with the time at log ingestion. If you'd like to use a custom time. Include an @timestamp with your record.
76
+ By default, when inserting records in logstash format, @timestamp is dynamically created with the time at log ingestion. If you'd like to use a custom time. Include an @timestamp with your record.
57
77
 
58
78
  ```
59
79
  {"@timestamp":"2014-04-07T000:00:00-00:00"}
@@ -3,9 +3,9 @@ $:.push File.expand_path('../lib', __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'fluent-plugin-elasticsearch'
6
- s.version = '0.4.0'
6
+ s.version = '0.5.0'
7
7
  s.authors = ['diogo', 'pitr']
8
- s.email = ['team@uken.com']
8
+ s.email = ['pitr@uken.com', 'diogo@uken.com']
9
9
  s.description = %q{ElasticSearch output plugin for Fluent event collector}
10
10
  s.summary = s.description
11
11
  s.homepage = 'https://github.com/uken/fluent-plugin-elasticsearch'
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
 
19
19
  s.add_runtime_dependency 'fluentd', '~> 0'
20
20
  s.add_runtime_dependency 'patron', '~> 0'
21
- s.add_runtime_dependency 'elasticsearch', '~> 0'
21
+ s.add_runtime_dependency 'elasticsearch', '>= 0'
22
22
 
23
23
  s.add_development_dependency 'rake', '~> 0'
24
24
  s.add_development_dependency 'webmock', '~> 1'
@@ -2,12 +2,20 @@
2
2
  require 'date'
3
3
  require 'patron'
4
4
  require 'elasticsearch'
5
+ require 'uri'
5
6
 
6
7
  class Fluent::ElasticsearchOutput < Fluent::BufferedOutput
8
+ class ConnectionFailure < StandardError; end
9
+
7
10
  Fluent::Plugin.register_output('elasticsearch', self)
8
11
 
9
12
  config_param :host, :string, :default => 'localhost'
10
13
  config_param :port, :integer, :default => 9200
14
+ config_param :user, :string, :default => nil
15
+ config_param :password, :string, :default => nil
16
+ config_param :path, :string, :default => nil
17
+ config_param :scheme, :string, :default => 'http'
18
+ config_param :hosts, :string, :default => nil
11
19
  config_param :logstash_format, :bool, :default => false
12
20
  config_param :logstash_prefix, :string, :default => "logstash"
13
21
  config_param :logstash_dateformat, :string, :default => "%Y.%m.%d"
@@ -16,7 +24,6 @@ class Fluent::ElasticsearchOutput < Fluent::BufferedOutput
16
24
  config_param :index_name, :string, :default => "fluentd"
17
25
  config_param :id_key, :string, :default => nil
18
26
  config_param :parent_key, :string, :default => nil
19
- config_param :hosts, :string, :default => nil
20
27
  config_param :request_timeout, :time, :default => 5
21
28
 
22
29
  include Fluent::SetTagKeyMixin
@@ -37,26 +44,66 @@ class Fluent::ElasticsearchOutput < Fluent::BufferedOutput
37
44
  def client
38
45
  @_es ||= begin
39
46
  adapter_conf = lambda {|f| f.adapter :patron }
40
- transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new({ hosts: get_hosts,
41
- options: {
42
- reload_connections: true,
43
- retry_on_failure: 5,
44
- transport_options: {
45
- request: { timeout: @request_timeout }
46
- }
47
- }}, &adapter_conf)
48
- Elasticsearch::Client.new transport: transport
47
+ transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(get_connection_options.merge(
48
+ options: {
49
+ reload_connections: true,
50
+ retry_on_failure: 5,
51
+ transport_options: {
52
+ request: { timeout: @request_timeout }
53
+ }
54
+ }), &adapter_conf)
55
+ es = Elasticsearch::Client.new transport: transport
56
+
57
+ begin
58
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description})!" unless es.ping
59
+ rescue Faraday::ConnectionFailed => e
60
+ raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description})! #{e.message}"
61
+ end
62
+
63
+ log.info "Connection opened to Elasticsearch cluster => #{connection_options_description}"
64
+ es
49
65
  end
50
- raise "Can not reach Elasticsearch cluster (#{@host}:#{@port})!" unless @_es.ping
51
- @_es
52
66
  end
53
67
 
54
- def get_hosts
55
- if @hosts
56
- @hosts.split(',').map {|x| hp = x.split(':'); { host: hp[0], port: hp[1] || @port } }.compact
57
- else
58
- [{host: @host, port: @port }]
59
- end
68
+ def get_connection_options
69
+ raise "`password` must be present if `user` is present" if @user && !@password
70
+
71
+ hosts = if @hosts
72
+ @hosts.split(',').map do |host_str|
73
+ # Support legacy hosts format host:port,host:port,host:port...
74
+ if host_str.match(%r{^[^:]+\:\d+$})
75
+ {
76
+ host: host_str.split(':')[0],
77
+ port: (host_str.split(':')[1] || @port).to_i,
78
+ scheme: @scheme
79
+ }
80
+ else
81
+ # New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
82
+ uri = URI(host_str)
83
+ %w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
84
+ hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
85
+ hash
86
+ end
87
+ end
88
+ end.compact
89
+ else
90
+ [{host: @host, port: @port, scheme: @scheme}]
91
+ end.each do |host|
92
+ host.merge!(user: @user, password: @password) if !host[:user] && @user
93
+ host.merge!(path: @path) if !host[:path] && @path
94
+ end
95
+
96
+ {
97
+ hosts: hosts
98
+ }
99
+ end
100
+
101
+ def connection_options_description
102
+ get_connection_options[:hosts].map do |host_info|
103
+ attributes = host_info.dup
104
+ attributes[:password] = 'obfuscated' if attributes.has_key?(:password)
105
+ attributes.inspect
106
+ end.join(', ')
60
107
  end
61
108
 
62
109
  def format(tag, time, record)
@@ -73,7 +120,11 @@ class Fluent::ElasticsearchOutput < Fluent::BufferedOutput
73
120
  chunk.msgpack_each do |tag, time, record|
74
121
  next unless record.is_a? Hash
75
122
  if @logstash_format
76
- record.merge!({"@timestamp" => Time.at(time).to_datetime.to_s}) unless record.has_key?("@timestamp")
123
+ if record.has_key?("@timestamp")
124
+ time = Time.parse record["@timestamp"]
125
+ else
126
+ record.merge!({"@timestamp" => Time.at(time).to_datetime.to_s})
127
+ end
77
128
  if @utc_index
78
129
  target_index = "#{@logstash_prefix}-#{Time.at(time).getutc.strftime("#{@logstash_dateformat}")}"
79
130
  else
@@ -105,6 +156,18 @@ class Fluent::ElasticsearchOutput < Fluent::BufferedOutput
105
156
  end
106
157
 
107
158
  def send(data)
108
- client.bulk body: data
159
+ retries = 0
160
+ begin
161
+ client.bulk body: data
162
+ rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
163
+ if retries < 2
164
+ retries += 1
165
+ @_es = nil
166
+ log.warn "Could not push logs to Elasticsearch, resetting connection and trying again. #{e.message}"
167
+ sleep 2**retries
168
+ retry
169
+ end
170
+ raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
171
+ end
109
172
  end
110
173
  end
@@ -6,11 +6,11 @@ require 'fluent/plugin/out_elasticsearch'
6
6
  require 'webmock/test_unit'
7
7
  require 'date'
8
8
 
9
- require 'helper'
10
-
11
- $:.push File.expand_path("../lib", __FILE__)
9
+ $:.push File.expand_path("../..", __FILE__)
12
10
  $:.push File.dirname(__FILE__)
13
11
 
12
+ require 'helper'
13
+
14
14
  WebMock.disable_net_connect!
15
15
 
16
16
  class ElasticsearchOutput < Test::Unit::TestCase
@@ -55,6 +55,87 @@ class ElasticsearchOutput < Test::Unit::TestCase
55
55
  end
56
56
  end
57
57
 
58
+ def test_configure
59
+ config = %{
60
+ host logs.google.com
61
+ port 777
62
+ scheme https
63
+ path /es/
64
+ user john
65
+ password doe
66
+ }
67
+ instance = driver('test', config).instance
68
+
69
+ assert_equal 'logs.google.com', instance.host
70
+ assert_equal 777, instance.port
71
+ assert_equal 'https', instance.scheme
72
+ assert_equal '/es/', instance.path
73
+ assert_equal 'john', instance.user
74
+ assert_equal 'doe', instance.password
75
+ end
76
+
77
+ def test_legacy_hosts_list
78
+ config = %{
79
+ hosts host1:50,host2:100
80
+ scheme https
81
+ path /es/
82
+ }
83
+ instance = driver('test', config).instance
84
+
85
+ assert_equal 2, instance.get_connection_options[:hosts].length
86
+ host1, host2 = instance.get_connection_options[:hosts]
87
+
88
+ assert_equal 'host1', host1[:host]
89
+ assert_equal 50, host1[:port]
90
+ assert_equal 'https', host1[:scheme]
91
+ assert_equal '/es/', host2[:path]
92
+ end
93
+
94
+ def test_hosts_list
95
+ config = %{
96
+ hosts https://john:password@host1:443/elastic/,http://host2
97
+ path /default_path
98
+ user default_user
99
+ password default_password
100
+ }
101
+ instance = driver('test', config).instance
102
+
103
+ assert_equal 2, instance.get_connection_options[:hosts].length
104
+ host1, host2 = instance.get_connection_options[:hosts]
105
+
106
+ assert_equal 'host1', host1[:host]
107
+ assert_equal 443, host1[:port]
108
+ assert_equal 'https', host1[:scheme]
109
+ assert_equal 'john', host1[:user]
110
+ assert_equal 'password', host1[:password]
111
+ assert_equal '/elastic/', host1[:path]
112
+
113
+ assert_equal 'host2', host2[:host]
114
+ assert_equal 'http', host2[:scheme]
115
+ assert_equal 'default_user', host2[:user]
116
+ assert_equal 'default_password', host2[:password]
117
+ assert_equal '/default_path', host2[:path]
118
+ end
119
+
120
+ def test_single_host_params_and_defaults
121
+ config = %{
122
+ host logs.google.com
123
+ user john
124
+ password doe
125
+ }
126
+ instance = driver('test', config).instance
127
+
128
+ assert_equal 1, instance.get_connection_options[:hosts].length
129
+ host1 = instance.get_connection_options[:hosts][0]
130
+
131
+ assert_equal 'logs.google.com', host1[:host]
132
+ assert_equal 9200, host1[:port]
133
+ assert_equal 'http', host1[:scheme]
134
+ assert_equal 'john', host1[:user]
135
+ assert_equal 'doe', host1[:password]
136
+ assert_equal nil, host1[:path]
137
+ end
138
+
58
139
  def test_writes_to_default_index
59
140
  stub_elastic_ping
60
141
  stub_elastic
@@ -332,4 +413,23 @@ class ElasticsearchOutput < Test::Unit::TestCase
332
413
  driver.emit("some garbage string")
333
414
  driver.run
334
415
  end
416
+
417
+ def test_connection_failed_retry
418
+ connection_resets = 0
419
+
420
+ stub_elastic_ping(url="http://localhost:9200").with do |req|
421
+ connection_resets += 1
422
+ end
423
+
424
+ stub_request(:post, "http://localhost:9200/_bulk").with do |req|
425
+ raise Faraday::ConnectionFailed, "Test message"
426
+ end
427
+
428
+ driver.emit(sample_record)
429
+
430
+ assert_raise(Fluent::ElasticsearchOutput::ConnectionFailure) {
431
+ driver.run
432
+ }
433
+ assert_equal(connection_resets, 3)
434
+ end
335
435
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - diogo
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-01 00:00:00.000000000 Z
12
+ date: 2014-10-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fluentd
@@ -43,14 +43,14 @@ dependencies:
43
43
  name: elasticsearch
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - "~>"
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
48
  version: '0'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "~>"
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
56
  - !ruby/object:Gem::Dependency
@@ -83,7 +83,8 @@ dependencies:
83
83
  version: '1'
84
84
  description: ElasticSearch output plugin for Fluent event collector
85
85
  email:
86
- - team@uken.com
86
+ - pitr@uken.com
87
+ - diogo@uken.com
87
88
  executables: []
88
89
  extensions: []
89
90
  extra_rdoc_files: []
@@ -119,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
120
  version: '0'
120
121
  requirements: []
121
122
  rubyforge_project:
122
- rubygems_version: 2.2.0
123
+ rubygems_version: 2.2.2
123
124
  signing_key:
124
125
  specification_version: 4
125
126
  summary: ElasticSearch output plugin for Fluent event collector