fluent-plugin-test 0.0.17 → 0.0.24

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.
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fluent/plugin/cloudwatch/logs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fluent-plugin-test"
8
+ spec.version = Fluent::Plugin::Cloudwatch::Logs::VERSION
9
+ spec.authors = ["Imcotop"]
10
+ spec.email = ["Imcotop@icloud.com"]
11
+ spec.summary = %q{CloudWatch Logs Plugin for Fluentd}
12
+ spec.homepage = "https://github.com/imcotop/fluent-plugin-cloudwatch-logs"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = spec.files = Dir.glob("lib/**/*") + Dir.glob("bin/*") + Dir.glob("*.gemspec") + Dir.glob("README*") + Dir.glob("LICENSE*")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency 'fluentd', '>= 1.8.0'
21
+ spec.add_dependency 'aws-sdk-cloudwatchlogs', '~> 1.0'
22
+
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "test-unit"
26
+ spec.add_development_dependency "test-unit-rr"
27
+ spec.add_development_dependency "mocha"
28
+ spec.add_development_dependency "nokogiri"
29
+ end
@@ -0,0 +1,9 @@
1
+ module Fluent
2
+ module Plugin
3
+ module Cloudwatch
4
+ module Logs
5
+ VERSION = "0.0.24"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require "fluent/plugin/cloudwatch/logs/version"
2
+
3
+ module Fluent
4
+ module Plugin
5
+ module Cloudwatch
6
+ module Logs
7
+ # Your code goes here...
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,374 @@
1
+ require 'date'
2
+ require 'time'
3
+ require 'fluent/plugin/input'
4
+ require 'fluent/plugin/parser'
5
+ require 'yajl'
6
+
7
+ module Fluent::Plugin
8
+ class CloudwatchLogsInput < Input
9
+ Fluent::Plugin.register_input('cloudwatch_logs', self)
10
+
11
+ helpers :parser, :thread, :compat_parameters, :storage
12
+
13
+ DEFAULT_STORAGE_TYPE = 'local'
14
+
15
+ config_param :aws_key_id, :string, default: nil, secret: true
16
+ config_param :aws_sec_key, :string, default: nil, secret: true
17
+ config_param :aws_use_sts, :bool, default: false
18
+ config_param :aws_sts_role_arn, :string, default: nil
19
+ config_param :aws_sts_session_name, :string, default: 'fluentd'
20
+ config_param :aws_sts_external_id, :string, default: nil
21
+ config_param :aws_sts_policy, :string, default: nil
22
+ config_param :aws_sts_duration_seconds, :time, default: nil
23
+ config_param :aws_sts_endpoint_url, :string, default: nil
24
+ config_param :aws_ecs_authentication, :bool, default: false
25
+ config_param :region, :string, default: nil
26
+ config_param :endpoint, :string, default: nil
27
+ config_param :ssl_verify_peer, :bool, :default => true
28
+ config_param :tag, :string
29
+ config_param :log_group_name, :string
30
+ config_param :add_log_group_name, :bool, default: false
31
+ config_param :log_group_name_key, :string, default: 'log_group'
32
+ config_param :use_log_group_name_prefix, :bool, default: false
33
+ config_param :log_stream_name, :string, default: nil
34
+ config_param :use_log_stream_name_prefix, :bool, default: false
35
+ config_param :state_file, :string, default: nil,
36
+ deprecated: "Use <storage> instead."
37
+ config_param :fetch_interval, :time, default: 60
38
+ config_param :http_proxy, :string, default: nil
39
+ config_param :json_handler, :enum, list: [:yajl, :json], default: :yajl
40
+ config_param :use_todays_log_stream, :bool, default: false
41
+ config_param :use_aws_timestamp, :bool, default: false
42
+ config_param :start_time, :string, default: nil
43
+ config_param :end_time, :string, default: nil
44
+ config_param :time_range_format, :string, default: "%Y-%m-%d %H:%M:%S"
45
+ config_param :throttling_retry_seconds, :time, default: nil
46
+ config_param :include_metadata, :bool, default: false
47
+ config_section :web_identity_credentials, multi: false do
48
+ config_param :role_arn, :string
49
+ config_param :role_session_name, :string
50
+ config_param :web_identity_token_file, :string, default: nil #required
51
+ config_param :policy, :string, default: nil
52
+ config_param :duration_seconds, :time, default: nil
53
+ end
54
+
55
+ config_section :parse do
56
+ config_set_default :@type, 'none'
57
+ end
58
+
59
+ config_section :storage do
60
+ config_set_default :usage, 'store_next_tokens'
61
+ config_set_default :@type, DEFAULT_STORAGE_TYPE
62
+ config_set_default :persistent, false
63
+ end
64
+
65
+ def initialize
66
+ super
67
+
68
+ @parser = nil
69
+ require 'aws-sdk-cloudwatchlogs'
70
+ end
71
+
72
+ def configure(conf)
73
+ compat_parameters_convert(conf, :parser)
74
+ super
75
+ configure_parser(conf)
76
+
77
+ @start_time = (Time.strptime(@start_time, @time_range_format).to_f * 1000).floor if @start_time
78
+ @end_time = (Time.strptime(@end_time, @time_range_format).to_f * 1000).floor if @end_time
79
+ if @start_time && @end_time && (@end_time < @start_time)
80
+ raise Fluent::ConfigError, "end_time(#{@end_time}) should be greater than start_time(#{@start_time})."
81
+ end
82
+ @next_token_storage = storage_create(usage: 'store_next_tokens', conf: config, default_type: DEFAULT_STORAGE_TYPE)
83
+ end
84
+
85
+ def start
86
+ super
87
+ options = {}
88
+ options[:region] = @region if @region
89
+ options[:endpoint] = @endpoint if @endpoint
90
+ options[:ssl_verify_peer] = @ssl_verify_peer
91
+ options[:http_proxy] = @http_proxy if @http_proxy
92
+
93
+ if @aws_use_sts
94
+ Aws.config[:region] = options[:region]
95
+ credentials_options = {
96
+ role_arn: @aws_sts_role_arn,
97
+ role_session_name: @aws_sts_session_name,
98
+ external_id: @aws_sts_external_id,
99
+ policy: @aws_sts_policy,
100
+ duration_seconds: @aws_sts_duration_seconds
101
+ }
102
+ credentials_options[:sts_endpoint_url] = @aws_sts_endpoint_url if @aws_sts_endpoint_url
103
+ if @region and @aws_sts_endpoint_url
104
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region, endpoint: @aws_sts_endpoint_url)
105
+ elsif @region
106
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
107
+ end
108
+ options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
109
+ elsif @web_identity_credentials
110
+ c = @web_identity_credentials
111
+ credentials_options = {}
112
+ credentials_options[:role_arn] = c.role_arn
113
+ credentials_options[:role_session_name] = c.role_session_name
114
+ credentials_options[:web_identity_token_file] = c.web_identity_token_file
115
+ credentials_options[:policy] = c.policy if c.policy
116
+ credentials_options[:duration_seconds] = c.duration_seconds if c.duration_seconds
117
+ if @region
118
+ credentials_options[:client] = Aws::STS::Client.new(:region => @region)
119
+ end
120
+ options[:credentials] = Aws::AssumeRoleWebIdentityCredentials.new(credentials_options)
121
+ elsif @aws_ecs_authentication
122
+ # collect AWS credential from ECS relative uri ENV variable
123
+ aws_container_credentials_relative_uri = ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
124
+ options[:credentials] = Aws::ECSCredentials.new({credential_path: aws_container_credentials_relative_uri}).credentials
125
+ else
126
+ options[:credentials] = Aws::Credentials.new(@aws_key_id, @aws_sec_key) if @aws_key_id && @aws_sec_key
127
+ end
128
+
129
+ @logs = Aws::CloudWatchLogs::Client.new(options)
130
+
131
+ @finished = false
132
+ thread_create(:in_cloudwatch_logs_runner, &method(:run))
133
+
134
+ @json_handler = case @json_handler
135
+ when :yajl
136
+ Yajl
137
+ when :json
138
+ JSON
139
+ end
140
+ end
141
+
142
+ def shutdown
143
+ @finished = true
144
+ super
145
+ end
146
+
147
+ # No private for testing
148
+ def state_key_for(log_stream_name, log_group_name = nil)
149
+ if log_group_name && log_stream_name
150
+ "#{@state_file}_#{log_group_name.gsub(File::SEPARATOR, '-')}_#{log_stream_name.gsub(File::SEPARATOR, '-')}"
151
+ elsif log_stream_name
152
+ "#{@state_file}_#{log_stream_name.gsub(File::SEPARATOR, '-')}"
153
+ else
154
+ @state_file
155
+ end
156
+ end
157
+
158
+ private
159
+ def configure_parser(conf)
160
+ if conf['format']
161
+ @parser = parser_create
162
+ elsif parser_config = conf.elements('parse').first
163
+ @parser = parser_create(conf: parser_config)
164
+ end
165
+ end
166
+
167
+ def migrate_state_file_to_storage(log_stream_name)
168
+ @next_token_storage.put(:"#{state_key_for(log_stream_name)}", File.read(state_key_for(log_stream_name)).chomp)
169
+ File.delete(state_key_for(log_stream_name))
170
+ end
171
+
172
+ def next_token(log_stream_name, log_group_name = nil)
173
+ if @next_token_storage.persistent && File.exist?(state_key_for(log_stream_name))
174
+ migrate_state_file_to_storage(log_stream_name)
175
+ end
176
+ @next_token_storage.get(:"#{state_key_for(log_stream_name, log_group_name)}")
177
+ end
178
+
179
+ def store_next_token(token, log_stream_name = nil, log_group_name = nil)
180
+ @next_token_storage.put(:"#{state_key_for(log_stream_name, log_group_name)}", token)
181
+ end
182
+
183
+ def run
184
+ @next_fetch_time = Time.now
185
+
186
+ until @finished
187
+ if Time.now > @next_fetch_time
188
+ @next_fetch_time += @fetch_interval
189
+
190
+ if @use_log_group_name_prefix
191
+ log_group_names = describe_log_groups(@log_group_name).map{|log_group|
192
+ log_group.log_group_name
193
+ }
194
+ else
195
+ log_group_names = [@log_group_name]
196
+ end
197
+ log_group_names.each do |log_group_name|
198
+ if @use_log_stream_name_prefix || @use_todays_log_stream
199
+ log_stream_name_prefix = @use_todays_log_stream ? get_todays_date : @log_stream_name
200
+ begin
201
+ log_streams = describe_log_streams(log_stream_name_prefix, nil, nil, log_group_name)
202
+ log_streams.concat(describe_log_streams(get_yesterdays_date, nil, nil, log_group_name)) if @use_todays_log_stream
203
+ log_streams.each do |log_stream|
204
+ log_stream_name = log_stream.log_stream_name
205
+ events = get_events(log_group_name, log_stream_name)
206
+ metadata = if @include_metadata
207
+ {
208
+ "log_stream_name" => log_stream_name,
209
+ "log_group_name" => log_group_name
210
+ }
211
+ else
212
+ {}
213
+ end
214
+ events.each do |event|
215
+ emit(log_group_name, log_stream_name, event, metadata)
216
+ end
217
+ end
218
+ rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
219
+ log.warn "'#{@log_stream_name}' prefixed log stream(s) are not found"
220
+ next
221
+ end
222
+ else
223
+ events = get_events(log_group_name, @log_stream_name)
224
+ metadata = if @include_metadata
225
+ {
226
+ "log_stream_name" => @log_stream_name,
227
+ "log_group_name" => @log_group_name
228
+ }
229
+ else
230
+ {}
231
+ end
232
+ events.each do |event|
233
+ emit(log_group_name, log_stream_name, event, metadata)
234
+ end
235
+ end
236
+ end
237
+ end
238
+ sleep 1
239
+ end
240
+ end
241
+
242
+ def emit(group, stream, event, metadata)
243
+ if @parser
244
+ @parser.parse(event.message) {|time,record|
245
+ if @use_aws_timestamp
246
+ time = (event.timestamp / 1000).floor
247
+ end
248
+ if @add_log_group_name
249
+ record[@log_group_name_key] = group
250
+ end
251
+ unless metadata.empty?
252
+ record.merge!("metadata" => metadata)
253
+ end
254
+ router.emit(@tag, time, record)
255
+ }
256
+ else
257
+ time = (event.timestamp / 1000).floor
258
+ begin
259
+ record = @json_handler.load(event.message)
260
+ if @add_log_group_name
261
+ record[@log_group_name_key] = group
262
+ end
263
+ unless metadata.empty?
264
+ record.merge!("metadata" => metadata)
265
+ end
266
+ router.emit(@tag, time, record)
267
+ rescue JSON::ParserError, Yajl::ParseError => error # Catch parser errors
268
+ log.error "Invalid JSON encountered while parsing event.message"
269
+ router.emit_error_event(@tag, time, { message: event.message }, error)
270
+ end
271
+ end
272
+ end
273
+ GET_LOG_EVENTS_RATE_LIMIT = 10 # змініть відповідно до вашого регіону
274
+ @@last_get_log_events_time = Time.at(0)
275
+ def get_events(log_group_name, log_stream_name)
276
+ throttling_handler('get_log_events') do
277
+ now = Time.now
278
+ min_interval = 1.0 / GET_LOG_EVENTS_RATE_LIMIT
279
+ sleep_time = min_interval - (now - @@last_get_log_events_time)
280
+ sleep(sleep_time) if sleep_time > 0
281
+ @@last_get_log_events_time = Time.now
282
+ request = {
283
+ log_group_name: log_group_name,
284
+ log_stream_name: log_stream_name
285
+ }
286
+ request.merge!(start_time: @start_time) if @start_time
287
+ request.merge!(end_time: @end_time) if @end_time
288
+ if @use_log_group_name_prefix
289
+ log_next_token = next_token(log_stream_name, log_group_name)
290
+ else
291
+ log_next_token = next_token(log_stream_name)
292
+ end
293
+ request[:next_token] = log_next_token if !log_next_token.nil? && !log_next_token.empty?
294
+ request[:start_from_head] = true if read_from_head?(log_next_token)
295
+ response = @logs.get_log_events(request)
296
+ if valid_next_token(log_next_token, response.next_forward_token)
297
+ if @use_log_group_name_prefix
298
+ store_next_token(response.next_forward_token, log_stream_name, log_group_name)
299
+ else
300
+ store_next_token(response.next_forward_token, log_stream_name)
301
+ end
302
+ end
303
+
304
+ response.events
305
+ end
306
+ end
307
+
308
+ def read_from_head?(next_token)
309
+ (!next_token.nil? && !next_token.empty?) || @start_time || @end_time
310
+ end
311
+
312
+ def describe_log_streams(log_stream_name_prefix, log_streams = nil, next_token = nil, log_group_name=nil)
313
+ throttling_handler('describe_log_streams') do
314
+ request = {
315
+ log_group_name: log_group_name != nil ? log_group_name : @log_group_name
316
+ }
317
+ request[:next_token] = next_token if next_token
318
+ request[:log_stream_name_prefix] = log_stream_name_prefix if log_stream_name_prefix
319
+ response = @logs.describe_log_streams(request)
320
+ if log_streams
321
+ log_streams.concat(response.log_streams)
322
+ else
323
+ log_streams = response.log_streams
324
+ end
325
+ if response.next_token
326
+ log_streams = describe_log_streams(log_stream_name_prefix, log_streams, response.next_token, log_group_name)
327
+ end
328
+ log_streams
329
+ end
330
+ end
331
+
332
+ def throttling_handler(method_name)
333
+ yield
334
+ rescue Aws::CloudWatchLogs::Errors::ThrottlingException => err
335
+ if throttling_retry_seconds
336
+ log.warn "ThrottlingException #{method_name}. Waiting #{throttling_retry_seconds} seconds to retry."
337
+ sleep throttling_retry_seconds
338
+
339
+ throttling_handler(method_name) { yield }
340
+ else
341
+ raise err
342
+ end
343
+ end
344
+
345
+ def describe_log_groups(log_group_name_prefix, log_groups = nil, next_token = nil)
346
+ request = {
347
+ log_group_name_prefix: log_group_name_prefix
348
+ }
349
+ request[:next_token] = next_token if next_token
350
+ response = @logs.describe_log_groups(request)
351
+ if log_groups
352
+ log_groups.concat(response.log_groups)
353
+ else
354
+ log_groups = response.log_groups
355
+ end
356
+ if response.next_token
357
+ log_groups = describe_log_groups(log_group_name_prefix, log_groups, response.next_token)
358
+ end
359
+ log_groups
360
+ end
361
+
362
+ def valid_next_token(prev_token, next_token)
363
+ next_token && prev_token != next_token.chomp
364
+ end
365
+
366
+ def get_todays_date
367
+ Date.today.strftime("%Y/%m/%d")
368
+ end
369
+
370
+ def get_yesterdays_date
371
+ (Date.today - 1).strftime("%Y/%m/%d")
372
+ end
373
+ end
374
+ end