jls-lumberjack 0.0.23 → 0.0.24

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
  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