fluent-plugin-vmware-log-intelligence 2.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +15 -0
- data/README.rdoc +106 -0
- data/Rakefile +16 -0
- data/VERSION +1 -0
- data/fluent-plugin-vmware-log-intelligence.gemspec +53 -0
- data/lib/fluent/plugin/http_client.rb +72 -0
- data/lib/fluent/plugin/out_vmware_log_intelligence.rb +179 -0
- data/test/helper.rb +31 -0
- data/test/plugin/test_http_client.rb +95 -0
- data/test/plugin/test_out_vmware_log_intelligence.rb +154 -0
- metadata +238 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fda3fc2dd74d505b8554a65f96cba15afe0a188485fe04546da4b0c0ff686df4
|
4
|
+
data.tar.gz: b1f298d59fd07a1e4dbdaf6de30e213efd1e9e8ce75589e7de40388880168bc2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b10231dc99aa642d6667fab12befc712a3552aaa6e0c4cadef3b0449df0bfe014683ecf3146b99fc5d1bd854b925c3d677046e990fca0809bb0db2416cb1919c
|
7
|
+
data.tar.gz: c8391559359760faec9bf61b4dc3f900415c2c822151953727202e58093394283ca44f00e5c268d7a750b0c549d0cb14f31db7ee153a7b1b2d13af9d475b3a1b
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
fluent-plugin-vmware-log-intelligence
|
2
|
+
|
3
|
+
Copyright (c) 2013 ablagoev
|
4
|
+
Copyright (c) 2018 VMware, Inc.
|
5
|
+
|
6
|
+
The MIT license (the "License") set forth below applies to all parts of the fluent-plugin-vmware-log-intelligence project. You may not use this file except in compliance with the License.
|
7
|
+
|
8
|
+
MIT License
|
9
|
+
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
|
11
|
+
so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
= Overview
|
2
|
+
|
3
|
+
vmware_log_intelligence is a Fluentd output plugin that buffers data and sends the data to VMware Log Intelligence.
|
4
|
+
|
5
|
+
== Requirements
|
6
|
+
|
7
|
+
fluentd version: 0.14.0 or above
|
8
|
+
|
9
|
+
Ruby version: 2.2 or above
|
10
|
+
|
11
|
+
== Build and install the plugin
|
12
|
+
|
13
|
+
`gem build fluent-plugin-vmware-log-intelligence.gemspec`
|
14
|
+
|
15
|
+
`gem install fluent-plugin-vmware-log-intelligence-1.0.0.gem`
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
vmware_log_intelligence does not support Fluentd v0.12 style configurations.
|
19
|
+
Please use Fluentd v1.0 style configurations when working with the plugin.
|
20
|
+
|
21
|
+
Here is an example of the vmware_log_intelligence plugin configurations:
|
22
|
+
|
23
|
+
<match pattern>
|
24
|
+
@type vmware_log_intelligence
|
25
|
+
endpoint_url https://data.upgrade.symphony-dev.com/le-mans/v1/streams/ingestion-pipeline-stream
|
26
|
+
verify_ssl false
|
27
|
+
<headers>
|
28
|
+
Content-Type application/json
|
29
|
+
Authorization Bearer Your-VMware-Log-Intelligence-API-Token
|
30
|
+
structure simple
|
31
|
+
</headers>
|
32
|
+
<buffer>
|
33
|
+
chunk_limit_records 300
|
34
|
+
flush_interval 3s
|
35
|
+
retry_max_times 3
|
36
|
+
</buffer>
|
37
|
+
<format>
|
38
|
+
@type json
|
39
|
+
tag_key text
|
40
|
+
</format>
|
41
|
+
</match>
|
42
|
+
|
43
|
+
You can find more configuration examples in the files under the examples folder.
|
44
|
+
|
45
|
+
Besides Fluentd, you can also use the plugin with td-agent.
|
46
|
+
|
47
|
+
== Payload format
|
48
|
+
VMware Log Intelligence expects the body of the HTTP requests you send to be in JSON format. It also expects the JSON payload to have a 'text' field that contains your raw log text.
|
49
|
+
|
50
|
+
The fluentd way of turning raw log text into a JSON is to use the JSON formatter plugin (https://docs.fluentd.org/v0.12/articles/formatter_json). The plugin uses a 'tag' field to store the text of a log. One configuration option of the JSON formatter plugin is called 'tag_key'. You need to set 'tag_key' to 'text' so that the JSON formatter will generate JSON payloads that conform to what VMware Log Intelligence expects.
|
51
|
+
|
52
|
+
== Configuration
|
53
|
+
|
54
|
+
=== endpoint_url
|
55
|
+
This is the URL to which you want to send the logs to. You should set it to the URL of VMware Log Intelligence:
|
56
|
+
https://data.upgrade.symphony-dev.com/le-mans/v1/streams/ingestion-pipeline-stream
|
57
|
+
|
58
|
+
=== verify_ssl
|
59
|
+
You can use this setting to control whether the vmware_log_intelligence plugin should verify
|
60
|
+
the SSL certificate of the endpoint_url. You should always set this to `true` except in testing
|
61
|
+
or troubleshooting.
|
62
|
+
|
63
|
+
=== <headers>
|
64
|
+
In order to send logs to VMware Log Intelligence, you need to have the `<headers>`
|
65
|
+
section in your configuration. The `<headers>` section should look like the following:
|
66
|
+
|
67
|
+
<headers>
|
68
|
+
Content-Type application/json
|
69
|
+
Authorization Bearer Your-VMware-Log-Intelligence-API-Token
|
70
|
+
structure simple
|
71
|
+
</headers>
|
72
|
+
|
73
|
+
=== <buffer>
|
74
|
+
The vmware_log_intelligence plugin supports buffering. All the buffering configurations
|
75
|
+
of Fluentd are supported. Detailed information of the buffering configurations can be
|
76
|
+
found here https://docs.fluentd.org/v1.0/articles/buffer-section.
|
77
|
+
|
78
|
+
== Run tests
|
79
|
+
If you would like to contribute to this project, you might want to be able to run the tests
|
80
|
+
under the `test` folder. You can do that in a Ruby environment you have set up on your dev
|
81
|
+
machine. You can also run the tests in a Docker container. To run the tests in a Docker container,
|
82
|
+
you can follow these steps:
|
83
|
+
|
84
|
+
Go to the root folder of this project and build the Docker image like this:
|
85
|
+
`docker build -t fluent-plugin-dev .`
|
86
|
+
|
87
|
+
Run the container like this:
|
88
|
+
`docker run --rm -i -t --name fluent-plugin-dev -v $(pwd):/app fluent-plugin-dev /bin/bash`
|
89
|
+
|
90
|
+
Once inside the container, you can run the following to make sure that all dependecies are installed:
|
91
|
+
`bundle install`
|
92
|
+
|
93
|
+
Then you can run tests like this:
|
94
|
+
`bundle exec rake`
|
95
|
+
|
96
|
+
== Contributing
|
97
|
+
The vmware_log_intelligence plugin project team welcomes contributions from the community. Before you start working with fluent-plugin-vmware-log-intelligence, please read our [Developer Certificate of Origin](https://cla.vmware.com/dco). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. For more detailed information, refer to [CONTRIBUTING.md](CONTRIBUTING.md).
|
98
|
+
|
99
|
+
== License
|
100
|
+
Please see LICENSE.txt[LICENSE.txt].
|
101
|
+
|
102
|
+
== Copyright
|
103
|
+
|
104
|
+
Copyright (c) 2013 ablagoev.
|
105
|
+
|
106
|
+
Copyright 2018 VMware, Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
# Copyright (c) 2013 ablagoev
|
4
|
+
# Copyright 2018 VMware, Inc.
|
5
|
+
# SPDX-License-Identifier: MIT
|
6
|
+
|
7
|
+
require "bundler/gem_tasks"
|
8
|
+
require 'rake/testtask'
|
9
|
+
|
10
|
+
Rake::TestTask.new(:test) do |test|
|
11
|
+
test.libs << 'lib' << 'test'
|
12
|
+
test.pattern = 'test/**/test_*.rb'
|
13
|
+
test.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :test
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.5
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright (c) 2013 ablagoev
|
2
|
+
# Copyright 2018 VMware, Inc.
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
|
5
|
+
$:.push File.expand_path('../lib', __FILE__)
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'fluent-plugin-vmware-log-intelligence'
|
9
|
+
s.version = File.read("VERSION").strip
|
10
|
+
s.date = '2018-08-12'
|
11
|
+
s.summary = "Fluentd buffered output plugin for VMware Log Intelligence"
|
12
|
+
s.description = "Send Fluentd buffered logs to VMware Log Intelligence"
|
13
|
+
s.authors = ["Alexander Blagoev", "Chaur Wu"]
|
14
|
+
s.email = 'gwu@vmware.com'
|
15
|
+
s.homepage =
|
16
|
+
'http://github.com/vmware/fluent-plugin-vmware-log-intelligence'
|
17
|
+
|
18
|
+
s.files = [
|
19
|
+
"lib/fluent/plugin/out_vmware_log_intelligence.rb",
|
20
|
+
"lib/fluent/plugin/http_client.rb",
|
21
|
+
"Gemfile",
|
22
|
+
"LICENSE.txt",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"fluent-plugin-vmware-log-intelligence.gemspec",
|
27
|
+
"test/helper.rb",
|
28
|
+
"test/plugin/test_out_vmware_log_intelligence.rb",
|
29
|
+
"test/plugin/test_http_client.rb",
|
30
|
+
]
|
31
|
+
|
32
|
+
s.extra_rdoc_files = [
|
33
|
+
"LICENSE.txt",
|
34
|
+
"README.rdoc"
|
35
|
+
]
|
36
|
+
s.licenses = ["MIT"]
|
37
|
+
|
38
|
+
s.require_paths = ['lib']
|
39
|
+
|
40
|
+
s.add_dependency "fluentd", ">= 0.14.20"
|
41
|
+
s.add_dependency "http", ">= 0.9.8"
|
42
|
+
s.add_dependency "myslog", "~> 0.0"
|
43
|
+
s.add_dependency "fluent-plugin-mysqlslowquery", ">= 0.0.9"
|
44
|
+
s.add_development_dependency "rake", ">= 0.9.2"
|
45
|
+
s.add_development_dependency "bundler", ">= 1.3.4"
|
46
|
+
s.add_development_dependency 'test-unit', '~> 3.1.0'
|
47
|
+
s.add_development_dependency 'webmock', '~> 3.4.0'
|
48
|
+
s.add_development_dependency 'fluent-plugin-detect-exceptions', '>= 0.0.12'
|
49
|
+
s.add_development_dependency 'fluent-plugin-concat', '>= 2.0.0'
|
50
|
+
s.add_development_dependency 'fluent-plugin-kubernetes_metadata_filter', '>= 2.0.0'
|
51
|
+
s.add_development_dependency 'fluent-plugin-multi-format-parser', '>= 1.0.0'
|
52
|
+
s.add_development_dependency 'fluent-plugin-postgres', '>= 0.0.1'
|
53
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright 2018 VMware, Inc.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
module Fluent::Plugin
|
5
|
+
class HttpClient
|
6
|
+
def initialize(endpoint_url, verify_ssl,
|
7
|
+
headers, statuses, open_timeout, read_timeout, log)
|
8
|
+
@log = log
|
9
|
+
@statuses = statuses
|
10
|
+
@options = {}
|
11
|
+
|
12
|
+
if !verify_ssl
|
13
|
+
@log.warn('SSL verification of the remote VMware Log Intelligence service is turned off. This is serious security risk. Please turn on SSL verification and restart the Fluentd/td-agent process.')
|
14
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
15
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
16
|
+
@options = {:ssl_context => ctx}
|
17
|
+
end
|
18
|
+
|
19
|
+
timeout_options = {
|
20
|
+
:connect_timeout => open_timeout,
|
21
|
+
:read_timeout => read_timeout
|
22
|
+
}
|
23
|
+
|
24
|
+
@conn = HTTP.persistent(endpoint_url)
|
25
|
+
.headers(headers)
|
26
|
+
.timeout(timeout_options)
|
27
|
+
|
28
|
+
@endpoint_path = HTTP::URI.parse(endpoint_url).path
|
29
|
+
@last_429_time = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_quota
|
33
|
+
if @last_429_time
|
34
|
+
if (Time.new - @last_429_time) < 600
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
|
38
|
+
@last_429_time = nil
|
39
|
+
end
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
|
43
|
+
def post(data)
|
44
|
+
if !check_quota
|
45
|
+
return
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
response = @conn.post(@endpoint_path, @options.merge(:body => data))
|
50
|
+
response.body.to_s
|
51
|
+
if (response.code == 429)
|
52
|
+
@log.warn('1GB quota of free account has been reached. Will stop sending data for 1 hour.')
|
53
|
+
@last_429_time = Time.new
|
54
|
+
else
|
55
|
+
@last_429_time = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
if @statuses.include? response.code.to_i
|
59
|
+
# Raise an exception so that fluent will retry based on the configurations.
|
60
|
+
fail "Server returned bad status: #{response.code}. #{response.to_s}"
|
61
|
+
end
|
62
|
+
|
63
|
+
rescue EOFError, SystemCallError, OpenSSL::SSL::SSLError => e
|
64
|
+
@log.warn "http post raises exception: #{e.class}, '#{e.message}'"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
@conn.close
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# Copyright (c) 2013 ablagoev
|
2
|
+
# Copyright 2018 VMware, Inc.
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
|
5
|
+
require "fluent/plugin/output"
|
6
|
+
require "fluent/plugin/http_client"
|
7
|
+
|
8
|
+
module Fluent::Plugin
|
9
|
+
class LogIntelligenceOutput < Output
|
10
|
+
Fluent::Plugin.register_output('vmware_log_intelligence', self)
|
11
|
+
|
12
|
+
config_param :endpoint_url, :string
|
13
|
+
config_param :http_retry_statuses, :string, default: ''
|
14
|
+
config_param :read_timeout, :integer, default: 60
|
15
|
+
config_param :open_timeout, :integer, default: 60
|
16
|
+
# config_param :use_ssl, :bool, :default => false
|
17
|
+
config_param :verify_ssl, :bool, :default => true
|
18
|
+
config_param :rate_limit_msec, :integer, :default => 0
|
19
|
+
# Keys from log event whose values should be added as log message/text
|
20
|
+
# to log-intelligence. Note these key/value pairs won't be added as metadata/fields
|
21
|
+
config_param :log_text_keys, :array, default: ["log", "message", "msg"], value_type: :string
|
22
|
+
# Flatten hashes to create one key/val pair w/o losing log data
|
23
|
+
config_param :flatten_hashes, :bool, :default => true
|
24
|
+
# Seperator to use for joining flattened keys
|
25
|
+
config_param :flatten_hashes_separator, :string, :default => "_"
|
26
|
+
|
27
|
+
config_section :buffer do
|
28
|
+
config_set_default :@type, "memory"
|
29
|
+
config_set_default :chunk_keys, []
|
30
|
+
config_set_default :timekey_use_utc, true
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
super
|
35
|
+
require 'http'
|
36
|
+
require 'uri'
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate_uri(uri_string)
|
40
|
+
unless uri_string =~ /^#{URI.regexp}$/
|
41
|
+
fail Fluent::ConfigError, 'endpoint_url invalid'
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
@uri = URI.parse(uri_string)
|
46
|
+
rescue URI::InvalidURIError
|
47
|
+
raise Fluent::ConfigError, 'endpoint_url invalid'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def retrieve_headers(conf)
|
52
|
+
headers = {}
|
53
|
+
conf.elements.each do |element|
|
54
|
+
if element.name == 'headers'
|
55
|
+
headers = element.to_hash
|
56
|
+
end
|
57
|
+
end
|
58
|
+
headers
|
59
|
+
end
|
60
|
+
|
61
|
+
def shorten_key(key)
|
62
|
+
# LINT doesn't allow some characters in field 'name'
|
63
|
+
# like '/', '-', '\', '.', etc. so replace them with @flatten_hashes_separator
|
64
|
+
key = key.gsub(/[\/\.\-\\]/,@flatten_hashes_separator).downcase
|
65
|
+
key
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_lint_event(record)
|
69
|
+
flattened_records = {}
|
70
|
+
merged_records = {}
|
71
|
+
if @flatten_hashes
|
72
|
+
flattened_records = flatten_record(record, [])
|
73
|
+
else
|
74
|
+
flattened_records = record
|
75
|
+
end
|
76
|
+
|
77
|
+
keys = []
|
78
|
+
log = ''
|
79
|
+
flattened_records.each do |key, value|
|
80
|
+
begin
|
81
|
+
next if value.nil?
|
82
|
+
# LINT doesn't support duplicate fields, make unique names by appending underscore
|
83
|
+
key = shorten_key(key)
|
84
|
+
if keys.include?(key)
|
85
|
+
value = merged_records[key] + " " + value
|
86
|
+
end
|
87
|
+
keys.push(key)
|
88
|
+
key.force_encoding("utf-8")
|
89
|
+
|
90
|
+
if value.is_a?(String)
|
91
|
+
value.force_encoding("utf-8")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
if @log_text_keys.include?(key)
|
96
|
+
if log != "#{value}"
|
97
|
+
if log.empty?
|
98
|
+
log = "#{value}"
|
99
|
+
else
|
100
|
+
log += " #{value}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
else
|
104
|
+
merged_records[key] = value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
merged_records["text"] = log
|
108
|
+
|
109
|
+
if log == "\\n"
|
110
|
+
{}
|
111
|
+
else
|
112
|
+
merged_records
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def flatten_record(record, prefix=[])
|
117
|
+
ret = {}
|
118
|
+
|
119
|
+
case record
|
120
|
+
when Hash
|
121
|
+
record.each do |key, value|
|
122
|
+
if @log_text_keys.include?(key)
|
123
|
+
ret.merge!({key.to_s => value})
|
124
|
+
else
|
125
|
+
ret.merge! flatten_record(value, prefix + [key.to_s])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
when Array
|
129
|
+
record.each do |value|
|
130
|
+
ret.merge! flatten_record(value, prefix)
|
131
|
+
end
|
132
|
+
else
|
133
|
+
return {prefix.join(@flatten_hashes_separator) => record}
|
134
|
+
end
|
135
|
+
ret
|
136
|
+
end
|
137
|
+
def configure(conf)
|
138
|
+
super
|
139
|
+
validate_uri(@endpoint_url)
|
140
|
+
|
141
|
+
@statuses = @http_retry_statuses.split(',').map { |status| status.to_i }
|
142
|
+
@statuses = [] if @statuses.nil?
|
143
|
+
|
144
|
+
@headers = retrieve_headers(conf)
|
145
|
+
|
146
|
+
@http_client = Fluent::Plugin::HttpClient.new(
|
147
|
+
@endpoint_url, @verify_ssl, @headers, @statuses,
|
148
|
+
@open_timeout, @read_timeout, @log)
|
149
|
+
end
|
150
|
+
|
151
|
+
def start
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
155
|
+
def shutdown
|
156
|
+
super
|
157
|
+
begin
|
158
|
+
@http_client.close if @http_client
|
159
|
+
rescue
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def write(chunk)
|
164
|
+
is_rate_limited = (@rate_limit_msec != 0 and not @last_request_time.nil?)
|
165
|
+
if is_rate_limited and ((Time.now.to_f - @last_request_time) * 1000.0 < @rate_limit_msec)
|
166
|
+
@log.info('Dropped request due to rate limiting')
|
167
|
+
return
|
168
|
+
end
|
169
|
+
|
170
|
+
data = []
|
171
|
+
chunk.each do |time, record|
|
172
|
+
data << create_lint_event(record)
|
173
|
+
end
|
174
|
+
|
175
|
+
@last_request_time = Time.now.to_f
|
176
|
+
@http_client.post(JSON.dump(data))
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Copyright (c) 2013 ablagoev
|
2
|
+
# Copyright 2018 VMware, Inc.
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
|
5
|
+
require 'coveralls'
|
6
|
+
Coveralls.wear!
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'bundler'
|
10
|
+
|
11
|
+
begin
|
12
|
+
Bundler.setup(:default, :development)
|
13
|
+
rescue Bundler::BundlerError => e
|
14
|
+
$stderr.puts e.message
|
15
|
+
$stderr.puts 'Run `bundle install` to install missing gems'
|
16
|
+
exit e.status_code
|
17
|
+
end
|
18
|
+
|
19
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
20
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
21
|
+
|
22
|
+
require 'test-unit'
|
23
|
+
require 'fluent/test'
|
24
|
+
require "fluent/test/driver/output"
|
25
|
+
require "fluent/test/helpers"
|
26
|
+
Test::Unit::TestCase.include(Fluent::Test::Helpers)
|
27
|
+
Test::Unit::TestCase.extend(Fluent::Test::Helpers)
|
28
|
+
|
29
|
+
require 'fluent/plugin/out_vmware_log_intelligence'
|
30
|
+
require 'webmock/test_unit'
|
31
|
+
WebMock.disable_net_connect!
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# Copyright 2018 VMware, Inc.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require 'helper'
|
5
|
+
|
6
|
+
class HttpClientTest < Test::Unit::TestCase
|
7
|
+
DEFAULT_URL = "https://local.endpoint:3000/dummy/xyz"
|
8
|
+
|
9
|
+
def stub_server_out_of_quota(url=DEFAULT_URL)
|
10
|
+
stub_request(:post, url).to_return(:status => [429, "User out of ingestion quota."])
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_check_quota
|
14
|
+
http_client = create_http_client()
|
15
|
+
assert_equal http_client.check_quota, true
|
16
|
+
|
17
|
+
stub_server_out_of_quota
|
18
|
+
http_client.post(JSON.dump(sample_record()))
|
19
|
+
assert_equal http_client.check_quota, false
|
20
|
+
end
|
21
|
+
|
22
|
+
def stub_server_unavailable(url=DEFAULT_URL)
|
23
|
+
stub_request(:post, url).to_return(:status => [503, "Service Unavailable"])
|
24
|
+
end
|
25
|
+
|
26
|
+
def stub_server_returns_500(url=DEFAULT_URL)
|
27
|
+
stub_request(:post, url).to_return(:status => [500, "Internal service error"])
|
28
|
+
end
|
29
|
+
|
30
|
+
def stub_server_raise_error(url=DEFAULT_URL)
|
31
|
+
stub_request(:post, url).with do |req|
|
32
|
+
raise IOError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def stub_post_logs(url=DEFAULT_URL)
|
37
|
+
stub_request(:post, url)
|
38
|
+
.with(
|
39
|
+
body: "[{\"field1\":26,\"field2\":\"value26\"},{\"field1\":27,\"field2\":\"value27\"},{\"field1\":28,\"field2\":\"value28\"}]",
|
40
|
+
headers: {
|
41
|
+
'Authorization'=>'Bearer EdaNNN68y',
|
42
|
+
'Connection'=>'Keep-Alive',
|
43
|
+
'Content-Type'=>'application/json',
|
44
|
+
'Format'=>'syslog',
|
45
|
+
'Structure'=>'simple'
|
46
|
+
})
|
47
|
+
.to_return(:status => 200, :body => "ok")
|
48
|
+
end
|
49
|
+
|
50
|
+
def sample_record()
|
51
|
+
[
|
52
|
+
{'field1' => 26, 'field2' => 'value26'},
|
53
|
+
{'field1' => 27, 'field2' => 'value27'},
|
54
|
+
{'field1' => 28, 'field2' => 'value28'}
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_http_client
|
59
|
+
Fluent::Plugin::HttpClient.new(
|
60
|
+
DEFAULT_URL, true,
|
61
|
+
{
|
62
|
+
'Authorization'=>'Bearer EdaNNN68y',
|
63
|
+
'Connection'=>'Keep-Alive',
|
64
|
+
'Content-Type'=>'application/json',
|
65
|
+
'Format'=>'syslog',
|
66
|
+
'Host'=>'local.endpoint:3000',
|
67
|
+
'Structure'=>'simple'
|
68
|
+
}, [500, 510], 60, 60, Logger.new(STDOUT))
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_retry_on_response_status_code
|
72
|
+
http_client = create_http_client()
|
73
|
+
stub_server_returns_500
|
74
|
+
assert_raise RuntimeError do
|
75
|
+
http_client.post(JSON.dump(sample_record()))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_server_raise_error
|
80
|
+
http_client = create_http_client()
|
81
|
+
stub_server_raise_error
|
82
|
+
assert_raise IOError do
|
83
|
+
http_client.post(JSON.dump(sample_record()))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_post_logs
|
88
|
+
stub_post_logs
|
89
|
+
http_client = create_http_client()
|
90
|
+
http_client.post(JSON.dump(sample_record()))
|
91
|
+
|
92
|
+
stub_post_logs
|
93
|
+
http_client.post(JSON.dump(sample_record()))
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# Copyright 2018 VMware, Inc.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require 'helper'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
class LogIntelligenceOutputTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
Fluent::Test.setup
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_driver(conf)
|
13
|
+
Fluent::Test::Driver::Output.new(Fluent::Plugin::LogIntelligenceOutput).configure(conf)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_configure
|
17
|
+
config = %[
|
18
|
+
endpoint_url http://localhost:9200/li
|
19
|
+
]
|
20
|
+
|
21
|
+
instance = create_driver(config).instance
|
22
|
+
assert_equal 'http://localhost:9200/li', instance.endpoint_url
|
23
|
+
assert_equal '', instance.instance_eval { @http_retry_statuses }
|
24
|
+
assert_equal [], instance.instance_eval { @statuses }
|
25
|
+
assert_equal 60, instance.instance_eval { @read_timeout }
|
26
|
+
assert_equal 60, instance.instance_eval { @open_timeout }
|
27
|
+
assert_equal({}, instance.instance_eval { @headers })
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_full_configure
|
31
|
+
config = %[
|
32
|
+
@type http_buffered
|
33
|
+
endpoint_url https://local.endpoint:3000/dummy/xyz
|
34
|
+
verify_ssl false
|
35
|
+
<headers>
|
36
|
+
Content-Type application/json
|
37
|
+
Authorization Bearer EdaNNN68y
|
38
|
+
structure simple
|
39
|
+
format syslog
|
40
|
+
</headers>
|
41
|
+
<buffer>
|
42
|
+
chunk_limit_records 3
|
43
|
+
flush_interval 12s
|
44
|
+
retry_max_times 3
|
45
|
+
</buffer>
|
46
|
+
]
|
47
|
+
|
48
|
+
instance = create_driver(config).instance
|
49
|
+
assert_equal 'https://local.endpoint:3000/dummy/xyz', instance.endpoint_url
|
50
|
+
assert_equal '', instance.http_retry_statuses
|
51
|
+
assert_equal [], instance.instance_eval { @statuses }
|
52
|
+
assert_equal 60, instance.read_timeout
|
53
|
+
assert_equal 60, instance.open_timeout
|
54
|
+
assert_equal({
|
55
|
+
"Authorization"=>"Bearer EdaNNN68y",
|
56
|
+
"Content-Type"=>"application/json",
|
57
|
+
"structure"=>"simple",
|
58
|
+
"format"=>"syslog"}, instance.instance_eval { @headers })
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_invalid_endpoint
|
62
|
+
assert_raise Fluent::ConfigError do
|
63
|
+
create_driver('endpoint_url \\@3')
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_raise Fluent::ConfigError do
|
67
|
+
create_driver('endpoint_url google.com')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_json_with_special_char1
|
72
|
+
$log = Logger.new(STDOUT)
|
73
|
+
$log.level = Logger::DEBUG
|
74
|
+
input = {"host" => "192.168.0.1", "log" => "2019-12-02 02:04:13.556787 I | mvcc: finished scheduled compaction at 2259 (took 822.573\xC2\xB5s)"}
|
75
|
+
output = [{"host" => "192.168.0.1", "text" => "2019-12-02 02:04:13.556787 I | mvcc: finished scheduled compaction at 2259 (took 822.573\xC2\xB5s)"}]
|
76
|
+
$log.debug("INPUT: #{input}")
|
77
|
+
$log.debug("OUTPUT: #{output}")
|
78
|
+
verify_write(input, output)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_json_with_special_char2
|
82
|
+
$log = Logger.new(STDOUT)
|
83
|
+
$log.level = Logger::DEBUG
|
84
|
+
input = {"host" => "192.168.0.1", "log" => "2019-12-02 02:04:13.556787 I | mvcc: finished scheduled compaction at 2259 (took 678.559µs)"}
|
85
|
+
output = [{"host" => "192.168.0.1", "text" => "2019-12-02 02:04:13.556787 I | mvcc: finished scheduled compaction at 2259 (took 678.559µs)"}]
|
86
|
+
$log.debug("INPUT: #{input}")
|
87
|
+
$log.debug("OUTPUT: #{output}")
|
88
|
+
verify_write(input, output)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_json_with_log
|
92
|
+
input = {"host" => "192.168.0.1", "log" => "machine reboot"}
|
93
|
+
output = [{"host" => "192.168.0.1", "text" => "machine reboot"}]
|
94
|
+
verify_write(input, output)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_json_with_msg
|
98
|
+
input = {"host" => "192.168.0.1", "msg" => "machine reboot"}
|
99
|
+
output = [{"host" => "192.168.0.1", "text" => "machine reboot"}]
|
100
|
+
verify_write(input, output)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_nested_json
|
104
|
+
input = {"host" => "192.168.0.1", "properties" => {"type" => "windows"}, "msg" => "machine reboot"}
|
105
|
+
output = [{"host" => "192.168.0.1", "properties_type" => "windows", "text" => "machine reboot"}]
|
106
|
+
verify_write(input, output)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_nested_json_with_log
|
110
|
+
input = {"host" => "192.168.0.1", "properties" => {"type" => "windows"}, "message" => "machine reboot"}
|
111
|
+
output = [{"host" => "192.168.0.1", "properties_type" => "windows", "text" => "machine reboot"}]
|
112
|
+
verify_write(input, output)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_json_with_new_line
|
116
|
+
input = {"host" => "192.168.0.1", "log" => "\\n"}
|
117
|
+
output = [{}]
|
118
|
+
verify_write(input, output)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_json_with_multiple_log_formats
|
122
|
+
input = {"host" => "192.168.0.1", "log" => "custom log:1", "message" => "custom message:2", "msg" => "custom msg:3"}
|
123
|
+
output = [{"host" => "192.168.0.1", "text" => "custom log:1 custom message:2 custom msg:3"}]
|
124
|
+
verify_write(input, output)
|
125
|
+
end
|
126
|
+
|
127
|
+
# For any null values, its key and values are not being populated to output.
|
128
|
+
def test_json_with_null_value
|
129
|
+
input = {"host" => "192.168.0.1", "source" => nil, "log" => "abc"}
|
130
|
+
output = [{"host" => "192.168.0.1", "text" => "abc"}]
|
131
|
+
verify_write(input, output)
|
132
|
+
end
|
133
|
+
|
134
|
+
# like '/', '-', '\', '.', etc. replace with '_'
|
135
|
+
def test_json_with_sperators
|
136
|
+
input = {"host" => "192.168.0.1", "/properties" => {"type-" => "windows"}, "msg" => "123"}
|
137
|
+
output = [{"host" => "192.168.0.1", "_properties_type_" => "windows", "text" => "123"}]
|
138
|
+
verify_write(input, output)
|
139
|
+
end
|
140
|
+
|
141
|
+
def verify_write(input, output)
|
142
|
+
config = %[
|
143
|
+
endpoint_url http://localhost:9200/li
|
144
|
+
]
|
145
|
+
stub = stub_request(:post, "http://localhost:9200/li").with(body: output.to_json).
|
146
|
+
to_return(status: 200, body: "", headers: {})
|
147
|
+
|
148
|
+
driver = create_driver(config)
|
149
|
+
driver.run(default_tag: 'test') do
|
150
|
+
driver.feed(input)
|
151
|
+
end
|
152
|
+
assert_requested(stub)
|
153
|
+
end
|
154
|
+
end
|
metadata
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-vmware-log-intelligence
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexander Blagoev
|
8
|
+
- Chaur Wu
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2018-08-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fluentd
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.14.20
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.14.20
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: http
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.9.8
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.9.8
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: myslog
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0.0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: fluent-plugin-mysqlslowquery
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.0.9
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.0.9
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rake
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.9.2
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 0.9.2
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: bundler
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 1.3.4
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 1.3.4
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: test-unit
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 3.1.0
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 3.1.0
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: webmock
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 3.4.0
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 3.4.0
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: fluent-plugin-detect-exceptions
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 0.0.12
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 0.0.12
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: fluent-plugin-concat
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 2.0.0
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: 2.0.0
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: fluent-plugin-kubernetes_metadata_filter
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: 2.0.0
|
161
|
+
type: :development
|
162
|
+
prerelease: false
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: 2.0.0
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: fluent-plugin-multi-format-parser
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: 1.0.0
|
175
|
+
type: :development
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - ">="
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: 1.0.0
|
182
|
+
- !ruby/object:Gem::Dependency
|
183
|
+
name: fluent-plugin-postgres
|
184
|
+
requirement: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: 0.0.1
|
189
|
+
type: :development
|
190
|
+
prerelease: false
|
191
|
+
version_requirements: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: 0.0.1
|
196
|
+
description: Send Fluentd buffered logs to VMware Log Intelligence
|
197
|
+
email: gwu@vmware.com
|
198
|
+
executables: []
|
199
|
+
extensions: []
|
200
|
+
extra_rdoc_files:
|
201
|
+
- LICENSE.txt
|
202
|
+
- README.rdoc
|
203
|
+
files:
|
204
|
+
- Gemfile
|
205
|
+
- LICENSE.txt
|
206
|
+
- README.rdoc
|
207
|
+
- Rakefile
|
208
|
+
- VERSION
|
209
|
+
- fluent-plugin-vmware-log-intelligence.gemspec
|
210
|
+
- lib/fluent/plugin/http_client.rb
|
211
|
+
- lib/fluent/plugin/out_vmware_log_intelligence.rb
|
212
|
+
- test/helper.rb
|
213
|
+
- test/plugin/test_http_client.rb
|
214
|
+
- test/plugin/test_out_vmware_log_intelligence.rb
|
215
|
+
homepage: http://github.com/vmware/fluent-plugin-vmware-log-intelligence
|
216
|
+
licenses:
|
217
|
+
- MIT
|
218
|
+
metadata: {}
|
219
|
+
post_install_message:
|
220
|
+
rdoc_options: []
|
221
|
+
require_paths:
|
222
|
+
- lib
|
223
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
224
|
+
requirements:
|
225
|
+
- - ">="
|
226
|
+
- !ruby/object:Gem::Version
|
227
|
+
version: '0'
|
228
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
229
|
+
requirements:
|
230
|
+
- - ">="
|
231
|
+
- !ruby/object:Gem::Version
|
232
|
+
version: '0'
|
233
|
+
requirements: []
|
234
|
+
rubygems_version: 3.1.4
|
235
|
+
signing_key:
|
236
|
+
specification_version: 4
|
237
|
+
summary: Fluentd buffered output plugin for VMware Log Intelligence
|
238
|
+
test_files: []
|