logstash-output-zeromq 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Zjg1YTY0YzUzZmUzOGZhNzkxYzY4ZDA4ZDA2MGZlNjIxNzJhNWQxMA==
5
+ data.tar.gz: !binary |-
6
+ OWJiYTY0NDMzNzBiMTU0Y2M3YTQzYzY3NzdiZmJjOTY1OWVmYjdjMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NGE4ZDhjYzA4M2Q0N2UyNDcxMmU0MjY3N2Q3OGYwYzRjYTVhYzVjMTI2NDhk
10
+ NWRhMDI3MmNiZTZmMDJjNTY5ZmE0M2U2MDYyOWYwZmFiZDg5ZTliMmY4ZmU2
11
+ YTc1NzgwY2MyNTVlZjA0YTkwZjRkNDMwMjQyM2MwZjVkMDMyYmM=
12
+ data.tar.gz: !binary |-
13
+ NmI0ZTMzMjU3M2RiYTVhMzU3MDJiMWE4M2I3ZTA5Nzg4OGQyODRjMDEzNGJk
14
+ NTM0NWIxMTBmMzViZTRiYzY3MmY4YzYwZTNjNmJkN2Q5ZTlhODFiZDI4NjNh
15
+ YmE0YmM3NmY4N2I0YTE0YTRhNmIxMzIzNjRmNjY3OTRiYmE3ZmI=
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ .bundle
4
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+ gem 'rake'
3
+ gem 'gem_publisher'
4
+ gem 'archive-tar-minitar'
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012-2014 Elasticsearch <http://www.elasticsearch.org>
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/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ @files=[]
2
+
3
+ task :default do
4
+ system("rake -T")
5
+ end
6
+
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require "logstash/namespace"
4
+
5
+ # Write events to a 0MQ PUB socket.
6
+ #
7
+ # You need to have the 0mq 2.1.x library installed to be able to use
8
+ # this output plugin.
9
+ #
10
+ # The default settings will create a publisher connecting to a subscriber
11
+ # bound to tcp://127.0.0.1:2120
12
+ #
13
+ class LogStash::Outputs::ZeroMQ < LogStash::Outputs::Base
14
+
15
+ config_name "zeromq"
16
+ milestone 2
17
+
18
+ default :codec, "json"
19
+
20
+ # 0mq socket address to connect or bind.
21
+ # Please note that `inproc://` will not work with logstashi.
22
+ # For each we use a context per thread.
23
+ # By default, inputs bind/listen and outputs connect.
24
+ config :address, :validate => :array, :default => ["tcp://127.0.0.1:2120"]
25
+
26
+ # The default logstash topologies work as follows:
27
+ #
28
+ # * pushpull - inputs are pull, outputs are push
29
+ # * pubsub - inputs are subscribers, outputs are publishers
30
+ # * pair - inputs are clients, inputs are servers
31
+ #
32
+ # If the predefined topology flows don't work for you,
33
+ # you can change the 'mode' setting
34
+ # TODO (lusis) add req/rep MAYBE
35
+ # TODO (lusis) add router/dealer
36
+ config :topology, :validate => ["pushpull", "pubsub", "pair"], :required => true
37
+
38
+ # This is used for the 'pubsub' topology only.
39
+ # On inputs, this allows you to filter messages by topic.
40
+ # On outputs, this allows you to tag a message for routing.
41
+ # NOTE: ZeroMQ does subscriber-side filtering
42
+ # NOTE: Topic is evaluated with `event.sprintf` so macros are valid here.
43
+ config :topic, :validate => :string, :default => ""
44
+
45
+ # Server mode binds/listens. Client mode connects.
46
+ config :mode, :validate => ["server", "client"], :default => "client"
47
+
48
+ # This exposes zmq_setsockopt for advanced tuning.
49
+ # See http://api.zeromq.org/2-1:zmq-setsockopt for details.
50
+ #
51
+ # This is where you would set values like:
52
+ #
53
+ # * ZMQ::HWM - high water mark
54
+ # * ZMQ::IDENTITY - named queues
55
+ # * ZMQ::SWAP_SIZE - space for disk overflow
56
+ #
57
+ # Example: sockopt => ["ZMQ::HWM", 50, "ZMQ::IDENTITY", "my_named_queue"]
58
+ config :sockopt, :validate => :hash
59
+
60
+ public
61
+ def register
62
+ require "ffi-rzmq"
63
+ require "logstash/util/zeromq"
64
+ self.class.send(:include, LogStash::Util::ZeroMQ)
65
+
66
+ if @mode == "server"
67
+ workers_not_supported("With 'mode => server', only one zeromq socket may bind to a port and may not be shared among threads. Going to single-worker mode for this plugin!")
68
+ end
69
+
70
+ # Translate topology shorthand to socket types
71
+ case @topology
72
+ when "pair"
73
+ zmq_const = ZMQ::PAIR
74
+ when "pushpull"
75
+ zmq_const = ZMQ::PUSH
76
+ when "pubsub"
77
+ zmq_const = ZMQ::PUB
78
+ end # case socket_type
79
+
80
+ @zsocket = context.socket(zmq_const)
81
+
82
+ error_check(@zsocket.setsockopt(ZMQ::LINGER, 1),
83
+ "while setting ZMQ::LINGER == 1)")
84
+
85
+ if @sockopt
86
+ setopts(@zsocket, @sockopt)
87
+ end
88
+
89
+ @address.each do |addr|
90
+ setup(@zsocket, addr)
91
+ end
92
+
93
+ @codec.on_event(&method(:publish))
94
+ end # def register
95
+
96
+ public
97
+ def teardown
98
+ error_check(@zsocket.close, "while closing the socket")
99
+ end # def teardown
100
+
101
+ private
102
+ def server?
103
+ @mode == "server"
104
+ end # def server?
105
+
106
+ public
107
+ def receive(event)
108
+ return unless output?(event)
109
+
110
+ @codec.encode(event)
111
+ end # def receive
112
+
113
+ def publish(payload)
114
+ @logger.debug? && @logger.debug("0mq: sending", :event => payload)
115
+ if @topology == "pubsub"
116
+ # TODO(sissel): Need to figure out how to fit this into the codecs system.
117
+ #@logger.debug("0mq output: setting topic to: #{event.sprintf(@topic)}")
118
+ #error_check(@zsocket.send_string(event.sprintf(@topic), ZMQ::SNDMORE),
119
+ #"in topic send_string")
120
+ end
121
+ error_check(@zsocket.send_string(payload), "in send_string")
122
+ rescue => e
123
+ @logger.warn("0mq output exception", :address => @address, :exception => e)
124
+ end
125
+ end # class LogStash::Outputs::ZeroMQ
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ require 'ffi-rzmq'
3
+ require "logstash/namespace"
4
+
5
+ module LogStash::Util::ZeroMQ
6
+ CONTEXT = ZMQ::Context.new
7
+ # LOGSTASH-400
8
+ # see https://github.com/chuckremes/ffi-rzmq/blob/master/lib/ffi-rzmq/socket.rb#L93-117
9
+ STRING_OPTS = %w{IDENTITY SUBSCRIBE UNSUBSCRIBE}
10
+
11
+ def context
12
+ CONTEXT
13
+ end
14
+
15
+ def setup(socket, address)
16
+ if server?
17
+ error_check(socket.bind(address), "binding to #{address}")
18
+ else
19
+ error_check(socket.connect(address), "connecting to #{address}")
20
+ end
21
+ @logger.info("0mq: #{server? ? 'connected' : 'bound'}", :address => address)
22
+ end
23
+
24
+ def error_check(rc, doing)
25
+ unless ZMQ::Util.resultcode_ok?(rc)
26
+ @logger.error("ZeroMQ error while #{doing}", { :error_code => rc })
27
+ raise "ZeroMQ Error while #{doing}"
28
+ end
29
+ end # def error_check
30
+
31
+ def setopts(socket, options)
32
+ options.each do |opt,value|
33
+ sockopt = opt.split('::')[1]
34
+ option = ZMQ.const_defined?(sockopt) ? ZMQ.const_get(sockopt) : ZMQ.const_missing(sockopt)
35
+ unless STRING_OPTS.include?(sockopt)
36
+ begin
37
+ Float(value)
38
+ value = value.to_i
39
+ rescue ArgumentError
40
+ raise "#{sockopt} requires a numeric value. #{value} is not numeric"
41
+ end
42
+ end # end unless
43
+ error_check(socket.setsockopt(option, value),
44
+ "while setting #{opt} == #{value}")
45
+ end # end each
46
+ end # end setopts
47
+ end # module LogStash::Util::ZeroMQ
@@ -0,0 +1,29 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-output-zeromq'
4
+ s.version = '0.1.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "Write events to a 0MQ PUB socket."
7
+ s.description = "Write events to a 0MQ PUB socket."
8
+ s.authors = ["Elasticsearch"]
9
+ s.email = 'richard.pijnenburg@elasticsearch.com'
10
+ s.homepage = "http://logstash.net/"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = `git ls-files`.split($\)+::Dir.glob('vendor/*')
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "group" => "output" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ s.add_runtime_dependency 'logstash-codec-json'
26
+ s.add_runtime_dependency 'ffi-rzmq', ['1.0.0']
27
+
28
+ end
29
+
@@ -0,0 +1,9 @@
1
+ require "gem_publisher"
2
+
3
+ desc "Publish gem to RubyGems.org"
4
+ task :publish_gem do |t|
5
+ gem_file = Dir.glob(File.expand_path('../*.gemspec',File.dirname(__FILE__))).first
6
+ gem = GemPublisher.publish_if_updated(gem_file, :rubygems)
7
+ puts "Published #{gem}" if gem
8
+ end
9
+
@@ -0,0 +1,169 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "digest/sha1"
4
+
5
+ def vendor(*args)
6
+ return File.join("vendor", *args)
7
+ end
8
+
9
+ directory "vendor/" => ["vendor"] do |task, args|
10
+ mkdir task.name
11
+ end
12
+
13
+ def fetch(url, sha1, output)
14
+
15
+ puts "Downloading #{url}"
16
+ actual_sha1 = download(url, output)
17
+
18
+ if actual_sha1 != sha1
19
+ fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
20
+ end
21
+ end # def fetch
22
+
23
+ def file_fetch(url, sha1)
24
+ filename = File.basename( URI(url).path )
25
+ output = "vendor/#{filename}"
26
+ task output => [ "vendor/" ] do
27
+ begin
28
+ actual_sha1 = file_sha1(output)
29
+ if actual_sha1 != sha1
30
+ fetch(url, sha1, output)
31
+ end
32
+ rescue Errno::ENOENT
33
+ fetch(url, sha1, output)
34
+ end
35
+ end.invoke
36
+
37
+ return output
38
+ end
39
+
40
+ def file_sha1(path)
41
+ digest = Digest::SHA1.new
42
+ fd = File.new(path, "r")
43
+ while true
44
+ begin
45
+ digest << fd.sysread(16384)
46
+ rescue EOFError
47
+ break
48
+ end
49
+ end
50
+ return digest.hexdigest
51
+ ensure
52
+ fd.close if fd
53
+ end
54
+
55
+ def download(url, output)
56
+ uri = URI(url)
57
+ digest = Digest::SHA1.new
58
+ tmp = "#{output}.tmp"
59
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
60
+ request = Net::HTTP::Get.new(uri.path)
61
+ http.request(request) do |response|
62
+ fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
63
+ size = (response["content-length"].to_i || -1).to_f
64
+ count = 0
65
+ File.open(tmp, "w") do |fd|
66
+ response.read_body do |chunk|
67
+ fd.write(chunk)
68
+ digest << chunk
69
+ if size > 0 && $stdout.tty?
70
+ count += chunk.bytesize
71
+ $stdout.write(sprintf("\r%0.2f%%", count/size * 100))
72
+ end
73
+ end
74
+ end
75
+ $stdout.write("\r \r") if $stdout.tty?
76
+ end
77
+ end
78
+
79
+ File.rename(tmp, output)
80
+
81
+ return digest.hexdigest
82
+ rescue SocketError => e
83
+ puts "Failure while downloading #{url}: #{e}"
84
+ raise
85
+ ensure
86
+ File.unlink(tmp) if File.exist?(tmp)
87
+ end # def download
88
+
89
+ def untar(tarball, &block)
90
+ require "archive/tar/minitar"
91
+ tgz = Zlib::GzipReader.new(File.open(tarball))
92
+ # Pull out typesdb
93
+ tar = Archive::Tar::Minitar::Input.open(tgz)
94
+ tar.each do |entry|
95
+ path = block.call(entry)
96
+ next if path.nil?
97
+ parent = File.dirname(path)
98
+
99
+ mkdir_p parent unless File.directory?(parent)
100
+
101
+ # Skip this file if the output file is the same size
102
+ if entry.directory?
103
+ mkdir path unless File.directory?(path)
104
+ else
105
+ entry_mode = entry.instance_eval { @mode } & 0777
106
+ if File.exists?(path)
107
+ stat = File.stat(path)
108
+ # TODO(sissel): Submit a patch to archive-tar-minitar upstream to
109
+ # expose headers in the entry.
110
+ entry_size = entry.instance_eval { @size }
111
+ # If file sizes are same, skip writing.
112
+ next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
113
+ end
114
+ puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
115
+ File.open(path, "w") do |fd|
116
+ # eof? check lets us skip empty files. Necessary because the API provided by
117
+ # Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
118
+ # IO object. Something about empty files in this EntryStream causes
119
+ # IO.copy_stream to throw "can't convert nil into String" on JRuby
120
+ # TODO(sissel): File a bug about this.
121
+ while !entry.eof?
122
+ chunk = entry.read(16384)
123
+ fd.write(chunk)
124
+ end
125
+ #IO.copy_stream(entry, fd)
126
+ end
127
+ File.chmod(entry_mode, path)
128
+ end
129
+ end
130
+ tar.close
131
+ File.unlink(tarball) if File.file?(tarball)
132
+ end # def untar
133
+
134
+ def ungz(file)
135
+
136
+ outpath = file.gsub('.gz', '')
137
+ tgz = Zlib::GzipReader.new(File.open(file))
138
+ begin
139
+ File.open(outpath, "w") do |out|
140
+ IO::copy_stream(tgz, out)
141
+ end
142
+ File.unlink(file)
143
+ rescue
144
+ File.unlink(outpath) if File.file?(outpath)
145
+ raise
146
+ end
147
+ tgz.close
148
+ end
149
+
150
+ desc "Process any vendor files required for this plugin"
151
+ task "vendor" do |task, args|
152
+
153
+ @files.each do |file|
154
+ download = file_fetch(file['url'], file['sha1'])
155
+ if download =~ /.tar.gz/
156
+ prefix = download.gsub('.tar.gz', '').gsub('vendor/', '')
157
+ untar(download) do |entry|
158
+ if !file['files'].nil?
159
+ next unless file['files'].include?(entry.full_name.gsub(prefix, ''))
160
+ out = entry.full_name.split("/").last
161
+ end
162
+ File.join('vendor', out)
163
+ end
164
+ elsif download =~ /.gz/
165
+ ungz(download)
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1 @@
1
+ require 'spec_helper'
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-output-zeromq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Elasticsearch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash
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-json
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: ffi-rzmq
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '='
59
+ - !ruby/object:Gem::Version
60
+ version: 1.0.0
61
+ description: Write events to a 0MQ PUB socket.
62
+ email: richard.pijnenburg@elasticsearch.com
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - .gitignore
68
+ - Gemfile
69
+ - LICENSE
70
+ - Rakefile
71
+ - lib/logstash/outputs/zeromq.rb
72
+ - lib/logstash/util/zeromq.rb
73
+ - logstash-output-zeromq.gemspec
74
+ - rakelib/publish.rake
75
+ - rakelib/vendor.rake
76
+ - spec/outputs/zeromq_spec.rb
77
+ homepage: http://logstash.net/
78
+ licenses:
79
+ - Apache License (2.0)
80
+ metadata:
81
+ logstash_plugin: 'true'
82
+ group: output
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 2.4.1
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Write events to a 0MQ PUB socket.
103
+ test_files:
104
+ - spec/outputs/zeromq_spec.rb