jls-lumberjack 0.0.23 → 0.0.24

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ecf8e491d03fcc59fb32625cd5ad69018071f89
4
- data.tar.gz: 3fc5d05089e0b192d736fb046854635757bf10e6
3
+ metadata.gz: d58912ce6afda9045d016510618a04363349628d
4
+ data.tar.gz: faeaba187853930185d78673f4b69c16c5b107bb
5
5
  SHA512:
6
- metadata.gz: 4d6cc57fc88054f98a72bf6ccb51bdd2d91b01d6e1ba4232ac7bda25d3cf21acdb07e3940b6c0938ac6f2807f8701404819b30cbe3cfaf9edcd6d7cf919fffe6
7
- data.tar.gz: 79946f5b1c4237e14e13a44a9d257635722469f57832a8ced321e45606236279c2b380d57c18ed8d9a18c13fa5306fb044c150a26ff46c74da5dc10d11d1b33b
6
+ metadata.gz: e40270de1f12efc47375be7a5a9cd9c83fae6ce2b615fe50885037746ecf074e1a26bf2dd4b2bf8a2ebdd1816c95f6d36b56e84de32e3fc5f73a225be7bc6aa7
7
+ data.tar.gz: 6224d5a7b5475195b0afd5168eea3c37a8c704cef432cf90b01cb8fcd520ea3293f1503e89b6bd4e0eaf2e84f24afeab78b7681ab9be4999e2720b2987291dd8
@@ -0,0 +1,3 @@
1
+ module Lumberjack
2
+ SEQUENCE_MAX = (2**32-1).freeze
3
+ end
@@ -1,20 +1,17 @@
1
1
  # encoding: utf-8
2
+ require "lumberjack"
2
3
  require "socket"
3
4
  require "thread"
4
5
  require "openssl"
5
6
  require "zlib"
6
7
 
7
8
  module Lumberjack
8
-
9
- SEQUENCE_MAX = (2**32-1).freeze
10
-
11
9
  class Client
12
10
  def initialize(opts={})
13
11
  @opts = {
14
12
  :port => 0,
15
13
  :addresses => [],
16
14
  :ssl_certificate => nil,
17
- :window_size => 5000
18
15
  }.merge(opts)
19
16
 
20
17
  @opts[:addresses] = [@opts[:addresses]] if @opts[:addresses].class == String
@@ -40,8 +37,8 @@ module Lumberjack
40
37
  end
41
38
 
42
39
  public
43
- def write(hash)
44
- @socket.write_hash(hash)
40
+ def write(elements)
41
+ @socket.write_sync(elements)
45
42
  end
46
43
 
47
44
  public
@@ -51,7 +48,6 @@ module Lumberjack
51
48
  end
52
49
 
53
50
  class Socket
54
-
55
51
  # Create a new Lumberjack Socket.
56
52
  #
57
53
  # - options is a hash. Valid options are:
@@ -60,7 +56,6 @@ module Lumberjack
60
56
  # * :address - the host/address to bind to
61
57
  # * :ssl_certificate - the path to the ssl cert to use
62
58
  attr_reader :sequence
63
- attr_reader :window_size
64
59
  attr_reader :host
65
60
  def initialize(opts={})
66
61
  @sequence = 0
@@ -69,10 +64,8 @@ module Lumberjack
69
64
  :port => 0,
70
65
  :address => "127.0.0.1",
71
66
  :ssl_certificate => nil,
72
- :window_size => 5000
73
67
  }.merge(opts)
74
68
  @host = @opts[:address]
75
- @window_size = @opts[:window_size]
76
69
 
77
70
  connection_start(opts)
78
71
  end
@@ -92,7 +85,6 @@ module Lumberjack
92
85
 
93
86
  @socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
94
87
  @socket.connect
95
- @socket.syswrite(["1", "W", @window_size].pack("AAN"))
96
88
  end
97
89
 
98
90
  private
@@ -102,9 +94,12 @@ module Lumberjack
102
94
  end
103
95
 
104
96
  private
105
- def write(msg)
106
- compress = Zlib::Deflate.deflate(msg)
107
- payload = ["1","C",compress.length,compress].pack("AANA#{compress.length}")
97
+ def send_window_size(size)
98
+ @socket.syswrite(["1", "W", size].pack("AAN"))
99
+ end
100
+
101
+ private
102
+ def send_payload(payload)
108
103
  # SSLSocket has a limit of 16k per message
109
104
  # execute multiple writes if needed
110
105
  bytes_written = 0
@@ -114,18 +109,28 @@ module Lumberjack
114
109
  end
115
110
 
116
111
  public
117
- def write_hash(hash)
118
- frame = Encoder.to_compressed_frame(hash, inc)
119
- ack if unacked_sequence_size >= @window_size
120
- write frame
112
+ def write_sync(elements)
113
+ elements = [elements] if elements.is_a?(Hash)
114
+ send_window_size(elements.size)
115
+
116
+ payload = elements.map { |element| Encoder.to_frame(element, inc) }.join
117
+ compress = compress_payload(payload)
118
+ send_payload(compress)
119
+
120
+ ack(elements.size)
121
+ end
122
+
123
+ private
124
+ def compress_payload(payload)
125
+ compress = Zlib::Deflate.deflate(payload)
126
+ ["1", "C", compress.bytesize, compress].pack("AANA*")
121
127
  end
122
128
 
123
129
  private
124
- def ack
130
+ def ack(size)
125
131
  _, type = read_version_and_type
126
132
  raise "Whoa we shouldn't get this frame: #{type}" if type != "A"
127
133
  @last_ack = read_last_ack
128
- ack if unacked_sequence_size >= @window_size
129
134
  end
130
135
 
131
136
  private
@@ -139,6 +144,7 @@ module Lumberjack
139
144
  type = @socket.read(1)
140
145
  [version, type]
141
146
  end
147
+
142
148
  private
143
149
  def read_last_ack
144
150
  @socket.read(4).unpack("N").first
@@ -146,11 +152,6 @@ module Lumberjack
146
152
  end
147
153
 
148
154
  module Encoder
149
- def self.to_compressed_frame(hash, sequence)
150
- compress = Zlib::Deflate.deflate(to_frame(hash, sequence))
151
- ["1", "C", compress.bytesize, compress].pack("AANA#{compress.length}")
152
- end
153
-
154
155
  def self.to_frame(hash, sequence)
155
156
  frame = ["1", "D", sequence]
156
157
  pack = "AAN"
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ require "lumberjack"
2
3
  require "socket"
3
4
  require "thread"
4
5
  require "openssl"
@@ -207,11 +208,12 @@ module Lumberjack
207
208
  end # class Parser
208
209
 
209
210
  class Connection
211
+ READ_SIZE = 16384
212
+
210
213
  def initialize(fd)
211
214
  super()
212
215
  @parser = Parser.new
213
216
  @fd = fd
214
- @last_ack = 0
215
217
 
216
218
  # a safe default until we are told by the client what window size to use
217
219
  @window_size = 1
@@ -219,19 +221,8 @@ module Lumberjack
219
221
 
220
222
  def run(&block)
221
223
  while true
222
- # TODO(sissel): Ack on idle.
223
- # X: - if any unacked, IO.select
224
- # X: - on timeout, ack all.
225
- # X: Doing so will prevent slow streams from retransmitting
226
- # X: too many events after errors.
227
- @parser.feed(@fd.sysread(16384)) do |event, *args|
228
- case event
229
- when :window_size; window_size(*args, &block)
230
- when :data; data(*args, &block)
231
- end
232
- #send(event, *args)
233
- end # feed
234
- end # while true
224
+ read_socket(&block)
225
+ end
235
226
  rescue EOFError, OpenSSL::SSL::SSLError, IOError, Errno::ECONNRESET
236
227
  # EOF or other read errors, only action is to shutdown which we'll do in
237
228
  # 'ensure'
@@ -239,6 +230,24 @@ module Lumberjack
239
230
  close rescue 'Already closed stream'
240
231
  end # def run
241
232
 
233
+ def read_socket(&block)
234
+ # TODO(sissel): Ack on idle.
235
+ # X: - if any unacked, IO.select
236
+ # X: - on timeout, ack all.
237
+ # X: Doing so will prevent slow streams from retransmitting
238
+ # X: too many events after errors.
239
+ @parser.feed(@fd.sysread(READ_SIZE)) do |event, *args|
240
+ case event
241
+ when :window_size
242
+ # We receive a new payload
243
+ window_size(*args)
244
+ reset_next_ack
245
+ when :data
246
+ data(*args, &block)
247
+ end
248
+ end
249
+ end
250
+
242
251
  def close
243
252
  @fd.close
244
253
  end
@@ -247,13 +256,24 @@ module Lumberjack
247
256
  @window_size = size
248
257
  end
249
258
 
259
+ def reset_next_ack
260
+ @next_ack = nil
261
+ end
262
+
250
263
  def data(sequence, map, &block)
251
264
  block.call(map) if block_given?
252
- if (sequence - @last_ack) >= @window_size
253
- @fd.syswrite(["1A", sequence].pack("A*N"))
254
- @last_ack = sequence
255
- end
265
+ ack_if_needed(sequence)
266
+ end
267
+
268
+ def compute_next_ack(sequence)
269
+ (sequence + @window_size - 1) % SEQUENCE_MAX
256
270
  end
257
- end # class Connection
258
271
 
272
+ def ack_if_needed(sequence)
273
+ # The first encoded event will contain the sequence number
274
+ # this is needed to know when we should ack.
275
+ @next_ack = compute_next_ack(sequence) if @next_ack.nil?
276
+ @fd.syswrite(["1A", sequence].pack("A*N")) if sequence == @next_ack
277
+ end
278
+ end # class Connection
259
279
  end # module Lumberjack
@@ -7,12 +7,15 @@ require "fileutils"
7
7
  require "thread"
8
8
  require "spec_helper"
9
9
 
10
+ Thread.abort_on_exception = true
11
+
10
12
  describe "A client" do
11
13
  let(:certificate) { Flores::PKI.generate }
12
14
  let(:certificate_file_crt) { "certificate.crt" }
13
15
  let(:certificate_file_key) { "certificate.key" }
14
16
  let(:port) { Flores::Random.integer(1024..65335) }
15
17
  let(:host) { "127.0.0.1" }
18
+ let(:queue) { [] }
16
19
 
17
20
  before do
18
21
  expect(File).to receive(:read).at_least(1).with(certificate_file_crt) { certificate.first.to_s }
@@ -23,8 +26,8 @@ describe "A client" do
23
26
  :ssl_certificate => certificate_file_crt,
24
27
  :ssl_key => certificate_file_key)
25
28
 
26
- Thread.new do
27
- server.run { |data| }
29
+ @server = Thread.new do
30
+ server.run { |data| queue << data }
28
31
  end
29
32
  end
30
33
 
@@ -58,4 +61,73 @@ describe "A client" do
58
61
  }.to raise_error(OpenSSL::SSL::SSLError, /certificate verify failed/)
59
62
  end
60
63
  end
64
+
65
+ shared_examples "send payload" do
66
+ it "supports single element" do
67
+ (1..random_number_of_events).each do |n|
68
+ expect(client.write(payload)).to eq(sequence_start + n)
69
+ end
70
+ sleep(0.5) # give time to the server to read the events
71
+ expect(queue.size).to eq(random_number_of_events)
72
+ end
73
+
74
+ it "support sending multiple elements in one payload" do
75
+ expect(client.write(batch_payload)).to eq(sequence_start + batch_size)
76
+ sleep(0.5)
77
+
78
+ expect(queue.size).to eq(batch_size)
79
+ expect(queue).to match_array(batch_payload)
80
+ end
81
+ end
82
+
83
+ context "When transmitting a payload" do
84
+ let(:random_number_of_events) { Flores::Random.integer(2..10) }
85
+ let(:payload) { { "line" => "foobar" } }
86
+ let(:client) do
87
+ Lumberjack::Client.new(:port => port,
88
+ :host => host,
89
+ :addresses => host,
90
+ :ssl_certificate => certificate_file_crt)
91
+ end
92
+ let(:batch_size) { Flores::Random.integer(1..1024) }
93
+ let(:batch_payload) do
94
+ batch = []
95
+ batch_size.times do |n|
96
+ batch << { "line" => "foobar #{n}" }
97
+ end
98
+ batch
99
+ end
100
+
101
+ context "when sequence start at 0" do
102
+ let(:sequence_start) { 0 }
103
+
104
+ include_examples "send payload"
105
+ end
106
+
107
+ context "when sequence doesn't start at zero" do
108
+ let(:sequence_start) { Flores::Random.integer(1..2000) }
109
+
110
+ before do
111
+ client.instance_variable_get(:@socket).instance_variable_set(:@sequence, sequence_start)
112
+ end
113
+
114
+ include_examples "send payload"
115
+ end
116
+
117
+ context "when the sequence rollover" do
118
+ let(:batch_size) { 100 }
119
+ let(:sequence_start) { Lumberjack::SEQUENCE_MAX - batch_size / 2 }
120
+
121
+ before do
122
+ client.instance_variable_get(:@socket).instance_variable_set(:@sequence, sequence_start)
123
+ end
124
+
125
+ it "adjusts the ack" do
126
+ expect(client.write(batch_payload)).to eq(batch_size / 2)
127
+ sleep(0.5)
128
+ expect(queue.size).to eq(batch_size)
129
+ expect(queue).to match_array(batch_payload)
130
+ end
131
+ end
132
+ end
61
133
  end
@@ -17,49 +17,25 @@ describe "Lumberjack::Client" do
17
17
 
18
18
  before do
19
19
  allow_any_instance_of(Lumberjack::Socket).to receive(:connection_start).and_return(true)
20
+ # mock any network call
21
+ allow(socket).to receive(:send_window_size).with(kind_of(Integer)).and_return(true)
22
+ allow(socket).to receive(:send_payload).with(kind_of(String)).and_return(true)
20
23
  end
21
24
 
22
25
  context "sequence" do
23
-
24
26
  let(:hash) { {:a => 1, :b => 2}}
25
27
  let(:max_unsigned_int) { (2**32)-1 }
26
28
 
27
29
  before(:each) do
28
30
  allow(socket).to receive(:ack).and_return(true)
29
- allow(socket).to receive(:write).and_return(true)
30
31
  end
31
32
 
32
33
  it "force sequence to be an unsigned 32 bits int" do
33
34
  socket.instance_variable_set(:@sequence, max_unsigned_int)
34
- socket.write_hash(hash)
35
+ socket.write_sync(hash)
35
36
  expect(socket.sequence).to eq(1)
36
37
  end
37
38
  end
38
-
39
- context "ack" do
40
-
41
- let(:hash) { {:a => 1, :b => 2}}
42
-
43
- before(:each) do
44
- allow(socket).to receive(:write).and_return(true)
45
- end
46
-
47
- it "increments the sequence per windows size" do
48
- allow(socket).to receive(:read_version_and_type).and_return([1, 'A'])
49
- expect(socket).to receive(:ack).twice.and_call_original
50
-
51
- [5000, 10000].each do |last_ack|
52
- windows_size = 5001
53
-
54
- allow(socket).to receive(:read_last_ack).and_return(last_ack)
55
-
56
- windows_size.times do
57
- socket.write_hash(hash)
58
- end
59
- end
60
-
61
- end
62
- end
63
39
  end
64
40
 
65
41
  describe Lumberjack::Encoder do
@@ -84,15 +60,5 @@ describe "Lumberjack::Client" do
84
60
  expect(data["message"].force_encoding('UTF-8')).to eq(content["message"])
85
61
  end
86
62
  end
87
-
88
- it 'should creates compressed frames' do
89
- content = {
90
- "message" => "国際ホッケー連盟" # International Hockey Federation
91
- }
92
- parser = Lumberjack::Parser.new
93
- parser.feed(Lumberjack::Encoder.to_compressed_frame(content, 0)) do |code, sequence, data|
94
- expect(data["message"].force_encoding('UTF-8')).to eq(content["message"])
95
- end
96
- end
97
63
  end
98
64
  end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ require "lumberjack/server"
3
+ require "spec_helper"
4
+ require "flores/random"
5
+
6
+ describe "Server" do
7
+ let(:socket) { double("socket") }
8
+ let(:connection) { Lumberjack::Connection.new(socket) }
9
+ let(:payload) { {"line" => "foobar" } }
10
+ let(:start_sequence) { Flores::Random.integer(0..2000) }
11
+ let(:random_number_of_events) { Flores::Random.integer(2..200) }
12
+
13
+ before do
14
+ expect(socket).to receive(:sysread).at_least(:once).with(Lumberjack::Connection::READ_SIZE).and_return("")
15
+ allow(socket).to receive(:syswrite).with(anything).and_return(true)
16
+ allow(socket).to receive(:close)
17
+
18
+ expectation = receive(:feed)
19
+ .with("")
20
+ .and_yield(:window_size, random_number_of_events)
21
+
22
+ random_number_of_events.times { |n| expectation.and_yield(:data, start_sequence + n + 1, payload) }
23
+
24
+ expect_any_instance_of(Lumberjack::Parser).to expectation
25
+ end
26
+
27
+ describe "Connnection" do
28
+ it "should ack the end of a sequence" do
29
+ expect(socket).to receive(:syswrite).with(["1A", random_number_of_events + start_sequence].pack("A*N"))
30
+ connection.read_socket
31
+ end
32
+ end
33
+ end
@@ -2,3 +2,7 @@
2
2
  require 'rspec'
3
3
  require 'rspec/mocks'
4
4
  $: << File.realpath(File.join(File.dirname(__FILE__), "..", "lib"))
5
+
6
+ RSpec.configure do |config|
7
+ config.order = :rand
8
+ end
metadata CHANGED
@@ -1,55 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jls-lumberjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.23
4
+ version: 0.0.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Sissel
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-31 00:00:00.000000000 Z
11
+ date: 2015-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: flores
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ~>
18
17
  - !ruby/object:Gem::Version
19
18
  version: 0.0.6
20
- type: :development
19
+ name: flores
21
20
  prerelease: false
21
+ type: :development
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.0.6
27
27
  - !ruby/object:Gem::Dependency
28
- name: rspec
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
- - - ">="
30
+ - - '>='
32
31
  - !ruby/object:Gem::Version
33
32
  version: '0'
34
- type: :development
33
+ name: rspec
35
34
  prerelease: false
35
+ type: :development
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: stud
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
- - - ">="
44
+ - - '>='
46
45
  - !ruby/object:Gem::Version
47
46
  version: '0'
47
+ name: stud
48
+ prerelease: false
48
49
  type: :development
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ name: pry
49
62
  prerelease: false
63
+ type: :development
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
- - - ">="
66
+ - - '>='
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
55
69
  description: lumberjack log transport library
@@ -59,35 +73,38 @@ executables: []
59
73
  extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
76
+ - lib/lumberjack.rb
62
77
  - lib/lumberjack/client.rb
63
78
  - lib/lumberjack/server.rb
64
79
  - spec/integration_spec.rb
65
80
  - spec/lumberjack/client_spec.rb
81
+ - spec/lumberjack/server_spec.rb
66
82
  - spec/spec_helper.rb
67
83
  homepage: https://github.com/jordansissel/lumberjack
68
84
  licenses: []
69
85
  metadata: {}
70
- post_install_message:
86
+ post_install_message:
71
87
  rdoc_options: []
72
88
  require_paths:
73
89
  - lib
74
90
  required_ruby_version: !ruby/object:Gem::Requirement
75
91
  requirements:
76
- - - ">="
92
+ - - '>='
77
93
  - !ruby/object:Gem::Version
78
94
  version: '0'
79
95
  required_rubygems_version: !ruby/object:Gem::Requirement
80
96
  requirements:
81
- - - ">="
97
+ - - '>='
82
98
  - !ruby/object:Gem::Version
83
99
  version: '0'
84
100
  requirements: []
85
- rubyforge_project:
86
- rubygems_version: 2.4.5
87
- signing_key:
101
+ rubyforge_project:
102
+ rubygems_version: 2.4.8
103
+ signing_key:
88
104
  specification_version: 4
89
105
  summary: lumberjack log transport library
90
106
  test_files:
91
107
  - spec/integration_spec.rb
92
- - spec/lumberjack/client_spec.rb
93
108
  - spec/spec_helper.rb
109
+ - spec/lumberjack/client_spec.rb
110
+ - spec/lumberjack/server_spec.rb