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 +4 -4
- data/History.md +12 -0
- data/Rakefile +11 -1
- data/analytics-ruby.gemspec +4 -0
- data/lib/segment/analytics/logging.rb +34 -7
- data/lib/segment/analytics/message_batch.rb +9 -2
- data/lib/segment/analytics/request.rb +5 -5
- data/lib/segment/analytics/version.rb +1 -1
- data/lib/segment/analytics/worker.rb +4 -6
- data/spec/isolated/json_example.rb +9 -0
- data/spec/isolated/with_active_support.rb +11 -0
- data/spec/isolated/with_active_support_and_oj.rb +16 -0
- data/spec/isolated/with_oj.rb +13 -0
- data/spec/segment/analytics/e2e_spec.rb +6 -0
- data/spec/segment/analytics/message_batch_spec.rb +9 -8
- metadata +23 -7
- data/lib/segment/analytics/message.rb +0 -26
- data/spec/segment/analytics/message_spec.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16a399d60ac370a68cbb28f5887da70711403133e683d7acfe5db4b6eb8fb78d
|
4
|
+
data.tar.gz: 02c96de2188fb9b79b4d567c36a61ddf2ce0b62610adb6848a777ad327e47535
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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'
|
data/analytics-ruby.gemspec
CHANGED
@@ -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
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
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 +=
|
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,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 =
|
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,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
|
@@ -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 <<
|
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
|
-
|
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 <<
|
25
|
+
99.times { subject << { a: 'b' } }
|
27
26
|
expect(subject.full?).to be(false)
|
28
27
|
|
29
|
-
subject <<
|
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 =
|
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(
|
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 *
|
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.
|
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-
|
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:
|
213
|
+
version: 1.3.1
|
198
214
|
requirements: []
|
199
215
|
rubyforge_project:
|
200
|
-
rubygems_version: 2.7.
|
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
|