fluent-plugin-insight 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []