rudder-sdk-ruby 0.0.5 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/analytics_rudder.rb +112 -0
- data/lib/rudder/analytics/client.rb +25 -21
- data/lib/rudder/analytics/configuration.rb +33 -0
- data/lib/rudder/analytics/defaults.rb +1 -3
- data/lib/rudder/analytics/field_parser.rb +73 -49
- data/lib/rudder/analytics/test_queue.rb +58 -0
- data/lib/rudder/analytics/{request.rb → transport.rb} +40 -44
- data/lib/rudder/analytics/utils.rb +9 -9
- data/lib/rudder/analytics/version.rb +1 -1
- data/lib/rudder/analytics/worker.rb +12 -10
- data/lib/rudder/analytics.rb +1 -2
- metadata +9 -21
- data/bin/analytics-rudder.rb +0 -111
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 683cbc4c49e184a2d483d4acd1e62cfb4ca781d299354e6b4997806e9a46066b
|
4
|
+
data.tar.gz: dd256aa63db7a6b3bc4c5b8dc6e1f8c81d669aa7a2a295e3f66e81b9d017973e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 45414d5e7f33eaabfac39840cf787fb3cb53e537b7b26c7b5bf3a9ec375a867dca78dc78c1c859e8281cbc8fd38783fd0df88a8e2e584ee48820199d0ca3c99c
|
7
|
+
data.tar.gz: 2aa1e175429767f75a0130e8ee81ccd9f8f11fa30ff742a1d8a593e3e42022810194c368f1e0c3b6cc2c2cbb5220ea8b21dffec95be90dbf5b10c7bc21f03fbb
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# !/usr/bin/env ruby
|
4
|
+
|
5
|
+
require 'rudder/analytics'
|
6
|
+
require 'rubygems'
|
7
|
+
require 'commander/import'
|
8
|
+
require 'time'
|
9
|
+
require 'json'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
program :name, 'simulator.rb'
|
13
|
+
program :version, '0.0.1'
|
14
|
+
program :description, 'scripting simulator'
|
15
|
+
|
16
|
+
def json_hash(str)
|
17
|
+
return JSON.parse(str) if str
|
18
|
+
end
|
19
|
+
|
20
|
+
# Run in dev with ruby -ilib bin/analytics_rudder.rb --writeKey=<write_key> --dataPlaneUrl=<data_plane_url>
|
21
|
+
# --type=track --userId=123456 --event=Test --properties={\"key_1\" => \"value_1\"} --trace
|
22
|
+
|
23
|
+
default_command :send
|
24
|
+
|
25
|
+
command :send do |c|
|
26
|
+
c.description = 'send a Rudder message'
|
27
|
+
|
28
|
+
c.option '--writeKey=<writeKey>', String, 'the Rudder writeKey'
|
29
|
+
c.option '--dataPlaneUrl=<dataPlaneUrl>', String, 'the Rudder data plane URL'
|
30
|
+
c.option '--type=<type>', String, 'The Rudder message type'
|
31
|
+
|
32
|
+
c.option '--userId=<userId>', String, 'the user id to send the event as'
|
33
|
+
c.option '--anonymousId=<anonymousId>', String, 'the anonymous user id to send the event as'
|
34
|
+
c.option '--context=<context>', 'additional context for the event (JSON-encoded)'
|
35
|
+
c.option '--integrations=<integrations>', 'additional integrations for the event (JSON-encoded)'
|
36
|
+
|
37
|
+
c.option '--event=<event>', String, 'the event name to send with the event'
|
38
|
+
c.option '--properties=<properties>', 'the event properties to send (JSON-encoded)'
|
39
|
+
|
40
|
+
c.option '--name=<name>', 'name of the screen or page to send with the message'
|
41
|
+
|
42
|
+
c.option '--traits=<traits>', 'the identify/group traits to send (JSON-encoded)'
|
43
|
+
|
44
|
+
c.option '--groupId=<groupId>', String, 'the group id'
|
45
|
+
c.option '--previousId=<previousId>', String, 'the previous id'
|
46
|
+
|
47
|
+
c.action do |_, options|
|
48
|
+
Analytics = Rudder::Analytics.new({
|
49
|
+
:write_key => options.writeKey,
|
50
|
+
:data_plane_url => options.dataPlaneUrl,
|
51
|
+
:on_error => proc { |_status, msg| print msg }
|
52
|
+
})
|
53
|
+
|
54
|
+
case options.type
|
55
|
+
when 'track'
|
56
|
+
Analytics.track({
|
57
|
+
:user_id => options.userId,
|
58
|
+
:event => options.event,
|
59
|
+
:anonymous_id => options.anonymousId,
|
60
|
+
:properties => json_hash(options.properties),
|
61
|
+
:context => json_hash(options.context),
|
62
|
+
:integrations => json_hash(options.integrations)
|
63
|
+
})
|
64
|
+
when 'page'
|
65
|
+
Analytics.page({
|
66
|
+
:user_id => options.userId,
|
67
|
+
:anonymous_id => options.anonymousId,
|
68
|
+
:name => options.name,
|
69
|
+
:properties => json_hash(options.properties),
|
70
|
+
:context => json_hash(options.context),
|
71
|
+
:integrations => json_hash(options.integrations)
|
72
|
+
})
|
73
|
+
when 'screen'
|
74
|
+
Analytics.screen({
|
75
|
+
:user_id => options.userId,
|
76
|
+
:anonymous_id => options.anonymousId,
|
77
|
+
:name => options.name,
|
78
|
+
:properties => json_hash(options.properties),
|
79
|
+
:context => json_hash(options.context),
|
80
|
+
:integrations => json_hash(options.integrations)
|
81
|
+
})
|
82
|
+
when 'identify'
|
83
|
+
Analytics.identify({
|
84
|
+
:user_id => options.userId,
|
85
|
+
:anonymous_id => options.anonymousId,
|
86
|
+
:traits => json_hash(options.traits),
|
87
|
+
:context => json_hash(options.context),
|
88
|
+
:integrations => json_hash(options.integrations)
|
89
|
+
})
|
90
|
+
when 'group'
|
91
|
+
Analytics.group({
|
92
|
+
:user_id => options.userId,
|
93
|
+
:anonymous_id => options.anonymousId,
|
94
|
+
:group_id => options.groupId,
|
95
|
+
:traits => json_hash(options.traits),
|
96
|
+
:context => json_hash(options.context),
|
97
|
+
:integrations => json_hash(options.integrations)
|
98
|
+
})
|
99
|
+
when 'alias'
|
100
|
+
Analytics.alias({
|
101
|
+
:previous_id => options.previousId,
|
102
|
+
:user_id => options.userId,
|
103
|
+
:anonymous_id => options.anonymousId,
|
104
|
+
:context => json_hash(options.context),
|
105
|
+
:integrations => json_hash(options.integrations)
|
106
|
+
})
|
107
|
+
else
|
108
|
+
raise "Invalid Message Type #{options.type}"
|
109
|
+
end
|
110
|
+
Analytics.flush
|
111
|
+
end
|
112
|
+
end
|
@@ -8,6 +8,8 @@ require 'rudder/analytics/logging'
|
|
8
8
|
require 'rudder/analytics/utils'
|
9
9
|
require 'rudder/analytics/worker'
|
10
10
|
require 'rudder/analytics/defaults'
|
11
|
+
require 'rudder/analytics/configuration'
|
12
|
+
require 'rudder/analytics/test_queue'
|
11
13
|
require 'net/http'
|
12
14
|
|
13
15
|
module Rudder
|
@@ -23,23 +25,11 @@ module Rudder
|
|
23
25
|
# remain queued.
|
24
26
|
# @option opts [Proc] :on_error Handles error calls from the API.
|
25
27
|
def initialize(opts = {})
|
26
|
-
|
27
|
-
|
28
|
+
@config = Configuration.new(opts)
|
28
29
|
@queue = Queue.new
|
29
|
-
@write_key = opts[:write_key]
|
30
|
-
@data_plane_url = opts[:data_plane_url]
|
31
|
-
@max_queue_size = opts[:max_queue_size] || Defaults::Queue::MAX_SIZE
|
32
30
|
@worker_mutex = Mutex.new
|
33
|
-
@worker = Worker.new(@queue, @
|
31
|
+
@worker = Worker.new(@queue, @config)
|
34
32
|
@worker_thread = nil
|
35
|
-
|
36
|
-
uri = URI(opts[:data_plane_url])
|
37
|
-
|
38
|
-
@host = uri.host
|
39
|
-
@port = uri.port
|
40
|
-
|
41
|
-
check_write_key!
|
42
|
-
|
43
33
|
at_exit { @worker_thread && @worker_thread[:should_exit] = true }
|
44
34
|
end
|
45
35
|
|
@@ -155,6 +145,12 @@ module Rudder
|
|
155
145
|
@queue.length
|
156
146
|
end
|
157
147
|
|
148
|
+
def test_queue
|
149
|
+
raise 'Test queue only available when setting :test to true.' unless @config.test
|
150
|
+
|
151
|
+
@test_queue ||= TestQueue.new
|
152
|
+
end
|
153
|
+
|
158
154
|
private
|
159
155
|
|
160
156
|
# private: Enqueues the action.
|
@@ -165,25 +161,33 @@ module Rudder
|
|
165
161
|
# add our request id for tracing purposes
|
166
162
|
action[:messageId] ||= uid
|
167
163
|
|
168
|
-
if @
|
164
|
+
if @config.test
|
165
|
+
test_queue << action
|
166
|
+
return true
|
167
|
+
end
|
168
|
+
|
169
|
+
if @queue.length < @config.max_queue_size
|
169
170
|
@queue << action
|
170
171
|
ensure_worker_running
|
171
172
|
|
172
173
|
true
|
173
174
|
else
|
174
175
|
logger.warn(
|
175
|
-
'Queue is full, dropping events. The :max_queue_size '
|
176
|
-
'configuration parameter can be increased to prevent this from ' \
|
177
|
-
'happening.'
|
176
|
+
'Queue is full, dropping events. The :max_queue_size configuration parameter can be increased to prevent this from happening.'
|
178
177
|
)
|
179
178
|
false
|
180
179
|
end
|
181
180
|
end
|
182
181
|
|
183
182
|
# private: Checks that the write_key is properly initialized
|
184
|
-
def check_write_key!
|
185
|
-
|
186
|
-
end
|
183
|
+
# def check_write_key!
|
184
|
+
# raise ArgumentError, 'Write key must be initialized' if @write_key.nil?
|
185
|
+
# end
|
186
|
+
|
187
|
+
# private: Checks that the data_plane_url is properly initialized
|
188
|
+
# def check_data_plane_url!
|
189
|
+
# raise ArgumentError, 'Data plane url must be initialized' if @data_plane_url.nil?
|
190
|
+
# end
|
187
191
|
|
188
192
|
def ensure_worker_running
|
189
193
|
return if worker_running?
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rudder/analytics/utils'
|
4
|
+
|
5
|
+
module Rudder
|
6
|
+
class Analytics
|
7
|
+
class Configuration
|
8
|
+
include Rudder::Analytics::Utils
|
9
|
+
|
10
|
+
attr_reader :write_key, :data_plane_url, :on_error, :stub, :gzip, :ssl, :batch_size, :test, :max_queue_size, :backoff_policy, :retries
|
11
|
+
|
12
|
+
def initialize(settings = {})
|
13
|
+
symbolized_settings = symbolize_keys(settings)
|
14
|
+
|
15
|
+
@test = symbolized_settings[:test]
|
16
|
+
@write_key = symbolized_settings[:write_key]
|
17
|
+
@data_plane_url = symbolized_settings[:data_plane_url]
|
18
|
+
@max_queue_size = symbolized_settings[:max_queue_size] || Defaults::Queue::MAX_SIZE
|
19
|
+
@ssl = symbolized_settings[:ssl]
|
20
|
+
@on_error = symbolized_settings[:on_error] || proc { |status, error| }
|
21
|
+
@stub = symbolized_settings[:stub]
|
22
|
+
@batch_size = symbolized_settings[:batch_size] || Defaults::MessageBatch::MAX_SIZE
|
23
|
+
@gzip = symbolized_settings[:gzip]
|
24
|
+
@backoff_policy = symbolized_settings[:backoff_policy]
|
25
|
+
@retries = symbolized_settings[:retries]
|
26
|
+
raise ArgumentError, 'Missing required option :write_key' \
|
27
|
+
unless @write_key
|
28
|
+
raise ArgumentError, 'Data plane url must be initialized' \
|
29
|
+
unless @data_plane_url
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -7,11 +7,9 @@ module Rudder
|
|
7
7
|
HOST = 'localhost'
|
8
8
|
PORT = 8080
|
9
9
|
PATH = '/v1/batch'
|
10
|
-
DATA_PLANE_URL = 'http://localhost:8080/v1/batch'
|
11
|
-
SSL = false
|
12
10
|
HEADERS = { 'Accept' => 'application/json',
|
13
11
|
'Content-Type' => 'application/json',
|
14
|
-
'
|
12
|
+
'Content-Encoding' => 'gzip' }
|
15
13
|
RETRIES = 10
|
16
14
|
end
|
17
15
|
|
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
module Rudder
|
4
4
|
class Analytics
|
5
|
-
# Handles parsing fields according to the
|
5
|
+
# Handles parsing fields according to the RudderStack Spec
|
6
6
|
#
|
7
|
-
# @see https://
|
8
|
-
class FieldParser
|
7
|
+
# @see https://www.rudderstack.com/docs/event-spec/standard-events/
|
8
|
+
class FieldParser # rubocop:disable Metrics/ClassLength
|
9
9
|
class << self
|
10
10
|
include Rudder::Analytics::Utils
|
11
11
|
|
@@ -17,17 +17,11 @@ module Rudder
|
|
17
17
|
common = parse_common_fields(fields)
|
18
18
|
|
19
19
|
event = fields[:event]
|
20
|
-
properties = fields[:properties] || {}
|
21
|
-
|
22
20
|
check_presence!(event, 'event')
|
23
|
-
check_is_hash!(properties, 'properties')
|
24
|
-
|
25
|
-
isoify_dates! properties
|
26
21
|
|
27
22
|
common.merge({
|
28
23
|
:type => 'track',
|
29
|
-
:event => event.to_s
|
30
|
-
:properties => properties
|
24
|
+
:event => event.to_s
|
31
25
|
})
|
32
26
|
end
|
33
27
|
|
@@ -37,13 +31,18 @@ module Rudder
|
|
37
31
|
def parse_for_identify(fields)
|
38
32
|
common = parse_common_fields(fields)
|
39
33
|
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
# add the traits if present
|
35
|
+
if fields[:traits]
|
36
|
+
traits = fields[:traits]
|
37
|
+
context = common[:context].merge({ :traits => traits })
|
38
|
+
|
39
|
+
common = common.merge({
|
40
|
+
:context => context
|
41
|
+
})
|
42
|
+
end
|
43
43
|
|
44
44
|
common.merge({
|
45
|
-
:type => 'identify'
|
46
|
-
:traits => traits
|
45
|
+
:type => 'identify'
|
47
46
|
})
|
48
47
|
end
|
49
48
|
|
@@ -55,6 +54,7 @@ module Rudder
|
|
55
54
|
|
56
55
|
previous_id = fields[:previous_id]
|
57
56
|
check_presence!(previous_id, 'previous_id')
|
57
|
+
check_string(previous_id, 'previous_id')
|
58
58
|
|
59
59
|
common.merge({
|
60
60
|
:type => 'alias',
|
@@ -70,17 +70,12 @@ module Rudder
|
|
70
70
|
common = parse_common_fields(fields)
|
71
71
|
|
72
72
|
group_id = fields[:group_id]
|
73
|
-
traits = fields[:traits] || {}
|
74
|
-
|
75
73
|
check_presence!(group_id, 'group_id')
|
76
|
-
|
77
|
-
|
78
|
-
isoify_dates! traits
|
74
|
+
check_string(group_id, 'group_id')
|
79
75
|
|
80
76
|
common.merge({
|
81
77
|
:type => 'group',
|
82
|
-
:groupId => group_id
|
83
|
-
:traits => traits
|
78
|
+
:groupId => group_id
|
84
79
|
})
|
85
80
|
end
|
86
81
|
|
@@ -92,15 +87,13 @@ module Rudder
|
|
92
87
|
common = parse_common_fields(fields)
|
93
88
|
|
94
89
|
name = fields[:name] || ''
|
95
|
-
properties =
|
96
|
-
|
97
|
-
check_is_hash!(properties, 'properties')
|
98
|
-
|
99
|
-
isoify_dates! properties
|
90
|
+
properties = common[:properties] || {}
|
91
|
+
properties = properties.merge({ :name => name })
|
100
92
|
|
101
93
|
common.merge({
|
102
94
|
:type => 'page',
|
103
|
-
:name => name
|
95
|
+
:name => name,
|
96
|
+
:event => name,
|
104
97
|
:properties => properties
|
105
98
|
})
|
106
99
|
end
|
@@ -109,22 +102,21 @@ module Rudder
|
|
109
102
|
#
|
110
103
|
# - "name"
|
111
104
|
# - "properties"
|
112
|
-
# - "category" (Not in spec, retained for backward compatibility"
|
113
105
|
def parse_for_screen(fields)
|
114
106
|
common = parse_common_fields(fields)
|
115
107
|
|
116
108
|
name = fields[:name]
|
117
|
-
properties =
|
109
|
+
properties = common[:properties] || {}
|
110
|
+
properties = properties.merge({ :name => name })
|
111
|
+
|
118
112
|
category = fields[:category]
|
119
113
|
|
120
114
|
check_presence!(name, 'name')
|
121
|
-
check_is_hash!(properties, 'properties')
|
122
|
-
|
123
|
-
isoify_dates! properties
|
124
115
|
|
125
116
|
parsed = common.merge({
|
126
117
|
:type => 'screen',
|
127
118
|
:name => name,
|
119
|
+
:event => name,
|
128
120
|
:properties => properties
|
129
121
|
})
|
130
122
|
|
@@ -135,40 +127,68 @@ module Rudder
|
|
135
127
|
|
136
128
|
private
|
137
129
|
|
138
|
-
def parse_common_fields(fields)
|
139
|
-
timestamp = fields[:timestamp] || Time.new
|
140
|
-
message_id = fields[:message_id].to_s if fields[:message_id]
|
141
|
-
context = fields[:context] || {}
|
142
|
-
|
130
|
+
def parse_common_fields(fields) # rubocop:disable Metrics/AbcSize Metrics/CyclomaticComplexity Metrics/PerceivedComplexity
|
143
131
|
check_user_id! fields
|
132
|
+
|
133
|
+
current_time = Time.now.utc
|
134
|
+
timestamp = fields[:timestamp] || current_time
|
144
135
|
check_timestamp! timestamp
|
145
136
|
|
137
|
+
context = fields[:context] || {}
|
138
|
+
delete_library_from_context! context
|
146
139
|
add_context! context
|
147
140
|
|
148
141
|
parsed = {
|
149
142
|
:context => context,
|
150
|
-
:
|
151
|
-
:timestamp => datetime_in_iso8601(timestamp)
|
143
|
+
:integrations => fields[:integrations] || { :All => true },
|
144
|
+
:timestamp => datetime_in_iso8601(timestamp),
|
145
|
+
:sentAt => datetime_in_iso8601(current_time),
|
146
|
+
:messageId => fields[:message_id] || uid,
|
147
|
+
:channel => 'server'
|
152
148
|
}
|
153
149
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
150
|
+
# add the userId if present
|
151
|
+
if fields[:user_id]
|
152
|
+
check_string(fields[:user_id], 'user_id')
|
153
|
+
parsed = parsed.merge({ :userId => fields[:user_id] })
|
154
|
+
end
|
155
|
+
# add the anonymousId if present
|
156
|
+
if fields[:anonymous_id]
|
157
|
+
check_string(fields[:anonymous_id], 'anonymous_id')
|
158
|
+
parsed = parsed.merge({ :anonymousId => fields[:anonymous_id] })
|
159
|
+
end
|
160
|
+
# add the properties if present
|
161
|
+
if fields[:properties]
|
162
|
+
properties = fields[:properties]
|
163
|
+
check_is_hash!(properties, 'properties')
|
164
|
+
isoify_dates! properties
|
165
|
+
parsed = parsed.merge({ :properties => properties })
|
166
|
+
end
|
167
|
+
# add the traits if present
|
168
|
+
if fields[:traits]
|
169
|
+
traits = fields[:traits]
|
170
|
+
check_is_hash!(traits, 'traits')
|
171
|
+
isoify_dates! traits
|
172
|
+
parsed = parsed.merge({ :traits => traits })
|
173
|
+
end
|
161
174
|
parsed
|
162
175
|
end
|
163
176
|
|
164
177
|
def check_user_id!(fields)
|
165
|
-
|
178
|
+
return unless blank?(fields[:user_id])
|
179
|
+
return unless blank?(fields[:anonymous_id])
|
180
|
+
|
181
|
+
raise ArgumentError, 'Must supply either user_id or anonymous_id'
|
166
182
|
end
|
167
183
|
|
168
184
|
def check_timestamp!(timestamp)
|
169
185
|
raise ArgumentError, 'Timestamp must be a Time' unless timestamp.is_a? Time
|
170
186
|
end
|
171
187
|
|
188
|
+
def delete_library_from_context!(context)
|
189
|
+
context.delete(:library) if context # rubocop:disable Style/SafeNavigation
|
190
|
+
end
|
191
|
+
|
172
192
|
def add_context!(context)
|
173
193
|
context[:library] = { :name => 'rudderanalytics-ruby', :version => Rudder::Analytics::VERSION.to_s }
|
174
194
|
end
|
@@ -178,7 +198,11 @@ module Rudder
|
|
178
198
|
# obj - String|Number that must be non-blank
|
179
199
|
# name - Name of the validated value
|
180
200
|
def check_presence!(obj, name)
|
181
|
-
raise ArgumentError, "#{name} must be given" if
|
201
|
+
raise ArgumentError, "#{name} must be given" if blank?(obj)
|
202
|
+
end
|
203
|
+
|
204
|
+
def blank?(obj)
|
205
|
+
obj.nil? || (obj.is_a?(String) && obj.empty?)
|
182
206
|
end
|
183
207
|
|
184
208
|
def check_is_hash!(obj, name)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rudder
|
4
|
+
class Analytics
|
5
|
+
class TestQueue
|
6
|
+
attr_reader :messages
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
reset!
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
all[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def count
|
17
|
+
all.count
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(message)
|
21
|
+
all << message
|
22
|
+
send(message[:type]) << message
|
23
|
+
end
|
24
|
+
|
25
|
+
def alias
|
26
|
+
messages[:alias] ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def all
|
30
|
+
messages[:all] ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def group
|
34
|
+
messages[:group] ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def identify
|
38
|
+
messages[:identify] ||= []
|
39
|
+
end
|
40
|
+
|
41
|
+
def page
|
42
|
+
messages[:page] ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def screen
|
46
|
+
messages[:screen] ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
def track
|
50
|
+
messages[:track] ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
def reset!
|
54
|
+
@messages = {}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -8,50 +8,39 @@ require 'rudder/analytics/backoff_policy'
|
|
8
8
|
require 'net/http'
|
9
9
|
require 'net/https'
|
10
10
|
require 'json'
|
11
|
-
require 'pry'
|
12
11
|
require 'uri'
|
12
|
+
require 'zlib'
|
13
13
|
|
14
14
|
module Rudder
|
15
15
|
class Analytics
|
16
|
-
class
|
16
|
+
class Transport
|
17
17
|
include Rudder::Analytics::Defaults::Request
|
18
18
|
include Rudder::Analytics::Utils
|
19
19
|
include Rudder::Analytics::Logging
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
def initialize(
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@
|
28
|
-
|
29
|
-
|
30
|
-
@backoff_policy =
|
31
|
-
options[:backoff_policy] || Rudder::Analytics::BackoffPolicy.new
|
32
|
-
|
33
|
-
uri = URI(options[:data_plane_url] || DATA_PLANE_URL)
|
34
|
-
# printf("************\n")
|
35
|
-
# printf("************\n")
|
36
|
-
# printf(options[:data_plane_url] || DATA_PLANE_URL)
|
37
|
-
# printf("\n************\n")
|
38
|
-
# printf(uri.host)
|
39
|
-
# printf("\n************\n")
|
40
|
-
# printf(uri.port.to_s)
|
41
|
-
# printf("************\n")
|
21
|
+
attr_reader :stub
|
22
|
+
|
23
|
+
def initialize(config)
|
24
|
+
@stub = config.stub || false
|
25
|
+
@path = PATH
|
26
|
+
@retries = config.retries || RETRIES
|
27
|
+
@backoff_policy = config.backoff_policy || Rudder::Analytics::BackoffPolicy.new
|
28
|
+
|
29
|
+
uri = URI(config.data_plane_url)
|
42
30
|
|
43
31
|
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
-
http.use_ssl =
|
32
|
+
http.use_ssl = config.ssl || true
|
45
33
|
http.read_timeout = 8
|
46
34
|
http.open_timeout = 4
|
47
35
|
|
48
36
|
@http = http
|
37
|
+
@gzip = config.gzip.nil? ? true : config.gzip
|
49
38
|
end
|
50
39
|
|
51
|
-
#
|
40
|
+
# Sends a batch of messages to the API
|
52
41
|
#
|
53
|
-
#
|
54
|
-
def
|
42
|
+
# @return [Response] API response
|
43
|
+
def send(write_key, batch)
|
55
44
|
logger.debug("Sending request for #{batch.length} items")
|
56
45
|
|
57
46
|
last_response, exception = retry_with_backoff(@retries) do
|
@@ -81,6 +70,11 @@ module Rudder
|
|
81
70
|
end
|
82
71
|
end
|
83
72
|
|
73
|
+
# Closes a persistent connection if it exists
|
74
|
+
def shutdown
|
75
|
+
@http.finish if @http.started?
|
76
|
+
end
|
77
|
+
|
84
78
|
private
|
85
79
|
|
86
80
|
def should_retry_request?(status_code, body)
|
@@ -126,32 +120,34 @@ module Rudder
|
|
126
120
|
|
127
121
|
# Sends a request for the batch, returns [status_code, body]
|
128
122
|
def send_request(write_key, batch)
|
129
|
-
|
130
|
-
:sentAt => datetime_in_iso8601(Time.now),
|
131
|
-
:batch => batch
|
132
|
-
)
|
133
|
-
request = Net::HTTP::Post.new(@path, @headers)
|
134
|
-
request.basic_auth(write_key, nil)
|
135
|
-
|
136
|
-
if self.class.stub
|
123
|
+
if stub
|
137
124
|
logger.debug "stubbed request to #{@path}: " \
|
138
125
|
"write key = #{write_key}, batch = #{JSON.generate(batch)}"
|
139
126
|
|
140
127
|
[200, '{}']
|
141
128
|
else
|
142
|
-
|
129
|
+
payload = {
|
130
|
+
:batch => batch
|
131
|
+
}
|
132
|
+
|
133
|
+
headers = HEADERS
|
134
|
+
|
135
|
+
if @gzip
|
136
|
+
gzip = Zlib::GzipWriter.new(StringIO.new)
|
137
|
+
gzip << payload.to_json
|
138
|
+
payload = gzip.close.string
|
139
|
+
else
|
140
|
+
headers.delete('Content-Encoding')
|
141
|
+
payload = JSON.generate(payload)
|
142
|
+
end
|
143
|
+
|
144
|
+
request = Net::HTTP::Post.new(@path, headers)
|
145
|
+
request.basic_auth(write_key, nil)
|
146
|
+
@http.start unless @http.started? # Maintain a persistent connection
|
143
147
|
response = @http.request(request, payload)
|
144
148
|
[response.code.to_i, response.body]
|
145
149
|
end
|
146
150
|
end
|
147
|
-
|
148
|
-
class << self
|
149
|
-
attr_writer :stub
|
150
|
-
|
151
|
-
def stub
|
152
|
-
@stub || ENV['STUB']
|
153
|
-
end
|
154
|
-
end
|
155
151
|
end
|
156
152
|
end
|
157
153
|
end
|
@@ -10,9 +10,7 @@ module Rudder
|
|
10
10
|
# public: Return a new hash with keys converted from strings to symbols
|
11
11
|
#
|
12
12
|
def symbolize_keys(hash)
|
13
|
-
hash.
|
14
|
-
memo[k.to_sym] = v
|
15
|
-
end
|
13
|
+
hash.transform_keys(&:to_sym)
|
16
14
|
end
|
17
15
|
|
18
16
|
# public: Convert hash keys from strings to symbols in place
|
@@ -50,7 +48,7 @@ module Rudder
|
|
50
48
|
arr = SecureRandom.random_bytes(16).unpack('NnnnnN')
|
51
49
|
arr[2] = (arr[2] & 0x0fff) | 0x4000
|
52
50
|
arr[3] = (arr[3] & 0x3fff) | 0x8000
|
53
|
-
'%08x-%04x-%04x-%04x-%04x%08x' % arr
|
51
|
+
'%08x-%04x-%04x-%04x-%04x%08x' % arr # rubocop:disable Style/FormatStringToken
|
54
52
|
end
|
55
53
|
|
56
54
|
def datetime_in_iso8601(datetime)
|
@@ -66,10 +64,8 @@ module Rudder
|
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
69
|
-
def time_in_iso8601(time
|
70
|
-
|
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.%3N')}#{formatted_offset(time, true, 'Z')}"
|
73
69
|
end
|
74
70
|
|
75
71
|
def date_in_iso8601(date)
|
@@ -81,7 +77,11 @@ module Rudder
|
|
81
77
|
end
|
82
78
|
|
83
79
|
def seconds_to_utc_offset(seconds, colon = true)
|
84
|
-
(colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds
|
80
|
+
(colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON) % [(seconds.negative? ? '-' : '+'), (seconds.abs / 3600), ((seconds.abs % 3600) / 60)]
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_string(obj, name)
|
84
|
+
raise ArgumentError, "#{name} must be a String" unless obj.is_a? String
|
85
85
|
end
|
86
86
|
|
87
87
|
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'rudder/analytics/defaults'
|
4
4
|
require 'rudder/analytics/message_batch'
|
5
|
-
require 'rudder/analytics/
|
5
|
+
require 'rudder/analytics/transport'
|
6
6
|
require 'rudder/analytics/utils'
|
7
7
|
|
8
8
|
module Rudder
|
@@ -23,16 +23,15 @@ module Rudder
|
|
23
23
|
# batch_size - Fixnum of how many items to send in a batch
|
24
24
|
# on_error - Proc of what to do on an error
|
25
25
|
#
|
26
|
-
def initialize(queue,
|
27
|
-
symbolize_keys! options
|
26
|
+
def initialize(queue, config)
|
28
27
|
@queue = queue
|
29
|
-
@data_plane_url = data_plane_url
|
30
|
-
@write_key = write_key
|
31
|
-
@ssl =
|
32
|
-
@on_error =
|
33
|
-
|
34
|
-
@batch = MessageBatch.new(batch_size)
|
28
|
+
@data_plane_url = config.data_plane_url
|
29
|
+
@write_key = config.write_key
|
30
|
+
@ssl = config.ssl
|
31
|
+
@on_error = config.on_error
|
32
|
+
@batch = MessageBatch.new(config.batch_size)
|
35
33
|
@lock = Mutex.new
|
34
|
+
@transport = Transport.new(config)
|
36
35
|
end
|
37
36
|
|
38
37
|
# public: Continuously runs the loop to check for new events
|
@@ -45,11 +44,14 @@ module Rudder
|
|
45
44
|
consume_message_from_queue! until @batch.full? || @queue.empty?
|
46
45
|
end
|
47
46
|
|
48
|
-
res = Request.new(:data_plane_url => @data_plane_url, :ssl => @ssl).post @write_key, @batch
|
47
|
+
# res = Request.new(:data_plane_url => @data_plane_url, :ssl => @ssl).post @write_key, @batch
|
48
|
+
res = @transport.send @write_key, @batch
|
49
49
|
@on_error.call(res.status, res.error) unless res.status == 200
|
50
50
|
|
51
51
|
@lock.synchronize { @batch.clear }
|
52
52
|
end
|
53
|
+
ensure
|
54
|
+
@transport.shutdown
|
53
55
|
end
|
54
56
|
|
55
57
|
# public: Check whether we have outstanding requests.
|
data/lib/rudder/analytics.rb
CHANGED
@@ -6,7 +6,7 @@ require 'rudder/analytics/utils'
|
|
6
6
|
require 'rudder/analytics/field_parser'
|
7
7
|
require 'rudder/analytics/client'
|
8
8
|
require 'rudder/analytics/worker'
|
9
|
-
require 'rudder/analytics/
|
9
|
+
require 'rudder/analytics/transport'
|
10
10
|
require 'rudder/analytics/response'
|
11
11
|
require 'rudder/analytics/logging'
|
12
12
|
|
@@ -20,7 +20,6 @@ module Rudder
|
|
20
20
|
# @option options [Boolean] :stub (false) If true, requests don't hit the
|
21
21
|
# server and are stubbed to be successful.
|
22
22
|
def initialize(options = {})
|
23
|
-
Request.stub = options[:stub] if options.has_key?(:stub)
|
24
23
|
@client = Rudder::Analytics::Client.new options
|
25
24
|
end
|
26
25
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rudder-sdk-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rudder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|
@@ -80,20 +80,6 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 6.0.2
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: pry
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: 0.9.12.2
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 0.9.12.2
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: oj
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -137,24 +123,26 @@ dependencies:
|
|
137
123
|
- !ruby/object:Gem::Version
|
138
124
|
version: 0.1.4
|
139
125
|
description: The Rudder ruby analytics library
|
140
|
-
email:
|
126
|
+
email: arnab@rudderstack.com
|
141
127
|
executables:
|
142
|
-
-
|
128
|
+
- analytics_rudder.rb
|
143
129
|
extensions: []
|
144
130
|
extra_rdoc_files: []
|
145
131
|
files:
|
146
|
-
- bin/
|
132
|
+
- bin/analytics_rudder.rb
|
147
133
|
- lib/rudder-sdk-ruby.rb
|
148
134
|
- lib/rudder.rb
|
149
135
|
- lib/rudder/analytics.rb
|
150
136
|
- lib/rudder/analytics/backoff_policy.rb
|
151
137
|
- lib/rudder/analytics/client.rb
|
138
|
+
- lib/rudder/analytics/configuration.rb
|
152
139
|
- lib/rudder/analytics/defaults.rb
|
153
140
|
- lib/rudder/analytics/field_parser.rb
|
154
141
|
- lib/rudder/analytics/logging.rb
|
155
142
|
- lib/rudder/analytics/message_batch.rb
|
156
|
-
- lib/rudder/analytics/request.rb
|
157
143
|
- lib/rudder/analytics/response.rb
|
144
|
+
- lib/rudder/analytics/test_queue.rb
|
145
|
+
- lib/rudder/analytics/transport.rb
|
158
146
|
- lib/rudder/analytics/utils.rb
|
159
147
|
- lib/rudder/analytics/version.rb
|
160
148
|
- lib/rudder/analytics/worker.rb
|
@@ -177,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
177
165
|
- !ruby/object:Gem::Version
|
178
166
|
version: '0'
|
179
167
|
requirements: []
|
180
|
-
rubygems_version: 3.0.3
|
168
|
+
rubygems_version: 3.0.3.1
|
181
169
|
signing_key:
|
182
170
|
specification_version: 4
|
183
171
|
summary: Rudder analytics library
|
data/bin/analytics-rudder.rb
DELETED
@@ -1,111 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'rudder/analytics'
|
4
|
-
require 'rubygems'
|
5
|
-
require 'commander/import'
|
6
|
-
require 'time'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
program :name, 'simulator.rb'
|
10
|
-
program :version, '0.0.1'
|
11
|
-
program :description, 'scripting simulator'
|
12
|
-
|
13
|
-
def json_hash(str)
|
14
|
-
if str
|
15
|
-
return JSON.parse(str)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# analytics -method=<method> -segment-write-key=<segmentWriteKey> [options]
|
20
|
-
# Run in dev with ruby -ilib bin/analytics -method=<method> -segment-write-key=<segmentWriteKey> [options]
|
21
|
-
|
22
|
-
default_command :send
|
23
|
-
|
24
|
-
command :send do |c|
|
25
|
-
c.description = 'send a segment message'
|
26
|
-
|
27
|
-
c.option '--writeKey=<writeKey>', String, 'the Rudder writeKey'
|
28
|
-
c.option '--dataPlaneUrl=<dataPlaneUrl>', String, 'the Rudder data plane URL'
|
29
|
-
c.option '--type=<type>', String, 'The Segment message type'
|
30
|
-
|
31
|
-
c.option '--userId=<userId>', String, 'the user id to send the event as'
|
32
|
-
c.option '--anonymousId=<anonymousId>', String, 'the anonymous user id to send the event as'
|
33
|
-
c.option '--context=<context>', 'additional context for the event (JSON-encoded)'
|
34
|
-
c.option '--integrations=<integrations>', 'additional integrations for the event (JSON-encoded)'
|
35
|
-
|
36
|
-
c.option '--event=<event>', String, 'the event name to send with the event'
|
37
|
-
c.option '--properties=<properties>', 'the event properties to send (JSON-encoded)'
|
38
|
-
|
39
|
-
c.option '--name=<name>', 'name of the screen or page to send with the message'
|
40
|
-
|
41
|
-
c.option '--traits=<traits>', 'the identify/group traits to send (JSON-encoded)'
|
42
|
-
|
43
|
-
c.option '--groupId=<groupId>', String, 'the group id'
|
44
|
-
c.option '--previousId=<previousId>', String, 'the previous id'
|
45
|
-
|
46
|
-
c.action do |args, options|
|
47
|
-
Analytics = Rudder::Analytics.new({
|
48
|
-
write_key: options.writeKey,
|
49
|
-
data_plane_url: options.dataPlaneUrl,
|
50
|
-
on_error: Proc.new { |status, msg| print msg }
|
51
|
-
})
|
52
|
-
|
53
|
-
case options.type
|
54
|
-
when "track"
|
55
|
-
Analytics.track({
|
56
|
-
user_id: options.userId,
|
57
|
-
event: options.event,
|
58
|
-
anonymous_id: options.anonymousId,
|
59
|
-
properties: json_hash(options.properties),
|
60
|
-
context: json_hash(options.context),
|
61
|
-
integrations: json_hash(options.integrations)
|
62
|
-
})
|
63
|
-
when "page"
|
64
|
-
Analytics.page({
|
65
|
-
user_id: options.userId,
|
66
|
-
anonymous_id: options.anonymousId,
|
67
|
-
name: options.name,
|
68
|
-
properties: json_hash(options.properties),
|
69
|
-
context: json_hash(options.context),
|
70
|
-
integrations: json_hash(options.integrations)
|
71
|
-
})
|
72
|
-
when "screen"
|
73
|
-
Analytics.screen({
|
74
|
-
user_id: options.userId,
|
75
|
-
anonymous_id: options.anonymousId,
|
76
|
-
name: options.name,
|
77
|
-
properties: json_hash(options.properties),
|
78
|
-
context: json_hash(options.context),
|
79
|
-
integrations: json_hash(options.integrations)
|
80
|
-
})
|
81
|
-
when "identify"
|
82
|
-
Analytics.identify({
|
83
|
-
user_id: options.userId,
|
84
|
-
anonymous_id: options.anonymousId,
|
85
|
-
traits: json_hash(options.traits),
|
86
|
-
context: json_hash(options.context),
|
87
|
-
integrations: json_hash(options.integrations)
|
88
|
-
})
|
89
|
-
when "group"
|
90
|
-
Analytics.group({
|
91
|
-
user_id: options.userId,
|
92
|
-
anonymous_id: options.anonymousId,
|
93
|
-
group_id: options.groupId,
|
94
|
-
traits: json_hash(options.traits),
|
95
|
-
context: json_hash(options.context),
|
96
|
-
integrations: json_hash(options.integrations)
|
97
|
-
})
|
98
|
-
when "alias"
|
99
|
-
Analytics.alias({
|
100
|
-
previous_id: options.previousId,
|
101
|
-
user_id: options.userId,
|
102
|
-
anonymous_id: options.anonymousId,
|
103
|
-
context: json_hash(options.context),
|
104
|
-
integrations: json_hash(options.integrations)
|
105
|
-
})
|
106
|
-
else
|
107
|
-
raise "Invalid Message Type #{options.type}"
|
108
|
-
end
|
109
|
-
Analytics.flush
|
110
|
-
end
|
111
|
-
end
|