fluent-plugin-cloudwatch-logs 0.9.0 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -1
- data/lib/fluent/plugin/cloudwatch/logs/version.rb +1 -1
- data/lib/fluent/plugin/in_cloudwatch_logs.rb +54 -24
- data/lib/fluent/plugin/out_cloudwatch_logs.rb +9 -1
- data/test/plugin/test_in_cloudwatch_logs.rb +108 -0
- data/test/plugin/test_out_cloudwatch_logs.rb +62 -0
- data/test/test_helper.rb +1 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13ef3e0d5f4fd4c12c49b16ccbd07db237b7cff26ea272e4ef6337b09b02e975
|
4
|
+
data.tar.gz: 8cefeed014b1842ccce9f15479ad8cc54aa51b976281abb5d3e1c8828239aaa3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41ca6fa20d0c26955fcbab7f7a5a9fb8c032a5fd09f8676ccaaf5e4dd12694ea74c7c5320590f076405c691d436bc87b820e6bc9b75dc516062b2f3e09d8a377
|
7
|
+
data.tar.gz: 1f5bbd694c5962de73ee1e0ca622d8892437fb82d00b76ed87086feeba49f6d2c3a4904edad81bec88ba2d3ca47701c15cb56395470f19742c16b3168dd870e5
|
data/README.md
CHANGED
@@ -149,7 +149,7 @@ Fetch sample log from CloudWatch Logs:
|
|
149
149
|
* `remove_log_group_aws_tags_key`: remove field specified by `log_group_aws_tags_key`
|
150
150
|
* `remove_log_group_name_key`: remove field specified by `log_group_name_key`
|
151
151
|
* `remove_log_stream_name_key`: remove field specified by `log_stream_name_key`
|
152
|
-
* `
|
152
|
+
* `remove_retention_in_days_key`: remove field specified by `retention_in_days_key`
|
153
153
|
* `retention_in_days`: use to set the expiry time for log group when created with `auto_create_stream`. (default to no expiry)
|
154
154
|
* `retention_in_days_key`: use specified field of records as retention period
|
155
155
|
* `use_tag_as_group`: to use tag as a group name
|
@@ -170,6 +170,9 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
|
|
170
170
|
state_file /var/lib/fluent/group_stream.in.state
|
171
171
|
#endpoint http://localhost:5000/
|
172
172
|
#json_handler json
|
173
|
+
# start_time "2020-03-01 00:00:00Z"
|
174
|
+
# end_time "2020-04-30 15:00:00Z"
|
175
|
+
# time_range_format "%Y-%m-%d %H:%M:%S%z"
|
173
176
|
# Users can use `format` or `<parse>` directive to parse non-JSON CloudwatchLogs' log
|
174
177
|
# format none # or csv, tsv, regexp etc.
|
175
178
|
#<parse>
|
@@ -190,11 +193,15 @@ Please refer to [the PutRetentionPolicy column in documentation](https://docs.aw
|
|
190
193
|
* `log_group_name`: name of log group to fetch logs
|
191
194
|
* `log_stream_name`: name of log stream to fetch logs
|
192
195
|
* `region`: AWS Region. See [Authentication](#authentication) for more information.
|
196
|
+
* `throttling_retry_seconds`: time period in seconds to retry a request when aws CloudWatch rate limit exceeds (default: nil)
|
193
197
|
* `state_file`: file to store current state (e.g. next\_forward\_token)
|
194
198
|
* `tag`: fluentd tag
|
195
199
|
* `use_log_stream_name_prefix`: to use `log_stream_name` as log stream name prefix (default false)
|
196
200
|
* `use_todays_log_stream`: use todays and yesterdays date as log stream name prefix (formatted YYYY/MM/DD). (default: `false`)
|
197
201
|
* `use_aws_timestamp`: get timestamp from Cloudwatch event for non json logs, otherwise fluentd will parse the log to get the timestamp (default `false`)
|
202
|
+
* `start_time`: specify starting time range for obtaining logs. (default: `nil`)
|
203
|
+
* `end_time`: specify ending time range for obtaining logs. (default: `nil`)
|
204
|
+
* `time_range_format`: specify time format for time range. (default: `%Y-%m-%d %H:%M:%S`)
|
198
205
|
* `format`: specify CloudWatchLogs' log format. (default `nil`)
|
199
206
|
* `<parse>`: specify parser plugin configuration. see also: https://docs.fluentd.org/v/1.0/parser#how-to-use
|
200
207
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'time'
|
2
3
|
require 'fluent/plugin/input'
|
3
4
|
require 'fluent/plugin/parser'
|
4
5
|
require 'yajl'
|
@@ -26,6 +27,10 @@ module Fluent::Plugin
|
|
26
27
|
config_param :json_handler, :enum, list: [:yajl, :json], default: :yajl
|
27
28
|
config_param :use_todays_log_stream, :bool, default: false
|
28
29
|
config_param :use_aws_timestamp, :bool, default: false
|
30
|
+
config_param :start_time, :string, default: nil
|
31
|
+
config_param :end_time, :string, default: nil
|
32
|
+
config_param :time_range_format, :string, default: "%Y-%m-%d %H:%M:%S"
|
33
|
+
config_param :throttling_retry_seconds, :time, default: nil
|
29
34
|
|
30
35
|
config_section :parse do
|
31
36
|
config_set_default :@type, 'none'
|
@@ -42,6 +47,12 @@ module Fluent::Plugin
|
|
42
47
|
compat_parameters_convert(conf, :parser)
|
43
48
|
super
|
44
49
|
configure_parser(conf)
|
50
|
+
|
51
|
+
@start_time = (Time.strptime(@start_time, @time_range_format).to_f * 1000).floor if @start_time
|
52
|
+
@end_time = (Time.strptime(@end_time, @time_range_format).to_f * 1000).floor if @end_time
|
53
|
+
if @start_time && @end_time && (@end_time < @start_time)
|
54
|
+
raise Fluent::ConfigError, "end_time(#{@end_time}) should be greater than start_time(#{@start_time})."
|
55
|
+
end
|
45
56
|
end
|
46
57
|
|
47
58
|
def start
|
@@ -159,36 +170,55 @@ module Fluent::Plugin
|
|
159
170
|
end
|
160
171
|
|
161
172
|
def get_events(log_stream_name)
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
173
|
+
throttling_handler('get_log_events') do
|
174
|
+
request = {
|
175
|
+
log_group_name: @log_group_name,
|
176
|
+
log_stream_name: log_stream_name
|
177
|
+
}
|
178
|
+
request.merge!(start_time: @start_time) if @start_time
|
179
|
+
request.merge!(end_time: @end_time) if @end_time
|
180
|
+
log_next_token = next_token(log_stream_name)
|
181
|
+
request[:next_token] = log_next_token if !log_next_token.nil? && !log_next_token.empty?
|
182
|
+
response = @logs.get_log_events(request)
|
183
|
+
if valid_next_token(log_next_token, response.next_forward_token)
|
184
|
+
store_next_token(response.next_forward_token, log_stream_name)
|
185
|
+
end
|
172
186
|
|
173
|
-
|
187
|
+
response.events
|
188
|
+
end
|
174
189
|
end
|
175
190
|
|
176
191
|
def describe_log_streams(log_stream_name_prefix, log_streams = nil, next_token = nil)
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
log_streams
|
185
|
-
|
186
|
-
|
192
|
+
throttling_handler('describe_log_streams') do
|
193
|
+
request = {
|
194
|
+
log_group_name: @log_group_name
|
195
|
+
}
|
196
|
+
request[:next_token] = next_token if next_token
|
197
|
+
request[:log_stream_name_prefix] = log_stream_name_prefix if log_stream_name_prefix
|
198
|
+
response = @logs.describe_log_streams(request)
|
199
|
+
if log_streams
|
200
|
+
log_streams.concat(response.log_streams)
|
201
|
+
else
|
202
|
+
log_streams = response.log_streams
|
203
|
+
end
|
204
|
+
if response.next_token
|
205
|
+
log_streams = describe_log_streams(log_stream_name_prefix, log_streams, response.next_token)
|
206
|
+
end
|
207
|
+
log_streams
|
187
208
|
end
|
188
|
-
|
189
|
-
|
209
|
+
end
|
210
|
+
|
211
|
+
def throttling_handler(method_name)
|
212
|
+
yield
|
213
|
+
rescue Aws::CloudWatchLogs::Errors::ThrottlingException => err
|
214
|
+
if throttling_retry_seconds
|
215
|
+
log.warn "ThrottlingException #{method_name}. Waiting #{throttling_retry_seconds} seconds to retry."
|
216
|
+
sleep throttling_retry_seconds
|
217
|
+
|
218
|
+
throttling_handler(method_name) { yield }
|
219
|
+
else
|
220
|
+
raise err
|
190
221
|
end
|
191
|
-
log_streams
|
192
222
|
end
|
193
223
|
|
194
224
|
def valid_next_token(prev_token, next_token)
|
@@ -41,7 +41,7 @@ module Fluent::Plugin
|
|
41
41
|
config_param :remove_log_group_aws_tags_key, :bool, default: false
|
42
42
|
config_param :retention_in_days, :integer, default: nil
|
43
43
|
config_param :retention_in_days_key, :string, default: nil
|
44
|
-
config_param :
|
44
|
+
config_param :remove_retention_in_days_key, :bool, default: false
|
45
45
|
config_param :json_handler, :enum, list: [:yajl, :json], :default => :yajl
|
46
46
|
config_param :log_rejected_request, :bool, :default => false
|
47
47
|
|
@@ -219,6 +219,14 @@ module Fluent::Plugin
|
|
219
219
|
|
220
220
|
events = []
|
221
221
|
rs.each do |t, time, record|
|
222
|
+
if @log_group_aws_tags_key && @remove_log_group_aws_tags_key
|
223
|
+
record.delete(@log_group_aws_tags_key)
|
224
|
+
end
|
225
|
+
|
226
|
+
if @retention_in_days_key && @remove_retention_in_days_key
|
227
|
+
record.delete(@retention_in_days_key)
|
228
|
+
end
|
229
|
+
|
222
230
|
record = drop_empty_record(record)
|
223
231
|
|
224
232
|
time_ms = (time.to_f * 1000).floor
|
@@ -25,6 +25,10 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
25
25
|
use_log_stream_name_prefix true
|
26
26
|
state_file /tmp/state
|
27
27
|
use_aws_timestamp true
|
28
|
+
start_time "2019-06-18 00:00:00Z"
|
29
|
+
end_time "2020-01-18 00:00:00Z"
|
30
|
+
time_range_format "%Y-%m-%d %H:%M:%S%z"
|
31
|
+
throttling_retry_seconds 30
|
28
32
|
EOC
|
29
33
|
|
30
34
|
assert_equal('test_id', d.instance.aws_key_id)
|
@@ -37,6 +41,30 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
37
41
|
assert_equal('/tmp/state', d.instance.state_file)
|
38
42
|
assert_equal(:yajl, d.instance.json_handler)
|
39
43
|
assert_equal(true, d.instance.use_aws_timestamp)
|
44
|
+
assert_equal(1560816000000, d.instance.start_time)
|
45
|
+
assert_equal(1579305600000, d.instance.end_time)
|
46
|
+
assert_equal("%Y-%m-%d %H:%M:%S%z", d.instance.time_range_format)
|
47
|
+
assert_equal(30, d.instance.throttling_retry_seconds)
|
48
|
+
end
|
49
|
+
|
50
|
+
test 'invalid time range' do
|
51
|
+
assert_raise(Fluent::ConfigError) do
|
52
|
+
create_driver(<<-EOC)
|
53
|
+
@type cloudwatch_logs
|
54
|
+
aws_key_id test_id
|
55
|
+
aws_sec_key test_key
|
56
|
+
region us-east-1
|
57
|
+
tag test
|
58
|
+
log_group_name group
|
59
|
+
log_stream_name stream
|
60
|
+
use_log_stream_name_prefix true
|
61
|
+
state_file /tmp/state
|
62
|
+
use_aws_timestamp true
|
63
|
+
start_time "2019-06-18 00:00:00Z"
|
64
|
+
end_time "2019-01-18 00:00:00Z"
|
65
|
+
time_range_format "%Y-%m-%d %H:%M:%S%z"
|
66
|
+
EOC
|
67
|
+
end
|
40
68
|
end
|
41
69
|
end
|
42
70
|
|
@@ -92,6 +120,33 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
92
120
|
assert_equal(['test', (time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs2"}], emits[1])
|
93
121
|
end
|
94
122
|
|
123
|
+
def test_emit_with_aws_timestamp_and_time_range
|
124
|
+
create_log_stream
|
125
|
+
|
126
|
+
time_ms = (Time.now.to_f * 1000).floor
|
127
|
+
before_6h_time_ms = ((Time.now.to_f - 60*60*6) * 1000).floor
|
128
|
+
log_time_ms = time_ms - 10000
|
129
|
+
put_log_events([
|
130
|
+
{timestamp: before_6h_time_ms, message: Time.at((before_6h_time_ms - 10000)/1000.floor).to_s + ",Cloudwatch non json logs1"},
|
131
|
+
{timestamp: before_6h_time_ms, message: Time.at((before_6h_time_ms - 10000)/1000.floor).to_s + ",Cloudwatch non json logs2"},
|
132
|
+
{timestamp: time_ms, message: Time.at(log_time_ms/1000.floor).to_s + ",Cloudwatch non json logs3"},
|
133
|
+
])
|
134
|
+
|
135
|
+
sleep 5
|
136
|
+
|
137
|
+
d = create_driver(csv_format_config_aws_timestamp + %[
|
138
|
+
start_time #{Time.at(Time.now.to_f - 60*60*8).to_s}
|
139
|
+
end_time #{Time.at(Time.now.to_f - 60*60*4).to_s}
|
140
|
+
time_range_format "%Y-%m-%d %H:%M:%S %z"
|
141
|
+
])
|
142
|
+
d.run(expect_emits: 2, timeout: 5)
|
143
|
+
|
144
|
+
emits = d.events
|
145
|
+
assert_equal(2, emits.size)
|
146
|
+
assert_equal(['test', (before_6h_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs1"}], emits[0])
|
147
|
+
assert_equal(['test', (before_6h_time_ms / 1000).floor, {"message"=>"Cloudwatch non json logs2"}], emits[1])
|
148
|
+
end
|
149
|
+
|
95
150
|
def test_emit_with_log_timestamp
|
96
151
|
create_log_stream
|
97
152
|
|
@@ -555,6 +610,59 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
|
|
555
610
|
assert_equal(["test", ((time_ms + 7000) / 1000), { "cloudwatch" => "logs7" }], events[6])
|
556
611
|
assert_equal(["test", ((time_ms + 8000) / 1000), { "cloudwatch" => "logs8" }], events[7])
|
557
612
|
end
|
613
|
+
|
614
|
+
test "retry on Aws::CloudWatchLogs::Errors::ThrottlingException in get_log_events" do
|
615
|
+
config = <<-CONFIG
|
616
|
+
tag test
|
617
|
+
@type cloudwatch_logs
|
618
|
+
log_group_name #{log_group_name}
|
619
|
+
state_file /tmp/state
|
620
|
+
fetch_interval 0.1
|
621
|
+
throttling_retry_seconds 0.2
|
622
|
+
CONFIG
|
623
|
+
|
624
|
+
# it will raises the error 2 times
|
625
|
+
counter = 0
|
626
|
+
times = 2
|
627
|
+
stub(@client).get_log_events(anything) {
|
628
|
+
counter += 1
|
629
|
+
counter <= times ? raise(Aws::CloudWatchLogs::Errors::ThrottlingException.new(nil, "error")) : OpenStruct.new(events: [], next_forward_token: nil)
|
630
|
+
}
|
631
|
+
|
632
|
+
d = create_driver(config)
|
633
|
+
|
634
|
+
# so, it is expected to valid_next_token once
|
635
|
+
mock(d.instance).valid_next_token(nil, nil).once
|
636
|
+
|
637
|
+
d.run
|
638
|
+
assert_equal(2, d.logs.select {|l| l =~ /ThrottlingException get_log_events. Waiting 0.2 seconds to retry/ }.size)
|
639
|
+
end
|
640
|
+
|
641
|
+
test "retry on Aws::CloudWatchLogs::Errors::ThrottlingException in describe_log_streams" do
|
642
|
+
config = <<-CONFIG
|
643
|
+
tag test
|
644
|
+
@type cloudwatch_logs
|
645
|
+
log_group_name #{log_group_name}
|
646
|
+
use_log_stream_name_prefix true
|
647
|
+
state_file /tmp/state
|
648
|
+
fetch_interval 0.1
|
649
|
+
throttling_retry_seconds 0.2
|
650
|
+
CONFIG
|
651
|
+
|
652
|
+
# it will raises the error 2 times
|
653
|
+
log_stream = Aws::CloudWatchLogs::Types::LogStream.new(log_stream_name: "stream_name")
|
654
|
+
counter = 0
|
655
|
+
times = 2
|
656
|
+
stub(@client).describe_log_streams(anything) {
|
657
|
+
counter += 1
|
658
|
+
counter <= times ? raise(Aws::CloudWatchLogs::Errors::ThrottlingException.new(nil, "error")) : OpenStruct.new(log_streams: [log_stream], next_token: nil)
|
659
|
+
}
|
660
|
+
|
661
|
+
d = create_driver(config)
|
662
|
+
|
663
|
+
d.run
|
664
|
+
assert_equal(2, d.logs.select {|l| l =~ /ThrottlingException describe_log_streams. Waiting 0.2 seconds to retry/ }.size)
|
665
|
+
end
|
558
666
|
end
|
559
667
|
|
560
668
|
private
|
@@ -547,6 +547,37 @@ class CloudwatchLogsOutputTest < Test::Unit::TestCase
|
|
547
547
|
assert(d.logs.any?{|log| log.include?("failed to set retention policy for Log group")})
|
548
548
|
end
|
549
549
|
|
550
|
+
def test_remove_retention_in_days_key
|
551
|
+
new_log_stream
|
552
|
+
|
553
|
+
d = create_driver(<<-EOC)
|
554
|
+
#{default_config}
|
555
|
+
log_group_name #{log_group_name}
|
556
|
+
log_stream_name #{log_stream_name}
|
557
|
+
retention_in_days_key retention_in_days
|
558
|
+
remove_retention_in_days_key true
|
559
|
+
EOC
|
560
|
+
|
561
|
+
records = [
|
562
|
+
{'cloudwatch' => 'logs1', 'message' => 'message1', 'retention_in_days' => '7'},
|
563
|
+
{'cloudwatch' => 'logs2', 'message' => 'message2', 'retention_in_days' => '7'},
|
564
|
+
]
|
565
|
+
|
566
|
+
time = Time.now
|
567
|
+
d.run(default_tag: fluentd_tag) do
|
568
|
+
records.each_with_index do |record, i|
|
569
|
+
d.feed(time.to_i + i, record)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
sleep 10
|
574
|
+
|
575
|
+
events = get_log_events
|
576
|
+
assert_equal(2, events.size)
|
577
|
+
assert_equal({'cloudwatch' => 'logs1', 'message' => 'message1'}, JSON.parse(events[0].message))
|
578
|
+
assert_equal({'cloudwatch' => 'logs2', 'message' => 'message2'}, JSON.parse(events[1].message))
|
579
|
+
end
|
580
|
+
|
550
581
|
def test_log_group_aws_tags_key
|
551
582
|
clear_log_group
|
552
583
|
|
@@ -576,6 +607,37 @@ class CloudwatchLogsOutputTest < Test::Unit::TestCase
|
|
576
607
|
assert_equal("value2", awstags.fetch("tag2"))
|
577
608
|
end
|
578
609
|
|
610
|
+
def test_remove_log_group_aws_tags_key
|
611
|
+
new_log_stream
|
612
|
+
|
613
|
+
d = create_driver(<<-EOC)
|
614
|
+
#{default_config}
|
615
|
+
log_group_name #{log_group_name}
|
616
|
+
log_stream_name #{log_stream_name}
|
617
|
+
log_group_aws_tags_key log_group_tags
|
618
|
+
remove_log_group_aws_tags_key true
|
619
|
+
EOC
|
620
|
+
|
621
|
+
records = [
|
622
|
+
{'cloudwatch' => 'logs1', 'message' => 'message1', 'log_group_tags' => {"tag1" => "value1", "tag2" => "value2"}},
|
623
|
+
{'cloudwatch' => 'logs2', 'message' => 'message2', 'log_group_tags' => {"tag1" => "value1", "tag2" => "value2"}},
|
624
|
+
]
|
625
|
+
|
626
|
+
time = Time.now
|
627
|
+
d.run(default_tag: fluentd_tag) do
|
628
|
+
records.each_with_index do |record, i|
|
629
|
+
d.feed(time.to_i + i, record)
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
sleep 10
|
634
|
+
|
635
|
+
events = get_log_events
|
636
|
+
assert_equal(2, events.size)
|
637
|
+
assert_equal({'cloudwatch' => 'logs1', 'message' => 'message1'}, JSON.parse(events[0].message))
|
638
|
+
assert_equal({'cloudwatch' => 'logs2', 'message' => 'message2'}, JSON.parse(events[1].message))
|
639
|
+
end
|
640
|
+
|
579
641
|
def test_log_group_aws_tags_key_same_group_diff_tags
|
580
642
|
clear_log_group
|
581
643
|
|
data/test/test_helper.rb
CHANGED
@@ -42,10 +42,7 @@ module CloudwatchLogsTestHelper
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def log_stream_name(log_stream_name_prefix = nil)
|
45
|
-
|
46
|
-
new_log_stream(log_stream_name_prefix)
|
47
|
-
end
|
48
|
-
@log_stream_name
|
45
|
+
@log_stream_name ||= new_log_stream(log_stream_name_prefix)
|
49
46
|
end
|
50
47
|
|
51
48
|
def new_log_stream(log_stream_name_prefix = nil)
|
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.9.
|
4
|
+
version: 0.9.5
|
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-
|
11
|
+
date: 2020-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fluentd
|