fluent-plugin-cloudwatch-logs 0.11.1 → 0.13.2

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: 7d78abf2df32b76a85618e07a9164b6b4b59085bb56054b9cff15a947674eb26
4
- data.tar.gz: 935d10a414f4ac4e83d619978695aa98bc2ec784c02ef5e60ec082ac7b39f667
3
+ metadata.gz: 51935a06f74f11a53f106e38e4f08132a7342d6509dc38c0d468d54e19e5fa28
4
+ data.tar.gz: 738e36cb222adca31a2f79e56ec71cc691f26468eb8025bb9d61773d0dd9d633
5
5
  SHA512:
6
- metadata.gz: 8f5113e44e0fb327000423a12292f1d6c25be4e5e491773e3e074b06a9c4206797ac0a3873c5b252da9dbbfa705d7989bb6b84f71d39e4080c5eb882555c914e
7
- data.tar.gz: ee663a6f50e7788703d3bea274c194ea83d4b330d25b284112682247984cf9de344d00a4a7aba8d7f6e7589c396510e0a0970cb48bea73d73841eb1c22e3cd77
6
+ metadata.gz: 06675b2f4beedcf0d39c79bf93c59586c0d11816b97a22ee3f472fa6fc892e666c96f8600cfc8f98fba3b2b82f40dd452ec9b451caf6170da199fcf71ad42bff
7
+ data.tar.gz: 294a5c7fe16c450a58478b982cba8d174d603885234619b5631c1da314b5b7b8a0874424d2b5c997a4790333f99739ecac3cd47a4af97836bf15c9da90ed25af
@@ -0,0 +1,26 @@
1
+ name: Testing on Ubuntu
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ build:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ '2.4', '2.5', '2.6', '2.7' ]
12
+ os:
13
+ - ubuntu-latest
14
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ - name: unit testing
21
+ env:
22
+ CI: true
23
+ run: |
24
+ gem install bundler rake
25
+ bundle install --jobs 4 --retry 3
26
+ bundle exec rake test
@@ -0,0 +1,26 @@
1
+ name: Testing on Windows
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ build:
7
+ runs-on: ${{ matrix.os }}
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [ '2.4', '2.5', '2.6', '2.7' ]
12
+ os:
13
+ - windows-latest
14
+ name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ - name: unit testing
21
+ env:
22
+ CI: true
23
+ run: |
24
+ gem install bundler rake
25
+ bundle install --jobs 4 --retry 3
26
+ bundle exec rake test
data/README.md CHANGED
@@ -165,6 +165,9 @@ Fetch sample log from CloudWatch Logs:
165
165
  # role_session_name ROLE_SESSION_NAME
166
166
  # web_identity_token_file "#{ENV['AWS_WEB_IDENTITY_TOKEN_FILE']}"
167
167
  #</web_identity_credentials>
168
+ #<format>
169
+ # @type ltsv
170
+ #</format>
168
171
  </match>
169
172
  ```
170
173
 
@@ -207,6 +210,7 @@ Fetch sample log from CloudWatch Logs:
207
210
  * `duration_seconds`: The duration, in seconds, of the role session. The value can range from
208
211
  900 seconds (15 minutes) to 43200 seconds (12 hours). By default, the value
209
212
  is set to 3600 seconds (1 hour). (default `nil`)
213
+ * `<format>`: For specifying records format. See [formatter overview](https://docs.fluentd.org/formatter) and [formatter section overview](https://docs.fluentd.org/configuration/format-section) on the official documentation.
210
214
 
211
215
  **NOTE:** `retention_in_days` requests additional IAM permission `logs:PutRetentionPolicy` for log_group.
212
216
  Please refer to [the PutRetentionPolicy column in documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/permissions-reference-cwl.html) for details.
@@ -218,6 +222,9 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
218
222
  @type cloudwatch_logs
219
223
  tag cloudwatch.in
220
224
  log_group_name group
225
+ #add_log_group_name true
226
+ #log_group_name_key group_name_key
227
+ #use_log_group_name_prefix true
221
228
  log_stream_name stream
222
229
  #use_log_stream_name_prefix true
223
230
  state_file /var/lib/fluent/group_stream.in.state
@@ -252,6 +259,9 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
252
259
  * `http_proxy`: use to set an optional HTTP proxy
253
260
  * `json_handler`: name of the library to be used to handle JSON data. For now, supported libraries are `json` (default) and `yajl`.
254
261
  * `log_group_name`: name of log group to fetch logs
262
+ * `add_log_group_name`: add record into the name of log group (default `false`)
263
+ * `log_group_name_key`: specify the key where adding record into the name of log group (default `'log_group'`)
264
+ * `use_log_group_name_prefix`: to use `log_group_name` as log group name prefix (default `false`)
255
265
  * `log_stream_name`: name of log stream to fetch logs
256
266
  * `region`: AWS Region. See [Authentication](#authentication) for more information.
257
267
  * `throttling_retry_seconds`: time period in seconds to retry a request when aws CloudWatch rate limit exceeds (default: nil)
@@ -2,7 +2,7 @@ module Fluent
2
2
  module Plugin
3
3
  module Cloudwatch
4
4
  module Logs
5
- VERSION = "0.11.1"
5
+ VERSION = "0.13.2"
6
6
  end
7
7
  end
8
8
  end
@@ -22,6 +22,9 @@ module Fluent::Plugin
22
22
  config_param :endpoint, :string, default: nil
23
23
  config_param :tag, :string
24
24
  config_param :log_group_name, :string
25
+ config_param :add_log_group_name, :bool, default: false
26
+ config_param :log_group_name_key, :string, default: 'log_group'
27
+ config_param :use_log_group_name_prefix, :bool, default: false
25
28
  config_param :log_stream_name, :string, default: nil
26
29
  config_param :use_log_stream_name_prefix, :bool, default: false
27
30
  config_param :state_file, :string, default: nil,
@@ -89,9 +92,9 @@ module Fluent::Plugin
89
92
  }
90
93
  credentials_options[:sts_endpoint_url] = @aws_sts_endpoint_url if @aws_sts_endpoint_url
91
94
  if @region and @aws_sts_endpoint_url
92
- credentails_options[:client] = Aws::STS::Client.new(:region => @region, endpoint: @aws_sts_endpoint_url)
95
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region, endpoint: @aws_sts_endpoint_url)
93
96
  elsif @region
94
- credentails_options[:client] = Aws::STS::Client.new(:region => @region)
97
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
95
98
  end
96
99
  options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
97
100
  elsif @web_identity_credentials
@@ -128,6 +131,17 @@ module Fluent::Plugin
128
131
  super
129
132
  end
130
133
 
134
+ # No private for testing
135
+ def state_key_for(log_stream_name, log_group_name = nil)
136
+ if log_group_name && log_stream_name
137
+ "#{@state_file}_#{log_group_name.gsub(File::SEPARATOR, '-')}_#{log_stream_name.gsub(File::SEPARATOR, '-')}"
138
+ elsif log_stream_name
139
+ "#{@state_file}_#{log_stream_name.gsub(File::SEPARATOR, '-')}"
140
+ else
141
+ @state_file
142
+ end
143
+ end
144
+
131
145
  private
132
146
  def configure_parser(conf)
133
147
  if conf['format']
@@ -137,28 +151,20 @@ module Fluent::Plugin
137
151
  end
138
152
  end
139
153
 
140
- def state_key_for(log_stream_name)
141
- if log_stream_name
142
- "#{@state_file}_#{log_stream_name.gsub(File::SEPARATOR, '-')}"
143
- else
144
- @state_file
145
- end
146
- end
147
-
148
154
  def migrate_state_file_to_storage(log_stream_name)
149
155
  @next_token_storage.put(:"#{state_key_for(log_stream_name)}", File.read(state_key_for(log_stream_name)).chomp)
150
156
  File.delete(state_key_for(log_stream_name))
151
157
  end
152
158
 
153
- def next_token(log_stream_name)
159
+ def next_token(log_stream_name, log_group_name = nil)
154
160
  if @next_token_storage.persistent && File.exist?(state_key_for(log_stream_name))
155
161
  migrate_state_file_to_storage(log_stream_name)
156
162
  end
157
- @next_token_storage.get(:"#{state_key_for(log_stream_name)}")
163
+ @next_token_storage.get(:"#{state_key_for(log_stream_name, log_group_name)}")
158
164
  end
159
165
 
160
- def store_next_token(token, log_stream_name = nil)
161
- @next_token_storage.put(:"#{state_key_for(log_stream_name)}", token)
166
+ def store_next_token(token, log_stream_name = nil, log_group_name = nil)
167
+ @next_token_storage.put(:"#{state_key_for(log_stream_name, log_group_name)}", token)
162
168
  end
163
169
 
164
170
  def run
@@ -168,55 +174,67 @@ module Fluent::Plugin
168
174
  if Time.now > @next_fetch_time
169
175
  @next_fetch_time += @fetch_interval
170
176
 
171
- if @use_log_stream_name_prefix || @use_todays_log_stream
172
- log_stream_name_prefix = @use_todays_log_stream ? get_todays_date : @log_stream_name
173
- begin
174
- log_streams = describe_log_streams(log_stream_name_prefix)
175
- log_streams.concat(describe_log_streams(get_yesterdays_date)) if @use_todays_log_stream
176
- log_streams.each do |log_stream|
177
- log_stream_name = log_stream.log_stream_name
178
- events = get_events(log_stream_name)
179
- metadata = if @include_metadata
180
- {
181
- "log_stream_name" => log_stream_name,
182
- "log_group_name" => @log_group_name
183
- }
184
- else
185
- {}
186
- end
187
- events.each do |event|
188
- emit(log_stream_name, event, metadata)
177
+ if @use_log_group_name_prefix
178
+ log_group_names = describe_log_groups(@log_group_name).map{|log_group|
179
+ log_group.log_group_name
180
+ }
181
+ else
182
+ log_group_names = [@log_group_name]
183
+ end
184
+ log_group_names.each do |log_group_name|
185
+ if @use_log_stream_name_prefix || @use_todays_log_stream
186
+ log_stream_name_prefix = @use_todays_log_stream ? get_todays_date : @log_stream_name
187
+ begin
188
+ log_streams = describe_log_streams(log_stream_name_prefix, nil, nil, log_group_name)
189
+ log_streams.concat(describe_log_streams(get_yesterdays_date)) if @use_todays_log_stream
190
+ log_streams.each do |log_stream|
191
+ log_stream_name = log_stream.log_stream_name
192
+ events = get_events(log_group_name, log_stream_name)
193
+ metadata = if @include_metadata
194
+ {
195
+ "log_stream_name" => log_stream_name,
196
+ "log_group_name" => log_group_name
197
+ }
198
+ else
199
+ {}
200
+ end
201
+ events.each do |event|
202
+ emit(log_group_name, log_stream_name, event, metadata)
203
+ end
189
204
  end
205
+ rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
206
+ log.warn "'#{@log_stream_name}' prefixed log stream(s) are not found"
207
+ next
208
+ end
209
+ else
210
+ events = get_events(log_group_name, @log_stream_name)
211
+ metadata = if @include_metadata
212
+ {
213
+ "log_stream_name" => @log_stream_name,
214
+ "log_group_name" => @log_group_name
215
+ }
216
+ else
217
+ {}
218
+ end
219
+ events.each do |event|
220
+ emit(log_group_name, log_stream_name, event, metadata)
190
221
  end
191
- rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
192
- log.warn "'#{@log_stream_name}' prefixed log stream(s) are not found"
193
- next
194
- end
195
- else
196
- events = get_events(@log_stream_name)
197
- metadata = if @include_metadata
198
- {
199
- "log_stream_name" => @log_stream_name,
200
- "log_group_name" => @log_group_name
201
- }
202
- else
203
- {}
204
- end
205
- events.each do |event|
206
- emit(log_stream_name, event, metadata)
207
222
  end
208
223
  end
209
- end
210
224
  sleep 1
225
+ end
211
226
  end
212
227
  end
213
228
 
214
- def emit(stream, event, metadata)
229
+ def emit(group, stream, event, metadata)
215
230
  if @parser
216
231
  @parser.parse(event.message) {|time,record|
217
232
  if @use_aws_timestamp
218
233
  time = (event.timestamp / 1000).floor
219
234
  end
235
+ if @add_log_group_name
236
+ record[@log_group_name_key] = group
237
+ end
220
238
  unless metadata.empty?
221
239
  record.merge!("metadata" => metadata)
222
240
  end
@@ -226,6 +244,9 @@ module Fluent::Plugin
226
244
  time = (event.timestamp / 1000).floor
227
245
  begin
228
246
  record = @json_handler.load(event.message)
247
+ if @add_log_group_name
248
+ record[@log_group_name_key] = group
249
+ end
229
250
  unless metadata.empty?
230
251
  record.merge!("metadata" => metadata)
231
252
  end
@@ -237,29 +258,33 @@ module Fluent::Plugin
237
258
  end
238
259
  end
239
260
 
240
- def get_events(log_stream_name)
261
+ def get_events(log_group_name, log_stream_name)
241
262
  throttling_handler('get_log_events') do
242
263
  request = {
243
- log_group_name: @log_group_name,
264
+ log_group_name: log_group_name,
244
265
  log_stream_name: log_stream_name
245
266
  }
246
267
  request.merge!(start_time: @start_time) if @start_time
247
268
  request.merge!(end_time: @end_time) if @end_time
248
- log_next_token = next_token(log_stream_name)
269
+ log_next_token = next_token(log_group_name, log_stream_name)
249
270
  request[:next_token] = log_next_token if !log_next_token.nil? && !log_next_token.empty?
250
271
  response = @logs.get_log_events(request)
251
272
  if valid_next_token(log_next_token, response.next_forward_token)
252
- store_next_token(response.next_forward_token, log_stream_name)
273
+ if @use_log_group_name_prefix
274
+ store_next_token(response.next_forward_token, log_stream_name, log_group_name)
275
+ else
276
+ store_next_token(response.next_forward_token, log_stream_name)
277
+ end
253
278
  end
254
279
 
255
280
  response.events
256
281
  end
257
282
  end
258
283
 
259
- def describe_log_streams(log_stream_name_prefix, log_streams = nil, next_token = nil)
284
+ def describe_log_streams(log_stream_name_prefix, log_streams = nil, next_token = nil, log_group_name=nil)
260
285
  throttling_handler('describe_log_streams') do
261
286
  request = {
262
- log_group_name: @log_group_name
287
+ log_group_name: log_group_name != nil ? log_group_name : @log_group_name
263
288
  }
264
289
  request[:next_token] = next_token if next_token
265
290
  request[:log_stream_name_prefix] = log_stream_name_prefix if log_stream_name_prefix
@@ -270,7 +295,7 @@ module Fluent::Plugin
270
295
  log_streams = response.log_streams
271
296
  end
272
297
  if response.next_token
273
- log_streams = describe_log_streams(log_stream_name_prefix, log_streams, response.next_token)
298
+ log_streams = describe_log_streams(log_stream_name_prefix, log_streams, response.next_token, log_group_name)
274
299
  end
275
300
  log_streams
276
301
  end
@@ -289,6 +314,23 @@ module Fluent::Plugin
289
314
  end
290
315
  end
291
316
 
317
+ def describe_log_groups(log_group_name_prefix, log_groups = nil, next_token = nil)
318
+ request = {
319
+ log_group_name_prefix: log_group_name_prefix
320
+ }
321
+ request[:next_token] = next_token if next_token
322
+ response = @logs.describe_log_groups(request)
323
+ if log_groups
324
+ log_groups.concat(response.log_groups)
325
+ else
326
+ log_groups = response.log_groups
327
+ end
328
+ if response.next_token
329
+ log_groups = describe_log_groups(log_group_name_prefix, log_groups, response.next_token)
330
+ end
331
+ log_groups
332
+ end
333
+
292
334
  def valid_next_token(prev_token, next_token)
293
335
  next_token && prev_token != next_token.chomp
294
336
  end
@@ -9,7 +9,7 @@ module Fluent::Plugin
9
9
 
10
10
  class TooLargeEventError < Fluent::UnrecoverableError; end
11
11
 
12
- helpers :compat_parameters, :inject
12
+ helpers :compat_parameters, :inject, :formatter
13
13
 
14
14
  DEFAULT_BUFFER_TYPE = "memory"
15
15
 
@@ -58,6 +58,9 @@ module Fluent::Plugin
58
58
  config_section :buffer do
59
59
  config_set_default :@type, DEFAULT_BUFFER_TYPE
60
60
  end
61
+ config_section :format do
62
+ config_set_default :@type, 'json'
63
+ end
61
64
 
62
65
  MAX_EVENTS_SIZE = 1_048_576
63
66
  MAX_EVENT_SIZE = 256 * 1024
@@ -88,6 +91,22 @@ module Fluent::Plugin
88
91
  if [conf['retention_in_days'], conf['retention_in_days_key']].compact.size > 1
89
92
  raise ConfigError, "Set only one of retention_in_days, retention_in_days_key"
90
93
  end
94
+
95
+ formatter_conf = conf.elements('format').first
96
+ @formatter_proc = unless formatter_conf
97
+ unless @message_keys.empty?
98
+ Proc.new { |tag, time, record|
99
+ @message_keys.map{|k| record[k].to_s }.reject{|e| e.empty? }.join(' ')
100
+ }
101
+ else
102
+ Proc.new { |tag, time, record|
103
+ @json_handler.dump(record)
104
+ }
105
+ end
106
+ else
107
+ formatter = formatter_create(usage: 'cloudwatch-logs-plugin', conf: formatter_conf)
108
+ formatter.method(:format)
109
+ end
91
110
  end
92
111
 
93
112
  def start
@@ -108,9 +127,9 @@ module Fluent::Plugin
108
127
  }
109
128
  credentials_options[:sts_endpoint_url] = @aws_sts_endpoint_url if @aws_sts_endpoint_url
110
129
  if @region and @aws_sts_endpoint_url
111
- credentails_options[:client] = Aws::STS::Client.new(:region => @region, endpoint: @aws_sts_endpoint_url)
130
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region, endpoint: @aws_sts_endpoint_url)
112
131
  elsif @region
113
- credentails_options[:client] = Aws::STS::Client.new(:region => @region)
132
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
114
133
  end
115
134
  options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
116
135
  elsif @web_identity_credentials
@@ -264,11 +283,7 @@ module Fluent::Plugin
264
283
  time_ms = (time.to_f * 1000).floor
265
284
 
266
285
  scrub_record!(record)
267
- unless @message_keys.empty?
268
- message = @message_keys.map{|k| record[k].to_s }.reject{|e| e.empty? }.join(' ')
269
- else
270
- message = @json_handler.dump(record)
271
- end
286
+ message = @formatter_proc.call(t, time, record)
272
287
 
273
288
  if message.empty?
274
289
  log.warn "Within specified message_key(s): (#{@message_keys.join(',')}) do not have non-empty record. Skip."
@@ -99,6 +99,128 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
99
99
  assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
100
100
  end
101
101
 
102
+ sub_test_case "use_log_group_name_prefix true" do
103
+ test "emit" do
104
+ set_log_group_name("fluent-plugin-cloudwatch-group-prefix-test-#{Time.now.to_f}")
105
+ create_log_stream
106
+
107
+ time_ms = (Time.now.to_f * 1000).floor
108
+ put_log_events([
109
+ {timestamp: time_ms, message: '{"cloudwatch":"logs1"}'},
110
+ {timestamp: time_ms, message: '{"cloudwatch":"logs2"}'},
111
+ ])
112
+
113
+ sleep 5
114
+
115
+ config = <<-EOC
116
+ tag test
117
+ @type cloudwatch_logs
118
+ log_group_name fluent-plugin-cloudwatch-group-prefix-test
119
+ use_log_group_name_prefix true
120
+ log_stream_name #{log_stream_name}
121
+ state_file /tmp/state
122
+ fetch_interval 1
123
+ #{aws_key_id}
124
+ #{aws_sec_key}
125
+ #{region}
126
+ #{endpoint}
127
+ EOC
128
+
129
+ d = create_driver(config)
130
+ d.run(expect_emits: 2, timeout: 5)
131
+
132
+ emits = d.events
133
+ assert_equal(2, emits.size)
134
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs1'}], emits[0])
135
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
136
+ end
137
+
138
+ test "emit with add_log_group_name" do
139
+ set_log_group_name("fluent-plugin-cloudwatch-add-log-group-#{Time.now.to_f}")
140
+ create_log_stream
141
+
142
+ time_ms = (Time.now.to_f * 1000).floor
143
+ put_log_events([
144
+ {timestamp: time_ms, message: '{"cloudwatch":"logs1"}'},
145
+ {timestamp: time_ms, message: '{"cloudwatch":"logs2"}'},
146
+ ])
147
+
148
+ sleep 5
149
+
150
+ log_group_name_key = 'log_group_key'
151
+ config = <<-EOC
152
+ tag test
153
+ @type cloudwatch_logs
154
+ log_group_name fluent-plugin-cloudwatch-add-log-group
155
+ use_log_group_name_prefix true
156
+ add_log_group_name true
157
+ log_group_name_key #{log_group_name_key}
158
+ log_stream_name #{log_stream_name}
159
+ state_file /tmp/state
160
+ fetch_interval 1
161
+ #{aws_key_id}
162
+ #{aws_sec_key}
163
+ #{region}
164
+ #{endpoint}
165
+ EOC
166
+
167
+ d = create_driver(config)
168
+ d.run(expect_emits: 2, timeout: 5)
169
+
170
+ emits = d.events
171
+ assert_equal(2, emits.size)
172
+ assert_true emits[0][2].has_key?(log_group_name_key)
173
+ emits[0][2].delete(log_group_name_key)
174
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs1'}], emits[0])
175
+ assert_true emits[1][2].has_key?(log_group_name_key)
176
+ emits[1][2].delete(log_group_name_key)
177
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
178
+ end
179
+
180
+ test "emit with add_log_group_name and <parse> csv" do
181
+ cloudwatch_config = {'tag' => "test",
182
+ '@type' => 'cloudwatch_logs',
183
+ 'log_group_name' => "fluent-plugin-cloudwatch-with-csv-format",
184
+ 'log_stream_name' => "#{log_stream_name}",
185
+ 'use_log_group_name_prefix' => true,
186
+ }
187
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_key_id)) if ENV['aws_key_id']
188
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_sec_key)) if ENV['aws_sec_key']
189
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(region)) if ENV['region']
190
+ cloudwatch_config = cloudwatch_config.merge!(config_elementify(endpoint)) if ENV['endpoint']
191
+
192
+ csv_format_config = config_element('ROOT', '', cloudwatch_config, [
193
+ config_element('parse', '', {'@type' => 'csv',
194
+ 'keys' => 'time,message',
195
+ 'time_key' => 'time'}),
196
+ config_element('storage', '', {'@type' => 'local',
197
+ 'path' => '/tmp/state'})
198
+ ])
199
+ log_group_name = "fluent-plugin-cloudwatch-with-csv-format-#{Time.now.to_f}"
200
+ set_log_group_name(log_group_name)
201
+ create_log_stream
202
+
203
+ time_ms = (Time.now.to_f * 1000).floor
204
+ log_time_ms = time_ms - 10000
205
+ put_log_events([
206
+ {timestamp: time_ms, message: Time.at(log_time_ms/1000.floor).to_s + ",Cloudwatch non json logs1"},
207
+ {timestamp: time_ms, message: Time.at(log_time_ms/1000.floor).to_s + ",Cloudwatch non json logs2"},
208
+ ])
209
+
210
+ sleep 5
211
+
212
+ d = create_driver(csv_format_config)
213
+ d.run(expect_emits: 2, timeout: 5)
214
+ next_token = d.instance.instance_variable_get(:@next_token_storage)
215
+ assert_true next_token.get(d.instance.state_key_for(log_stream_name, log_group_name)).is_a?(String)
216
+
217
+ emits = d.events
218
+ assert_equal(2, emits.size)
219
+ assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs1"}], emits[0])
220
+ assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs2"}], emits[1])
221
+ end
222
+ end
223
+
102
224
  def test_emit_with_metadata
103
225
  create_log_stream
104
226
 
@@ -76,6 +76,143 @@ class CloudwatchLogsOutputTest < Test::Unit::TestCase
76
76
  assert(logs.any?{|log| log.include?("Called PutLogEvents API") })
77
77
  end
78
78
 
79
+ sub_test_case "formatter" do
80
+ test "csv" do
81
+ new_log_stream
82
+
83
+ config = {'@type' => 'cloudwatch_logs',
84
+ 'auto_create_stream' => true,
85
+ 'log_stream_name' => log_stream_name,
86
+ 'log_group_name' => log_group_name,
87
+ '@log_level' => 'debug'}
88
+ config.merge!(config_elementify(aws_key_id)) if aws_key_id
89
+ config.merge!(config_elementify(aws_sec_key)) if aws_sec_key
90
+ config.merge!(config_elementify(region)) if region
91
+ config.merge!(config_elementify(endpoint)) if endpoint
92
+
93
+ d = create_driver(
94
+ Fluent::Config::Element.new('ROOT', '', config, [
95
+ Fluent::Config::Element.new('buffer', 'tag, time', {
96
+ '@type' => 'memory',
97
+ 'timekey' => 3600
98
+ }, []),
99
+ Fluent::Config::Element.new('format', '', {
100
+ '@type' => 'csv',
101
+ 'fields' => ["message","cloudwatch"],
102
+ }, []),
103
+ ]))
104
+
105
+ time = event_time
106
+ d.run(default_tag: fluentd_tag, flush: true) do
107
+ d.feed(time, {'cloudwatch' => 'logs1'})
108
+ # Addition converts EventTime to seconds
109
+ d.feed(time + 1, {'cloudwatch' => 'logs2'})
110
+ end
111
+
112
+ sleep 10
113
+
114
+ logs = d.logs
115
+ events = get_log_events
116
+ assert_equal(2, events.size)
117
+ assert_equal((time.to_f * 1000).floor, events[0].timestamp)
118
+ assert_equal('"","logs1"', events[0].message.strip)
119
+ assert_equal((time.to_i + 1) * 1000, events[1].timestamp)
120
+ assert_equal('"","logs2"', events[1].message.strip)
121
+
122
+ assert(logs.any?{|log| log.include?("Called PutLogEvents API") })
123
+ end
124
+
125
+ test "ltsv" do
126
+ new_log_stream
127
+
128
+ config = {'@type' => 'cloudwatch_logs',
129
+ 'auto_create_stream' => true,
130
+ 'log_stream_name' => log_stream_name,
131
+ 'log_group_name' => log_group_name,
132
+ '@log_level' => 'debug'}
133
+ config.merge!(config_elementify(aws_key_id)) if aws_key_id
134
+ config.merge!(config_elementify(aws_sec_key)) if aws_sec_key
135
+ config.merge!(config_elementify(region)) if region
136
+ config.merge!(config_elementify(endpoint)) if endpoint
137
+
138
+ d = create_driver(
139
+ Fluent::Config::Element.new('ROOT', '', config, [
140
+ Fluent::Config::Element.new('buffer', 'tag, time', {
141
+ '@type' => 'memory',
142
+ 'timekey' => 3600
143
+ }, []),
144
+ Fluent::Config::Element.new('format', '', {
145
+ '@type' => 'ltsv',
146
+ 'fields' => ["message","cloudwatch"],
147
+ }, []),
148
+ ]))
149
+
150
+ time = event_time
151
+ d.run(default_tag: fluentd_tag, flush: true) do
152
+ d.feed(time, {'cloudwatch' => 'logs1'})
153
+ # Addition converts EventTime to seconds
154
+ d.feed(time + 1, {'cloudwatch' => 'logs2'})
155
+ end
156
+
157
+ sleep 10
158
+
159
+ logs = d.logs
160
+ events = get_log_events
161
+ assert_equal(2, events.size)
162
+ assert_equal((time.to_f * 1000).floor, events[0].timestamp)
163
+ assert_equal('cloudwatch:logs1', events[0].message.strip)
164
+ assert_equal((time.to_i + 1) * 1000, events[1].timestamp)
165
+ assert_equal('cloudwatch:logs2', events[1].message.strip)
166
+
167
+ assert(logs.any?{|log| log.include?("Called PutLogEvents API") })
168
+ end
169
+
170
+ test "single_value" do
171
+ new_log_stream
172
+
173
+ config = {'@type' => 'cloudwatch_logs',
174
+ 'auto_create_stream' => true,
175
+ 'log_stream_name' => log_stream_name,
176
+ 'log_group_name' => log_group_name,
177
+ '@log_level' => 'debug'}
178
+ config.merge!(config_elementify(aws_key_id)) if aws_key_id
179
+ config.merge!(config_elementify(aws_sec_key)) if aws_sec_key
180
+ config.merge!(config_elementify(region)) if region
181
+ config.merge!(config_elementify(endpoint)) if endpoint
182
+
183
+ d = create_driver(
184
+ Fluent::Config::Element.new('ROOT', '', config, [
185
+ Fluent::Config::Element.new('buffer', 'tag, time', {
186
+ '@type' => 'memory',
187
+ 'timekey' => 3600
188
+ }, []),
189
+ Fluent::Config::Element.new('format', '', {
190
+ '@type' => 'single_value',
191
+ 'message_key' => "cloudwatch",
192
+ }, []),
193
+ ]))
194
+
195
+ time = event_time
196
+ d.run(default_tag: fluentd_tag, flush: true) do
197
+ d.feed(time, {'cloudwatch' => 'logs1', 'message' => 'Hi!'})
198
+ # Addition converts EventTime to seconds
199
+ d.feed(time + 1, {'cloudwatch' => 'logs2', 'message' => 'Hi!'})
200
+ end
201
+
202
+ sleep 10
203
+
204
+ logs = d.logs
205
+ events = get_log_events
206
+ assert_equal(2, events.size)
207
+ assert_equal((time.to_f * 1000).floor, events[0].timestamp)
208
+ assert_equal('logs1', events[0].message.strip)
209
+ assert_equal((time.to_i + 1) * 1000, events[1].timestamp)
210
+ assert_equal('logs2', events[1].message.strip)
211
+
212
+ assert(logs.any?{|log| log.include?("Called PutLogEvents API") })
213
+ end
214
+ end
215
+
79
216
  def test_write_utf8
80
217
  new_log_stream
81
218
 
@@ -17,6 +17,10 @@ module CloudwatchLogsTestHelper
17
17
  @logs ||= Aws::CloudWatchLogs::Client.new(options)
18
18
  end
19
19
 
20
+ def set_log_group_name(log_group_name)
21
+ @log_group_name = log_group_name
22
+ end
23
+
20
24
  def log_group_name
21
25
  @log_group_name ||= "fluent-plugin-cloudwatch-test-#{Time.now.to_f}"
22
26
  end
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.11.1
4
+ version: 0.13.2
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-10-20 00:00:00.000000000 Z
11
+ date: 2021-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -116,6 +116,8 @@ extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
118
  - ".github/workflows/issue-auto-closer.yml"
119
+ - ".github/workflows/linux.yml"
120
+ - ".github/workflows/windows.yml"
119
121
  - ".gitignore"
120
122
  - ".travis.yml"
121
123
  - Gemfile