analytics-ruby 2.2.5 → 2.2.6.pre

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