greenfinch-ruby 0.1.0

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.
@@ -0,0 +1,40 @@
1
+ require 'greenfinch-ruby'
2
+ require 'faraday'
3
+
4
+ # The Mixpanel library's default consumer will use the standard
5
+ # Net::HTTP library to communicate with servers, but you can extend
6
+ # your consumers to use other libraries. This example sends data using
7
+ # the Faraday library (so you'll need that library available to run it)
8
+
9
+ class FaradayConsumer < Mixpanel::Consumer
10
+ def request(endpoint, form_data)
11
+ conn = ::Faraday.new(endpoint)
12
+ response = conn.post(nil, form_data)
13
+ [response.status, response.body]
14
+ end
15
+ end
16
+
17
+ if __FILE__ == $0
18
+ # Replace this with the token from your project settings
19
+ DEMO_TOKEN = '072f77c15bd04a5d0044d3d76ced7fea'
20
+ faraday_consumer = FaradayConsumer.new
21
+
22
+ faraday_tracker = Mixpanel::Tracker.new(DEMO_TOKEN) do |type, message|
23
+ faraday_consumer.send!(type, message)
24
+ end
25
+ faraday_tracker.track('ID', 'Event tracked through Faraday')
26
+
27
+ # It's also easy to delegate from a BufferedConsumer to your custom
28
+ # consumer.
29
+
30
+ buffered_faraday_consumer = Mixpanel::BufferedConsumer.new do |type, message|
31
+ faraday_consumer.send!(type, message)
32
+ end
33
+
34
+ buffered_faraday_tracker = Mixpanel::Tracker.new(DEMO_TOKEN) do |type, message|
35
+ buffered_faraday_consumer.send!(type, message)
36
+ end
37
+
38
+ buffered_faraday_tracker.track('ID', 'Event tracked (buffered) through faraday')
39
+ buffered_faraday_consumer.flush
40
+ end
@@ -0,0 +1,71 @@
1
+ require 'greenfinch-ruby'
2
+ require 'thread'
3
+ require 'json'
4
+ require 'securerandom'
5
+
6
+ # As your application scales, it's likely you'll want to
7
+ # to detect events in one place and send them somewhere
8
+ # else. For example, you might write the events to a queue
9
+ # to be consumed by another process.
10
+ #
11
+ # This demo shows how you might do things, using
12
+ # the block constructor in Mixpanel to enqueue events,
13
+ # and a MixpanelBufferedConsumer to send them to
14
+ # Mixpanel
15
+
16
+ # Mixpanel uses the Net::HTTP library, which by default
17
+ # will not verify remote SSL certificates. In your app,
18
+ # you'll need to call Mixpanel.config_http with the path
19
+ # to your Certificate authority resources, or the library
20
+ # won't verify the remote certificate identity.
21
+ Mixpanel.config_http do |http|
22
+ http.ca_path = '/etc/ssl/certs'
23
+ http.ca_file = "/etc/ssl/certs/ca-certificates.crt"
24
+ http.use_ssl = true
25
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
26
+ end
27
+
28
+ class OutOfProcessExample
29
+ class << self
30
+ def run(token, distinct_id)
31
+ open('|-', 'w+') do |subprocess|
32
+ if subprocess
33
+ # This is the tracking process. Once we configure
34
+ # The tracker to write to our subprocess, we can quickly
35
+ # call #track without delaying our other work.
36
+ mixpanel_tracker = Mixpanel::Tracker.new(token) do |*message|
37
+ subprocess.write(message.to_json + "\n")
38
+ end
39
+
40
+ 100.times do |i|
41
+ event = 'Tick'
42
+ mixpanel_tracker.track(distinct_id, event, {'Tick Number' => i})
43
+ puts "tick #{i}"
44
+ end
45
+
46
+ else
47
+ # This is the consumer process. In your applications, code
48
+ # like this may end up in queue consumers or in a separate
49
+ # thread.
50
+ mixpanel_consumer = Mixpanel::BufferedConsumer.new
51
+ begin
52
+ $stdin.each_line do |line|
53
+ message = JSON.load(line)
54
+ type, content = message
55
+ mixpanel_consumer.send!(type, content)
56
+ end
57
+ ensure
58
+ mixpanel_consumer.flush
59
+ end
60
+ end
61
+ end
62
+ end # run
63
+ end
64
+ end
65
+
66
+ if __FILE__ == $0
67
+ # Replace this with the token from your project settings
68
+ DEMO_TOKEN = '072f77c15bd04a5d0044d3d76ced7fea'
69
+ run_id = SecureRandom.base64
70
+ OutOfProcessExample.run(DEMO_TOKEN, run_id)
71
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH << "/Users/terry/Documents/Github/greenfinch-ruby/lib"
2
+ require 'greenfinch-ruby'
3
+
4
+ if __FILE__ == $0
5
+ # Replace this with the token from your project settings
6
+ DEMO_TOKEN = 'eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTE2NzIwMzksImV4cCI6MzY4NTI3MDAwMDAwMCwic2VydmljZV9uYW1lIjoiZmx5Y2F0Y2hlciJ9.lY6RVf1rsaYkuLCLfCs4cKFRNTvcbmZcR2IXEYZkdzw'
7
+ tracker = Greenfinch::Tracker.new(DEMO_TOKEN, 'flycatcher', true)
8
+ tracker.track('ID', 'Script run')
9
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), 'lib/greenfinch-ruby/version.rb')
2
+
3
+ spec = Gem::Specification.new do |spec|
4
+ spec.name = 'greenfinch-ruby'
5
+ spec.version = Greenfinch::VERSION
6
+ spec.files = Dir.glob(`git ls-files`.split("\n"))
7
+ spec.require_paths = ['lib']
8
+ spec.summary = 'Official Greenfinch tracking library for ruby'
9
+ spec.description = 'The official Greenfinch tracking library for ruby'
10
+ spec.authors = [ 'KoreaCreditData' ]
11
+ spec.email = 'terry@kcd.co.kr'
12
+ spec.homepage = 'https://github.com/koreacreditdata/greenfinch-ruby'
13
+ spec.license = 'Apache-2.0'
14
+
15
+ spec.required_ruby_version = '>= 2.0.0'
16
+
17
+ spec.add_development_dependency 'activesupport', '~> 4.0'
18
+ spec.add_development_dependency 'rake', '~> 0'
19
+ spec.add_development_dependency 'rspec', '~> 3.0'
20
+ spec.add_development_dependency 'webmock', '~> 1.18'
21
+ end
@@ -0,0 +1,3 @@
1
+ require 'greenfinch-ruby/consumer.rb'
2
+ require 'greenfinch-ruby/tracker.rb'
3
+ require 'greenfinch-ruby/version.rb'
@@ -0,0 +1,271 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'net/https'
4
+
5
+ module Greenfinch
6
+ @@init_http = nil
7
+
8
+ # This method exists for backwards compatibility. The preferred
9
+ # way to customize or configure the HTTP library of a consumer
10
+ # is to override Consumer#request.
11
+ #
12
+ # Ruby's default SSL does not verify the server certificate.
13
+ # To verify a certificate, or install a proxy, pass a block
14
+ # to Greenfinch.config_http that configures the Net::HTTP object.
15
+ # For example, if running in Ubuntu Linux, you can run
16
+ #
17
+ # Greenfinch.config_http do |http|
18
+ # http.ca_path = '/etc/ssl/certs'
19
+ # http.ca_file = '/etc/ssl/certs/ca-certificates.crt'
20
+ # http.verify_mode = OpenSSL::SSL::VERIFY_PEER
21
+ # end
22
+ #
23
+ # \Greenfinch Consumer and BufferedConsumer will call your block
24
+ # to configure their connections
25
+ def self.config_http(&block)
26
+ @@init_http = block
27
+ end
28
+
29
+ # A Consumer receives messages from a Greenfinch::Tracker, and
30
+ # sends them elsewhere- probably to Greenfinch's analytics services,
31
+ # but can also enqueue them for later processing, log them to a
32
+ # file, or do whatever else you might find useful.
33
+ #
34
+ # You can provide your own consumer to your Greenfinch::Trackers,
35
+ # either by passing in an argument with a #send! method when you construct
36
+ # the tracker, or just passing a block to Greenfinch::Tracker.new
37
+ #
38
+ # tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN) do |type, message|
39
+ # # type will be one of :event, :profile_update or :import
40
+ # @kestrel.set(ANALYTICS_QUEUE, [type, message].to_json)
41
+ # end
42
+ #
43
+ # You can also instantiate the library consumers yourself, and use
44
+ # them wherever you would like. For example, the working that
45
+ # consumes the above queue might work like this:
46
+ #
47
+ # greenfinch = Greenfinch::Consumer
48
+ # while true
49
+ # message_json = @kestrel.get(ANALYTICS_QUEUE)
50
+ # greenfinch.send!(*JSON.load(message_json))
51
+ # end
52
+ #
53
+ # Greenfinch::Consumer is the default consumer. It sends each message,
54
+ # as the message is recieved, directly to Greenfinch.
55
+ class Consumer
56
+
57
+ # Create a Greenfinch::Consumer. If you provide endpoint arguments,
58
+ # they will be used instead of the default Greenfinch endpoints.
59
+ # This can be useful for proxying, debugging, or if you prefer
60
+ # not to use SSL for your events.
61
+ def initialize(events_endpoint=nil,
62
+ update_endpoint=nil,
63
+ groups_endpoint=nil,
64
+ import_endpoint=nil)
65
+
66
+ @events_endpoint = events_endpoint || 'https://event.kcd.partners/api/publish'
67
+ @update_endpoint = update_endpoint || 'https://api.greenfinch.com/engage'
68
+ @groups_endpoint = groups_endpoint || 'https://api.greenfinch.com/groups'
69
+ @import_endpoint = import_endpoint || 'https://api.greenfinch.com/import'
70
+ end
71
+
72
+ # Send the given string message to Greenfinch. Type should be
73
+ # one of :event, :profile_update or :import, which will determine the endpoint.
74
+ #
75
+ # Greenfinch::Consumer#send! sends messages to Greenfinch immediately on
76
+ # each call. To reduce the overall bandwidth you use when communicating
77
+ # with Greenfinch, you can also use Greenfinch::BufferedConsumer
78
+ def send!(type, message)
79
+ type = type.to_sym
80
+ endpoint = {
81
+ :event => @events_endpoint,
82
+ :profile_update => @update_endpoint,
83
+ :group_update => @groups_endpoint,
84
+ :import => @import_endpoint,
85
+ }[type]
86
+
87
+ decoded_message = JSON.load(message)
88
+ jwt_token = decoded_message["jwt_token"]
89
+ service_name = decoded_message["service_name"]
90
+ debug = decoded_message["debug"]
91
+ api_key = decoded_message["api_key"]
92
+
93
+ if debug == true
94
+ endpoint = "https://event-staging.kcd.partners/api/publish/#{service_name}"
95
+ else
96
+ endpoint = "#{endpoint}/#{service_name}"
97
+ end
98
+
99
+ data = decoded_message["data"]
100
+
101
+ form_data = { "data" => data, "verbose" => 1 }
102
+ form_data.merge!("api_key" => api_key) if api_key
103
+
104
+ begin
105
+ response_code, response_body = request(endpoint, form_data, jwt_token)
106
+ rescue => e
107
+ raise ConnectionError.new("Could not connect to Greenfinch, with error \"#{e.message}\".")
108
+ end
109
+
110
+ result = {}
111
+ if response_code.to_i == 200
112
+ begin
113
+ result = JSON.parse(response_body.to_s)
114
+ rescue JSON::JSONError
115
+ raise ServerError.new("Could not interpret Greenfinch server response: '#{response_body}'")
116
+ end
117
+ end
118
+
119
+ if result['status'] != 1
120
+ raise ServerError.new("Could not write to Greenfinch, server responded with #{response_code} returning: '#{response_body}'")
121
+ end
122
+ end
123
+
124
+ # This method was deprecated in release 2.0.0, please use send! instead
125
+ def send(type, message)
126
+ warn '[DEPRECATION] send has been deprecated as of release 2.0.0, please use send! instead'
127
+ send!(type, message)
128
+ end
129
+
130
+ # Request takes an endpoint HTTP or HTTPS url, and a Hash of data
131
+ # to post to that url. It should return a pair of
132
+ #
133
+ # [response code, response body]
134
+ #
135
+ # as the result of the response. Response code should be nil if
136
+ # the request never receives a response for some reason.
137
+ def request(endpoint, form_data, jwt_token=nil)
138
+ uri = URI(endpoint)
139
+
140
+ headers = {
141
+ "jwt" => jwt_token,
142
+ "content-type" => "application/json",
143
+ "label" => "ruby"
144
+ }
145
+
146
+ client = Net::HTTP.new(uri.host, uri.port)
147
+ client.use_ssl = true
148
+ client.open_timeout = 10
149
+ client.continue_timeout = 10
150
+ client.read_timeout = 10
151
+ client.ssl_timeout = 10
152
+
153
+ Greenfinch.with_http(client)
154
+
155
+ response = client.request_post uri.request_uri, form_data.to_json, headers
156
+
157
+ [response.code, response.body]
158
+ end
159
+ end
160
+
161
+ # BufferedConsumer buffers messages in memory, and sends messages as
162
+ # a batch. This can improve performance, but calls to #send! may
163
+ # still block if the buffer is full. If you use this consumer, you
164
+ # should call #flush when your application exits or the messages
165
+ # remaining in the buffer will not be sent.
166
+ #
167
+ # To use a BufferedConsumer directly with a Greenfinch::Tracker,
168
+ # instantiate your Tracker like this
169
+ #
170
+ # buffered_consumer = Greenfinch::BufferedConsumer.new
171
+ # begin
172
+ # buffered_tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN) do |type, message|
173
+ # buffered_consumer.send!(type, message)
174
+ # end
175
+ # # Do some tracking here
176
+ # ...
177
+ # ensure
178
+ # buffered_consumer.flush
179
+ # end
180
+ #
181
+ class BufferedConsumer
182
+ MAX_LENGTH = 50
183
+
184
+ # Create a Greenfinch::BufferedConsumer. If you provide endpoint arguments,
185
+ # they will be used instead of the default Greenfinch endpoints.
186
+ # This can be useful for proxying, debugging, or if you prefer
187
+ # not to use SSL for your events.
188
+ #
189
+ # You can also change the preferred buffer size before the
190
+ # consumer automatically sends its buffered events. The Greenfinch
191
+ # endpoints have a limit of 50 events per HTTP request, but
192
+ # you can lower the limit if your individual events are very large.
193
+ #
194
+ # By default, BufferedConsumer will use a standard Greenfinch
195
+ # consumer to send the events once the buffer is full (or on calls
196
+ # to #flush), but you can override this behavior by passing a
197
+ # block to the constructor, in the same way you might pass a block
198
+ # to the Greenfinch::Tracker constructor. If a block is passed to
199
+ # the constructor, the *_endpoint constructor arguments are
200
+ # ignored.
201
+ def initialize(events_endpoint=nil, update_endpoint=nil, import_endpoint=nil, max_buffer_length=MAX_LENGTH, &block)
202
+ @max_length = [max_buffer_length, MAX_LENGTH].min
203
+ @buffers = {
204
+ :event => [],
205
+ :profile_update => [],
206
+ }
207
+
208
+ if block
209
+ @sink = block
210
+ else
211
+ consumer = Consumer.new(events_endpoint, update_endpoint, import_endpoint)
212
+ @sink = consumer.method(:send!)
213
+ end
214
+ end
215
+
216
+ # Stores a message for Greenfinch in memory. When the buffer
217
+ # hits a maximum length, the consumer will flush automatically.
218
+ # Flushes are synchronous when they occur.
219
+ #
220
+ # Currently, only :event and :profile_update messages are buffered,
221
+ # :import messages will be send immediately on call.
222
+ def send!(type, message)
223
+ type = type.to_sym
224
+
225
+ if @buffers.has_key? type
226
+ @buffers[type] << message
227
+ flush_type(type) if @buffers[type].length >= @max_length
228
+ else
229
+ @sink.call(type, message)
230
+ end
231
+ end
232
+
233
+ # This method was deprecated in release 2.0.0, please use send! instead
234
+ def send(type, message)
235
+ warn '[DEPRECATION] send has been deprecated as of release 2.0.0, please use send! instead'
236
+ send!(type, message)
237
+ end
238
+
239
+ # Pushes all remaining messages in the buffer to Greenfinch.
240
+ # You should call #flush before your application exits or
241
+ # messages may not be sent.
242
+ def flush
243
+ @buffers.keys.each { |k| flush_type(k) }
244
+ end
245
+
246
+ private
247
+
248
+ def flush_type(type)
249
+ sent_messages = 0
250
+ begin
251
+ @buffers[type].each_slice(@max_length) do |chunk|
252
+ data = chunk.map {|message| JSON.load(message)['data'] }
253
+ @sink.call(type, {'data' => data}.to_json)
254
+ sent_messages += chunk.length
255
+ end
256
+ rescue
257
+ @buffers[type].slice!(0, sent_messages)
258
+ raise
259
+ end
260
+ @buffers[type] = []
261
+ end
262
+ end
263
+
264
+ private
265
+
266
+ def self.with_http(http)
267
+ if @@init_http
268
+ @@init_http.call(http)
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,46 @@
1
+ module Greenfinch
2
+
3
+ # Greenfinch specific errors that are thrown in the gem.
4
+ # In the default consumer we catch all errors and raise
5
+ # Greenfinch specific errors that can be handled using a
6
+ # custom error handler.
7
+ class GreenfinchError < StandardError
8
+ end
9
+
10
+ class ConnectionError < GreenfinchError
11
+ end
12
+
13
+ class ServerError < GreenfinchError
14
+ end
15
+
16
+
17
+ # The default behavior of the gem is to silence all errors
18
+ # thrown in the consumer. If you wish to handle GreenfinchErrors
19
+ # yourself you can pass an instance of a class that extends
20
+ # Greenfinch::ErrorHandler to Greenfinch::Tracker on initialize.
21
+ #
22
+ # require 'logger'
23
+ #
24
+ # class MyErrorHandler < Greenfinch::ErrorHandler
25
+ #
26
+ # def initialize
27
+ # @logger = Logger.new('mylogfile.log')
28
+ # @logger.level = Logger::ERROR
29
+ # end
30
+ #
31
+ # def handle(error)
32
+ # logger.error "#{error.inspect}\n Backtrace: #{error.backtrace}"
33
+ # end
34
+ #
35
+ # end
36
+ #
37
+ # my_error_handler = MyErrorHandler.new
38
+ # tracker = Greenfinch::Tracker.new(YOUR_GREENFINCH_TOKEN, my_error_handler)
39
+ class ErrorHandler
40
+
41
+ # Override #handle to customize error handling
42
+ def handle(error)
43
+ false
44
+ end
45
+ end
46
+ end