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.
- checksums.yaml +4 -4
- data/LICENSE.txt +22 -201
- data/README.md +347 -1496
- data/fluent-plugin-cloudwatch-logs.gemspec +29 -0
- data/lib/fluent/plugin/cloudwatch/logs/version.rb +9 -0
- data/lib/fluent/plugin/cloudwatch/logs.rb +11 -0
- data/lib/fluent/plugin/in_cloudwatch_logs.rb +374 -0
- data/lib/fluent/plugin/out_cloudwatch_logs.rb +574 -0
- metadata +48 -127
- data/lib/fluent/log-ext.rb +0 -64
- data/lib/fluent/plugin/filter_opensearch_genid.rb +0 -103
- data/lib/fluent/plugin/in_opensearch.rb +0 -441
- data/lib/fluent/plugin/oj_serializer.rb +0 -48
- data/lib/fluent/plugin/opensearch_constants.rb +0 -39
- data/lib/fluent/plugin/opensearch_error.rb +0 -31
- data/lib/fluent/plugin/opensearch_error_handler.rb +0 -182
- data/lib/fluent/plugin/opensearch_fallback_selector.rb +0 -36
- data/lib/fluent/plugin/opensearch_index_template.rb +0 -155
- data/lib/fluent/plugin/opensearch_simple_sniffer.rb +0 -36
- data/lib/fluent/plugin/opensearch_tls.rb +0 -96
- data/lib/fluent/plugin/out_opensearch.rb +0 -1162
- data/lib/fluent/plugin/out_opensearch_data_stream.rb +0 -231
@@ -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,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
|