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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4b45da3678f7685f04b1ca6053a2f396dd1847421002e4d7358d501916328f6
4
- data.tar.gz: acc891b68cf5182063f4093c1b0c2db65c07f9b7e115bc05678128b97eb3d6b0
3
+ metadata.gz: 13ef3e0d5f4fd4c12c49b16ccbd07db237b7cff26ea272e4ef6337b09b02e975
4
+ data.tar.gz: 8cefeed014b1842ccce9f15479ad8cc54aa51b976281abb5d3e1c8828239aaa3
5
5
  SHA512:
6
- metadata.gz: dd37edc8daf19fc3c8b2ea3df12786b906960f546dbd8bb9bcdcf047cb602138ee215edac5dc51891c3956ef95e7ec05a512219d62e18974dfdb03a16f62595a
7
- data.tar.gz: 6f102f4499c68f3ee2ebeacf388ecafa9cf21d3d0c5b3091420986b5dd8b5849a75fdd1c59a274892592b2e7dab0613e71829b3986905c144dd20e4c8aec02b5
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
- * `remove_retention_in_days`: remove field specified by `retention_in_days`
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
 
@@ -2,7 +2,7 @@ module Fluent
2
2
  module Plugin
3
3
  module Cloudwatch
4
4
  module Logs
5
- VERSION = "0.9.0"
5
+ VERSION = "0.9.5"
6
6
  end
7
7
  end
8
8
  end
@@ -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
- request = {
163
- log_group_name: @log_group_name,
164
- log_stream_name: log_stream_name
165
- }
166
- log_next_token = next_token(log_stream_name)
167
- request[:next_token] = log_next_token if !log_next_token.nil? && !log_next_token.empty?
168
- response = @logs.get_log_events(request)
169
- if valid_next_token(log_next_token, response.next_forward_token)
170
- store_next_token(response.next_forward_token, log_stream_name)
171
- end
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
- response.events
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
- request = {
178
- log_group_name: @log_group_name
179
- }
180
- request[:next_token] = next_token if next_token
181
- request[:log_stream_name_prefix] = log_stream_name_prefix
182
- response = @logs.describe_log_streams(request)
183
- if log_streams
184
- log_streams.concat(response.log_streams)
185
- else
186
- log_streams = response.log_streams
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
- if response.next_token
189
- log_streams = describe_log_streams(log_stream_name_prefix, log_streams, response.next_token)
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 :remove_retention_in_days, :bool, default: false
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
 
@@ -42,10 +42,7 @@ module CloudwatchLogsTestHelper
42
42
  end
43
43
 
44
44
  def log_stream_name(log_stream_name_prefix = nil)
45
- if !@log_stream_name
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.0
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-03-23 00:00:00.000000000 Z
11
+ date: 2020-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd