logstash-output-jut 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/CONTRIBUTORS +11 -0
- data/Gemfile +3 -0
- data/LICENSE +13 -0
- data/README.md +80 -0
- data/Rakefile +1 -0
- data/lib/logstash/outputs/HTTPBatcher.rb +134 -0
- data/lib/logstash/outputs/jut.rb +74 -0
- data/logstash-output-jut.gemspec +27 -0
- data/spec/outputs/jut_spec.rb +29 -0
- data/spec/spec_helper.rb +3 -0
- metadata +120 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a9eb2e11117af72725830240a0c80dd9ac967620
|
4
|
+
data.tar.gz: e0302a18404130b4c21ced3836e7bb91c8d8c03f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d9a292c278f0430a56e0f0f1aa10a4605a357bbe2ef2f24672095bd25213edb94a9d85144c0534fd3a3b192542d52ca218a442b5f592238e17db20602e23345f
|
7
|
+
data.tar.gz: d389e4ddb0ca8af5da51525ffbe9e680996130a685eea176dd8f629a4ee716f1c01b423bcacddcf9a7d16d86ee4dab21ad9de58ab6d3862209ad92f6f9925786
|
data/.gitignore
ADDED
data/CONTRIBUTORS
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
The following is a list of people who have contributed ideas, code, bug
|
2
|
+
reports, or in general have helped this plugin along its way.
|
3
|
+
|
4
|
+
Contributors:
|
5
|
+
* Stephan Liu (stephan-x-liu)
|
6
|
+
* Jut, Inc.
|
7
|
+
|
8
|
+
Note: If you've sent us patches, bug reports, or otherwise contributed to
|
9
|
+
Logstash, and you aren't on the list above and want to be, please let us know
|
10
|
+
and we'll make sure you're here. Contributions from folks like you are what make
|
11
|
+
open source awesome.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2015 Jut, Inc. <www.jut.io>
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Batch HTTP Plugin
|
2
|
+
|
3
|
+
This is a plugin for [Logstash](https://github.com/elasticsearch/logstash).
|
4
|
+
|
5
|
+
## Documentation
|
6
|
+
|
7
|
+
This plugin sends events to a [Jut](https://jut.io) data engine.
|
8
|
+
Events are formatted to be suitable for
|
9
|
+
[direct import via http](http://docs.jut.io/user-guide/#data_ingest_http)
|
10
|
+
and are grouped into batches to reduce the total number of http
|
11
|
+
transactions required to import many records.
|
12
|
+
|
13
|
+
## Developing
|
14
|
+
|
15
|
+
### 1. Plugin Developement and Testing
|
16
|
+
|
17
|
+
#### Code
|
18
|
+
- To get started, you'll need JRuby with the Bundler gem installed.
|
19
|
+
|
20
|
+
- Install dependencies
|
21
|
+
```sh
|
22
|
+
bundle install
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Test
|
26
|
+
|
27
|
+
- Update your dependencies
|
28
|
+
|
29
|
+
```sh
|
30
|
+
bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
- Run tests
|
34
|
+
|
35
|
+
```sh
|
36
|
+
bundle exec rspec
|
37
|
+
```
|
38
|
+
|
39
|
+
### 2. Running your unpublished Plugin in Logstash
|
40
|
+
|
41
|
+
#### 2.1 Run in a local Logstash clone
|
42
|
+
|
43
|
+
##### 2.1.1 Version 1.5 and above
|
44
|
+
|
45
|
+
- Edit Logstash `Gemfile` and add the local plugin path, for example:
|
46
|
+
```ruby
|
47
|
+
gem "logstash-output-jut", :path => "/your/local/logstash-output-jut"
|
48
|
+
```
|
49
|
+
- Install plugin
|
50
|
+
```sh
|
51
|
+
bin/plugin install --no-verify
|
52
|
+
```
|
53
|
+
- Run Logstash with your plugin
|
54
|
+
```sh
|
55
|
+
bin/logstash -e 'input { stdin {} } output { jut { url => URL }}'
|
56
|
+
```
|
57
|
+
At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
|
58
|
+
|
59
|
+
##### 2.1.1 Version 1.4 and below
|
60
|
+
|
61
|
+
- Copy the files jut.rb and HTTPBatcher.rb into /path/to/logstash-1.4.2/lib/logstash/outputs/
|
62
|
+
|
63
|
+
- Run Logstash with your plugin
|
64
|
+
```sh
|
65
|
+
bin/logstash -e 'input { stdin {} } output { jut { url => URL }}'
|
66
|
+
```
|
67
|
+
|
68
|
+
#### 2.2 Run in an installed Logstash
|
69
|
+
|
70
|
+
You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
|
71
|
+
|
72
|
+
- Build your plugin gem
|
73
|
+
```sh
|
74
|
+
gem build logstash-output-jut.gemspec
|
75
|
+
```
|
76
|
+
- Install the plugin from the Logstash home
|
77
|
+
```sh
|
78
|
+
bin/plugin install /your/local/plugin/logstash-output-jut.gem
|
79
|
+
```
|
80
|
+
- Start Logstash and proceed to test the plugin
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "logstash/devutils/rake"
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/namespace"
|
3
|
+
require "json"
|
4
|
+
require "thread"
|
5
|
+
require "work_queue"
|
6
|
+
require "net/https"
|
7
|
+
require "openssl"
|
8
|
+
require "uri"
|
9
|
+
|
10
|
+
class LogStash::Outputs::HTTPBatcher
|
11
|
+
def initialize(url, idle_flush, logger, headers, limit, nthreads, verbose)
|
12
|
+
@url = URI.parse(url)
|
13
|
+
@idle_flush = idle_flush
|
14
|
+
@logger = logger
|
15
|
+
@headers = headers
|
16
|
+
@limit = limit
|
17
|
+
@verbose = verbose
|
18
|
+
|
19
|
+
@content_type = "application/json"
|
20
|
+
@stopped = false
|
21
|
+
|
22
|
+
@queue_mutex = Mutex.new
|
23
|
+
@queue = []
|
24
|
+
|
25
|
+
@flush_time = nil
|
26
|
+
@flush_thread = create_flush_thread()
|
27
|
+
@work_queue = WorkQueue.new nthreads, nil
|
28
|
+
end # def initialize
|
29
|
+
|
30
|
+
def stop
|
31
|
+
if @verbose
|
32
|
+
puts "stopping batcher (have #{@queue.size()} queued message)"
|
33
|
+
end
|
34
|
+
|
35
|
+
@stopped = true
|
36
|
+
while @queue.size() > 0 do
|
37
|
+
enqueue_batch
|
38
|
+
end
|
39
|
+
|
40
|
+
@flush_thread.join
|
41
|
+
@work_queue.join
|
42
|
+
end
|
43
|
+
|
44
|
+
def receive(event)
|
45
|
+
size = 0
|
46
|
+
@queue_mutex.synchronize do
|
47
|
+
@queue << event
|
48
|
+
size = @queue.size
|
49
|
+
end
|
50
|
+
|
51
|
+
if size >= @limit
|
52
|
+
enqueue_batch
|
53
|
+
end
|
54
|
+
|
55
|
+
@flush_time = Time.now + @idle_flush
|
56
|
+
end # def receive
|
57
|
+
|
58
|
+
def create_flush_thread
|
59
|
+
return Thread.new do
|
60
|
+
while !@stopped do
|
61
|
+
now = Time.now
|
62
|
+
if @flush_time != nil && @flush_time <= now
|
63
|
+
enqueue_batch
|
64
|
+
@flush_time = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
sleep(@flush_time == nil ? @idle_flush : @flush_time - now)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def enqueue_batch
|
73
|
+
tosend = []
|
74
|
+
@queue_mutex.synchronize do
|
75
|
+
tosend = @queue.shift(@limit)
|
76
|
+
end
|
77
|
+
|
78
|
+
if tosend.size > 0
|
79
|
+
@work_queue.enqueue_b do
|
80
|
+
send_batch tosend
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def send_batch(tosend)
|
86
|
+
connection = Thread.current["connection"]
|
87
|
+
if connection == nil
|
88
|
+
if @verbose
|
89
|
+
puts "creating new https connection"
|
90
|
+
end
|
91
|
+
|
92
|
+
connection = Net::HTTP.new(@url.host, @url.port)
|
93
|
+
connection.use_ssl = true
|
94
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
95
|
+
Thread.current["connection"] = connection
|
96
|
+
end
|
97
|
+
|
98
|
+
beginning = Time.now
|
99
|
+
request = Net::HTTP::Post.new(@url.request_uri)
|
100
|
+
|
101
|
+
request["Content-Type"] = @content_type
|
102
|
+
if @headers
|
103
|
+
@headers.each do |k,v|
|
104
|
+
request.headers[k] = v
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if @verbose
|
109
|
+
puts "posting #{tosend.size} records"
|
110
|
+
end
|
111
|
+
|
112
|
+
request.body = tosend.to_json
|
113
|
+
response = connection.request request
|
114
|
+
|
115
|
+
status = response.code
|
116
|
+
rbody = response.read_body
|
117
|
+
|
118
|
+
if status != "200"
|
119
|
+
raise "POST failed with status #{status} (#{rbody})"
|
120
|
+
end
|
121
|
+
|
122
|
+
if @verbose
|
123
|
+
time = Time.now - beginning
|
124
|
+
puts "POST response in #{time.to_s} #{status} #{rbody}"
|
125
|
+
end
|
126
|
+
|
127
|
+
rescue Exception => e
|
128
|
+
if @verbose
|
129
|
+
@logger.warn("Unhandled exception", :request => request, :exception => e, :stacktrace => e.backtrace)
|
130
|
+
else
|
131
|
+
@logger.warn("Unhandled exception", :host => request["host"], :exception => e, :stacktrace => e.backtrace)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/outputs/base"
|
3
|
+
require "logstash/namespace"
|
4
|
+
require "logstash/outputs/HTTPBatcher"
|
5
|
+
|
6
|
+
class LogStash::Outputs::Jut < LogStash::Outputs::Base
|
7
|
+
# This output lets you batch JSON objects and POSTs them to
|
8
|
+
# generic HTTP endpoints. This supports only sending JSON
|
9
|
+
# and sends a request every X seconds.
|
10
|
+
#
|
11
|
+
# Additionally, you are given the option to customize
|
12
|
+
# the headers.
|
13
|
+
|
14
|
+
config_name "jut"
|
15
|
+
milestone 1
|
16
|
+
|
17
|
+
# URL to use
|
18
|
+
config :url, :validate => :string, :required => :true
|
19
|
+
|
20
|
+
# Minimum interval at which requests are made per thread
|
21
|
+
config :interval, :validate => :number, :default => 1
|
22
|
+
|
23
|
+
# Maximum number of events per request
|
24
|
+
config :limit, :validate => :number, :default => 200
|
25
|
+
|
26
|
+
# Number of concurrent threads making requests
|
27
|
+
config :threads, :validate => :number, :default => 5
|
28
|
+
|
29
|
+
# Headers, if necessary, in the form of a hash
|
30
|
+
config :headers, :validate => :hash
|
31
|
+
|
32
|
+
# Verbose error messages
|
33
|
+
config :verbose, :validate => :boolean, :default => false
|
34
|
+
|
35
|
+
public
|
36
|
+
def register
|
37
|
+
@batcher = LogStash::Outputs::HTTPBatcher.new(@url, @interval, @logger, @headers, @limit, @threads, @verbose)
|
38
|
+
end # def register
|
39
|
+
|
40
|
+
public
|
41
|
+
def receive(event)
|
42
|
+
return unless output?(event)
|
43
|
+
|
44
|
+
# logstash wants to create "@timestamp", but jut wants "time"
|
45
|
+
# you could of course do this with a mutate filter but lets make
|
46
|
+
# the common case easy...
|
47
|
+
if event.include?('@timestamp')
|
48
|
+
event['time'] = event.remove('@timestamp')
|
49
|
+
end
|
50
|
+
|
51
|
+
if event.include?('@version')
|
52
|
+
event.remove('@version')
|
53
|
+
end
|
54
|
+
|
55
|
+
evt = event.to_hash
|
56
|
+
@batcher.receive(evt)
|
57
|
+
|
58
|
+
rescue Exception => e
|
59
|
+
@logger.warn("Unhandled exception", :exception => e, :stacktrace => e.backtrace)
|
60
|
+
end
|
61
|
+
|
62
|
+
public
|
63
|
+
def teardown
|
64
|
+
if @verbose
|
65
|
+
puts "tearing down jut output plugin"
|
66
|
+
end
|
67
|
+
|
68
|
+
@batcher.stop
|
69
|
+
|
70
|
+
if @verbose
|
71
|
+
puts "finished"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'logstash-output-jut'
|
3
|
+
s.version = "0.3"
|
4
|
+
s.licenses = ["Apache License (2.0)"]
|
5
|
+
s.summary = "sends records to a Jut data engine"
|
6
|
+
s.description = "logstash output plugin that sends records to a Jut data engine"
|
7
|
+
s.authors = ["Jut, Inc."]
|
8
|
+
s.email = "josa@jut.io"
|
9
|
+
s.homepage = "https://github.com/jut-io/logstash-output-jut"
|
10
|
+
s.require_paths = ["lib"]
|
11
|
+
|
12
|
+
# Files
|
13
|
+
s.files = `git ls-files`.split($\)
|
14
|
+
# Tests
|
15
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
16
|
+
|
17
|
+
# Special flag to let us know this is actually a logstash plugin
|
18
|
+
# This metadata attribute blew up for me on Ubuntu 14.04 with the latest rubgems installed
|
19
|
+
s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
|
20
|
+
|
21
|
+
# Gem dependencies
|
22
|
+
s.add_runtime_dependency "logstash-core", ">= 1.4.0", "< 2.0.0"
|
23
|
+
s.add_runtime_dependency "logstash-codec-plain"
|
24
|
+
s.add_runtime_dependency "work_queue"
|
25
|
+
|
26
|
+
s.add_development_dependency "logstash-devutils"
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "logstash/outputs/jut"
|
3
|
+
require "logstash/codecs/plain"
|
4
|
+
require "logstash/event"
|
5
|
+
|
6
|
+
describe LogStash::Outputs::Jut do
|
7
|
+
let(:jut_config) {
|
8
|
+
{
|
9
|
+
'url' => 'http://localhost:9000/'
|
10
|
+
}
|
11
|
+
}
|
12
|
+
let(:sample_event) { LogStash::Event.new({'message' => 'hello', '@timestamp'=>LogStash::Timestamp.now}) }
|
13
|
+
|
14
|
+
context 'when intializing' do
|
15
|
+
it 'should register' do
|
16
|
+
output = LogStash::Outputs::Jut.new(jut_config)
|
17
|
+
expect {output.register}.to_not raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should populate jut config with default values' do
|
21
|
+
cfg = LogStash::Outputs::Jut.new(jut_config)
|
22
|
+
insist {cfg.url} == 'http://localhost:9000/'
|
23
|
+
insist {cfg.interval} == 1
|
24
|
+
insist {cfg.limit} == 200
|
25
|
+
insist {cfg.threads} == 5
|
26
|
+
insist {cfg.verbose} == false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logstash-output-jut
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.3'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jut, Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: logstash-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.0
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.4.0
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: logstash-codec-plain
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: work_queue
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: logstash-devutils
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
description: logstash output plugin that sends records to a Jut data engine
|
76
|
+
email: josa@jut.io
|
77
|
+
executables: []
|
78
|
+
extensions: []
|
79
|
+
extra_rdoc_files: []
|
80
|
+
files:
|
81
|
+
- .gitignore
|
82
|
+
- CONTRIBUTORS
|
83
|
+
- Gemfile
|
84
|
+
- LICENSE
|
85
|
+
- README.md
|
86
|
+
- Rakefile
|
87
|
+
- lib/logstash/outputs/HTTPBatcher.rb
|
88
|
+
- lib/logstash/outputs/jut.rb
|
89
|
+
- logstash-output-jut.gemspec
|
90
|
+
- spec/outputs/jut_spec.rb
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
homepage: https://github.com/jut-io/logstash-output-jut
|
93
|
+
licenses:
|
94
|
+
- Apache License (2.0)
|
95
|
+
metadata:
|
96
|
+
logstash_plugin: 'true'
|
97
|
+
logstash_group: output
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.0.14
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: sends records to a Jut data engine
|
118
|
+
test_files:
|
119
|
+
- spec/outputs/jut_spec.rb
|
120
|
+
- spec/spec_helper.rb
|