fluent-plugin-cloudwatch-logs 0.10.2 → 0.13.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: 3a66a32521964e4be0392f1d786e81750850b4f027b5ec9649a9033ab330595a
4
- data.tar.gz: 6041e92eea6e3148133a78a72ad3d9702ff33f89da89216e4bff579693459725
3
+ metadata.gz: 17e367fb1fc1c2b58cd9a22cd9caefaf6396b3542f589b44616f160c40c3219d
4
+ data.tar.gz: 37eee2b6a48d17d86451a8eee88435fc1ddd572248b3ed181bf3d833d97a3d96
5
5
  SHA512:
6
- metadata.gz: e8c2a9720f9e309698c1ac04e51bcb1acbe141443997dde1e9daf636cc9ce5356d3f87f7fb9ea18b7c9c760e1e9a57039e9d7aafb1bbcf98d2cbe3f657e89483
7
- data.tar.gz: bf1d58dd34328aedb4d18529b9f172c05b442a3040a332ee0f5d4edfd1e76b50b2df72f2edeb195f44a77ea45628627e3f4413847203581c9610bc5cee69fb75
6
+ metadata.gz: 30807c60113fe1eed915092e005e83863a4cfc5911a859eb00925299a1a4e45dc0dd581006d0486d3ab176ed2babf7b1fb5e90f0b41e04838c855a578db31faf
7
+ data.tar.gz: a44051e6aa3f1010dd5ee17f1046dfccabd59a929ed5b8976eeda135410f681400f1f024c61a84992cc28500285148e598aad7aa2daab10a965aca0204398a28
data/README.md CHANGED
@@ -160,6 +160,14 @@ Fetch sample log from CloudWatch Logs:
160
160
  #endpoint http://localhost:5000/
161
161
  #json_handler json
162
162
  #log_rejected_request true
163
+ #<web_identity_credentials>
164
+ # role_arn "#{ENV['AWS_ROLE_ARN']}"
165
+ # role_session_name ROLE_SESSION_NAME
166
+ # web_identity_token_file "#{ENV['AWS_WEB_IDENTITY_TOKEN_FILE']}"
167
+ #</web_identity_credentials>
168
+ #<format>
169
+ # @type ltsv
170
+ #</format>
163
171
  </match>
164
172
  ```
165
173
 
@@ -194,6 +202,15 @@ Fetch sample log from CloudWatch Logs:
194
202
  * `retention_in_days_key`: use specified field of records as retention period
195
203
  * `use_tag_as_group`: to use tag as a group name
196
204
  * `use_tag_as_stream`: to use tag as a stream name
205
+ * `<web_identity_credentials>`: For EKS authentication.
206
+ * `role_arn`: The Amazon Resource Name (ARN) of the role to assume. This parameter is required when using `<web_identity_credentials>`.
207
+ * `role_session_name`: An identifier for the assumed role session. This parameter is required when using `<web_identity_credentials>`.
208
+ * `web_identity_token_file`: The absolute path to the file on disk containing the OIDC token. This parameter is required when using `<web_identity_credentials>`.
209
+ * `policy`: An IAM policy in JSON format. (default `nil`)
210
+ * `duration_seconds`: The duration, in seconds, of the role session. The value can range from
211
+ 900 seconds (15 minutes) to 43200 seconds (12 hours). By default, the value
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.
197
214
 
198
215
  **NOTE:** `retention_in_days` requests additional IAM permission `logs:PutRetentionPolicy` for log_group.
199
216
  Please refer to [the PutRetentionPolicy column in documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/permissions-reference-cwl.html) for details.
@@ -205,6 +222,9 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
205
222
  @type cloudwatch_logs
206
223
  tag cloudwatch.in
207
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
208
228
  log_stream_name stream
209
229
  #use_log_stream_name_prefix true
210
230
  state_file /var/lib/fluent/group_stream.in.state
@@ -221,6 +241,11 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
221
241
  #<storage>
222
242
  # @type local # or redis, memcached, etc.
223
243
  #</storage>
244
+ #<web_identity_credentials>
245
+ # role_arn "#{ENV['AWS_ROLE_ARN']}"
246
+ # role_session_name ROLE_SESSION_NAME
247
+ # web_identity_token_file "#{ENV['AWS_WEB_IDENTITY_TOKEN_FILE']}"
248
+ #</web_identity_credentials>
224
249
  </source>
225
250
  ```
226
251
 
@@ -234,6 +259,9 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
234
259
  * `http_proxy`: use to set an optional HTTP proxy
235
260
  * `json_handler`: name of the library to be used to handle JSON data. For now, supported libraries are `json` (default) and `yajl`.
236
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`)
237
265
  * `log_stream_name`: name of log stream to fetch logs
238
266
  * `region`: AWS Region. See [Authentication](#authentication) for more information.
239
267
  * `throttling_retry_seconds`: time period in seconds to retry a request when aws CloudWatch rate limit exceeds (default: nil)
@@ -249,6 +277,14 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
249
277
  * `format`: specify CloudWatchLogs' log format. (default `nil`)
250
278
  * `<parse>`: specify parser plugin configuration. see also: https://docs.fluentd.org/v/1.0/parser#how-to-use
251
279
  * `<storage>`: specify storage plugin configuration. see also: https://docs.fluentd.org/v/1.0/storage#how-to-use
280
+ * `<web_identity_credentials>`: For EKS authentication.
281
+ * `role_arn`: The Amazon Resource Name (ARN) of the role to assume. This parameter is required when using `<web_identity_credentials>`.
282
+ * `role_session_name`: An identifier for the assumed role session. This parameter is required when using `<web_identity_credentials>`.
283
+ * `web_identity_token_file`: The absolute path to the file on disk containing the OIDC token. This parameter is required when using `<web_identity_credentials>`.
284
+ * `policy`: An IAM policy in JSON format. (default `nil`)
285
+ * `duration_seconds`: The duration, in seconds, of the role session. The value can range from
286
+ 900 seconds (15 minutes) to 43200 seconds (12 hours). By default, the value
287
+ is set to 3600 seconds (1 hour). (default `nil`)
252
288
 
253
289
  ## Test
254
290
 
@@ -2,7 +2,7 @@ module Fluent
2
2
  module Plugin
3
3
  module Cloudwatch
4
4
  module Logs
5
- VERSION = "0.10.2"
5
+ VERSION = "0.13.0"
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,
@@ -35,6 +39,13 @@ module Fluent::Plugin
35
39
  config_param :time_range_format, :string, default: "%Y-%m-%d %H:%M:%S"
36
40
  config_param :throttling_retry_seconds, :time, default: nil
37
41
  config_param :include_metadata, :bool, default: false
42
+ config_section :web_identity_credentials, multi: false do
43
+ config_param :role_arn, :string
44
+ config_param :role_session_name, :string
45
+ config_param :web_identity_token_file, :string, default: nil #required
46
+ config_param :policy, :string, default: nil
47
+ config_param :duration_seconds, :time, default: nil
48
+ end
38
49
 
39
50
  config_section :parse do
40
51
  config_set_default :@type, 'none'
@@ -75,10 +86,29 @@ module Fluent::Plugin
75
86
 
76
87
  if @aws_use_sts
77
88
  Aws.config[:region] = options[:region]
78
- options[:credentials] = Aws::AssumeRoleCredentials.new(
89
+ credentials_options = {
79
90
  role_arn: @aws_sts_role_arn,
80
91
  role_session_name: @aws_sts_session_name
81
- )
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)
100
+ elsif @web_identity_credentials
101
+ c = @web_identity_credentials
102
+ credentials_options = {}
103
+ credentials_options[:role_arn] = c.role_arn
104
+ credentials_options[:role_session_name] = c.role_session_name
105
+ credentials_options[:web_identity_token_file] = c.web_identity_token_file
106
+ credentials_options[:policy] = c.policy if c.policy
107
+ credentials_options[:duration_seconds] = c.duration_seconds if c.duration_seconds
108
+ if @region
109
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
110
+ end
111
+ options[:credentials] = Aws::AssumeRoleWebIdentityCredentials.new(credentials_options)
82
112
  else
83
113
  options[:credentials] = Aws::Credentials.new(@aws_key_id, @aws_sec_key) if @aws_key_id && @aws_sec_key
84
114
  end
@@ -101,6 +131,17 @@ module Fluent::Plugin
101
131
  super
102
132
  end
103
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
+
104
145
  private
105
146
  def configure_parser(conf)
106
147
  if conf['format']
@@ -110,28 +151,20 @@ module Fluent::Plugin
110
151
  end
111
152
  end
112
153
 
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
154
  def migrate_state_file_to_storage(log_stream_name)
122
155
  @next_token_storage.put(:"#{state_key_for(log_stream_name)}", File.read(state_key_for(log_stream_name)).chomp)
123
156
  File.delete(state_key_for(log_stream_name))
124
157
  end
125
158
 
126
- def next_token(log_stream_name)
159
+ def next_token(log_stream_name, log_group_name = nil)
127
160
  if @next_token_storage.persistent && File.exist?(state_key_for(log_stream_name))
128
161
  migrate_state_file_to_storage(log_stream_name)
129
162
  end
130
- @next_token_storage.get(:"#{state_key_for(log_stream_name)}")
163
+ @next_token_storage.get(:"#{state_key_for(log_stream_name, log_group_name)}")
131
164
  end
132
165
 
133
- def store_next_token(token, log_stream_name = nil)
134
- @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)
135
168
  end
136
169
 
137
170
  def run
@@ -141,55 +174,67 @@ module Fluent::Plugin
141
174
  if Time.now > @next_fetch_time
142
175
  @next_fetch_time += @fetch_interval
143
176
 
144
- if @use_log_stream_name_prefix || @use_todays_log_stream
145
- log_stream_name_prefix = @use_todays_log_stream ? get_todays_date : @log_stream_name
146
- begin
147
- log_streams = describe_log_streams(log_stream_name_prefix)
148
- log_streams.concat(describe_log_streams(get_yesterdays_date)) if @use_todays_log_stream
149
- log_streams.each do |log_stream|
150
- log_stream_name = log_stream.log_stream_name
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
160
- events.each do |event|
161
- 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)
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
162
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)
163
221
  end
164
- rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
165
- log.warn "'#{@log_stream_name}' prefixed log stream(s) are not found"
166
- next
167
- end
168
- else
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
178
- events.each do |event|
179
- emit(log_stream_name, event, metadata)
180
222
  end
181
223
  end
182
- end
183
224
  sleep 1
225
+ end
184
226
  end
185
227
  end
186
228
 
187
- def emit(stream, event, metadata)
229
+ def emit(group, stream, event, metadata)
188
230
  if @parser
189
231
  @parser.parse(event.message) {|time,record|
190
232
  if @use_aws_timestamp
191
233
  time = (event.timestamp / 1000).floor
192
234
  end
235
+ if @add_log_group_name
236
+ record[@log_group_name_key] = group
237
+ end
193
238
  unless metadata.empty?
194
239
  record.merge!("metadata" => metadata)
195
240
  end
@@ -199,6 +244,9 @@ module Fluent::Plugin
199
244
  time = (event.timestamp / 1000).floor
200
245
  begin
201
246
  record = @json_handler.load(event.message)
247
+ if @add_log_group_name
248
+ record[@log_group_name_key] = group
249
+ end
202
250
  unless metadata.empty?
203
251
  record.merge!("metadata" => metadata)
204
252
  end
@@ -210,19 +258,23 @@ module Fluent::Plugin
210
258
  end
211
259
  end
212
260
 
213
- def get_events(log_stream_name)
261
+ def get_events(log_group_name, log_stream_name)
214
262
  throttling_handler('get_log_events') do
215
263
  request = {
216
- log_group_name: @log_group_name,
264
+ log_group_name: log_group_name,
217
265
  log_stream_name: log_stream_name
218
266
  }
219
267
  request.merge!(start_time: @start_time) if @start_time
220
268
  request.merge!(end_time: @end_time) if @end_time
221
- log_next_token = next_token(log_stream_name)
269
+ log_next_token = next_token(log_group_name, log_stream_name)
222
270
  request[:next_token] = log_next_token if !log_next_token.nil? && !log_next_token.empty?
223
271
  response = @logs.get_log_events(request)
224
272
  if valid_next_token(log_next_token, response.next_forward_token)
225
- 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
226
278
  end
227
279
 
228
280
  response.events
@@ -262,6 +314,23 @@ module Fluent::Plugin
262
314
  end
263
315
  end
264
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
+
265
334
  def valid_next_token(prev_token, next_token)
266
335
  next_token && prev_token != next_token.chomp
267
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
@@ -46,10 +47,20 @@ module Fluent::Plugin
46
47
  config_param :remove_retention_in_days_key, :bool, default: false
47
48
  config_param :json_handler, :enum, list: [:yajl, :json], :default => :yajl
48
49
  config_param :log_rejected_request, :bool, :default => false
50
+ config_section :web_identity_credentials, multi: false do
51
+ config_param :role_arn, :string
52
+ config_param :role_session_name, :string
53
+ config_param :web_identity_token_file, :string, default: nil #required
54
+ config_param :policy, :string, default: nil
55
+ config_param :duration_seconds, :time, default: nil
56
+ end
49
57
 
50
58
  config_section :buffer do
51
59
  config_set_default :@type, DEFAULT_BUFFER_TYPE
52
60
  end
61
+ config_section :format do
62
+ config_set_default :@type, 'json'
63
+ end
53
64
 
54
65
  MAX_EVENTS_SIZE = 1_048_576
55
66
  MAX_EVENT_SIZE = 256 * 1024
@@ -80,6 +91,22 @@ module Fluent::Plugin
80
91
  if [conf['retention_in_days'], conf['retention_in_days_key']].compact.size > 1
81
92
  raise ConfigError, "Set only one of retention_in_days, retention_in_days_key"
82
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
83
110
  end
84
111
 
85
112
  def start
@@ -94,10 +121,29 @@ module Fluent::Plugin
94
121
 
95
122
  if @aws_use_sts
96
123
  Aws.config[:region] = options[:region]
97
- options[:credentials] = Aws::AssumeRoleCredentials.new(
124
+ credentials_options = {
98
125
  role_arn: @aws_sts_role_arn,
99
126
  role_session_name: @aws_sts_session_name
100
- )
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)
135
+ elsif @web_identity_credentials
136
+ c = @web_identity_credentials
137
+ credentials_options = {}
138
+ credentials_options[:role_arn] = c.role_arn
139
+ credentials_options[:role_session_name] = c.role_session_name
140
+ credentials_options[:web_identity_token_file] = c.web_identity_token_file
141
+ credentials_options[:policy] = c.policy if c.policy
142
+ credentials_options[:duration_seconds] = c.duration_seconds if c.duration_seconds
143
+ if @region
144
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
145
+ end
146
+ options[:credentials] = Aws::AssumeRoleWebIdentityCredentials.new(credentials_options)
101
147
  else
102
148
  options[:credentials] = Aws::Credentials.new(@aws_key_id, @aws_sec_key) if @aws_key_id && @aws_sec_key
103
149
  end
@@ -237,11 +283,7 @@ module Fluent::Plugin
237
283
  time_ms = (time.to_f * 1000).floor
238
284
 
239
285
  scrub_record!(record)
240
- unless @message_keys.empty?
241
- message = @message_keys.map{|k| record[k].to_s }.reject{|e| e.empty? }.join(' ')
242
- else
243
- message = @json_handler.dump(record)
244
- end
286
+ message = @formatter_proc.call(t, time, record)
245
287
 
246
288
  if message.empty?
247
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.10.2
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryota Arai
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-21 00:00:00.000000000 Z
11
+ date: 2020-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd
@@ -108,7 +108,7 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
- description:
111
+ description:
112
112
  email:
113
113
  - ryota.arai@gmail.com
114
114
  executables: []
@@ -136,7 +136,7 @@ homepage: https://github.com/fluent-plugins-nursery/fluent-plugin-cloudwatch-log
136
136
  licenses:
137
137
  - MIT
138
138
  metadata: {}
139
- post_install_message:
139
+ post_install_message:
140
140
  rdoc_options: []
141
141
  require_paths:
142
142
  - lib
@@ -151,8 +151,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  requirements: []
154
- rubygems_version: 3.0.3
155
- signing_key:
154
+ rubygems_version: 3.1.2
155
+ signing_key:
156
156
  specification_version: 4
157
157
  summary: CloudWatch Logs Plugin for Fluentd
158
158
  test_files: