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 +4 -4
- data/README.md +36 -0
- data/lib/fluent/plugin/cloudwatch/logs/version.rb +1 -1
- data/lib/fluent/plugin/in_cloudwatch_logs.rb +123 -54
- data/lib/fluent/plugin/out_cloudwatch_logs.rb +50 -8
- data/test/plugin/test_in_cloudwatch_logs.rb +122 -0
- data/test/plugin/test_out_cloudwatch_logs.rb +137 -0
- data/test/test_helper.rb +4 -0
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17e367fb1fc1c2b58cd9a22cd9caefaf6396b3542f589b44616f160c40c3219d
|
4
|
+
data.tar.gz: 37eee2b6a48d17d86451a8eee88435fc1ddd572248b3ed181bf3d833d97a3d96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
|
@@ -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
|
-
|
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 @
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/test/test_helper.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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:
|