fluent-plugin-cloudwatch-logs 0.9.5 → 0.10.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
  SHA256:
3
- metadata.gz: 13ef3e0d5f4fd4c12c49b16ccbd07db237b7cff26ea272e4ef6337b09b02e975
4
- data.tar.gz: 8cefeed014b1842ccce9f15479ad8cc54aa51b976281abb5d3e1c8828239aaa3
3
+ metadata.gz: 105e96fa516c4f972fc2b01f83c1daa30d39ff4b827b46b811b9f30514231516
4
+ data.tar.gz: 85741a1b0ccad0f9e207c837e7417b786ef68d1fc1552cbe80b73030e5b4191b
5
5
  SHA512:
6
- metadata.gz: 41ca6fa20d0c26955fcbab7f7a5a9fb8c032a5fd09f8676ccaaf5e4dd12694ea74c7c5320590f076405c691d436bc87b820e6bc9b75dc516062b2f3e09d8a377
7
- data.tar.gz: 1f5bbd694c5962de73ee1e0ca622d8892437fb82d00b76ed87086feeba49f6d2c3a4904edad81bec88ba2d3ca47701c15cb56395470f19742c16b3168dd870e5
6
+ metadata.gz: 84a8458e9704102609b382b9cb8fe4c332a1c1b9c9562b89b959961bb3624958cbffc1f965eba2a502c10a0e45f1ceb427a987681d5420c35663bc6df88295ed
7
+ data.tar.gz: d6e22ad22c4eef5cb6c8fab7690ca75585e7ad99756926181f50c43fef70df0e52e12722e14809b793947e56ed8199fc393a318f42308f05fd6d23d6aa9a4b13
data/README.md CHANGED
@@ -178,6 +178,9 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
178
178
  #<parse>
179
179
  # @type none # or csv, tsv, regexp etc.
180
180
  #</parse>
181
+ #<storage>
182
+ # @type local # or redis, memcached, etc.
183
+ #</storage>
181
184
  </source>
182
185
  ```
183
186
 
@@ -194,7 +197,8 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
194
197
  * `log_stream_name`: name of log stream to fetch logs
195
198
  * `region`: AWS Region. See [Authentication](#authentication) for more information.
196
199
  * `throttling_retry_seconds`: time period in seconds to retry a request when aws CloudWatch rate limit exceeds (default: nil)
197
- * `state_file`: file to store current state (e.g. next\_forward\_token)
200
+ * `include_metadata`: include metadata such as `log_group_name` and `log_stream_name`. (default: false)
201
+ * `state_file`: file to store current state (e.g. next\_forward\_token). This parameter is deprecated. Use `<storage>` instead.
198
202
  * `tag`: fluentd tag
199
203
  * `use_log_stream_name_prefix`: to use `log_stream_name` as log stream name prefix (default false)
200
204
  * `use_todays_log_stream`: use todays and yesterdays date as log stream name prefix (formatted YYYY/MM/DD). (default: `false`)
@@ -204,6 +208,7 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
204
208
  * `time_range_format`: specify time format for time range. (default: `%Y-%m-%d %H:%M:%S`)
205
209
  * `format`: specify CloudWatchLogs' log format. (default `nil`)
206
210
  * `<parse>`: specify parser plugin configuration. see also: https://docs.fluentd.org/v/1.0/parser#how-to-use
211
+ * `<storage>`: specify storage plugin configuration. see also: https://docs.fluentd.org/v/1.0/storage#how-to-use
207
212
 
208
213
  ## Test
209
214
 
@@ -2,7 +2,7 @@ module Fluent
2
2
  module Plugin
3
3
  module Cloudwatch
4
4
  module Logs
5
- VERSION = "0.9.5"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
8
8
  end
@@ -8,7 +8,9 @@ module Fluent::Plugin
8
8
  class CloudwatchLogsInput < Input
9
9
  Fluent::Plugin.register_input('cloudwatch_logs', self)
10
10
 
11
- helpers :parser, :thread, :compat_parameters
11
+ helpers :parser, :thread, :compat_parameters, :storage
12
+
13
+ DEFAULT_STORAGE_TYPE = 'local'
12
14
 
13
15
  config_param :aws_key_id, :string, default: nil, secret: true
14
16
  config_param :aws_sec_key, :string, default: nil, secret: true
@@ -21,7 +23,8 @@ module Fluent::Plugin
21
23
  config_param :log_group_name, :string
22
24
  config_param :log_stream_name, :string, default: nil
23
25
  config_param :use_log_stream_name_prefix, :bool, default: false
24
- config_param :state_file, :string
26
+ config_param :state_file, :string, default: nil,
27
+ deprecated: "Use <stroage> instead."
25
28
  config_param :fetch_interval, :time, default: 60
26
29
  config_param :http_proxy, :string, default: nil
27
30
  config_param :json_handler, :enum, list: [:yajl, :json], default: :yajl
@@ -31,11 +34,18 @@ module Fluent::Plugin
31
34
  config_param :end_time, :string, default: nil
32
35
  config_param :time_range_format, :string, default: "%Y-%m-%d %H:%M:%S"
33
36
  config_param :throttling_retry_seconds, :time, default: nil
37
+ config_param :include_metadata, :bool, default: false
34
38
 
35
39
  config_section :parse do
36
40
  config_set_default :@type, 'none'
37
41
  end
38
42
 
43
+ config_section :storage do
44
+ config_set_default :usage, 'store_next_tokens'
45
+ config_set_default :@type, DEFAULT_STORAGE_TYPE
46
+ config_set_default :persistent, false
47
+ end
48
+
39
49
  def initialize
40
50
  super
41
51
 
@@ -53,6 +63,7 @@ module Fluent::Plugin
53
63
  if @start_time && @end_time && (@end_time < @start_time)
54
64
  raise Fluent::ConfigError, "end_time(#{@end_time}) should be greater than start_time(#{@start_time})."
55
65
  end
66
+ @next_token_storage = storage_create(usage: 'store_next_tokens', conf: config, default_type: DEFAULT_STORAGE_TYPE)
56
67
  end
57
68
 
58
69
  def start
@@ -99,20 +110,28 @@ module Fluent::Plugin
99
110
  end
100
111
  end
101
112
 
102
- def state_file_for(log_stream_name)
103
- return "#{@state_file}_#{log_stream_name.gsub(File::SEPARATOR, '-')}" if log_stream_name
104
- return @state_file
113
+ def state_key_for(log_stream_name)
114
+ if log_stream_name
115
+ "#{@state_file}_#{log_stream_name.gsub(File::SEPARATOR, '-')}"
116
+ else
117
+ @state_file
118
+ end
119
+ end
120
+
121
+ def migrate_state_file_to_storage(log_stream_name)
122
+ @next_token_storage.put(:"#{state_key_for(log_stream_name)}", File.read(state_key_for(log_stream_name)).chomp)
123
+ File.delete(state_key_for(log_stream_name))
105
124
  end
106
125
 
107
126
  def next_token(log_stream_name)
108
- return nil unless File.exist?(state_file_for(log_stream_name))
109
- File.read(state_file_for(log_stream_name)).chomp
127
+ if @next_token_storage.persistent && File.exist?(state_key_for(log_stream_name))
128
+ migrate_state_file_to_storage(log_stream_name)
129
+ end
130
+ @next_token_storage.get(:"#{state_key_for(log_stream_name)}")
110
131
  end
111
132
 
112
133
  def store_next_token(token, log_stream_name = nil)
113
- File.open(state_file_for(log_stream_name), 'w') do |f|
114
- f.write token
115
- end
134
+ @next_token_storage.put(:"#{state_key_for(log_stream_name)}", token)
116
135
  end
117
136
 
118
137
  def run
@@ -130,8 +149,16 @@ module Fluent::Plugin
130
149
  log_streams.each do |log_stream|
131
150
  log_stream_name = log_stream.log_stream_name
132
151
  events = get_events(log_stream_name)
152
+ metadata = if @include_metadata
153
+ {
154
+ "log_stream_name" => log_stream_name,
155
+ "log_group_name" => @log_group_name
156
+ }
157
+ else
158
+ {}
159
+ end
133
160
  events.each do |event|
134
- emit(log_stream_name, event)
161
+ emit(log_stream_name, event, metadata)
135
162
  end
136
163
  end
137
164
  rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
@@ -140,8 +167,16 @@ module Fluent::Plugin
140
167
  end
141
168
  else
142
169
  events = get_events(@log_stream_name)
170
+ metadata = if @include_metadata
171
+ {
172
+ "log_stream_name" => @log_stream_name,
173
+ "log_group_name" => @log_group_name
174
+ }
175
+ else
176
+ {}
177
+ end
143
178
  events.each do |event|
144
- emit(log_stream_name, event)
179
+ emit(log_stream_name, event, metadata)
145
180
  end
146
181
  end
147
182
  end
@@ -149,18 +184,24 @@ module Fluent::Plugin
149
184
  end
150
185
  end
151
186
 
152
- def emit(stream, event)
187
+ def emit(stream, event, metadata)
153
188
  if @parser
154
189
  @parser.parse(event.message) {|time,record|
155
190
  if @use_aws_timestamp
156
191
  time = (event.timestamp / 1000).floor
157
192
  end
193
+ unless metadata.empty?
194
+ record.merge!("metadata" => metadata)
195
+ end
158
196
  router.emit(@tag, time, record)
159
197
  }
160
198
  else
161
199
  time = (event.timestamp / 1000).floor
162
200
  begin
163
201
  record = @json_handler.load(event.message)
202
+ unless metadata.empty?
203
+ record.merge!("metadata" => metadata)
204
+ end
164
205
  router.emit(@tag, time, record)
165
206
  rescue JSON::ParserError, Yajl::ParseError => error # Catch parser errors
166
207
  log.error "Invalid JSON encountered while parsing event.message"
@@ -377,8 +377,7 @@ module Fluent::Plugin
377
377
  end
378
378
  rescue Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException, Aws::CloudWatchLogs::Errors::DataAlreadyAcceptedException => err
379
379
  sleep 1 # to avoid too many API calls
380
- log_stream = find_log_stream(group_name, stream_name)
381
- store_next_sequence_token(group_name, stream_name, log_stream.upload_sequence_token)
380
+ store_next_sequence_token(group_name, stream_name, err.expected_sequence_token)
382
381
  log.warn "updating upload sequence token forcefully because unrecoverable error occured", {
383
382
  "error" => err,
384
383
  "log_group" => group_name,
@@ -99,6 +99,34 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
99
99
  assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
100
100
  end
101
101
 
102
+ def test_emit_with_metadata
103
+ create_log_stream
104
+
105
+ time_ms = (Time.now.to_f * 1000).floor
106
+ put_log_events([
107
+ {timestamp: time_ms, message: '{"cloudwatch":"logs1"}'},
108
+ {timestamp: time_ms, message: '{"cloudwatch":"logs2"}'},
109
+ ])
110
+
111
+ sleep 5
112
+
113
+ d = create_driver(default_config + %[include_metadata true])
114
+ d.run(expect_emits: 2, timeout: 5)
115
+
116
+ emits = d.events
117
+ assert_true(emits[0][2].has_key?("metadata"))
118
+ assert_true(emits[1][2].has_key?("metadata"))
119
+ emits[0][2].delete_if {|k, v|
120
+ k == "metadata"
121
+ }
122
+ emits[1][2].delete_if {|k, v|
123
+ k == "metadata"
124
+ }
125
+ assert_equal(2, emits.size)
126
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs1'}], emits[0])
127
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
128
+ end
129
+
102
130
  def test_emit_with_aws_timestamp
103
131
  create_log_stream
104
132
 
@@ -173,7 +201,6 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
173
201
  '@type' => 'cloudwatch_logs',
174
202
  'log_group_name' => "#{log_group_name}",
175
203
  'log_stream_name' => "#{log_stream_name}",
176
- 'state_file' => '/tmp/state',
177
204
  }
178
205
  cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_key_id)) if ENV['aws_key_id']
179
206
  cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_sec_key)) if ENV['aws_sec_key']
@@ -183,7 +210,9 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
183
210
  csv_format_config = config_element('ROOT', '', cloudwatch_config, [
184
211
  config_element('parse', '', {'@type' => 'csv',
185
212
  'keys' => 'time,message',
186
- 'time_key' => 'time'})
213
+ 'time_key' => 'time'}),
214
+ config_element('storage', '', {'@type' => 'local',
215
+ 'path' => '/tmp/state'})
187
216
  ])
188
217
  create_log_stream
189
218
 
@@ -205,6 +234,54 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
205
234
  assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs2"}], emits[1])
206
235
  end
207
236
 
237
+ test "emit with <parse> csv with metadata" do
238
+ cloudwatch_config = {'tag' => "test",
239
+ '@type' => 'cloudwatch_logs',
240
+ 'log_group_name' => "#{log_group_name}",
241
+ 'log_stream_name' => "#{log_stream_name}",
242
+ 'include_metadata' => true,
243
+ }
244
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_key_id)) if ENV['aws_key_id']
245
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_sec_key)) if ENV['aws_sec_key']
246
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(region)) if ENV['region']
247
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(endpoint)) if ENV['endpoint']
248
+
249
+ csv_format_config = config_element('ROOT', '', cloudwatch_config, [
250
+ config_element('parse', '', {'@type' => 'csv',
251
+ 'keys' => 'time,message',
252
+ 'time_key' => 'time'}),
253
+ config_element('storage', '', {'@type' => 'local',
254
+ 'path' => '/tmp/state'})
255
+
256
+ ])
257
+ create_log_stream
258
+
259
+ time_ms = (Time.now.to_f * 1000).floor
260
+ log_time_ms = time_ms - 10000
261
+ put_log_events([
262
+ {timestamp: time_ms, message: Time.at(log_time_ms/1000.floor).to_s + ",Cloudwatch non json logs1"},
263
+ {timestamp: time_ms, message: Time.at(log_time_ms/1000.floor).to_s + ",Cloudwatch non json logs2"},
264
+ ])
265
+
266
+ sleep 5
267
+
268
+ d = create_driver(csv_format_config)
269
+ d.run(expect_emits: 2, timeout: 5)
270
+
271
+ emits = d.events
272
+ assert_true(emits[0][2].has_key?("metadata"))
273
+ assert_true(emits[1][2].has_key?("metadata"))
274
+ emits[0][2].delete_if {|k, v|
275
+ k == "metadata"
276
+ }
277
+ emits[1][2].delete_if {|k, v|
278
+ k == "metadata"
279
+ }
280
+ assert_equal(2, emits.size)
281
+ assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs1"}], emits[0])
282
+ assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs2"}], emits[1])
283
+ end
284
+
208
285
  def test_emit_width_format
209
286
  create_log_stream
210
287
 
@@ -246,7 +323,6 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
246
323
  '@type' => 'cloudwatch_logs',
247
324
  'log_group_name' => "#{log_group_name}",
248
325
  'log_stream_name' => "#{log_stream_name}",
249
- 'state_file' => '/tmp/state',
250
326
  }
251
327
  cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_key_id)) if ENV['aws_key_id']
252
328
  cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_sec_key)) if ENV['aws_sec_key']
@@ -256,7 +332,9 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
256
332
  regex_format_config = config_element('ROOT', '', cloudwatch_config, [
257
333
  config_element('parse', '', {'@type' => 'regexp',
258
334
  'expression' => "/^(?<cloudwatch>[^ ]*)?/",
259
- })
335
+ }),
336
+ config_element('storage', '', {'@type' => 'local',
337
+ 'path' => '/tmp/state'})
260
338
  ])
261
339
  create_log_stream
262
340
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-cloudwatch-logs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryota Arai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-19 00:00:00.000000000 Z
11
+ date: 2020-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd