log-courier 1.0.21.ga82ca4c
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 +7 -0
- data/lib/log-courier/client.rb +352 -0
- data/lib/log-courier/client_tls.rb +217 -0
- data/lib/log-courier/event_queue.rb +194 -0
- data/lib/log-courier/server.rb +185 -0
- data/lib/log-courier/server_tcp.rb +275 -0
- data/lib/log-courier/server_zmq.rb +219 -0
- metadata +78 -0
@@ -0,0 +1,219 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright 2014 Jason Woods.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'ffi-rzmq-core'
|
19
|
+
require 'ffi-rzmq-core/version'
|
20
|
+
require 'ffi-rzmq'
|
21
|
+
require 'ffi-rzmq/version'
|
22
|
+
rescue LoadError => e
|
23
|
+
raise "[LogCourierServer] Could not initialise: #{e}"
|
24
|
+
end
|
25
|
+
|
26
|
+
module LogCourier
|
27
|
+
# ZMQ transport implementation for the server
|
28
|
+
class ServerZmq
|
29
|
+
class ZMQError < StandardError; end
|
30
|
+
|
31
|
+
attr_reader :port
|
32
|
+
|
33
|
+
def initialize(options = {})
|
34
|
+
@options = {
|
35
|
+
logger: nil,
|
36
|
+
transport: 'zmq',
|
37
|
+
port: 0,
|
38
|
+
address: '0.0.0.0',
|
39
|
+
curve_secret_key: nil,
|
40
|
+
max_packet_size: 10_485_760,
|
41
|
+
}.merge!(options)
|
42
|
+
|
43
|
+
@logger = @options[:logger]
|
44
|
+
|
45
|
+
libversion = LibZMQ.version
|
46
|
+
libversion = "#{libversion[:major]}.#{libversion[:minor]}.#{libversion[:patch]}"
|
47
|
+
|
48
|
+
if @options[:transport] == 'zmq'
|
49
|
+
raise "[LogCourierServer] Transport 'zmq' requires libzmq version >= 4 (the current version is #{libversion})" unless LibZMQ.version4?
|
50
|
+
|
51
|
+
raise '[LogCourierServer] \'curve_secret_key\' is required' if @options[:curve_secret_key].nil?
|
52
|
+
|
53
|
+
raise '[LogCourierServer] \'curve_secret_key\' must be a valid 40 character Z85 encoded string' if @options[:curve_secret_key].length != 40 || !z85validate(@options[:curve_secret_key])
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
@context = ZMQ::Context.new
|
58
|
+
# Router so we can send multiple responses
|
59
|
+
@socket = @context.socket(ZMQ::ROUTER)
|
60
|
+
|
61
|
+
if @options[:transport] == 'zmq'
|
62
|
+
rc = @socket.setsockopt(ZMQ::CURVE_SERVER, 1)
|
63
|
+
raise ZMQError, 'setsockopt CURVE_SERVER failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
|
64
|
+
|
65
|
+
rc = @socket.setsockopt(ZMQ::CURVE_SECRETKEY, @options[:curve_secret_key])
|
66
|
+
raise ZMQError, 'setsockopt CURVE_SECRETKEY failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
|
67
|
+
end
|
68
|
+
|
69
|
+
bind = 'tcp://' + @options[:address] + (@options[:port] == 0 ? ':*' : ':' + @options[:port].to_s)
|
70
|
+
rc = @socket.bind(bind)
|
71
|
+
raise ZMQError, 'failed to bind at ' + bind + ': ' + rZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc)
|
72
|
+
|
73
|
+
# Lookup port number that was allocated in case it was set to 0
|
74
|
+
endpoint = ''
|
75
|
+
rc = @socket.getsockopt(ZMQ::LAST_ENDPOINT, endpoint)
|
76
|
+
raise ZMQError, 'getsockopt LAST_ENDPOINT failure: ' + ZMQ::Util.error_string unless ZMQ::Util.resultcode_ok?(rc) && %r{\Atcp://(?:.*):(?<endpoint_port>\d+)\0\z} =~ endpoint
|
77
|
+
@port = endpoint_port.to_i
|
78
|
+
|
79
|
+
@poller = ZMQ::Poller.new
|
80
|
+
|
81
|
+
if @options[:port] == 0
|
82
|
+
@logger.warn '[LogCourierServer] Transport ' + @options[:transport] + ' is listening on ephemeral port ' + @port.to_s
|
83
|
+
end
|
84
|
+
rescue => e
|
85
|
+
raise "[LogCourierServer] Failed to initialise: #{e}"
|
86
|
+
end
|
87
|
+
|
88
|
+
@logger.info "[LogCourierServer] libzmq version #{libversion}"
|
89
|
+
@logger.info "[LogCourierServer] ffi-rzmq-core version #{LibZMQ::VERSION}"
|
90
|
+
@logger.info "[LogCourierServer] ffi-rzmq version #{ZMQ.version}"
|
91
|
+
|
92
|
+
# TODO: Implement workers option by receiving on a ROUTER and proxying to a DEALER, with workers connecting to the DEALER
|
93
|
+
|
94
|
+
reset_timeout
|
95
|
+
end
|
96
|
+
|
97
|
+
def z85validate(z85)
|
98
|
+
# ffi-rzmq does not implement decode - but we want to validate during startup
|
99
|
+
decoded = FFI::MemoryPointer.from_string(' ' * (8 * z85.length / 10))
|
100
|
+
ret = LibZMQ.zmq_z85_decode decoded, z85
|
101
|
+
return false if ret.nil?
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def run(&block)
|
107
|
+
loop do
|
108
|
+
begin
|
109
|
+
begin
|
110
|
+
# Try to receive a message
|
111
|
+
data = []
|
112
|
+
rc = @socket.recv_strings(data, ZMQ::DONTWAIT)
|
113
|
+
unless ZMQ::Util.resultcode_ok?(rc)
|
114
|
+
raise ZMQError, 'recv_string error: ' + ZMQ::Util.error_string if ZMQ::Util.errno != ZMQ::EAGAIN
|
115
|
+
|
116
|
+
# Wait for a message to arrive, handling timeouts
|
117
|
+
@poller.deregister @socket, ZMQ::POLLIN | ZMQ::POLLOUT
|
118
|
+
@poller.register @socket, ZMQ::POLLIN
|
119
|
+
while @poller.poll(1_000) == 0
|
120
|
+
raise TimeoutError if Time.now.to_i >= @timeout
|
121
|
+
end
|
122
|
+
next
|
123
|
+
end
|
124
|
+
rescue ZMQError => e
|
125
|
+
@logger.warn "[LogCourierServer] ZMQ recv_string failed: #{e}" unless @logger.nil?
|
126
|
+
next
|
127
|
+
end
|
128
|
+
|
129
|
+
# Pre-send the routing information and remove it from data
|
130
|
+
data.delete_if do |msg|
|
131
|
+
reset_timeout
|
132
|
+
send_with_poll msg, true
|
133
|
+
if ZMQ::Util.errno != ZMQ::EAGAIN
|
134
|
+
@logger.warn "[LogCourierServer] Message send failed: #{ZMQ::Util.error_string}" unless @logger.nil?
|
135
|
+
raise TimeoutError
|
136
|
+
end
|
137
|
+
break if msg == ""
|
138
|
+
true
|
139
|
+
end
|
140
|
+
data.shift
|
141
|
+
|
142
|
+
if data.length != 1
|
143
|
+
@logger.warn '[LogCourierServer] Invalid message: multipart unexpected' unless @logger.nil?
|
144
|
+
else
|
145
|
+
recv(data.first, &block)
|
146
|
+
end
|
147
|
+
rescue TimeoutError
|
148
|
+
# We'll let ZeroMQ manage reconnections and new connections
|
149
|
+
# There is no point in us doing any form of reconnect ourselves
|
150
|
+
# We will keep this timeout in however, for shutdown checks
|
151
|
+
reset_timeout
|
152
|
+
next
|
153
|
+
end
|
154
|
+
end
|
155
|
+
rescue ShutdownSignal
|
156
|
+
# Shutting down
|
157
|
+
@logger.warn('[LogCourierServer] Server shutting down') unless @logger.nil?
|
158
|
+
rescue => e
|
159
|
+
# Some other unknown problem
|
160
|
+
@logger.warn("[LogCourierServer] Unknown error: #{e}") unless @logger.nil?
|
161
|
+
@logger.warn("[LogCourierServer] #{e.backtrace}: #{e.message} (#{e.class})") unless @logger.nil?
|
162
|
+
ensure
|
163
|
+
@socket.close
|
164
|
+
@context.terminate
|
165
|
+
end
|
166
|
+
|
167
|
+
def recv(data)
|
168
|
+
if data.length < 8
|
169
|
+
@logger.warn '[LogCourierServer] Invalid message: not enough data' unless @logger.nil?
|
170
|
+
return
|
171
|
+
end
|
172
|
+
|
173
|
+
# Unpack the header
|
174
|
+
signature, length = data.unpack('A4N')
|
175
|
+
|
176
|
+
# Verify length
|
177
|
+
if data.length - 8 != length
|
178
|
+
@logger.warn "[LogCourierServer] Invalid message: data has invalid length (#{data.length - 8} != #{length})" unless @logger.nil?
|
179
|
+
return
|
180
|
+
elsif length > @options[:max_packet_size]
|
181
|
+
@logger.warn "[LogCourierServer] Invalid message: packet too large (#{length} > #{@options[:max_packet_size]})" unless @logger.nil?
|
182
|
+
return
|
183
|
+
end
|
184
|
+
|
185
|
+
# Yield the parts
|
186
|
+
yield signature, data[8, length], self
|
187
|
+
end
|
188
|
+
|
189
|
+
def send(signature, message)
|
190
|
+
reset_timeout
|
191
|
+
data = signature + [message.length].pack('N') + message
|
192
|
+
send_with_poll data
|
193
|
+
end
|
194
|
+
|
195
|
+
def send_with_poll(data, more = false)
|
196
|
+
loop do
|
197
|
+
# Try to send a message but never block
|
198
|
+
rc = @socket.send_string(data, (more ? ZMQ::SNDMORE : 0) | ZMQ::DONTWAIT)
|
199
|
+
break if ZMQ::Util.resultcode_ok?(rc)
|
200
|
+
if ZMQ::Util.errno != ZMQ::EAGAIN
|
201
|
+
@logger.warn "[LogCourierServer] Message send failed: #{ZMQ::Util.error_string}" unless @logger.nil?
|
202
|
+
raise TimeoutError
|
203
|
+
end
|
204
|
+
|
205
|
+
# Wait for send to become available, handling timeouts
|
206
|
+
@poller.deregister @socket, ZMQ::POLLIN | ZMQ::POLLOUT
|
207
|
+
@poller.register @socket, ZMQ::POLLOUT
|
208
|
+
while @poller.poll(1_000) == 0
|
209
|
+
raise TimeoutError if Time.now.to_i >= @timeout
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def reset_timeout()
|
215
|
+
# TODO: Make configurable?
|
216
|
+
@timeout = Time.now.to_i + 1_800
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: log-courier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.21.ga82ca4c
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jason Woods
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-10-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi-rzmq
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
description: Log Courier library
|
42
|
+
email:
|
43
|
+
- devel@jasonwoods.me.uk
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/log-courier/client.rb
|
49
|
+
- lib/log-courier/client_tls.rb
|
50
|
+
- lib/log-courier/event_queue.rb
|
51
|
+
- lib/log-courier/server.rb
|
52
|
+
- lib/log-courier/server_tcp.rb
|
53
|
+
- lib/log-courier/server_zmq.rb
|
54
|
+
homepage: https://github.com/driskell/log-courier
|
55
|
+
licenses:
|
56
|
+
- Apache
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '>'
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 1.3.1
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project: nowarning
|
74
|
+
rubygems_version: 2.4.2
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: Receive events from Log Courier and transmit between LogStash instances
|
78
|
+
test_files: []
|