fluent-plugin-cloudwatch-logs 0.9.5 → 0.10.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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 105e96fa516c4f972fc2b01f83c1daa30d39ff4b827b46b811b9f30514231516
|
4
|
+
data.tar.gz: 85741a1b0ccad0f9e207c837e7417b786ef68d1fc1552cbe80b73030e5b4191b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84a8458e9704102609b382b9cb8fe4c332a1c1b9c9562b89b959961bb3624958cbffc1f965eba2a502c10a0e45f1ceb427a987681d5420c35663bc6df88295ed
|
7
|
+
data.tar.gz: d6e22ad22c4eef5cb6c8fab7690ca75585e7ad99756926181f50c43fef70df0e52e12722e14809b793947e56ed8199fc393a318f42308f05fd6d23d6aa9a4b13
|
data/README.md
CHANGED
@@ -178,6 +178,9 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
|
|
178
178
|
#<parse>
|
179
179
|
# @type none # or csv, tsv, regexp etc.
|
180
180
|
#</parse>
|
181
|
+
#<storage>
|
182
|
+
# @type local # or redis, memcached, etc.
|
183
|
+
#</storage>
|
181
184
|
</source>
|
182
185
|
```
|
183
186
|
|
@@ -194,7 +197,8 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
|
|
194
197
|
* `log_stream_name`: name of log stream to fetch logs
|
195
198
|
* `region`: AWS Region. See [Authentication](#authentication) for more information.
|
196
199
|
* `throttling_retry_seconds`: time period in seconds to retry a request when aws CloudWatch rate limit exceeds (default: nil)
|
197
|
-
* `
|
200
|
+
* `include_metadata`: include metadata such as `log_group_name` and `log_stream_name`. (default: false)
|
201
|
+
* `state_file`: file to store current state (e.g. next\_forward\_token). This parameter is deprecated. Use `<storage>` instead.
|
198
202
|
* `tag`: fluentd tag
|
199
203
|
* `use_log_stream_name_prefix`: to use `log_stream_name` as log stream name prefix (default false)
|
200
204
|
* `use_todays_log_stream`: use todays and yesterdays date as log stream name prefix (formatted YYYY/MM/DD). (default: `false`)
|
@@ -204,6 +208,7 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
|
|
204
208
|
* `time_range_format`: specify time format for time range. (default: `%Y-%m-%d %H:%M:%S`)
|
205
209
|
* `format`: specify CloudWatchLogs' log format. (default `nil`)
|
206
210
|
* `<parse>`: specify parser plugin configuration. see also: https://docs.fluentd.org/v/1.0/parser#how-to-use
|
211
|
+
* `<storage>`: specify storage plugin configuration. see also: https://docs.fluentd.org/v/1.0/storage#how-to-use
|
207
212
|
|
208
213
|
## Test
|
209
214
|
|
@@ -8,7 +8,9 @@ module Fluent::Plugin
|
|
8
8
|
class CloudwatchLogsInput < Input
|
9
9
|
Fluent::Plugin.register_input('cloudwatch_logs', self)
|
10
10
|
|
11
|
-
helpers :parser, :thread, :compat_parameters
|
11
|
+
helpers :parser, :thread, :compat_parameters, :storage
|
12
|
+
|
13
|
+
DEFAULT_STORAGE_TYPE = 'local'
|
12
14
|
|
13
15
|
config_param :aws_key_id, :string, default: nil, secret: true
|
14
16
|
config_param :aws_sec_key, :string, default: nil, secret: true
|
@@ -21,7 +23,8 @@ module Fluent::Plugin
|
|
21
23
|
config_param :log_group_name, :string
|
22
24
|
config_param :log_stream_name, :string, default: nil
|
23
25
|
config_param :use_log_stream_name_prefix, :bool, default: false
|
24
|
-
config_param :state_file, :string
|
26
|
+
config_param :state_file, :string, default: nil,
|
27
|
+
deprecated: "Use <stroage> instead."
|
25
28
|
config_param :fetch_interval, :time, default: 60
|
26
29
|
config_param :http_proxy, :string, default: nil
|
27
30
|
config_param :json_handler, :enum, list: [:yajl, :json], default: :yajl
|
@@ -31,11 +34,18 @@ module Fluent::Plugin
|
|
31
34
|
config_param :end_time, :string, default: nil
|
32
35
|
config_param :time_range_format, :string, default: "%Y-%m-%d %H:%M:%S"
|
33
36
|
config_param :throttling_retry_seconds, :time, default: nil
|
37
|
+
config_param :include_metadata, :bool, default: false
|
34
38
|
|
35
39
|
config_section :parse do
|
36
40
|
config_set_default :@type, 'none'
|
37
41
|
end
|
38
42
|
|
43
|
+
config_section :storage do
|
44
|
+
config_set_default :usage, 'store_next_tokens'
|
45
|
+
config_set_default :@type, DEFAULT_STORAGE_TYPE
|
46
|
+
config_set_default :persistent, false
|
47
|
+
end
|
48
|
+
|
39
49
|
def initialize
|
40
50
|
super
|
41
51
|
|
@@ -53,6 +63,7 @@ module Fluent::Plugin
|
|
53
63
|
if @start_time && @end_time && (@end_time < @start_time)
|
54
64
|
raise Fluent::ConfigError, "end_time(#{@end_time}) should be greater than start_time(#{@start_time})."
|
55
65
|
end
|
66
|
+
@next_token_storage = storage_create(usage: 'store_next_tokens', conf: config, default_type: DEFAULT_STORAGE_TYPE)
|
56
67
|
end
|
57
68
|
|
58
69
|
def start
|
@@ -99,20 +110,28 @@ module Fluent::Plugin
|
|
99
110
|
end
|
100
111
|
end
|
101
112
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
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
|
+
def migrate_state_file_to_storage(log_stream_name)
|
122
|
+
@next_token_storage.put(:"#{state_key_for(log_stream_name)}", File.read(state_key_for(log_stream_name)).chomp)
|
123
|
+
File.delete(state_key_for(log_stream_name))
|
105
124
|
end
|
106
125
|
|
107
126
|
def next_token(log_stream_name)
|
108
|
-
|
109
|
-
|
127
|
+
if @next_token_storage.persistent && File.exist?(state_key_for(log_stream_name))
|
128
|
+
migrate_state_file_to_storage(log_stream_name)
|
129
|
+
end
|
130
|
+
@next_token_storage.get(:"#{state_key_for(log_stream_name)}")
|
110
131
|
end
|
111
132
|
|
112
133
|
def store_next_token(token, log_stream_name = nil)
|
113
|
-
|
114
|
-
f.write token
|
115
|
-
end
|
134
|
+
@next_token_storage.put(:"#{state_key_for(log_stream_name)}", token)
|
116
135
|
end
|
117
136
|
|
118
137
|
def run
|
@@ -130,8 +149,16 @@ module Fluent::Plugin
|
|
130
149
|
log_streams.each do |log_stream|
|
131
150
|
log_stream_name = log_stream.log_stream_name
|
132
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
|
133
160
|
events.each do |event|
|
134
|
-
emit(log_stream_name, event)
|
161
|
+
emit(log_stream_name, event, metadata)
|
135
162
|
end
|
136
163
|
end
|
137
164
|
rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
|
@@ -140,8 +167,16 @@ module Fluent::Plugin
|
|
140
167
|
end
|
141
168
|
else
|
142
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
|
143
178
|
events.each do |event|
|
144
|
-
emit(log_stream_name, event)
|
179
|
+
emit(log_stream_name, event, metadata)
|
145
180
|
end
|
146
181
|
end
|
147
182
|
end
|
@@ -149,18 +184,24 @@ module Fluent::Plugin
|
|
149
184
|
end
|
150
185
|
end
|
151
186
|
|
152
|
-
def emit(stream, event)
|
187
|
+
def emit(stream, event, metadata)
|
153
188
|
if @parser
|
154
189
|
@parser.parse(event.message) {|time,record|
|
155
190
|
if @use_aws_timestamp
|
156
191
|
time = (event.timestamp / 1000).floor
|
157
192
|
end
|
193
|
+
unless metadata.empty?
|
194
|
+
record.merge!("metadata" => metadata)
|
195
|
+
end
|
158
196
|
router.emit(@tag, time, record)
|
159
197
|
}
|
160
198
|
else
|
161
199
|
time = (event.timestamp / 1000).floor
|
162
200
|
begin
|
163
201
|
record = @json_handler.load(event.message)
|
202
|
+
unless metadata.empty?
|
203
|
+
record.merge!("metadata" => metadata)
|
204
|
+
end
|
164
205
|
router.emit(@tag, time, record)
|
165
206
|
rescue JSON::ParserError, Yajl::ParseError => error # Catch parser errors
|
166
207
|
log.error "Invalid JSON encountered while parsing event.message"
|
@@ -377,8 +377,7 @@ module Fluent::Plugin
|
|
377
377
|
end
|
378
378
|
rescue Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException, Aws::CloudWatchLogs::Errors::DataAlreadyAcceptedException => err
|
379
379
|
sleep 1 # to avoid too many API calls
|
380
|
-
|
381
|
-
store_next_sequence_token(group_name, stream_name, log_stream.upload_sequence_token)
|
380
|
+
store_next_sequence_token(group_name, stream_name, err.expected_sequence_token)
|
382
381
|
log.warn "updating upload sequence token forcefully because unrecoverable error occured", {
|
383
382
|
"error" => err,
|
384
383
|
"log_group" => group_name,
|
@@ -99,6 +99,34 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
99
99
|
assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
|
100
100
|
end
|
101
101
|
|
102
|
+
def test_emit_with_metadata
|
103
|
+
create_log_stream
|
104
|
+
|
105
|
+
time_ms = (Time.now.to_f * 1000).floor
|
106
|
+
put_log_events([
|
107
|
+
{timestamp: time_ms, message: '{"cloudwatch":"logs1"}'},
|
108
|
+
{timestamp: time_ms, message: '{"cloudwatch":"logs2"}'},
|
109
|
+
])
|
110
|
+
|
111
|
+
sleep 5
|
112
|
+
|
113
|
+
d = create_driver(default_config + %[include_metadata true])
|
114
|
+
d.run(expect_emits: 2, timeout: 5)
|
115
|
+
|
116
|
+
emits = d.events
|
117
|
+
assert_true(emits[0][2].has_key?("metadata"))
|
118
|
+
assert_true(emits[1][2].has_key?("metadata"))
|
119
|
+
emits[0][2].delete_if {|k, v|
|
120
|
+
k == "metadata"
|
121
|
+
}
|
122
|
+
emits[1][2].delete_if {|k, v|
|
123
|
+
k == "metadata"
|
124
|
+
}
|
125
|
+
assert_equal(2, emits.size)
|
126
|
+
assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs1'}], emits[0])
|
127
|
+
assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
|
128
|
+
end
|
129
|
+
|
102
130
|
def test_emit_with_aws_timestamp
|
103
131
|
create_log_stream
|
104
132
|
|
@@ -173,7 +201,6 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
173
201
|
'@type' => 'cloudwatch_logs',
|
174
202
|
'log_group_name' => "#{log_group_name}",
|
175
203
|
'log_stream_name' => "#{log_stream_name}",
|
176
|
-
'state_file' => '/tmp/state',
|
177
204
|
}
|
178
205
|
cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_key_id)) if ENV['aws_key_id']
|
179
206
|
cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_sec_key)) if ENV['aws_sec_key']
|
@@ -183,7 +210,9 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
183
210
|
csv_format_config = config_element('ROOT', '', cloudwatch_config, [
|
184
211
|
config_element('parse', '', {'@type' => 'csv',
|
185
212
|
'keys' => 'time,message',
|
186
|
-
'time_key' => 'time'})
|
213
|
+
'time_key' => 'time'}),
|
214
|
+
config_element('storage', '', {'@type' => 'local',
|
215
|
+
'path' => '/tmp/state'})
|
187
216
|
])
|
188
217
|
create_log_stream
|
189
218
|
|
@@ -205,6 +234,54 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
205
234
|
assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs2"}], emits[1])
|
206
235
|
end
|
207
236
|
|
237
|
+
test "emit with <parse> csv with metadata" do
|
238
|
+
cloudwatch_config = {'tag' => "test",
|
239
|
+
'@type' => 'cloudwatch_logs',
|
240
|
+
'log_group_name' => "#{log_group_name}",
|
241
|
+
'log_stream_name' => "#{log_stream_name}",
|
242
|
+
'include_metadata' => true,
|
243
|
+
}
|
244
|
+
cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_key_id)) if ENV['aws_key_id']
|
245
|
+
cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_sec_key)) if ENV['aws_sec_key']
|
246
|
+
cloudwatch_config = cloudwatch_config.merge!(config_elementify(region)) if ENV['region']
|
247
|
+
cloudwatch_config = cloudwatch_config.merge!(config_elementify(endpoint)) if ENV['endpoint']
|
248
|
+
|
249
|
+
csv_format_config = config_element('ROOT', '', cloudwatch_config, [
|
250
|
+
config_element('parse', '', {'@type' => 'csv',
|
251
|
+
'keys' => 'time,message',
|
252
|
+
'time_key' => 'time'}),
|
253
|
+
config_element('storage', '', {'@type' => 'local',
|
254
|
+
'path' => '/tmp/state'})
|
255
|
+
|
256
|
+
])
|
257
|
+
create_log_stream
|
258
|
+
|
259
|
+
time_ms = (Time.now.to_f * 1000).floor
|
260
|
+
log_time_ms = time_ms - 10000
|
261
|
+
put_log_events([
|
262
|
+
{timestamp: time_ms, message: Time.at(log_time_ms/1000.floor).to_s + ",Cloudwatch non json logs1"},
|
263
|
+
{timestamp: time_ms, message: Time.at(log_time_ms/1000.floor).to_s + ",Cloudwatch non json logs2"},
|
264
|
+
])
|
265
|
+
|
266
|
+
sleep 5
|
267
|
+
|
268
|
+
d = create_driver(csv_format_config)
|
269
|
+
d.run(expect_emits: 2, timeout: 5)
|
270
|
+
|
271
|
+
emits = d.events
|
272
|
+
assert_true(emits[0][2].has_key?("metadata"))
|
273
|
+
assert_true(emits[1][2].has_key?("metadata"))
|
274
|
+
emits[0][2].delete_if {|k, v|
|
275
|
+
k == "metadata"
|
276
|
+
}
|
277
|
+
emits[1][2].delete_if {|k, v|
|
278
|
+
k == "metadata"
|
279
|
+
}
|
280
|
+
assert_equal(2, emits.size)
|
281
|
+
assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs1"}], emits[0])
|
282
|
+
assert_equal(['test', (log_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs2"}], emits[1])
|
283
|
+
end
|
284
|
+
|
208
285
|
def test_emit_width_format
|
209
286
|
create_log_stream
|
210
287
|
|
@@ -246,7 +323,6 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
246
323
|
'@type' => 'cloudwatch_logs',
|
247
324
|
'log_group_name' => "#{log_group_name}",
|
248
325
|
'log_stream_name' => "#{log_stream_name}",
|
249
|
-
'state_file' => '/tmp/state',
|
250
326
|
}
|
251
327
|
cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_key_id)) if ENV['aws_key_id']
|
252
328
|
cloudwatch_config = cloudwatch_config.merge!(config_elementify(aws_sec_key)) if ENV['aws_sec_key']
|
@@ -256,7 +332,9 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
256
332
|
regex_format_config = config_element('ROOT', '', cloudwatch_config, [
|
257
333
|
config_element('parse', '', {'@type' => 'regexp',
|
258
334
|
'expression' => "/^(?<cloudwatch>[^ ]*)?/",
|
259
|
-
})
|
335
|
+
}),
|
336
|
+
config_element('storage', '', {'@type' => 'local',
|
337
|
+
'path' => '/tmp/state'})
|
260
338
|
])
|
261
339
|
create_log_stream
|
262
340
|
|
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.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryota Arai
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|