fluent-plugin-cloudwatch-logs 0.11.0 → 0.13.1

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: 1dc48c250b022126a1de2b125bfa8ad3320daaa5eca5613f51ba7e6571a0b9a9
4
- data.tar.gz: 23993ce51cac3aacfbe6937c1f928a00a61fbd94f64fb4ccf8c38ac8e4656787
3
+ metadata.gz: 7bed409ac04e91c38f792645f4a3a4dda6871273d97230c1fadf733a4c1d42fa
4
+ data.tar.gz: d9d49dc6cac3f3cf4035d2888f1e121cec268d73ba960245df00a2039eb88d12
5
5
  SHA512:
6
- metadata.gz: 84fd2ea44c0b498364a13da89d422d39b6ea18abdb38add8fbacbc9f0c7b04b6ed18498f26e85920ffe8a7c80e5c14dce8f191c6ecc1a2f1c36809ce67e6961b
7
- data.tar.gz: e16ab191ba87408d82e1ffa73564aec909a0795cca65c0ab506b2bb538f4c1cd0ad61641035c6b42056ccd4459e4ba677cb35a28d8af74edc3d1d0bd04422db1
6
+ metadata.gz: e16807714477b6b0b4c862875c0f94b6e07b7dd6ff183279ea0f13f7cd2467f756a2c5eb6aee60234d4bc69ba3188ab5cfd13294f0d9f3ff3cdaaeee9570ce08
7
+ data.tar.gz: dc25160c660f4e461b89db0517952baf2601491e523c53100eff0369f06cb856173acf829c32cef981384161bf2c55cb132c9de23a864469d1496209fe7a708a
@@ -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.0"
5
+ VERSION = "0.13.1"
6
6
  end
7
7
  end
8
8
  end
@@ -17,10 +17,14 @@ module Fluent::Plugin
17
17
  config_param :aws_use_sts, :bool, default: false
18
18
  config_param :aws_sts_role_arn, :string, default: nil
19
19
  config_param :aws_sts_session_name, :string, default: 'fluentd'
20
+ config_param :aws_sts_endpoint_url, :string, default: nil
20
21
  config_param :region, :string, default: nil
21
22
  config_param :endpoint, :string, default: nil
22
23
  config_param :tag, :string
23
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
24
28
  config_param :log_stream_name, :string, default: nil
25
29
  config_param :use_log_stream_name_prefix, :bool, default: false
26
30
  config_param :state_file, :string, default: nil,
@@ -82,10 +86,17 @@ module Fluent::Plugin
82
86
 
83
87
  if @aws_use_sts
84
88
  Aws.config[:region] = options[:region]
85
- options[:credentials] = Aws::AssumeRoleCredentials.new(
89
+ credentials_options = {
86
90
  role_arn: @aws_sts_role_arn,
87
91
  role_session_name: @aws_sts_session_name
88
- )
92
+ }
93
+ credentials_options[:sts_endpoint_url] = @aws_sts_endpoint_url if @aws_sts_endpoint_url
94
+ if @region and @aws_sts_endpoint_url
95
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region, endpoint: @aws_sts_endpoint_url)
96
+ elsif @region
97
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
98
+ end
99
+ options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
89
100
  elsif @web_identity_credentials
90
101
  c = @web_identity_credentials
91
102
  credentials_options = {}
@@ -120,6 +131,17 @@ module Fluent::Plugin
120
131
  super
121
132
  end
122
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
+
123
145
  private
124
146
  def configure_parser(conf)
125
147
  if conf['format']
@@ -129,28 +151,20 @@ module Fluent::Plugin
129
151
  end
130
152
  end
131
153
 
132
- def state_key_for(log_stream_name)
133
- if log_stream_name
134
- "#{@state_file}_#{log_stream_name.gsub(File::SEPARATOR, '-')}"
135
- else
136
- @state_file
137
- end
138
- end
139
-
140
154
  def migrate_state_file_to_storage(log_stream_name)
141
155
  @next_token_storage.put(:"#{state_key_for(log_stream_name)}", File.read(state_key_for(log_stream_name)).chomp)
142
156
  File.delete(state_key_for(log_stream_name))
143
157
  end
144
158
 
145
- def next_token(log_stream_name)
159
+ def next_token(log_stream_name, log_group_name = nil)
146
160
  if @next_token_storage.persistent && File.exist?(state_key_for(log_stream_name))
147
161
  migrate_state_file_to_storage(log_stream_name)
148
162
  end
149
- @next_token_storage.get(:"#{state_key_for(log_stream_name)}")
163
+ @next_token_storage.get(:"#{state_key_for(log_stream_name, log_group_name)}")
150
164
  end
151
165
 
152
- def store_next_token(token, log_stream_name = nil)
153
- @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)
154
168
  end
155
169
 
156
170
  def run
@@ -160,55 +174,67 @@ module Fluent::Plugin
160
174
  if Time.now > @next_fetch_time
161
175
  @next_fetch_time += @fetch_interval
162
176
 
163
- if @use_log_stream_name_prefix || @use_todays_log_stream
164
- log_stream_name_prefix = @use_todays_log_stream ? get_todays_date : @log_stream_name
165
- begin
166
- log_streams = describe_log_streams(log_stream_name_prefix)
167
- log_streams.concat(describe_log_streams(get_yesterdays_date)) if @use_todays_log_stream
168
- log_streams.each do |log_stream|
169
- log_stream_name = log_stream.log_stream_name
170
- events = get_events(log_stream_name)
171
- metadata = if @include_metadata
172
- {
173
- "log_stream_name" => log_stream_name,
174
- "log_group_name" => @log_group_name
175
- }
176
- else
177
- {}
178
- end
179
- events.each do |event|
180
- 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
181
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)
182
221
  end
183
- rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
184
- log.warn "'#{@log_stream_name}' prefixed log stream(s) are not found"
185
- next
186
- end
187
- else
188
- events = get_events(@log_stream_name)
189
- metadata = if @include_metadata
190
- {
191
- "log_stream_name" => @log_stream_name,
192
- "log_group_name" => @log_group_name
193
- }
194
- else
195
- {}
196
- end
197
- events.each do |event|
198
- emit(log_stream_name, event, metadata)
199
222
  end
200
223
  end
201
- end
202
224
  sleep 1
225
+ end
203
226
  end
204
227
  end
205
228
 
206
- def emit(stream, event, metadata)
229
+ def emit(group, stream, event, metadata)
207
230
  if @parser
208
231
  @parser.parse(event.message) {|time,record|
209
232
  if @use_aws_timestamp
210
233
  time = (event.timestamp / 1000).floor
211
234
  end
235
+ if @add_log_group_name
236
+ record[@log_group_name_key] = group
237
+ end
212
238
  unless metadata.empty?
213
239
  record.merge!("metadata" => metadata)
214
240
  end
@@ -218,6 +244,9 @@ module Fluent::Plugin
218
244
  time = (event.timestamp / 1000).floor
219
245
  begin
220
246
  record = @json_handler.load(event.message)
247
+ if @add_log_group_name
248
+ record[@log_group_name_key] = group
249
+ end
221
250
  unless metadata.empty?
222
251
  record.merge!("metadata" => metadata)
223
252
  end
@@ -229,29 +258,33 @@ module Fluent::Plugin
229
258
  end
230
259
  end
231
260
 
232
- def get_events(log_stream_name)
261
+ def get_events(log_group_name, log_stream_name)
233
262
  throttling_handler('get_log_events') do
234
263
  request = {
235
- log_group_name: @log_group_name,
264
+ log_group_name: log_group_name,
236
265
  log_stream_name: log_stream_name
237
266
  }
238
267
  request.merge!(start_time: @start_time) if @start_time
239
268
  request.merge!(end_time: @end_time) if @end_time
240
- log_next_token = next_token(log_stream_name)
269
+ log_next_token = next_token(log_group_name, log_stream_name)
241
270
  request[:next_token] = log_next_token if !log_next_token.nil? && !log_next_token.empty?
242
271
  response = @logs.get_log_events(request)
243
272
  if valid_next_token(log_next_token, response.next_forward_token)
244
- 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
245
278
  end
246
279
 
247
280
  response.events
248
281
  end
249
282
  end
250
283
 
251
- 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)
252
285
  throttling_handler('describe_log_streams') do
253
286
  request = {
254
- log_group_name: @log_group_name
287
+ log_group_name: log_group_name != nil ? log_group_name : @log_group_name
255
288
  }
256
289
  request[:next_token] = next_token if next_token
257
290
  request[:log_stream_name_prefix] = log_stream_name_prefix if log_stream_name_prefix
@@ -281,6 +314,23 @@ module Fluent::Plugin
281
314
  end
282
315
  end
283
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
+
284
334
  def valid_next_token(prev_token, next_token)
285
335
  next_token && prev_token != next_token.chomp
286
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
 
@@ -19,6 +19,7 @@ module Fluent::Plugin
19
19
  config_param :aws_use_sts, :bool, default: false
20
20
  config_param :aws_sts_role_arn, :string, default: nil
21
21
  config_param :aws_sts_session_name, :string, default: 'fluentd'
22
+ config_param :aws_sts_endpoint_url, :string, default: nil
22
23
  config_param :region, :string, :default => nil
23
24
  config_param :endpoint, :string, :default => nil
24
25
  config_param :log_group_name, :string, :default => nil
@@ -57,6 +58,9 @@ module Fluent::Plugin
57
58
  config_section :buffer do
58
59
  config_set_default :@type, DEFAULT_BUFFER_TYPE
59
60
  end
61
+ config_section :format do
62
+ config_set_default :@type, 'json'
63
+ end
60
64
 
61
65
  MAX_EVENTS_SIZE = 1_048_576
62
66
  MAX_EVENT_SIZE = 256 * 1024
@@ -87,6 +91,22 @@ module Fluent::Plugin
87
91
  if [conf['retention_in_days'], conf['retention_in_days_key']].compact.size > 1
88
92
  raise ConfigError, "Set only one of retention_in_days, retention_in_days_key"
89
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
90
110
  end
91
111
 
92
112
  def start
@@ -101,10 +121,17 @@ module Fluent::Plugin
101
121
 
102
122
  if @aws_use_sts
103
123
  Aws.config[:region] = options[:region]
104
- options[:credentials] = Aws::AssumeRoleCredentials.new(
124
+ credentials_options = {
105
125
  role_arn: @aws_sts_role_arn,
106
126
  role_session_name: @aws_sts_session_name
107
- )
127
+ }
128
+ credentials_options[:sts_endpoint_url] = @aws_sts_endpoint_url if @aws_sts_endpoint_url
129
+ if @region and @aws_sts_endpoint_url
130
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region, endpoint: @aws_sts_endpoint_url)
131
+ elsif @region
132
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
133
+ end
134
+ options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
108
135
  elsif @web_identity_credentials
109
136
  c = @web_identity_credentials
110
137
  credentials_options = {}
@@ -256,11 +283,7 @@ module Fluent::Plugin
256
283
  time_ms = (time.to_f * 1000).floor
257
284
 
258
285
  scrub_record!(record)
259
- unless @message_keys.empty?
260
- message = @message_keys.map{|k| record[k].to_s }.reject{|e| e.empty? }.join(' ')
261
- else
262
- message = @json_handler.dump(record)
263
- end
286
+ message = @formatter_proc.call(t, time, record)
264
287
 
265
288
  if message.empty?
266
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.0
4
+ version: 0.13.1
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-19 00:00:00.000000000 Z
11
+ date: 2021-01-14 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