fluent-plugin-cloudwatch-logs 0.10.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: