analytics-ruby 2.2.5 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,9 +1,12 @@
1
+ require 'forwardable'
1
2
  require 'segment/analytics/logging'
2
3
 
3
4
  module Segment
4
5
  class Analytics
5
6
  # A batch of `Message`s to be sent to the API
6
7
  class MessageBatch
8
+ class JSONGenerationError < StandardError; end
9
+
7
10
  extend Forwardable
8
11
  include Segment::Analytics::Logging
9
12
  include Segment::Analytics::Defaults::MessageBatch
@@ -15,11 +18,18 @@ module Segment
15
18
  end
16
19
 
17
20
  def <<(message)
18
- if message.too_big?
21
+ begin
22
+ message_json = message.to_json
23
+ rescue StandardError => e
24
+ raise JSONGenerationError, "Serialization error: #{e}"
25
+ end
26
+
27
+ message_json_size = message_json.bytesize
28
+ if message_too_big?(message_json_size)
19
29
  logger.error('a message exceeded the maximum allowed size')
20
30
  else
21
31
  @messages << message
22
- @json_size += message.json_size + 1 # One byte for the comma
32
+ @json_size += message_json_size + 1 # One byte for the comma
23
33
  end
24
34
  end
25
35
 
@@ -42,6 +52,10 @@ module Segment
42
52
  @messages.length >= @max_message_count
43
53
  end
44
54
 
55
+ def message_too_big?(message_json_size)
56
+ message_json_size > Defaults::Message::MAX_BYTES
57
+ end
58
+
45
59
  # We consider the max size here as just enough to leave room for one more
46
60
  # message of the largest size possible. This is a shortcut that allows us
47
61
  # to use a native Ruby `Queue` that doesn't allow peeking. The tradeoff
@@ -0,0 +1,56 @@
1
+ module Segment
2
+ class Analytics
3
+ class TestQueue
4
+ attr_reader :messages
5
+
6
+ def initialize
7
+ reset!
8
+ end
9
+
10
+ def [](key)
11
+ all[key]
12
+ end
13
+
14
+ def count
15
+ all.count
16
+ end
17
+
18
+ def <<(message)
19
+ all << message
20
+ send(message[:type]) << message
21
+ end
22
+
23
+ def alias
24
+ messages[:alias] ||= []
25
+ end
26
+
27
+ def all
28
+ messages[:all] ||= []
29
+ end
30
+
31
+ def group
32
+ messages[:group] ||= []
33
+ end
34
+
35
+ def identify
36
+ messages[:identify] ||= []
37
+ end
38
+
39
+ def page
40
+ messages[:page] ||= []
41
+ end
42
+
43
+ def screen
44
+ messages[:screen] ||= []
45
+ end
46
+
47
+ def track
48
+ messages[:track] ||= []
49
+ end
50
+
51
+ def reset!
52
+ @messages = {}
53
+ end
54
+ end
55
+ end
56
+ end
@@ -9,13 +9,11 @@ require 'json'
9
9
 
10
10
  module Segment
11
11
  class Analytics
12
- class Request
12
+ class Transport
13
13
  include Segment::Analytics::Defaults::Request
14
14
  include Segment::Analytics::Utils
15
15
  include Segment::Analytics::Logging
16
16
 
17
- # public: Creates a new request object to send analytics batch
18
- #
19
17
  def initialize(options = {})
20
18
  options[:host] ||= HOST
21
19
  options[:port] ||= PORT
@@ -34,14 +32,18 @@ module Segment
34
32
  @http = http
35
33
  end
36
34
 
37
- # public: Posts the write key and batch of messages to the API.
35
+ # Sends a batch of messages to the API
38
36
  #
39
- # returns - Response of the status and error if it exists
40
- def post(write_key, batch)
37
+ # @return [Response] API response
38
+ def send(write_key, batch)
39
+ logger.debug("Sending request for #{batch.length} items")
40
+
41
41
  last_response, exception = retry_with_backoff(@retries) do
42
42
  status_code, body = send_request(write_key, batch)
43
43
  error = JSON.parse(body)['error']
44
44
  should_retry = should_retry_request?(status_code, body)
45
+ logger.debug("Response status code: #{status_code}")
46
+ logger.debug("Response error: #{error}") if error
45
47
 
46
48
  [Response.new(status_code, error), should_retry]
47
49
  end
@@ -49,12 +51,17 @@ module Segment
49
51
  if exception
50
52
  logger.error(exception.message)
51
53
  exception.backtrace.each { |line| logger.error(line) }
52
- Response.new(-1, "Connection error: #{exception}")
54
+ Response.new(-1, exception.to_s)
53
55
  else
54
56
  last_response
55
57
  end
56
58
  end
57
59
 
60
+ # Closes a persistent connection if it exists
61
+ def shutdown
62
+ @http.finish if @http.started?
63
+ end
64
+
58
65
  private
59
66
 
60
67
  def should_retry_request?(status_code, body)
@@ -90,6 +97,7 @@ module Segment
90
97
  end
91
98
 
92
99
  if should_retry && (retries_remaining > 1)
100
+ logger.debug("Retrying request, #{retries_remaining} retries left")
93
101
  sleep(@backoff_policy.next_interval.to_f / 1000)
94
102
  retry_with_backoff(retries_remaining - 1, &block)
95
103
  else
@@ -108,15 +116,11 @@ module Segment
108
116
 
109
117
  if self.class.stub
110
118
  logger.debug "stubbed request to #{@path}: " \
111
- "write key = #{write_key}, batch = JSON.generate(#{batch})"
119
+ "write key = #{write_key}, batch = #{JSON.generate(batch)}"
112
120
 
113
121
  [200, '{}']
114
122
  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
-
123
+ @http.start unless @http.started? # Maintain a persistent connection
120
124
  response = @http.request(request, payload)
121
125
  [response.code.to_i, response.body]
122
126
  end
@@ -64,12 +64,8 @@ module Segment
64
64
  end
65
65
  end
66
66
 
67
- def time_in_iso8601(time, fraction_digits = 3)
68
- fraction = if fraction_digits > 0
69
- ('.%06i' % time.usec)[0, fraction_digits + 1]
70
- end
71
-
72
- "#{time.strftime('%Y-%m-%dT%H:%M:%S')}#{fraction}#{formatted_offset(time, true, 'Z')}"
67
+ def time_in_iso8601(time)
68
+ "#{time.strftime('%Y-%m-%dT%H:%M:%S.%6N')}#{formatted_offset(time, true, 'Z')}"
73
69
  end
74
70
 
75
71
  def date_in_iso8601(date)
@@ -1,5 +1,5 @@
1
1
  module Segment
2
2
  class Analytics
3
- VERSION = '2.2.5'
3
+ VERSION = '2.4.0'
4
4
  end
5
5
  end
@@ -1,7 +1,6 @@
1
1
  require 'segment/analytics/defaults'
2
- require 'segment/analytics/message'
3
2
  require 'segment/analytics/message_batch'
4
- require 'segment/analytics/request'
3
+ require 'segment/analytics/transport'
5
4
  require 'segment/analytics/utils'
6
5
 
7
6
  module Segment
@@ -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,7 @@ 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
32
+ @transport = Transport.new(options)
33
33
  end
34
34
 
35
35
  # public: Continuously runs the loop to check for new events
@@ -39,16 +39,16 @@ module Segment
39
39
  return if @queue.empty?
40
40
 
41
41
  @lock.synchronize do
42
- until @batch.full? || @queue.empty?
43
- @batch << Message.new(@queue.pop)
44
- end
42
+ consume_message_from_queue! until @batch.full? || @queue.empty?
45
43
  end
46
44
 
47
- res = @request.post(@write_key, @batch)
45
+ res = @transport.send @write_key, @batch
48
46
  @on_error.call(res.status, res.error) unless res.status == 200
49
47
 
50
48
  @lock.synchronize { @batch.clear }
51
49
  end
50
+ ensure
51
+ @transport.shutdown
52
52
  end
53
53
 
54
54
  # public: Check whether we have outstanding requests.
@@ -56,6 +56,14 @@ module Segment
56
56
  def is_requesting?
57
57
  @lock.synchronize { !@batch.empty? }
58
58
  end
59
+
60
+ private
61
+
62
+ def consume_message_from_queue!
63
+ @batch << @queue.pop
64
+ rescue MessageBatch::JSONGenerationError => e
65
+ @on_error.call(-1, e.to_s)
66
+ end
59
67
  end
60
68
  end
61
69
  end
@@ -1,11 +1,13 @@
1
1
  require 'segment/analytics/version'
2
2
  require 'segment/analytics/defaults'
3
3
  require 'segment/analytics/utils'
4
+ require 'segment/analytics/field_parser'
4
5
  require 'segment/analytics/client'
5
6
  require 'segment/analytics/worker'
6
- require 'segment/analytics/request'
7
+ require 'segment/analytics/transport'
7
8
  require 'segment/analytics/response'
8
9
  require 'segment/analytics/logging'
10
+ require 'segment/analytics/test_queue'
9
11
 
10
12
  module Segment
11
13
  class Analytics
@@ -17,7 +19,7 @@ module Segment
17
19
  # @option options [Boolean] :stub (false) If true, requests don't hit the
18
20
  # server and are stubbed to be successful.
19
21
  def initialize(options = {})
20
- Request.stub = options[:stub] if options.has_key?(:stub)
22
+ Transport.stub = options[:stub] if options.has_key?(:stub)
21
23
  @client = Segment::Analytics::Client.new options
22
24
  end
23
25
 
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.4.0
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: 2021-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander
@@ -17,7 +17,7 @@ dependencies:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.4'
20
- type: :runtime
20
+ type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
@@ -81,33 +81,19 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: 4.1.11
83
83
  - !ruby/object:Gem::Dependency
84
- name: faraday
84
+ name: oj
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.13'
89
+ version: 3.6.2
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.13'
97
- - !ruby/object:Gem::Dependency
98
- name: pmap
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.1'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.1'
96
+ version: 3.6.2
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: rubocop
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -143,40 +129,22 @@ executables:
143
129
  extensions: []
144
130
  extra_rdoc_files: []
145
131
  files:
146
- - Gemfile
147
- - History.md
148
- - Makefile
149
- - README.md
150
- - RELEASING.md
151
- - Rakefile
152
- - analytics-ruby.gemspec
153
132
  - bin/analytics
154
- - codecov.yml
155
133
  - lib/analytics-ruby.rb
156
134
  - lib/segment.rb
157
135
  - lib/segment/analytics.rb
158
136
  - lib/segment/analytics/backoff_policy.rb
159
137
  - lib/segment/analytics/client.rb
160
138
  - lib/segment/analytics/defaults.rb
139
+ - lib/segment/analytics/field_parser.rb
161
140
  - lib/segment/analytics/logging.rb
162
- - lib/segment/analytics/message.rb
163
141
  - lib/segment/analytics/message_batch.rb
164
- - lib/segment/analytics/request.rb
165
142
  - lib/segment/analytics/response.rb
143
+ - lib/segment/analytics/test_queue.rb
144
+ - lib/segment/analytics/transport.rb
166
145
  - lib/segment/analytics/utils.rb
167
146
  - lib/segment/analytics/version.rb
168
147
  - lib/segment/analytics/worker.rb
169
- - spec/helpers/runscope_client.rb
170
- - spec/segment/analytics/backoff_policy_spec.rb
171
- - spec/segment/analytics/client_spec.rb
172
- - spec/segment/analytics/e2e_spec.rb
173
- - spec/segment/analytics/message_batch_spec.rb
174
- - spec/segment/analytics/message_spec.rb
175
- - spec/segment/analytics/request_spec.rb
176
- - spec/segment/analytics/response_spec.rb
177
- - spec/segment/analytics/worker_spec.rb
178
- - spec/segment/analytics_spec.rb
179
- - spec/spec_helper.rb
180
148
  homepage: https://github.com/segmentio/analytics-ruby
181
149
  licenses:
182
150
  - MIT
@@ -189,15 +157,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
157
  requirements:
190
158
  - - ">="
191
159
  - !ruby/object:Gem::Version
192
- version: '0'
160
+ version: '2.0'
193
161
  required_rubygems_version: !ruby/object:Gem::Requirement
194
162
  requirements:
195
163
  - - ">="
196
164
  - !ruby/object:Gem::Version
197
165
  version: '0'
198
166
  requirements: []
199
- rubyforge_project:
200
- rubygems_version: 2.7.6
167
+ rubygems_version: 3.0.8
201
168
  signing_key:
202
169
  specification_version: 4
203
170
  summary: Segment.io analytics library
data/Gemfile DELETED
@@ -1,2 +0,0 @@
1
- source 'http://rubygems.org'
2
- gemspec
data/History.md DELETED
@@ -1,222 +0,0 @@
1
- 2.2.5 / 2018-05-01
2
- ==================
3
-
4
- * [Fix](https://github.com/segmentio/analytics-ruby/pull/158): Require `version` module first.
5
-
6
- 2.2.4 / 2018-04-30
7
- ==================
8
-
9
- * Promote pre-release version to stable.
10
-
11
- 2.2.4.pre / 2018-02-04
12
- ======================
13
-
14
- * [Fix](https://github.com/segmentio/analytics-ruby/pull/147): Prevent 'batch
15
- size exceeded' errors by automatically batching
16
- items according to size
17
- * [Performance](https://github.com/segmentio/analytics-ruby/pull/149): Reuse
18
- TCP connections
19
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/145): Emit logs
20
- when in-memory queue is full
21
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/143): Emit logs
22
- when messages exceed maximum allowed size
23
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/134): Add
24
- exponential backoff to retries
25
- * [Improvement](https://github.com/segmentio/analytics-ruby/pull/132): Handle
26
- HTTP status code failure appropriately
27
-
28
- 2.2.3.pre / 2017-09-14
29
- ==================
30
-
31
- * [Fix](https://github.com/segmentio/analytics-ruby/pull/120): Override `respond_to_missing` instead of `respond_to?` to facilitate mock the library in tests.
32
-
33
-
34
- 2.2.2 / 2016-08-03
35
- ==================
36
-
37
- * adding commander as dep (for CLI)
38
-
39
- 2.2.1 / 2016-08-03
40
- ==================
41
-
42
- * add executables to spec
43
-
44
- 2.2.0 / 2016-08-03
45
- ==================
46
-
47
- * Adding an (experimental) CLI
48
-
49
- 2.1.0 / 2016-06-17
50
- ==================
51
-
52
- * Fix: Ensure error handler is called before Client#flush finishes.
53
- * Feature: Support setting a custom message ID.
54
-
55
- 2.0.13 / 2015-09-15
56
- ==================
57
-
58
- * readme: updated install docs
59
- * fix: page/screen to allow no name
60
- * git: ignore ruby version
61
- * travis-ci: remove old rubys
62
-
63
- 2.0.12 / 2015-01-10
64
- ==================
65
-
66
- * Fix batch being cleared and causing duplicates
67
-
68
- 2.0.11 / 2014-09-22
69
- ==================
70
-
71
- * fix: don't clear batch if request failed
72
-
73
- 2.0.10 / 2014-09-22
74
- ==================
75
-
76
- * Move timeout retry above output
77
-
78
- 2.0.9 / 2014-09-22
79
- ==================
80
-
81
- * Fix rescuing timeouts
82
-
83
- 2.0.8 / 2014-09-11
84
- ==================
85
- * fix: add 3 ms to timestamp
86
-
87
- 2.0.7 / 2014-08-27
88
- ==================
89
- * fix: include optional options hash in calls
90
-
91
- 2.0.6 / 2014-08-12
92
- ==================
93
- * fix: category param on #page and #screen
94
-
95
- 2.0.5 / 2014-05-26
96
- ==================
97
- * fix: datetime conversions
98
-
99
- 2.0.4 / 2014-06-11
100
- ==================
101
- * fix: isofying trait dates in #group
102
-
103
- 2.0.3 / 2014-06-04
104
- ==================
105
- * fix: undefined method `is_requesting?' for nil:NilClass when calling flush (#51)
106
-
107
- 2.0.2 / 2014-05-30
108
- ==================
109
- * fix: formatting ios dates
110
- * fix: respond_to? implementation
111
- * add: able to stub requests by setting STUB env var
112
-
113
- 2.0.1 / 2014-05-15
114
- ==================
115
- * add: namespace under Segment::Analytics
116
- * add: can create multiple instances with creator method (rather than
117
- having a singleton)
118
- * add: logging with Logger instance or Rails.logger if available
119
- * add: able to stub requests so they just log and don't hit the server
120
- * fix: worker continues running across forked processes
121
- * fix: removed usage of ActiveSupport methods since its not a dependency
122
- * fix: sending data that matches segment's new api spec
123
-
124
- (there is no v2.0.0)
125
-
126
- 1.1.0 / 2014-04-17
127
- ==================
128
- * adding .initialized? by [@lumberj](https://github.com/lumberj)
129
-
130
- 1.0.0 / 2014-03-12
131
- ==================
132
- * removing faraday dependency
133
-
134
- 0.6.0 / 2014-02-19
135
- ==================
136
- * adding .group(), .page(), and .screen() calls
137
- * relaxing faraday dependency, fixes #31
138
-
139
- 0.5.4 / 2013-12-31
140
- ==================
141
- * Add `requestId` fields to all requests for tracing.
142
-
143
- 0.5.3 / 2013-12-31
144
- ==================
145
- * Allow the consumer thread to shut down so it won't remain live in hot deploy scenarios. This fixes the jruby memory leak by [@nirvdrum](https://github.com/nirvdrum)
146
-
147
- 0.5.2 / 2013-12-02
148
- ==================
149
- * adding `sleep` backoff between connection retries
150
-
151
- 0.5.1 / 2013-11-22
152
- ==================
153
- * adding retries for connection hangups
154
-
155
- 0.5.0 / 2013-10-03
156
- ==================
157
- * Removing global Analytics constant in favor of adding it to our config. NOTE: If you are upgrading from a previous version and want to continue using the `Analytics` namespace, you'll have to add `Analytics = AnalyticsRuby` to your config. Otherwise you WILL NOT be sending analytics data. See the [setup docs for more info](https://segment.io/libraries/ruby)
158
-
159
- 0.4.0 / 2013-08-30
160
- ==================
161
- * Adding support and tests for 1.8.7
162
-
163
- 0.3.4 / 2013-08-26
164
- ==================
165
- * Pass `Time` values as iso8601 timestamp strings
166
-
167
- 0.3.3 / 2013-08-02
168
- ==================
169
- * Allow init/track/identify/alias to accept strings as keys. by [@shipstar](https://github.com/shipstar)
170
-
171
- 0.3.2 / 2013-05-28
172
- ==================
173
- * Adding faraday timeout by [@yanchenyun](https://github.com/yangchenyun)
174
-
175
- 0.3.1 / 2013-04-29
176
- ==================
177
- * Adding check for properties to be a Hash
178
-
179
- 0.3.0 / 2013-04-05
180
- ==================
181
- * Adding alias call
182
-
183
- 0.2.0 / 2013-03-21
184
- ==================
185
- * Adding flush method
186
-
187
- 0.1.4 / 2013-03-19
188
- ==================
189
- * Adding ClassMethods for more extensibility by [arronmabrey](https://github.com/arronmabrey)
190
-
191
- 0.1.3 / 2013-03-19
192
- ==================
193
- * Fixing user_id.to_s semantics, reported by [arronmabrey](https://github.com/arronmabrey)
194
- * Reduced faraday requirements by [arronmabrey](https://github.com/arronmabrey)
195
-
196
- 0.1.2 / 2013-03-11
197
- ==================
198
- * Fixing thrown exception on non-initialized tracks thanks to [sbellity](https://github.com/sbellity)
199
-
200
- 0.1.1 / 2013-02-11
201
- ==================
202
- * Updating dependencies
203
- * Adding actual support for MultiJson 1.0
204
-
205
- 0.1.0 / 2013-01-22
206
- ==================
207
- * Updated docs to point at segment.io
208
-
209
- 0.0.5 / 2013-01-21
210
- ==================
211
- * Renaming of all the files for proper bundling usage
212
-
213
- 0.0.4 / 2013-01-17
214
- ==================
215
- * Updated readme and install instruction courtesy of [@zeke](https://github.com/zeke)
216
- * Removed typhoeus and reverted to default adapter
217
- * Removing session_id in favor of a single user_id
218
-
219
- 0.0.3 / 2013-01-16
220
- ==================
221
- * Rakefile and renaming courtesy of [@kiennt](https://github.com/kiennt)
222
- * Updated tests with mocks