fluent-plugin-elasticsearch 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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