logstash-input-multirds 0.0.2 → 0.1.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: 7dfd5f53cd59c0c7d8c89f79973a92bacc911b87
4
- data.tar.gz: 184bb1f97be2b8d5896427d9ac4b5b92a585894b
3
+ metadata.gz: 93c7bd1bdecf5dcf2dfa932257933cdb60ba75aa
4
+ data.tar.gz: c8900191f3a7f8b48de91d4f1d3cd88670a70295
5
5
  SHA512:
6
- metadata.gz: ef525b86cb6430c1e35776a1101c22d7f3355d36c95ca79f7dc3a839fd59223c9c025c76411bcfa591ca54a79dd19e18ece8107948de4099b10ad760273dedef
7
- data.tar.gz: 6ef768857f2d767e864f42d8afadc513fb15d722dd565e891e9c9927893bcbca2c26213d5017be1b3ea79f3e696d7d8d8c43949c127fb560fedb56b7c734f1a9
6
+ metadata.gz: f95d31355e93ba96590b29d1559d5102373df4bcf1f07830e85260dbfc3580d208db6e8a84f51292f5642de56bb4809490c9c17144b45121cd9fed8574be1df2
7
+ data.tar.gz: a5a5a0327183bac93810f10b6180400c82846fdfe82f803deae5961431590921c0e088c1f07da861901dfd19944e40704e96ad1c239c6ee548bb22d19659a34f
data/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # Logstash Input Multi-RDS
2
2
 
3
- Inputs RDS logs because Postgres doesn't support cloudwatch. Forked from discourse/logstash-input-rds I needed competing consumer and multi-db support. Uses DynamoDB for distributed locking and marker tracking. The plugin will create the table automatically if you give it permission, otherwise you can create it yourself where the table name is `group_name` and the primary key is a string called `id`.
3
+ RDS Postgres instances do not support cloudwatch, logs must be polled using the AWS API. This logstash input plugin polls RDS logs at a configured interval with support for multiple instances of logstash and multiple RDS databases (thus the name: multirds). Locking and marker tracking are done with DynamoDB.
4
+
5
+ The plugin will automatically create a DynamoDB table, but if you want to do it manually, the name must match the configured `group_name` and the primary key is `id`.
6
+
7
+ ## Special thanks
8
+
9
+ * This is a fork of logstash input RDS (https://github.com/discourse/logstash-input-rds) though I think I made too many changes to ever merge it back in
10
+ * Using DynamoDB for locking and marker tracking was stolen from the Kinesis input plugin (https://github.com/logstash-plugins/logstash-input-kinesis) (which we're also using for Cloudwatch logs)
11
+ * The lock code was taken from the Dynalock gem (https://github.com/tourlane/dynalock) which works fine, but I needed to store extra data on the lock record so I just copied their code
12
+
13
+ ## Configuration
14
+
4
15
  ```
5
16
  input {
6
17
  multirds {
@@ -13,8 +24,6 @@ Inputs RDS logs because Postgres doesn't support cloudwatch. Forked from discour
13
24
  }
14
25
  ```
15
26
 
16
- ## Configuration
17
-
18
27
  * `region`: The AWS region for RDS. The AWS SDK reads this info from the usual places, so it's not required, but if you don't set it somewhere the plugin won't run
19
28
  * **required**: false
20
29
 
@@ -1,136 +1,154 @@
1
- # encoding: utf-8
2
- require "logstash/inputs/base"
3
- require "logstash/namespace"
4
- require "stud/interval"
5
- require "aws-sdk"
6
- require "logstash/inputs/multirds/patch"
7
- require "logstash/plugin_mixins/aws_config"
8
- require "time"
9
- require "socket"
1
+ require 'logstash/inputs/base'
2
+ require 'logstash/namespace'
3
+ require 'stud/interval'
4
+ require 'aws-sdk'
5
+ require 'logstash/inputs/multirds/patch'
6
+ require 'logstash/plugin_mixins/aws_config'
7
+ require 'time'
8
+ require 'socket'
10
9
  Aws.eager_autoload!
11
10
 
12
11
  class LogStash::Inputs::Multirds < LogStash::Inputs::Base
13
12
  include LogStash::PluginMixins::AwsConfig::V2
14
13
 
15
- config_name "multirds"
14
+ config_name 'multirds'
16
15
  milestone 1
17
- default :codec, "plain"
18
-
19
- config :instance_name_pattern, :validate => :string, :default => '.*'
20
- config :log_file_name_pattern, :validate => :string, :default => '.*'
21
- config :polling_frequency, :validate => :number, :default => 600
22
- config :group_name, :validate => :string, :required => true
23
- config :client_id, :validate => :string
24
-
16
+ default :codec, 'plain'
17
+
18
+ config :instance_name_pattern, validate: :string, default: '.*'
19
+ config :log_file_name_pattern, validate: :string, default: '.*'
20
+ config :polling_frequency, validate: :number, default: 600
21
+ config :group_name, validate: :string, required: true
22
+ config :client_id, validate: :string
23
+
25
24
  def ensure_lock_table(db, table)
26
25
  begin
27
- tables = db.list_tables({
28
-
29
- })
30
- return true if tables.to_h[:table_names].to_a.include?(table)
31
- # TODO: there is a potential race condition here where a table could come back in list_tables but not be in ACTIVE state we should check this better
32
- result = db.create_table({
33
- table_name: table,
34
- key_schema: [
35
- {
36
- attribute_name: 'id',
37
- key_type: 'HASH'
38
- }
39
- ],
40
- attribute_definitions: [
41
- {
42
- attribute_name: 'id',
43
- attribute_type: 'S'
44
- }
45
- ],
46
- provisioned_throughput: {
47
- read_capacity_units: 10,
48
- write_capacity_units: 10
49
- }
50
- })
51
-
52
- # wait here for the table to be ready
53
- (1..10).each do |i|
54
- sleep i
55
- rsp = db.describe_table({
56
- table_name: table
57
- })
58
- return true if rsp.to_h[:table][:table_status] == 'ACTIVE'
59
- end
60
- rescue => e
61
- @logger.error "logstash-input-multirds ensure_lock_table exception\n #{e}"
62
- return false
26
+ tables = db.list_tables({
27
+
28
+ })
29
+ return true if tables.to_h[:table_names].to_a.include?(table)
30
+ # TODO: there is a potential race condition here where a table could come back in list_tables but not be in ACTIVE state we should check this better
31
+ db.create_table(
32
+ table_name: table,
33
+ key_schema: [
34
+ {
35
+ attribute_name: 'id',
36
+ key_type: 'HASH'
37
+ }
38
+ ],
39
+ attribute_definitions: [
40
+ {
41
+ attribute_name: 'id',
42
+ attribute_type: 'S'
43
+ }
44
+ ],
45
+ provisioned_throughput: {
46
+ read_capacity_units: 10,
47
+ write_capacity_units: 10
48
+ }
49
+ )
50
+
51
+ # wait here for the table to be ready
52
+ (1..10).each do |i|
53
+ sleep i
54
+ rsp = db.describe_table(
55
+ table_name: table
56
+ )
57
+ return true if rsp.to_h[:table][:table_status] == 'ACTIVE'
58
+ end
59
+ rescue StandardError => e
60
+ @logger.error "logstash-input-multirds ensure_lock_table exception\n #{e}"
61
+ return false
63
62
  end
64
- return false
63
+ false
65
64
  end
66
- def acquire_lock(db, table, id, lock_owner, expire_time: 10)
65
+ def acquire_lock(db, table, id, lock_owner, expire_time)
67
66
  begin
68
- db.update_item({
69
- key: {
70
- id: id
71
- },
72
- table_name: table,
73
- update_expression: "SET lock_owner = :lock_owner, expires = :expires",
74
- expression_attribute_values: {
75
- ':lock_owner' => lock_owner,
76
- ':expires' => Time.now.utc.to_i + expire_time
77
- },
78
- return_values: "UPDATED_NEW",
79
- condition_expression: "attribute_not_exists(lock_owner) OR lock_owner = :lock_owner OR expires < :expires"
80
- })
81
- rescue => e
67
+ db.update_item(
68
+ key: {
69
+ id: id
70
+ },
71
+ table_name: table,
72
+ update_expression: 'SET lock_owner = :lock_owner, expires = :expires',
73
+ expression_attribute_values: {
74
+ ':lock_owner' => lock_owner,
75
+ ':expires' => Time.now.utc.to_i + expire_time
76
+ },
77
+ return_values: 'UPDATED_NEW',
78
+ # condition_expression: 'attribute_not_exists(lock_owner) OR lock_owner = :lock_owner OR expires < :expires'
79
+ condition_expression: 'attribute_not_exists(lock_owner) OR expires < :expires'
80
+ )
81
+ # TODO: handle condition exception else throw error
82
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
83
+ return false
84
+ rescue StandardError => e
82
85
  @logger.error "logstash-input-multirds acquire_lock exception\n #{e}"
86
+ false
83
87
  end
88
+ true
84
89
  end
90
+
85
91
  def get_logfile_list(rds, instance_pattern, logfile_pattern)
86
92
  log_files = []
87
93
  begin
88
94
  dbs = rds.describe_db_instances
89
95
  dbs.to_h[:db_instances].each do |db|
90
- next unless db[:db_instance_identifier] =~ /#{instance_pattern}/
91
- logs = rds.describe_db_log_files({
92
- db_instance_identifier: db[:db_instance_identifier]
93
- })
94
-
95
- logs.to_h[:describe_db_log_files].each do |log|
96
- next unless log[:log_file_name] =~ /#{logfile_pattern}/
97
- log[:db_instance_identifier] = db[:db_instance_identifier]
98
- log_files.push(log)
99
- end
96
+ next unless db[:db_instance_identifier] =~ /#{instance_pattern}/
97
+ logs = rds.describe_db_log_files(
98
+ db_instance_identifier: db[:db_instance_identifier]
99
+ )
100
+
101
+ logs.to_h[:describe_db_log_files].each do |log|
102
+ next unless log[:log_file_name] =~ /#{logfile_pattern}/
103
+ log[:db_instance_identifier] = db[:db_instance_identifier]
104
+ log_files.push(log)
105
+ end
100
106
  end
101
- rescue => e
107
+ rescue StandardError => e
102
108
  @logger.error "logstash-input-multirds get_logfile_list instance_pattern: #{instance_pattern} logfile_pattern:#{logfile_pattern} exception \n#{e}"
103
109
  end
104
110
  log_files
105
- end
106
- def get_logfile_record(db, id, tablename)
107
- res = db.get_item({
108
- key: {
111
+ end
112
+
113
+ def get_logfile_record(db, id, tablename)
114
+ out = {}
115
+ begin
116
+ res = db.get_item(
117
+ key: {
109
118
  id: id
110
- },
111
- table_name: tablename
112
- })
113
- extra_fields = {'marker' => '0:0'}
114
- extra_fields.merge(res.item)
115
- end
116
- def set_logfile_record(db, id, tablename, key, value)
117
- db.update_item({
118
- key: {
119
+ },
120
+ table_name: tablename
121
+ )
122
+ extra_fields = { 'marker' => '0:0' }
123
+ out = extra_fields.merge(res.item)
124
+ rescue StandardError => e
125
+ @logger.error "logstash-input-multirds get_logfile_record exception \n#{e}"
126
+ end
127
+ out
128
+ end
129
+
130
+ def set_logfile_record(db, id, tablename, key, value)
131
+ begin
132
+ out = db.update_item(
133
+ key: {
119
134
  id: id
120
- },
121
- table_name: tablename,
122
- update_expression: "SET #{key} = :v",
123
- expression_attribute_values: {
135
+ },
136
+ table_name: tablename,
137
+ update_expression: "SET #{key} = :v",
138
+ expression_attribute_values: {
124
139
  ':v' => value
125
- },
126
- return_values: "UPDATED_NEW"
127
-
128
- })
129
- end
140
+ },
141
+ return_values: 'UPDATED_NEW'
142
+ )
143
+ rescue StandardError => e
144
+ @logger.error "logstash-input-multirds set_logfile_record exception \n#{e}"
145
+ end
146
+ out
147
+ end
130
148
 
131
149
  def register
132
- @client_id = "#{Socket.gethostname}:#{java.util::UUID.randomUUID.to_s}" unless @client_id
133
- @logger.info "Registering multi-rds input", :instance_name_pattern => @instance_name_pattern, :log_file_name_pattern => @log_file_name_pattern, :group_name => @group_name, :region => @region, :client_id => @client_id
150
+ @client_id ||= "#{Socket.gethostname}:#{java.util::UUID.randomUUID}"
151
+ @logger.info 'Registering multi-rds input', instance_name_pattern: @instance_name_pattern, log_file_name_pattern: @log_file_name_pattern, group_name: @group_name, region: @region, client_id: @client_id
134
152
 
135
153
  @db = Aws::DynamoDB::Client.new aws_options_hash
136
154
  @rds = Aws::RDS::Client.new aws_options_hash
@@ -139,8 +157,8 @@ end
139
157
  end
140
158
 
141
159
  def run(queue)
142
- if !@ready
143
- @logger.warn "multi-rds dynamodb lock table not ready, unable to proceed"
160
+ unless @ready
161
+ @logger.warn 'multi-rds dynamodb lock table not ready, unable to proceed'
144
162
  return false
145
163
  end
146
164
  @thread = Thread.current
@@ -149,67 +167,39 @@ end
149
167
 
150
168
  logs.each do |log|
151
169
  id = "#{log[:db_instance_identifier]}:#{log[:log_file_name]}"
152
- lock = acquire_lock @db, @group_name, id, @client_id
170
+ lock = acquire_lock @db, @group_name, id, @client_id, (@polling_frequency - 1)
153
171
  next unless lock # we won't do anything with the data unless we get a lock on the file
154
-
172
+
155
173
  rec = get_logfile_record @db, id, @group_name
156
174
  next unless rec['marker'].split(':')[1].to_i < log[:size].to_i # No new data in the log file so just continue
157
175
 
158
176
  # start reading log data at the marker
159
177
  more = true
160
178
  marker = rec[:marker]
161
- while more do
162
- rsp = @rds.download_db_log_file_portion(
163
- db_instance_identifier: log[:db_instance_identifier],
164
- log_file_name: log[:log_file_name],
165
- marker: rec[:marker],
166
- )
167
- rsp[:log_file_data].lines.each do |line|
168
- @codec.decode(line) do |event|
169
- decorate event
170
- event.set "rds_instance", log[:db_instance_identifier]
171
- event.set "log_file", log[:log_file_name]
172
- queue << event
173
- end
179
+ while more
180
+ rsp = @rds.download_db_log_file_portion(
181
+ db_instance_identifier: log[:db_instance_identifier],
182
+ log_file_name: log[:log_file_name],
183
+ marker: rec[:marker]
184
+ )
185
+ rsp[:log_file_data].lines.each do |line|
186
+ @codec.decode(line) do |event|
187
+ decorate event
188
+ event.set 'rds_instance', log[:db_instance_identifier]
189
+ event.set 'log_file', log[:log_file_name]
190
+ queue << event
174
191
  end
175
- more = rsp[:additional_data_pending]
176
- marker = rsp[:marker]
192
+ end
193
+ more = rsp[:additional_data_pending]
194
+ marker = rsp[:marker]
177
195
  end
178
196
  # set the marker back in the lock table
179
197
  set_logfile_record @db, id, @group_name, 'marker', marker
180
198
  end
181
199
  end
182
-
183
200
  end
184
201
 
185
202
  def stop
186
203
  Stud.stop! @thread
187
204
  end
188
-
189
- def filename2datetime(name)
190
- parts = name.match /(\d{4})-(\d{2})-(\d{2})-(\d{2})$/
191
- Time.utc parts[1], parts[2], parts[3], parts[4]
192
- end
193
-
194
- private
195
- module SinceDB
196
- class File
197
- def initialize(file)
198
- @db = file
199
- end
200
-
201
- def read
202
- if ::File.exists?(@db)
203
- content = ::File.read(@db).chomp.strip
204
- return content.empty? ? Time.new : Time.parse(content)
205
- else
206
- return Time.new("1999-01-01")
207
- end
208
- end
209
-
210
- def write(time)
211
- ::File.open(@db, 'w') { |file| file.write time.to_s }
212
- end
213
- end
214
- end
215
205
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-input-multirds'
3
- s.version = '0.0.2'
3
+ s.version = '0.1.0'
4
4
  s.summary = 'Ingest RDS log files to Logstash with competing consumers and multiple databases'
5
5
 
6
6
  s.authors = ['Robert Labrie']
@@ -8,15 +8,15 @@ Gem::Specification.new do |s|
8
8
  s.homepage = 'https://github.com/robertlabrie/logstash-input-multi-rds'
9
9
 
10
10
  s.require_paths = ['lib']
11
- s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
11
+ s.files = Dir['lib/**/*', 'spec/**/*', 'vendor/**/*', '*.gemspec', '*.md', 'CONTRIBUTORS', 'Gemfile', 'LICENSE', 'NOTICE.TXT']
12
12
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
13
13
 
14
14
  # Special flag to let us know this is actually a logstash plugin
15
15
  s.metadata = { 'logstash_plugin' => 'true', 'logstash_group' => 'input' }
16
16
 
17
17
  # Gem dependencies
18
- s.add_runtime_dependency 'logstash-core-plugin-api', '~> 2.0'
19
18
  s.add_runtime_dependency 'logstash-codec-plain'
19
+ s.add_runtime_dependency 'logstash-core-plugin-api', '~> 2.0'
20
20
  s.add_runtime_dependency 'logstash-mixin-aws'
21
21
  s.add_runtime_dependency 'stud', '>= 0.0.22'
22
22
  s.add_development_dependency 'logstash-devutils', '>= 0.0.16'
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-input-multirds
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Labrie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-21 00:00:00.000000000 Z
11
+ date: 2018-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: logstash-core-plugin-api
14
+ name: logstash-codec-plain
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: logstash-codec-plain
28
+ name: logstash-core-plugin-api
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '2.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '2.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: logstash-mixin-aws
43
43
  requirement: !ruby/object:Gem::Requirement