fluent-plugin-cloudwatch-logs 0.1.1 → 0.1.2

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
  SHA1:
3
- metadata.gz: 88a161bb1764e810bbf9a56b73fc7e47b05ebeb3
4
- data.tar.gz: 04838391a2d452903c11bf0f5878f06c554b92f2
3
+ metadata.gz: 7433cbc405896d7a542689ab5e23c6f199043760
4
+ data.tar.gz: c8f7680af5579da676e5b0379b9e911f5afa7dbe
5
5
  SHA512:
6
- metadata.gz: 00153e08af56da4d830290799b162b3935c9f88bc415467f558e96c191dd6bdeebcd4497f1c51975deb6a597841d35c04dba1ec1f2b19cdf851772357c8d49a7
7
- data.tar.gz: 2ae3cf9e79a22595d80f99c043b3808a640f39fe7c11ae299b4c92641fd9fa2bb2dfd6cc45b4dc7230136ae56af1e845b420f95d0c0c2fd93c86b28e3fe9abf6
6
+ metadata.gz: 0bb5a93717b9cddfc9d29fd4e4dca9c84751747b6f10b3225f5a8be0542a418223525e4f03527131daf9d3bfec543fe2d06b372f391760535deeb1cf149c555a
7
+ data.tar.gz: 86be56949e121311e7ad8ebaf5fb41667fdede3d0a0a75f80418659df35664f4924756614e1bf174c03105f028fca9009a2f80fddb6a4f1a74e2bf8ae79a9d83
data/README.md CHANGED
@@ -73,18 +73,21 @@ Fetch sample log from CloudWatch Logs:
73
73
  #max_message_length 32768
74
74
  #use_tag_as_group false
75
75
  #use_tag_as_stream false
76
+ #include_time_key true
77
+ #localtime true
76
78
  </match>
77
79
  ```
78
80
 
79
81
  * `log_group_name`: name of log group to store logs
80
82
  * `log_stream_name`: name of log stream to store logs
81
- * `sequence_token_file`: file to store next sequence token
82
83
  * `auto_create_stream`: to create log group and stream automatically
83
84
  * `message_keys`: keys to send messages as events
84
85
  * `max_message_length`: maximum length of the message
85
86
  * `max_events_per_batch`: maximum number of events to send at once (default 10000)
86
87
  * `use_tag_as_group`: to use tag as a group name
87
88
  * `use_tag_as_stream`: to use tag as a stream name
89
+ * `include_time_key`: include time key as part of the log entry (defaults to UTC)
90
+ * `localtime`: use localtime timezone for `include_time_key` output (overrides UTC default)
88
91
 
89
92
  ### in_cloudwatch_logs
90
93
 
@@ -94,6 +97,7 @@ Fetch sample log from CloudWatch Logs:
94
97
  tag cloudwatch.in
95
98
  log_group_name group
96
99
  log_stream_name stream
100
+ #use_log_stream_name_prefix true
97
101
  state_file /var/lib/fluent/group_stream.in.state
98
102
  </source>
99
103
  ```
@@ -101,6 +105,7 @@ Fetch sample log from CloudWatch Logs:
101
105
  * `tag`: fluentd tag
102
106
  * `log_group_name`: name of log group to fetch logs
103
107
  * `log_stream_name`: name of log stream to fetch logs
108
+ * `use_log_stream_name_prefix`: to use `log_stream_name` as log stream name prefix (default false)
104
109
  * `state_file`: file to store current state (e.g. next\_forward\_token)
105
110
 
106
111
  ## Test
@@ -14,7 +14,6 @@
14
14
  type cloudwatch_logs
15
15
  log_group_name fluent-plugin-cloudwatch-example
16
16
  log_stream_name fluent-plugin-cloudwatch-example
17
- sequence_token_file /tmp/fluent-plugin-cloudwatch-example.seq
18
17
  auto_create_stream true
19
18
  </match>
20
19
 
@@ -2,7 +2,7 @@ module Fluent
2
2
  module Plugin
3
3
  module Cloudwatch
4
4
  module Logs
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
8
8
  end
@@ -13,6 +13,7 @@ module Fluent
13
13
  config_param :tag, :string
14
14
  config_param :log_group_name, :string
15
15
  config_param :log_stream_name, :string
16
+ config_param :use_log_stream_name_prefix, :bool, default: false
16
17
  config_param :state_file, :string
17
18
  config_param :fetch_interval, :time, default: 60
18
19
  config_param :http_proxy, :string, default: nil
@@ -57,8 +58,10 @@ module Fluent
57
58
  File.read(@state_file).chomp
58
59
  end
59
60
 
60
- def store_next_token(token)
61
- open(@state_file, 'w') do |f|
61
+ def store_next_token(token, log_stream_name = nil)
62
+ state_file = @state_file
63
+ state_file = "#{@state_file}_#{log_stream_name}" if log_stream_name
64
+ open(state_file, 'w') do |f|
62
65
  f.write token
63
66
  end
64
67
  end
@@ -70,15 +73,19 @@ module Fluent
70
73
  if Time.now > @next_fetch_time
71
74
  @next_fetch_time += @fetch_interval
72
75
 
73
- events = get_events
74
- events.each do |event|
75
- if @parser
76
- record = @parser.parse(event.message)
77
- router.emit(@tag, record[0], record[1])
78
- else
79
- time = (event.timestamp / 1000).floor
80
- record = JSON.parse(event.message)
81
- router.emit(@tag, time, record)
76
+ if @use_log_stream_name_prefix
77
+ log_streams = describe_log_streams
78
+ log_streams.each do |log_stram|
79
+ log_stream_name = log_stram.log_stream_name
80
+ events = get_events(log_stream_name)
81
+ events.each do |event|
82
+ emit(event)
83
+ end
84
+ end
85
+ else
86
+ events = get_events(@log_stream_name)
87
+ events.each do |event|
88
+ emit(event)
82
89
  end
83
90
  end
84
91
  end
@@ -86,10 +93,21 @@ module Fluent
86
93
  end
87
94
  end
88
95
 
89
- def get_events
96
+ def emit(event)
97
+ if @parser
98
+ record = @parser.parse(event.message)
99
+ router.emit(@tag, record[0], record[1])
100
+ else
101
+ time = (event.timestamp / 1000).floor
102
+ record = JSON.parse(event.message)
103
+ router.emit(@tag, time, record)
104
+ end
105
+ end
106
+
107
+ def get_events(log_stream_name)
90
108
  request = {
91
109
  log_group_name: @log_group_name,
92
- log_stream_name: @log_stream_name,
110
+ log_stream_name: log_stream_name
93
111
  }
94
112
  request[:next_token] = next_token if next_token
95
113
  response = @logs.get_log_events(request)
@@ -97,5 +115,23 @@ module Fluent
97
115
 
98
116
  response.events
99
117
  end
118
+
119
+ def describe_log_streams(log_streams = nil, next_token = nil)
120
+ request = {
121
+ log_group_name: @log_group_name
122
+ }
123
+ request[:next_token] = next_token if next_token
124
+ request[:log_stream_name_prefix] = @log_stream_name
125
+ response = @logs.describe_log_streams(request)
126
+ if log_streams
127
+ log_streams << response.log_streams
128
+ else
129
+ log_streams = response.log_streams
130
+ end
131
+ if response.next_token
132
+ log_streams = describe_log_streams(log_streams, response.next_token)
133
+ end
134
+ log_streams
135
+ end
100
136
  end
101
137
  end
@@ -2,6 +2,8 @@ module Fluent
2
2
  class CloudwatchLogsOutput < BufferedOutput
3
3
  Plugin.register_output('cloudwatch_logs', self)
4
4
 
5
+ include Fluent::SetTimeKeyMixin
6
+
5
7
  config_param :aws_key_id, :string, :default => nil, :secret => true
6
8
  config_param :aws_sec_key, :string, :default => nil, :secret => true
7
9
  config_param :region, :string, :default => nil
@@ -54,7 +56,7 @@ module Fluent
54
56
  if @auto_create_stream
55
57
  create_log_group(group_name)
56
58
  else
57
- log.warn "Log group '#{group_name}' dose not exists"
59
+ log.warn "Log group '#{group_name}' does not exist"
58
60
  next
59
61
  end
60
62
  end
@@ -63,7 +65,7 @@ module Fluent
63
65
  if @auto_create_stream
64
66
  create_log_stream(group_name, stream_name)
65
67
  else
66
- log.warn "Log stream '#{stream_name}' dose not exists"
68
+ log.warn "Log stream '#{stream_name}' does not exist"
67
69
  next
68
70
  end
69
71
  end
@@ -77,7 +79,9 @@ module Fluent
77
79
  message = record.to_json
78
80
  end
79
81
 
80
- message.force_encoding('ASCII-8BIT')
82
+ # CloudWatchLogs API only accepts valid UTF-8 strings
83
+ # so we should encode the message to UTF-8
84
+ message.encode('UTF-8', :invalid => :replace)
81
85
 
82
86
  if @max_message_length
83
87
  message = message.slice(0, @max_message_length)
@@ -109,7 +113,7 @@ module Fluent
109
113
  while event = events.shift
110
114
  new_chunk = chunk + [event]
111
115
  chunk_span_too_big = new_chunk.size > 1 && new_chunk[-1][:timestamp] - new_chunk[0][:timestamp] >= 1000 * 60 * 60 * 24
112
- chunk_too_big = new_chunk.inject(0) {|sum, e| sum + e[:message].length + 26 } > MAX_EVENTS_SIZE
116
+ chunk_too_big = new_chunk.inject(0) {|sum, e| sum + e[:message].bytesize + 26 } > MAX_EVENTS_SIZE
113
117
  chunk_too_long = @max_events_per_batch && chunk.size >= @max_events_per_batch
114
118
  if chunk_too_big or chunk_span_too_big or chunk_too_long
115
119
  put_events(group_name, stream_name, chunk)
@@ -22,6 +22,7 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
22
22
  tag test
23
23
  log_group_name group
24
24
  log_stream_name stream
25
+ use_log_stream_name_prefix true
25
26
  state_file /tmp/state
26
27
  EOC
27
28
 
@@ -31,6 +32,7 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
31
32
  assert_equal('test', d.instance.tag)
32
33
  assert_equal('group', d.instance.log_group_name)
33
34
  assert_equal('stream', d.instance.log_stream_name)
35
+ assert_equal(true, d.instance.use_log_stream_name_prefix)
34
36
  assert_equal('/tmp/state', d.instance.state_file)
35
37
  end
36
38
 
@@ -93,6 +95,48 @@ class CloudwatchLogsInputTest < Test::Unit::TestCase
93
95
  assert_equal({'cloudwatch' => 'logs2'}, emits[1][2])
94
96
  end
95
97
 
98
+ def test_emit_with_prefix
99
+ new_log_stream("testprefix")
100
+ create_log_stream
101
+
102
+ time_ms = (Time.now.to_f * 1000).floor
103
+ put_log_events([
104
+ {timestamp: time_ms, message: '{"cloudwatch":"logs1"}'},
105
+ {timestamp: time_ms, message: '{"cloudwatch":"logs2"}'},
106
+ ])
107
+
108
+ new_log_stream("testprefix")
109
+ create_log_stream
110
+ put_log_events([
111
+ {timestamp: time_ms, message: '{"cloudwatch":"logs3"}'},
112
+ {timestamp: time_ms, message: '{"cloudwatch":"logs4"}'},
113
+ ])
114
+
115
+ sleep 5
116
+
117
+ d = create_driver(<<-EOC)
118
+ tag test
119
+ type cloudwatch_logs
120
+ log_group_name #{log_group_name}
121
+ log_stream_name testprefix
122
+ use_log_stream_name_prefix true
123
+ state_file /tmp/state
124
+ #{aws_key_id}
125
+ #{aws_sec_key}
126
+ #{region}
127
+ EOC
128
+ d.run do
129
+ sleep 5
130
+ end
131
+
132
+ emits = d.emits
133
+ assert_equal(4, emits.size)
134
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs1'}], emits[0])
135
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs2'}], emits[1])
136
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs3'}], emits[2])
137
+ assert_equal(['test', (time_ms / 1000).floor, {'cloudwatch' => 'logs4'}], emits[3])
138
+ end
139
+
96
140
  private
97
141
  def default_config
98
142
  <<-EOC
@@ -12,7 +12,6 @@ class CloudwatchLogsOutputTest < Test::Unit::TestCase
12
12
 
13
13
  def teardown
14
14
  clear_log_group
15
- FileUtils.rm_f(sequence_token_file)
16
15
  end
17
16
 
18
17
 
@@ -54,6 +53,22 @@ class CloudwatchLogsOutputTest < Test::Unit::TestCase
54
53
  assert_equal('{"cloudwatch":"logs2"}', events[1].message)
55
54
  end
56
55
 
56
+ def test_write_utf8
57
+ new_log_stream
58
+
59
+ d = create_driver
60
+ time = Time.now
61
+ d.emit({'cloudwatch' => 'これは日本語です'.force_encoding('UTF-8')}, time.to_i)
62
+ d.run
63
+
64
+ sleep 20
65
+
66
+ events = get_log_events
67
+ assert_equal(1, events.size)
68
+ assert_equal(time.to_i * 1000, events[0].timestamp)
69
+ assert_equal('{"cloudwatch":"これは日本語です"}', events[0].message)
70
+ end
71
+
57
72
  def test_write_24h_apart
58
73
  new_log_stream
59
74
 
@@ -171,13 +186,59 @@ class CloudwatchLogsOutputTest < Test::Unit::TestCase
171
186
  assert_equal('message2 logs2', events[1].message)
172
187
  end
173
188
 
189
+ def test_include_time_key
190
+ new_log_stream
191
+
192
+ d = create_driver(<<-EOC)
193
+ #{default_config}
194
+ include_time_key true
195
+ EOC
196
+
197
+ time = Time.now
198
+ d.emit({'cloudwatch' => 'logs1'}, time.to_i)
199
+ d.emit({'cloudwatch' => 'logs2'}, time.to_i + 1)
200
+ d.run
201
+
202
+ sleep 20
203
+
204
+ events = get_log_events
205
+ assert_equal(2, events.size)
206
+ assert_equal(time.to_i * 1000, events[0].timestamp)
207
+ assert_equal("{\"cloudwatch\":\"logs1\",\"time\":\"#{time.utc.strftime("%Y-%m-%dT%H:%M:%SZ")}\"}", events[0].message)
208
+ assert_equal((time.to_i + 1) * 1000, events[1].timestamp)
209
+ assert_equal("{\"cloudwatch\":\"logs2\",\"time\":\"#{(time+1).utc.strftime("%Y-%m-%dT%H:%M:%SZ")}\"}", events[1].message)
210
+ end
211
+
212
+ def test_include_time_key_localtime
213
+ new_log_stream
214
+
215
+ d = create_driver(<<-EOC)
216
+ #{default_config}
217
+ include_time_key true
218
+ localtime true
219
+ EOC
220
+
221
+ time = Time.now
222
+ d.emit({'cloudwatch' => 'logs1'}, time.to_i)
223
+ d.emit({'cloudwatch' => 'logs2'}, time.to_i + 1)
224
+ d.run
225
+
226
+ sleep 20
227
+
228
+ events = get_log_events
229
+ assert_equal(2, events.size)
230
+ assert_equal(time.to_i * 1000, events[0].timestamp)
231
+ assert_equal("{\"cloudwatch\":\"logs1\",\"time\":\"#{time.strftime("%Y-%m-%dT%H:%M:%S%:z")}\"}", events[0].message)
232
+ assert_equal((time.to_i + 1) * 1000, events[1].timestamp)
233
+ assert_equal("{\"cloudwatch\":\"logs2\",\"time\":\"#{(time+1).strftime("%Y-%m-%dT%H:%M:%S%:z")}\"}", events[1].message)
234
+ end
235
+
174
236
  private
175
237
  def default_config
176
238
  <<-EOC
177
239
  type cloudwatch_logs
178
240
  log_group_name #{log_group_name}
179
241
  log_stream_name #{log_stream_name}
180
- sequence_token_file #{sequence_token_file}
181
242
  auto_create_stream true
182
243
  #{aws_key_id}
183
244
  #{aws_sec_key}
@@ -185,11 +246,6 @@ class CloudwatchLogsOutputTest < Test::Unit::TestCase
185
246
  EOC
186
247
  end
187
248
 
188
- def sequence_token_file
189
- File.expand_path('../../tmp/sequence_token', __FILE__)
190
- end
191
-
192
-
193
249
  def create_driver(conf = default_config)
194
250
  Fluent::Test::BufferedOutputTestDriver.new(Fluent::CloudwatchLogsOutput, fluentd_tag).configure(conf)
195
251
  end
@@ -29,15 +29,15 @@ module CloudwatchLogsTestHelper
29
29
  "region #{ENV['region']}" if ENV['region']
30
30
  end
31
31
 
32
- def log_stream_name
32
+ def log_stream_name(log_stream_name_prefix = nil)
33
33
  if !@log_stream_name
34
- new_log_stream
34
+ new_log_stream(log_stream_name_prefix)
35
35
  end
36
36
  @log_stream_name
37
37
  end
38
38
 
39
- def new_log_stream
40
- @log_stream_name = Time.now.to_f.to_s
39
+ def new_log_stream(log_stream_name_prefix = nil)
40
+ @log_stream_name = log_stream_name_prefix ? log_stream_name_prefix + Time.now.to_f.to_s : Time.now.to_f.to_s
41
41
  end
42
42
 
43
43
  def clear_log_group
@@ -54,7 +54,7 @@ module CloudwatchLogsTestHelper
54
54
  @fluentd_tag ||= "fluent.plugin.cloudwatch.test.#{Time.now.to_f}"
55
55
  end
56
56
 
57
- def create_log_stream
57
+ def create_log_stream()
58
58
  begin
59
59
  logs.create_log_group(log_group_name: log_group_name)
60
60
  rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException
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.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryota Arai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-06 00:00:00.000000000 Z
11
+ date: 2015-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fluentd