logstash-output-stackify 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ee3bea3022070998d5caf289643c7b356eeb29751f34109b5bae9520d55aa65
4
- data.tar.gz: 28b8b344a668ad2072516a1f3030d713019aad36d84082aba567e2a33772efea
3
+ metadata.gz: adaf7be7736925de7b4a9d6af06d3a0e83079c22b4b966999560eae67ec62e7d
4
+ data.tar.gz: a7b120bd7de7f818acbcb9452adc4b9d2b9ef33f336a145e547f5dfe362462fa
5
5
  SHA512:
6
- metadata.gz: 8d09b810c70cefde834eec5bc36c88412fc4a354c017a895ff54a0fea5c2baee4701dc3573ae331be1e0c2779813b9b4a4e8eed6be1c87d1267aa13b8f27bdd5
7
- data.tar.gz: c09ab5ca029229d99f4d374c59034a801b98ab09656adcb0e8cdcab6150cb84d8136bb2106de319d313a59d6db654ad06ca2cbc80bc5dd419e773fa4c29d31b4
6
+ metadata.gz: 69d4a6bdca008bb34313dfcb9946c403eae443314d9001c650f9798325c43dff6458c6d679448ea197d30828006f04be73cbad19a84adffb8128464d69ace3a2
7
+ data.tar.gz: e9989d729f6a46c7e344b4d6d0bd60572fb051da8e25064e9dc4582ba7eacc25f4ecb4d8c67dc03403bd9e55a5d529a351ee61f2ee8d93903d0c7bcc0f951bae
@@ -1,15 +1,37 @@
1
1
  # encoding: utf-8
2
+
3
+ # Core classes
2
4
  require 'date'
3
5
  require 'socket'
4
6
  require 'json'
5
7
  require 'uri'
6
- require "net/http"
7
- require "net/https"
8
- require "logstash/outputs/base"
8
+
9
+ # Logstash classes
9
10
  require "logstash/namespace"
11
+ require "logstash/json"
12
+ require "logstash/outputs/base"
13
+ require "logstash/outputs/stackify/http_client"
14
+
10
15
 
11
16
  # Use logstash to ship logs to Stackify
12
17
  class LogStash::Outputs::Stackify < LogStash::Outputs::Base
18
+ include HttpClient
19
+
20
+ CONTENT_TYPE = "application/json".freeze
21
+ MAX_ATTEMPTS = 3
22
+ METHOD = :post.freeze
23
+ RETRYABLE_MANTICORE_EXCEPTIONS = [
24
+ ::Manticore::Timeout,
25
+ ::Manticore::SocketException,
26
+ ::Manticore::ClientProtocolException,
27
+ ::Manticore::ResolutionFailure,
28
+ ::Manticore::SocketTimeout
29
+ ].freeze
30
+ RETRYABLE_CODES = [429, 500, 502, 503, 504].freeze
31
+ URL = "https://api.stackify.com/Log/Save".freeze
32
+
33
+ concurrency :shared
34
+
13
35
  config_name "stackify"
14
36
 
15
37
  # Your Stackify Activation Key
@@ -39,14 +61,27 @@ class LogStash::Outputs::Stackify < LogStash::Outputs::Base
39
61
  public
40
62
 
41
63
  def register
64
+ @headers = {
65
+ "Content-Type" => CONTENT_TYPE,
66
+ "Accept" => CONTENT_TYPE,
67
+ "X-Stackify-PV" => "V1",
68
+ "X-Stackify-Key" => @key.to_s
69
+ }
70
+ @url = URL
71
+ @logger.info("Stackify Plugin Registered")
42
72
  end
43
73
 
44
- # def register
74
+ def multi_receive(events)
75
+ send_events(events)
76
+ end
45
77
 
46
- public
78
+ def close
79
+ http_client.close
80
+ end
47
81
 
48
- def multi_receive(events)
82
+ private
49
83
 
84
+ def send_events(events)
50
85
  begin
51
86
 
52
87
  default_level = 'info'
@@ -109,10 +144,10 @@ class LogStash::Outputs::Stackify < LogStash::Outputs::Base
109
144
  begin
110
145
  timestamp = (event.get('@timestamp').to_f * 1000).to_i
111
146
  rescue StandardError => e
112
- $stdout.write('Error parsing @timestamp: ' + e.to_s)
147
+ @logger.warn('Error parsing @timestamp: ' + e.to_s)
113
148
  end
114
149
  if timestamp.nil?
115
- timestamp = DateTime.now.strftime('%Q')
150
+ timestamp = DateTime.now.strftime('%Q')
116
151
  end
117
152
 
118
153
  # generate key
@@ -140,7 +175,7 @@ class LogStash::Outputs::Stackify < LogStash::Outputs::Base
140
175
  log_msg_groups[log_msg_group_key]['Msgs'].push(log_msg)
141
176
 
142
177
  rescue StandardError => e
143
- $stdout.write(e.to_s)
178
+ @logger.warn(e.to_s)
144
179
  end
145
180
 
146
181
  end
@@ -149,31 +184,96 @@ class LogStash::Outputs::Stackify < LogStash::Outputs::Base
149
184
  log_msg_groups.each do |key, log_msg_group|
150
185
 
151
186
  begin
152
-
153
- data = log_msg_group.to_json
154
- header = {
155
- 'Content-Type' => 'application/json',
156
- 'Accept' => 'application/json',
157
- 'X-Stackify-PV' => 'V1',
158
- 'X-Stackify-Key' => @key.to_s
159
- }
160
-
161
- uri = URI.parse('https://api.stackify.com/Log/Save')
162
- https = Net::HTTP.new(uri.host, uri.port)
163
- https.use_ssl = true
164
- req = Net::HTTP::Post.new(uri.path, initheader = header)
165
- req.body = data
166
- https.request(req)
167
-
187
+ post_log_msg_group(log_msg_group, 0)
168
188
  rescue StandardError => e
169
- $stdout.write(e.to_s)
189
+ @logger.warn(e.to_s)
170
190
  end
171
191
 
172
192
  end
173
193
 
174
194
  rescue StandardError => e
175
- $stdout.write(e.to_s)
195
+ @logger.warn(e.to_s)
196
+ end
197
+
198
+ end
199
+
200
+
201
+ def post_log_msg_group(log_msg_group, attempt)
202
+
203
+ @logger.debug("Stackify Request, Attempt " + attempt.to_s)
204
+
205
+ if attempt > MAX_ATTEMPTS
206
+ @logger.warn(
207
+ "Max attempts exceeded, dropping events",
208
+ :attempt => attempt
209
+ )
210
+ return false
211
+ end
212
+
213
+ response =
214
+ begin
215
+ body = log_msg_group.to_json
216
+ http_client.post(@url, :body => body, :headers => @headers)
217
+ rescue Exception => e
218
+ if retryable_exception?(e)
219
+ @logger.warn(
220
+ "Attempt #{attempt}, retryable exception when making request",
221
+ :attempt => attempt,
222
+ :class => e.class.name,
223
+ :message => e.message,
224
+ :backtrace => e.backtrace
225
+ )
226
+ return post_log_msg_group(log_msg_group, attempt + 1)
227
+ else
228
+ @logger.error(
229
+ "Attempt #{attempt}, fatal exception when making request",
230
+ :attempt => attempt,
231
+ :class => e.class.name,
232
+ :message => e.message,
233
+ :backtrace => e.backtrace
234
+ )
235
+ return false
236
+ end
237
+ end
238
+
239
+ @logger.debug("Stackify Response: " + response.code.to_s)
240
+
241
+ code = response.code
242
+
243
+ if code >= 200 && code <= 299
244
+ true
245
+ elsif RETRYABLE_CODES.include?(code)
246
+ @logger.warn(
247
+ "Bad retryable response from the Stackify API",
248
+ :attempt => attempt,
249
+ :code => code
250
+ )
251
+ sleep_time = sleep_for_attempt(attempt)
252
+ sleep(sleep_time)
253
+ post_log_msg_group(log_msg_group, attempt + 1)
254
+ else
255
+ @logger.error(
256
+ "Bad fatal response from the Stackify API",
257
+ :attempt => attempt,
258
+ :code => code
259
+ )
260
+ false
176
261
  end
177
262
 
178
263
  end
264
+
265
+
266
+ def retryable_exception?(e)
267
+ RETRYABLE_MANTICORE_EXCEPTIONS.any? do |exception_class|
268
+ e.is_a?(exception_class)
269
+ end
270
+ end
271
+
272
+ def sleep_for_attempt(attempt)
273
+ sleep_for = attempt ** 2
274
+ sleep_for = sleep_for <= 60 ? sleep_for : 60
275
+ (sleep_for / 2) + (rand(0..sleep_for) / 2)
276
+ end
277
+
278
+
179
279
  end # class LogStash::Outputs::Stackify
@@ -0,0 +1,131 @@
1
+ # encoding: utf-8
2
+ require "logstash/config/mixin"
3
+ require "manticore"
4
+
5
+ # This contains the HTTP client code within it's own namespace. It is based off
6
+ # of the logstash-mixin-http_client plugin.
7
+ class LogStash::Outputs::Stackify < LogStash::Outputs::Base
8
+ module HttpClient
9
+ class InvalidHTTPConfigError < StandardError; end
10
+
11
+ def self.included(base)
12
+ base.extend(self)
13
+ base.setup_http_client_config
14
+ end
15
+
16
+ def setup_http_client_config
17
+ # Timeout (in seconds) for the entire request
18
+ config :request_timeout, :validate => :number, :default => 60
19
+
20
+ # Timeout (in seconds) to wait for data on the socket. Default is `10s`
21
+ config :socket_timeout, :validate => :number, :default => 10
22
+
23
+ # Timeout (in seconds) to wait for a connection to be established. Default is `10s`
24
+ config :connect_timeout, :validate => :number, :default => 10
25
+
26
+ # Max number of concurrent connections. Defaults to `50`
27
+ config :pool_max, :validate => :number, :default => 50
28
+
29
+ # If you need to use a custom X.509 CA (.pem certs) specify the path to that here
30
+ config :cacert, :validate => :path
31
+
32
+ # If you'd like to use a client certificate (note, most people don't want this) set the path to the x509 cert here
33
+ config :client_cert, :validate => :path
34
+
35
+ # If you're using a client certificate specify the path to the encryption key here
36
+ config :client_key, :validate => :path
37
+
38
+ # If you need to use a custom keystore (`.jks`) specify that here. This does not work with .pem keys!
39
+ config :keystore, :validate => :path
40
+
41
+ # Specify the keystore password here.
42
+ # Note, most .jks files created with keytool require a password!
43
+ config :keystore_password, :validate => :password
44
+
45
+ # Specify the keystore type here. One of `JKS` or `PKCS12`. Default is `JKS`
46
+ config :keystore_type, :validate => :string, :default => "JKS"
47
+
48
+ # If you need to use a custom truststore (`.jks`) specify that here. This does not work with .pem certs!
49
+ config :truststore, :validate => :path
50
+
51
+ # Specify the truststore password here.
52
+ # Note, most .jks files created with keytool require a password!
53
+ config :truststore_password, :validate => :password
54
+
55
+ # Specify the truststore type here. One of `JKS` or `PKCS12`. Default is `JKS`
56
+ config :truststore_type, :validate => :string, :default => "JKS"
57
+
58
+ # If you'd like to use an HTTP proxy . This supports multiple configuration syntaxes:
59
+ #
60
+ # 1. Proxy host in form: `http://proxy.org:1234`
61
+ # 2. Proxy host in form: `{host => "proxy.org", port => 80, scheme => 'http', user => 'username@host', password => 'password'}`
62
+ # 3. Proxy host in form: `{url => 'http://proxy.org:1234', user => 'username@host', password => 'password'}`
63
+ config :proxy
64
+ end
65
+
66
+ def build_http_client_config
67
+ c = {
68
+ connect_timeout: @connect_timeout,
69
+ socket_timeout: @socket_timeout,
70
+ request_timeout: @request_timeout,
71
+ follow_redirects: true,
72
+ automatic_retries: 1,
73
+ retry_non_idempotent: true,
74
+ check_connection_timeout: 200,
75
+ pool_max: @pool_max,
76
+ pool_max_per_route: @pool_max,
77
+ cookies: false,
78
+ keepalive: true
79
+ }
80
+
81
+ if @proxy
82
+ # Symbolize keys if necessary
83
+ c[:proxy] = @proxy.is_a?(Hash) ?
84
+ @proxy.reduce({}) {|memo,(k,v)| memo[k.to_sym] = v; memo} :
85
+ @proxy
86
+ end
87
+
88
+ c[:ssl] = {}
89
+ if @cacert
90
+ c[:ssl][:ca_file] = @cacert
91
+ end
92
+
93
+ if @truststore
94
+ c[:ssl].merge!(
95
+ :truststore => @truststore,
96
+ :truststore_type => @truststore_type,
97
+ :truststore_password => @truststore_password.value
98
+ )
99
+
100
+ if c[:ssl][:truststore_password].nil?
101
+ raise LogStash::ConfigurationError, "Truststore declared without a password! This is not valid, please set the 'truststore_password' option"
102
+ end
103
+ end
104
+
105
+ if @keystore
106
+ c[:ssl].merge!(
107
+ :keystore => @keystore,
108
+ :keystore_type => @keystore_type,
109
+ :keystore_password => @keystore_password.value
110
+ )
111
+
112
+ if c[:ssl][:keystore_password].nil?
113
+ raise LogStash::ConfigurationError, "Keystore declared without a password! This is not valid, please set the 'keystore_password' option"
114
+ end
115
+ end
116
+
117
+ if @client_cert && @client_key
118
+ c[:ssl][:client_cert] = @client_cert
119
+ c[:ssl][:client_key] = @client_key
120
+ elsif !!@client_cert ^ !!@client_key
121
+ raise InvalidHTTPConfigError, "You must specify both client_cert and client_key for an HTTP client, or neither!"
122
+ end
123
+
124
+ c
125
+ end
126
+
127
+ def http_client
128
+ @http_client ||= Manticore::Client.new(build_http_client_config)
129
+ end
130
+ end
131
+ end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-output-stackify'
3
- s.version = '1.0.3'
3
+ s.version = '1.0.4'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Sends logs to Stackify"
6
6
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
 
21
21
  # Gem dependencies
22
22
  s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
23
+ s.add_runtime_dependency 'manticore', '>= 0.5.2', '< 1.0.0'
23
24
 
24
25
  s.add_development_dependency 'logstash-devutils'
25
26
  s.add_development_dependency 'logstash-codec-plain'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-output-stackify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stackify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-05 00:00:00.000000000 Z
11
+ date: 2019-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +30,26 @@ dependencies:
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: 0.5.2
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: 1.0.0
42
+ name: manticore
43
+ prerelease: false
44
+ type: :runtime
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 0.5.2
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 1.0.0
33
53
  - !ruby/object:Gem::Dependency
34
54
  requirement: !ruby/object:Gem::Requirement
35
55
  requirements:
@@ -85,6 +105,7 @@ files:
85
105
  - README.md
86
106
  - docs/DEVELOPMENT.md
87
107
  - lib/logstash/outputs/stackify.rb
108
+ - lib/logstash/outputs/stackify/http_client.rb
88
109
  - logstash-output-stackify.gemspec
89
110
  - spec/outputs/stackify_spec.rb
90
111
  homepage: https://www.stackify.com