analytics-ruby 2.2.5 → 2.2.6.pre

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
  SHA256:
3
- metadata.gz: f217e8929c1705f8602e90bdd0f23b2e77fdb97aa57c530e7ae325005a640258
4
- data.tar.gz: 2a5674bf8575c46c472622cd2c2974a0f110215edb76dbce90126f1e62b43a99
3
+ metadata.gz: 16a399d60ac370a68cbb28f5887da70711403133e683d7acfe5db4b6eb8fb78d
4
+ data.tar.gz: 02c96de2188fb9b79b4d567c36a61ddf2ce0b62610adb6848a777ad327e47535
5
5
  SHA512:
6
- metadata.gz: 5a118f808ca6a2fb9235ac9464665b618213e0e8ff372e001d4243da059e8cd4b5c2f8fbbac0c1eb8590f637c2d6594d5a5624183f11ba54355dc52769e8177c
7
- data.tar.gz: 1ab60c691e8422bb33b7e14b1803cab36e457056871c366dabc1143289fede5e174c812dbca492176189f79a33419b1d2d3ea90ea61f827bd0e7248bef61cc95
6
+ metadata.gz: 69d989c8e9483dea80c707ddd4ca456d0d7891cd8b5624056a2131bd006dca153b00e46a22c582e26b0ad94978bd6beca4036a13b9921b1511b4d3ffb76b1339
7
+ data.tar.gz: 2c9a86a62efe64cbe7baea4faa9b605e32394b207eac8bd277bf1b3ca420aea6f9e34c7da4d476996f38907d1ca7a0d9e4d3abf2bd5088bd397d94a1db6641cc
data/History.md CHANGED
@@ -1,3 +1,15 @@
1
+ 2.2.6.pre / 2018-06-27
2
+ ==================
3
+
4
+ * [Fix](https://github.com/segmentio/analytics-ruby/pull/168): Revert 'reuse
5
+ TCP connections' to fix EMFILE errors
6
+ * [Fix](https://github.com/segmentio/analytics-ruby/pull/166): Fix oj/rails
7
+ conflict
8
+ * [Fix](https://github.com/segmentio/analytics-ruby/pull/162): Add missing
9
+ 'Forwardable' requirement
10
+ * [Improvement](https://github.com/segmentio/analytics-ruby/pull/163): Better
11
+ logging
12
+
1
13
  2.2.5 / 2018-05-01
2
14
  ==================
3
15
 
data/Rakefile CHANGED
@@ -3,12 +3,22 @@ require 'rspec/core/rake_task'
3
3
  default_tasks = []
4
4
 
5
5
  RSpec::Core::RakeTask.new(:spec) do |spec|
6
- spec.pattern = 'spec/**/*_spec.rb'
6
+ spec.pattern = 'spec/segment/**/*_spec.rb'
7
7
  spec.rspec_opts = "--tag ~e2e" if ENV["RUN_E2E_TESTS"] != "true"
8
8
  end
9
9
 
10
10
  default_tasks << :spec
11
11
 
12
+ # Isolated tests are run as separate rake tasks so that gem conflicts can be
13
+ # tests in different processes
14
+ Dir.glob('spec/isolated/**/*.rb').each do |isolated_test_path|
15
+ RSpec::Core::RakeTask.new(isolated_test_path) do |spec|
16
+ spec.pattern = isolated_test_path
17
+ end
18
+
19
+ default_tasks << isolated_test_path
20
+ end
21
+
12
22
  # Rubocop doesn't support < 2.1
13
23
  if RUBY_VERSION >= "2.1"
14
24
  require 'rubocop/rake_task'
@@ -25,6 +25,10 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency 'faraday', '~> 0.13'
26
26
  spec.add_development_dependency 'pmap', '~> 1.1'
27
27
 
28
+ if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java'
29
+ spec.add_development_dependency 'oj', '~> 3.6.2'
30
+ end
31
+
28
32
  if RUBY_VERSION >= "2.1"
29
33
  spec.add_development_dependency 'rubocop', '~> 0.51.0'
30
34
  end
@@ -2,16 +2,43 @@ require 'logger'
2
2
 
3
3
  module Segment
4
4
  class Analytics
5
+ # Wraps an existing logger and adds a prefix to all messages
6
+ class PrefixedLogger
7
+ def initialize(logger, prefix)
8
+ @logger = logger
9
+ @prefix = prefix
10
+ end
11
+
12
+ def debug(msg)
13
+ @logger.debug("#{@prefix} #{msg}")
14
+ end
15
+
16
+ def info(msg)
17
+ @logger.info("#{@prefix} #{msg}")
18
+ end
19
+
20
+ def warn(msg)
21
+ @logger.warn("#{@prefix} #{msg}")
22
+ end
23
+
24
+ def error(msg)
25
+ @logger.error("#{@prefix} #{msg}")
26
+ end
27
+ end
28
+
5
29
  module Logging
6
30
  class << self
7
31
  def logger
8
- @logger ||= if defined?(Rails)
9
- Rails.logger
10
- else
11
- logger = Logger.new STDOUT
12
- logger.progname = 'Segment::Analytics'
13
- logger
14
- end
32
+ return @logger if @logger
33
+
34
+ base_logger = if defined?(Rails)
35
+ Rails.logger
36
+ else
37
+ logger = Logger.new STDOUT
38
+ logger.progname = 'Segment::Analytics'
39
+ logger
40
+ end
41
+ @logger = PrefixedLogger.new(base_logger, '[analytics-ruby]')
15
42
  end
16
43
 
17
44
  attr_writer :logger
@@ -1,3 +1,4 @@
1
+ require 'forwardable'
1
2
  require 'segment/analytics/logging'
2
3
 
3
4
  module Segment
@@ -15,11 +16,13 @@ module Segment
15
16
  end
16
17
 
17
18
  def <<(message)
18
- if message.too_big?
19
+ message_json_size = message.to_json.bytesize
20
+
21
+ if message_too_big?(message_json_size)
19
22
  logger.error('a message exceeded the maximum allowed size')
20
23
  else
21
24
  @messages << message
22
- @json_size += message.json_size + 1 # One byte for the comma
25
+ @json_size += message_json_size + 1 # One byte for the comma
23
26
  end
24
27
  end
25
28
 
@@ -42,6 +45,10 @@ module Segment
42
45
  @messages.length >= @max_message_count
43
46
  end
44
47
 
48
+ def message_too_big?(message_json_size)
49
+ message_json_size > Defaults::Message::MAX_BYTES
50
+ end
51
+
45
52
  # We consider the max size here as just enough to leave room for one more
46
53
  # message of the largest size possible. This is a shortcut that allows us
47
54
  # to use a native Ruby `Queue` that doesn't allow peeking. The tradeoff
@@ -38,10 +38,14 @@ module Segment
38
38
  #
39
39
  # returns - Response of the status and error if it exists
40
40
  def post(write_key, batch)
41
+ logger.debug("Sending request for #{batch.length} items")
42
+
41
43
  last_response, exception = retry_with_backoff(@retries) do
42
44
  status_code, body = send_request(write_key, batch)
43
45
  error = JSON.parse(body)['error']
44
46
  should_retry = should_retry_request?(status_code, body)
47
+ logger.debug("Response status code: #{status_code}")
48
+ logger.debug("Response error: #{error}") if error
45
49
 
46
50
  [Response.new(status_code, error), should_retry]
47
51
  end
@@ -90,6 +94,7 @@ module Segment
90
94
  end
91
95
 
92
96
  if should_retry && (retries_remaining > 1)
97
+ logger.debug("Retrying request, #{retries_remaining} retries left")
93
98
  sleep(@backoff_policy.next_interval.to_f / 1000)
94
99
  retry_with_backoff(retries_remaining - 1, &block)
95
100
  else
@@ -112,11 +117,6 @@ module Segment
112
117
 
113
118
  [200, '{}']
114
119
  else
115
- # If `start` is not called, Ruby adds a 'Connection: close' header to
116
- # all requests, preventing us from reusing a connection for multiple
117
- # HTTP requests
118
- @http.start unless @http.started?
119
-
120
120
  response = @http.request(request, payload)
121
121
  [response.code.to_i, response.body]
122
122
  end
@@ -1,5 +1,5 @@
1
1
  module Segment
2
2
  class Analytics
3
- VERSION = '2.2.5'
3
+ VERSION = '2.2.6.pre'
4
4
  end
5
5
  end
@@ -1,5 +1,4 @@
1
1
  require 'segment/analytics/defaults'
2
- require 'segment/analytics/message'
3
2
  require 'segment/analytics/message_batch'
4
3
  require 'segment/analytics/request'
5
4
  require 'segment/analytics/utils'
@@ -9,6 +8,7 @@ module Segment
9
8
  class Worker
10
9
  include Segment::Analytics::Utils
11
10
  include Segment::Analytics::Defaults
11
+ include Segment::Analytics::Logging
12
12
 
13
13
  # public: Creates a new worker
14
14
  #
@@ -29,7 +29,6 @@ module Segment
29
29
  batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE
30
30
  @batch = MessageBatch.new(batch_size)
31
31
  @lock = Mutex.new
32
- @request = Request.new
33
32
  end
34
33
 
35
34
  # public: Continuously runs the loop to check for new events
@@ -39,12 +38,11 @@ module Segment
39
38
  return if @queue.empty?
40
39
 
41
40
  @lock.synchronize do
42
- until @batch.full? || @queue.empty?
43
- @batch << Message.new(@queue.pop)
44
- end
41
+ @batch << @queue.pop until @batch.full? || @queue.empty?
45
42
  end
46
43
 
47
- res = @request.post(@write_key, @batch)
44
+ res = Request.new.post @write_key, @batch
45
+
48
46
  @on_error.call(res.status, res.error) unless res.status == 200
49
47
 
50
48
  @lock.synchronize { @batch.clear }
@@ -0,0 +1,9 @@
1
+ RSpec.shared_examples 'message_batch_json' do
2
+ it 'MessageBatch generates proper JSON' do
3
+ batch = Segment::Analytics::MessageBatch.new(100)
4
+ batch << { 'a' => 'b' }
5
+ batch << { 'c' => 'd' }
6
+
7
+ expect(JSON.generate(batch)).to eq('[{"a":"b"},{"c":"d"}]')
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'isolated/json_example'
3
+
4
+ describe 'with active_support' do
5
+ before do
6
+ require 'active_support'
7
+ require 'active_support/json'
8
+ end
9
+
10
+ include_examples 'message_batch_json'
11
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'isolated/json_example'
3
+
4
+ if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java'
5
+ describe 'with active_support and oj' do
6
+ before do
7
+ require 'active_support'
8
+ require 'active_support/json'
9
+
10
+ require 'oj'
11
+ Oj.mimic_JSON
12
+ end
13
+
14
+ include_examples 'message_batch_json'
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ require 'isolated/json_example'
3
+
4
+ if RUBY_VERSION >= '2.0' && RUBY_PLATFORM != 'java'
5
+ describe 'with oj' do
6
+ before do
7
+ require 'oj'
8
+ Oj.mimic_JSON
9
+ end
10
+
11
+ include_examples 'message_batch_json'
12
+ end
13
+ end
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Segment
@@ -19,6 +21,10 @@ module Segment
19
21
  let(:runscope_client) { RunscopeClient.new(ENV.fetch('RUNSCOPE_TOKEN')) }
20
22
 
21
23
  it 'tracks events' do
24
+ # Runscope inspector has shut down, disable for a while until we've
25
+ # found a replacement.
26
+ skip if Date.today < Date.new(2018, 7, 27)
27
+
22
28
  id = SecureRandom.uuid
23
29
  client.track(
24
30
  user_id: 'dummy_user_id',
@@ -7,14 +7,13 @@ module Segment
7
7
 
8
8
  describe '#<<' do
9
9
  it 'appends messages' do
10
- subject << Message.new('a' => 'b')
10
+ subject << { 'a' => 'b' }
11
11
  expect(subject.length).to eq(1)
12
12
  end
13
13
 
14
14
  it 'rejects messages that exceed the maximum allowed size' do
15
15
  max_bytes = Defaults::Message::MAX_BYTES
16
- hash = { 'a' => 'b' * max_bytes }
17
- message = Message.new(hash)
16
+ message = { 'a' => 'b' * max_bytes }
18
17
 
19
18
  subject << message
20
19
  expect(subject.length).to eq(0)
@@ -23,21 +22,23 @@ module Segment
23
22
 
24
23
  describe '#full?' do
25
24
  it 'returns true once item count is exceeded' do
26
- 99.times { subject << Message.new(a: 'b') }
25
+ 99.times { subject << { a: 'b' } }
27
26
  expect(subject.full?).to be(false)
28
27
 
29
- subject << Message.new(a: 'b')
28
+ subject << { a: 'b' }
30
29
  expect(subject.full?).to be(true)
31
30
  end
32
31
 
33
32
  it 'returns true once max size is almost exceeded' do
34
- message = Message.new(a: 'b' * (Defaults::Message::MAX_BYTES - 10))
33
+ message = { a: 'b' * (Defaults::Message::MAX_BYTES - 10) }
34
+
35
+ message_size = message.to_json.bytesize
35
36
 
36
37
  # Each message is under the individual limit
37
- expect(message.json_size).to be < Defaults::Message::MAX_BYTES
38
+ expect(message_size).to be < Defaults::Message::MAX_BYTES
38
39
 
39
40
  # Size of the batch is over the limit
40
- expect(50 * message.json_size).to be > Defaults::MessageBatch::MAX_BYTES
41
+ expect(50 * message_size).to be > Defaults::MessageBatch::MAX_BYTES
41
42
 
42
43
  expect(subject.full?).to be(false)
43
44
  50.times { subject << message }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: analytics-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.5
4
+ version: 2.2.6.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Segment.io
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-01 00:00:00.000000000 Z
11
+ date: 2018-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: oj
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 3.6.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 3.6.2
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rubocop
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -159,7 +173,6 @@ files:
159
173
  - lib/segment/analytics/client.rb
160
174
  - lib/segment/analytics/defaults.rb
161
175
  - lib/segment/analytics/logging.rb
162
- - lib/segment/analytics/message.rb
163
176
  - lib/segment/analytics/message_batch.rb
164
177
  - lib/segment/analytics/request.rb
165
178
  - lib/segment/analytics/response.rb
@@ -167,11 +180,14 @@ files:
167
180
  - lib/segment/analytics/version.rb
168
181
  - lib/segment/analytics/worker.rb
169
182
  - spec/helpers/runscope_client.rb
183
+ - spec/isolated/json_example.rb
184
+ - spec/isolated/with_active_support.rb
185
+ - spec/isolated/with_active_support_and_oj.rb
186
+ - spec/isolated/with_oj.rb
170
187
  - spec/segment/analytics/backoff_policy_spec.rb
171
188
  - spec/segment/analytics/client_spec.rb
172
189
  - spec/segment/analytics/e2e_spec.rb
173
190
  - spec/segment/analytics/message_batch_spec.rb
174
- - spec/segment/analytics/message_spec.rb
175
191
  - spec/segment/analytics/request_spec.rb
176
192
  - spec/segment/analytics/response_spec.rb
177
193
  - spec/segment/analytics/worker_spec.rb
@@ -192,12 +208,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
192
208
  version: '0'
193
209
  required_rubygems_version: !ruby/object:Gem::Requirement
194
210
  requirements:
195
- - - ">="
211
+ - - ">"
196
212
  - !ruby/object:Gem::Version
197
- version: '0'
213
+ version: 1.3.1
198
214
  requirements: []
199
215
  rubyforge_project:
200
- rubygems_version: 2.7.6
216
+ rubygems_version: 2.7.7
201
217
  signing_key:
202
218
  specification_version: 4
203
219
  summary: Segment.io analytics library
@@ -1,26 +0,0 @@
1
- require 'segment/analytics/defaults'
2
-
3
- module Segment
4
- class Analytics
5
- # Represents a message to be sent to the API
6
- class Message
7
- def initialize(hash)
8
- @hash = hash
9
- end
10
-
11
- def too_big?
12
- json_size > Defaults::Message::MAX_BYTES
13
- end
14
-
15
- def json_size
16
- to_json.bytesize
17
- end
18
-
19
- # Since the hash is expected to not be modified (set at initialization),
20
- # the JSON version can be cached after the first computation.
21
- def to_json(*args)
22
- @json ||= @hash.to_json(*args)
23
- end
24
- end
25
- end
26
- end
@@ -1,35 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Segment
4
- class Analytics
5
- describe Message do
6
- describe '#to_json' do
7
- it 'caches JSON conversions' do
8
- # Keeps track of the number of times to_json was called
9
- nested_obj = Class.new do
10
- attr_reader :to_json_call_count
11
-
12
- def initialize
13
- @to_json_call_count = 0
14
- end
15
-
16
- def to_json(*_)
17
- @to_json_call_count += 1
18
- '{}'
19
- end
20
- end.new
21
-
22
- message = Message.new('some_key' => nested_obj)
23
- expect(nested_obj.to_json_call_count).to eq(0)
24
-
25
- message.to_json
26
- expect(nested_obj.to_json_call_count).to eq(1)
27
-
28
- # When called a second time, the call count shouldn't increase
29
- message.to_json
30
- expect(nested_obj.to_json_call_count).to eq(1)
31
- end
32
- end
33
- end
34
- end
35
- end