fluent-plugin-cloudwatch-logs 0.9.0 → 0.9.5
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 +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
|