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 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