fluent-plugin-insight 0.0.2

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 287cbbf3842b2af256f8ee95a49a7a6e182f2104
4
+ data.tar.gz: ac0ff0277597bc853337c2e3ce67d4b916442844
5
+ SHA512:
6
+ metadata.gz: 55ec27d97d5b8a49a72e2679cfbead30b32f66411e9b7c39f8c38f2e64aaddc453d39e16cb387be4a6b729bbc7471ef6fed8405d0e08ae0a54f8243804343741
7
+ data.tar.gz: dc1da043b1d38347fe060261bfab70f80dc83a9e58aeb7bb3bfe3446383a3ca8d70916d369a54392bcf6c4966dbb31a44280358324881160a1a01f1a4c99a28d
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-insight.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Tweddle Group
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,73 @@
1
+ # Fluent::Plugin::Insight
2
+ Forward logs to Insight, using token based input.
3
+
4
+ Using Insight REST API gets log tokens by logset id and matches `record['log']` of every record to a log name in logset
5
+
6
+ ## Installation
7
+
8
+ install with gem or fluent-gem command as:
9
+
10
+ ### native gem
11
+ $ gem install fluent-plugin-insight
12
+
13
+ ### fluentd gem
14
+ $ /opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-insight
15
+
16
+ ## Usage
17
+
18
+ ```
19
+ <match pattern>
20
+ type insight
21
+ tags hostname,environment,application
22
+ prefix %{hostname} %{environment}-%{application}
23
+ api_key test
24
+ logset_id id
25
+ region eu
26
+ </match>
27
+ ```
28
+
29
+ ## Parameters
30
+
31
+ ### type (required)
32
+ The value must be `insight`.
33
+
34
+ ### logset_id (required)
35
+ InsightOPS logset id
36
+
37
+ ### region (required)
38
+ InsightOPS region
39
+
40
+ ### tags
41
+ List of record keys which can be retrieved from record to build a log record prefix
42
+
43
+ ### prefix
44
+ Prefix is added to every log record. It's a format string which uses `tags` values
45
+
46
+ ### protocol
47
+ The default is `tcp`.
48
+
49
+ ### use_ssl
50
+ Enable/disable SSL for data transfers between Fluentd and Insight. The default is `true`.
51
+
52
+ ### port
53
+ Only in case you don't use SSL, the value must be `80`, `514`, or `10000`. The default is `20000` (SSL)
54
+
55
+ ### max_retries
56
+ Number of retries on failure.
57
+
58
+ ## Contributing
59
+
60
+ 1. Fork it ( http://github.com/Tweddle-SE-Team/fluent-plugin-insight/fork )
61
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
62
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
63
+ 4. Push to the branch (`git push origin my-new-feature`)
64
+ 5. Create new Pull Request
65
+
66
+ ## MIT
67
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
73
+ THE SOFTWARE.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fluent-plugin-insight"
7
+ spec.version = "0.0.2"
8
+ spec.authors = ["AndrewChubatiuk"]
9
+ spec.email = ["ops@tweddle.com"]
10
+ spec.summary = "InsightOPS output plugin for Fluent event collector"
11
+ spec.homepage = "https://github.com/Tweddle-SE-Team/fluent-plugin-insight"
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_development_dependency "bundler", "~> 1.5"
20
+ spec.add_development_dependency "rake", "~> 0"
21
+ end
@@ -0,0 +1,161 @@
1
+ require 'socket'
2
+ require 'yaml'
3
+ require 'openssl'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'thread'
7
+
8
+ class Fluent::InsightOutput < Fluent::BufferedOutput
9
+ class ConnectionFailure < StandardError; end
10
+
11
+ Fluent::Plugin.register_output('insight', self)
12
+
13
+ INSIGHT_DATA_TEMPLATE = "%{region}.data.logs.insight.rapid7.com"
14
+ INSIGHT_REST_TEMPLATE = "https://%{region}.rest.logs.insight.rapid7.com"
15
+ INSIGHT_LOGSETS_TEMPLATE = "/management/logsets/%{logset_id}"
16
+ THREAD_COUNT = 4
17
+
18
+ config_param :use_ssl, :bool, :default => true
19
+ config_param :port, :integer, :default => 20000
20
+ config_param :protocol, :string, :default => 'tcp'
21
+ config_param :max_retries, :integer, :default => 3
22
+ config_param :tags, :string, :default => ''
23
+ config_param :prefix, :string, :default => ''
24
+ config_param :default, :string, :default => 'default'
25
+ config_param :api_key
26
+ config_param :logset_id
27
+ config_param :region
28
+
29
+ def configure(conf)
30
+ super
31
+ end
32
+
33
+ def start
34
+ logsets_url = (INSIGHT_REST_TEMPLATE + INSIGHT_LOGSETS_TEMPLATE) % { :region => @region, :logset_id => @logset_id }
35
+ @insight_tags = Hash[@tags.split(",").each_with_object(nil).to_a]
36
+ logset_body = insight_rest_request(logsets_url)
37
+ threads = []
38
+ @tokens = Hash.new
39
+ mutex = Mutex.new
40
+ if logset_body.key?('logset')
41
+ logset_info = logset_body['logset']
42
+ if logset_info.key?('logs_info')
43
+ logs_info = logset_info['logs_info']
44
+ log_urls = logs_info.map { |log| log['links'][0]['href'] }
45
+ THREAD_COUNT.times.map {
46
+ Thread.new(log_urls, @tokens) do |urls, tokens|
47
+ while url = mutex.synchronize { urls.pop }
48
+ log_name, token = insight_log_token(url)
49
+ mutex.synchronize { tokens[log_name] = token }
50
+ end
51
+ end
52
+ }.each(&:join)
53
+ else
54
+ log.warn "No logs info found in logset response"
55
+ end
56
+ else
57
+ log.warn "Logset emtity is empty"
58
+ end
59
+ super
60
+ end
61
+
62
+ def shutdown
63
+ super
64
+ end
65
+
66
+ def insight_rest_request(url)
67
+ uri = URI(url)
68
+ request = Net::HTTP::Get.new(uri)
69
+ request['content-type'] = 'application/json'
70
+ request['x-api-key'] = @api_key
71
+ response = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == 'https') {|http|
72
+ http.request(request)
73
+ }
74
+ if response.code == "200"
75
+ return JSON.parse(response.body)
76
+ end
77
+ log.error "Request was failed HTTP #{response.code}: \n#{response.body}"
78
+ end
79
+
80
+ def insight_log_token(url)
81
+ log_body = insight_rest_request(url)
82
+ if log_body.key?('log')
83
+ log_info = log_body['log']
84
+ if log_info.key?('tokens')
85
+ log.info "Found log #{log_info['name']}"
86
+ return log_info['name'], log_info['tokens'][0]
87
+ else
88
+ log.warn "Log is empty"
89
+ end
90
+ else
91
+ log.warn "Response doesn't contain log info"
92
+ end
93
+ end
94
+
95
+ def client
96
+ insight_data_host = INSIGHT_DATA_TEMPLATE % { :region => @region }
97
+ @_socket ||= if @use_ssl
98
+ context = OpenSSL::SSL::SSLContext.new
99
+ socket = TCPSocket.new insight_data_host, @port
100
+ ssl_client = OpenSSL::SSL::SSLSocket.new socket, context
101
+ ssl_client.connect
102
+ else
103
+ if @protocol == 'tcp'
104
+ TCPSocket.new insight_data_host, @port
105
+ else
106
+ udp_client = UDPSocket.new
107
+ udp_client.connect insight_data_host, @port
108
+ udp_client
109
+ end
110
+ end
111
+ end
112
+
113
+ def format(tag, time, record)
114
+ return [tag, time, record].to_msgpack
115
+ end
116
+
117
+ def write(chunk)
118
+ return if @tokens.empty?
119
+ chunk.msgpack_each do |(tag, time, record)|
120
+ next unless record.is_a? Hash
121
+ message = (record.delete('message')&.to_s&.rstrip || '')
122
+ next if message.empty?
123
+ @insight_tags.each { |k,v|
124
+ @insight_tags[k] = record[k]
125
+ }
126
+ symbolized_tags = @insight_tags.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
127
+ if @tokens.key?(record['log'])
128
+ token = @tokens[record['log']]
129
+ prefix = @prefix % symbolized_tags
130
+ send_insight(token, "#{prefix} #{message}")
131
+ elsif @tokens.key?(@default)
132
+ token = @tokens[@default]
133
+ prefix = @prefix % symbolized_tags
134
+ send_insight(token, "#{prefix} #{message}")
135
+ else
136
+ log.debug "No token found for #{record['log']} and default log doesn't exist"
137
+ end
138
+ end
139
+ end
140
+
141
+ def send_insight(token, data)
142
+ retries = 0
143
+ begin
144
+ client.write("#{token} #{data} \n")
145
+ rescue Errno::EMSGSIZE
146
+ str_length = data.length
147
+ send_insight(token, data[0..str_length/2])
148
+ send_insight(token, data[(str_length/2) + 1..str_length])
149
+ log.warn "Message Too Long, re-sending it in two part..."
150
+ rescue => e
151
+ if retries < @max_retries
152
+ retries += 1
153
+ @_socket = nil
154
+ log.warn "Could not push logs to Insight, resetting connection and trying again. #{e.message}"
155
+ sleep 5**retries
156
+ retry
157
+ end
158
+ raise ConnectionFailure, "Could not push logs to Insight after #{retries} retries. #{e.message}"
159
+ end
160
+ end
161
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-insight
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - AndrewChubatiuk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - ops@tweddle.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE
51
+ - README.md
52
+ - Rakefile
53
+ - fluent-plugin-insight.gemspec
54
+ - lib/fluent/plugin/out_insight.rb
55
+ homepage: https://github.com/Tweddle-SE-Team/fluent-plugin-insight
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.5.2.3
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: InsightOPS output plugin for Fluent event collector
79
+ test_files: []