framed_rails 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,13 +1,12 @@
1
1
  `framed_rails`
2
2
  ------------
3
3
 
4
- `framed_rails` is a gem to add Framed instrumentation to your rails4
5
- app. For each pageview, it sends an event to Framed.
4
+ `framed_rails` is a gem to add Framed instrumentation to your Rails 4
5
+ app. For each request, it sends an event to Framed.
6
6
 
7
- To use this with rails:
7
+ To use this in your Rails project:
8
8
 
9
-
10
- * Add `framed_rails` to your Gemfile.
9
+ * Add `gem 'framed_rails', '~> 0.1.4'` to your Gemfile.
11
10
  * Add the following to `config/initializers/framed_rails.rb`:
12
11
 
13
12
  ```ruby
@@ -18,18 +17,7 @@ Framed.configure do |config|
18
17
  end
19
18
  ```
20
19
 
21
- Note that the threaded emitter works on a background thread. It is
22
- possible that events will be unreported when your containing process
23
- ends, unless you drain at process shutdown, i.e.
24
-
25
- ```ruby
26
- Framed.drain
27
- ```
28
-
29
- If reporting fails, the exception will be logged to STDERR by default,
30
- unless Rails is detected. In that case, the `Rails.logger` will be
31
- used.
32
-
20
+ If reporting fails, the exception will be logged to `Rails.logger` by default.
33
21
 
34
22
  Configuration
35
23
  -------------
@@ -45,63 +33,70 @@ Configuration
45
33
  <td>:consumer</td>
46
34
  <td>The emitter to be used for reporting. See the Emitters
47
35
  section below.</td>
48
- <td>Framed::Emitters::Blocking</td>
49
- </tr>
50
-
51
- <tr>
52
- <td>:endpoint</td>
53
- <td>The URL to POST to, using basic auth with your write key</td>
54
- <td>Framed::SEGMENT_API</td>
36
+ <td>`Framed::Emitters::Blocking`</td>
55
37
  </tr>
56
38
 
57
39
  <tr>
58
40
  <td>:user_id_controller_method</td>
59
41
  <td>The name of a controller method which returns the user ID, if
60
42
  any</td>
61
- <td>'framed_devise_user_id'</td>
43
+ <td>`framed_current_user_id`</td>
62
44
  </tr>
63
45
 
64
46
  <tr>
65
47
  <td>:logger</td>
66
48
  <td>A Logger for reporting errors.</td>
67
- <td>STDERR, or Rails.logger if detected.</td>
49
+ <td>`Rails.logger`</td>
68
50
  </tr>
69
51
 
70
52
  <tr>
71
53
  <td>:anonymous_cookie</td>
72
54
  <td>The name of the in signed cookie for anonymous user IDs.
73
55
  Long-lived anonymous user IDs are issued anonymous users.</td>
74
- <td>Framed::COOKIE_NAME</td>
56
+ <td>`Framed::COOKIE_NAME`</td>
75
57
  </tr>
76
58
 
77
59
  <tr>
78
60
  <td>:user_id_controller_method</td>
79
61
  <td>The name of a controller method which can provide the current
80
- User ID. Devise just works.</td>
81
- <td>'framed_devise_user_id'</td>
62
+ User ID. (Also works with Devise).</td>
63
+ <td>'framed_current_user_id'</td>
82
64
  </tr>
65
+
66
+ <tr>
67
+ <td>:include_xhr</td>
68
+ <td>Whether to include requests sent via AJAX. (Turbolinks are always included.)</td>
69
+ <td>false</td>
70
+ </tr>
71
+
83
72
  </table>
84
73
 
85
74
  Emitters
86
75
  --------
87
76
 
88
- By default, the report is sent with a blocking Emitter. If you would
89
- prefer a non-blocking emitter, you can include the following line in
77
+ By default, events are sent with a blocking emitter, which sends each request to Framed
78
+ as it happens. If you would prefer a non-blocking emitter, you can include the following line in
90
79
  your configure block:
91
80
 
92
81
 
93
82
  ```ruby
94
- config[:consumer] => Framed::Emitters::Threaded
83
+ config[:consumer] = Framed::Emitters::Buffered
95
84
  ```
96
85
 
97
- If you'd like to implement your own, see `lib/framed/emitters.rb`,
98
- particularly Base and Blocking as examples.
99
-
100
86
  Emitters included in this gem:
101
87
 
88
+ * `Framed::Emitters::Blocking` - Logs each request to Framed using a single blocking request (default)
89
+ * `Framed::Emitters::Buffered` - Logs to Framed 1) if no request is in progress, immediately 2) otherwise in batches of up to 100 as soon as the previous request completes. All requests are sent on a background thread.
102
90
  * `Framed::Emitters::InMemory` - stores reported events in memory,
103
- rather than transmitting them. Events are later available as
104
- Framed.consumer.reported.
105
- * `Framed::Emitters::Logger` - Logs an info message to config[:logger].
106
- * `Framed::Emitters::Blocking` - Logs to Framed, using a blocking request.
107
- * `Framed::Emitters::Threaded` - Logs to Framed, using a background-threaded request.
91
+ rather than transmitting them. Events are later available as `Framed.consumer.reported`.
92
+ * `Framed::Emitters::Logger` - Logs an info message to `config[:logger]`.
93
+
94
+ Both `InMemory` and `Logger` should be considered for debugging/diagnostic purposes only.
95
+
96
+ Note that the `Buffered` emitter works on a background thread. It is
97
+ possible that events will be unreported when your containing process
98
+ ends, unless you explicitly call `#drain` at process shutdown, i.e.
99
+
100
+ ```ruby
101
+ Framed.drain
102
+ ```
data/lib/framed/client.rb CHANGED
@@ -16,6 +16,8 @@ module Framed
16
16
  end
17
17
 
18
18
  def track(data)
19
+ Framed.logger.info("Client#track #{data.length}")
20
+
19
21
  creds = Base64.strict_encode64(@config[:api_key] + ':')
20
22
  payload = JSON.generate(data)
21
23
  response = Excon.post(@config[:endpoint],
@@ -21,10 +21,12 @@ module Framed
21
21
 
22
22
  private
23
23
 
24
- def transmit(event)
24
+ def transmit(events)
25
+ return unless events && events.length > 0
26
+
25
27
  begin
26
- @client.track(event)
27
- rescue StandardError => exc
28
+ @client.track(events)
29
+ rescue Exception => exc
28
30
  Framed.logger.error("framed_rails: transmit failed: #{exc}")
29
31
  end
30
32
  end
@@ -49,64 +51,136 @@ module Framed
49
51
  end
50
52
  end
51
53
 
52
- class Threaded < Base
54
+ class Blocking < Base
55
+ def start
56
+ end
57
+ def stop(drain = false)
58
+ end
59
+ def enqueue(event)
60
+ transmit([event])
61
+ end
62
+ end
63
+
64
+ class Buffered < Base
65
+ MAX_REQUEST_BATCH_SIZE = 100
66
+ MAX_QUEUE_SIZE = 10_000
67
+
53
68
  def initialize(client)
54
69
  super
55
- @queue = Queue.new
56
- end
70
+ @event_queue = Queue.new
71
+ @batch_lock = Mutex.new
57
72
 
58
- def enqueue(event)
59
- @queue << event
60
- start
73
+ @request_queue = Queue.new
74
+ @request_pending = Mutex.new
75
+
76
+ @request_thread = nil
61
77
  end
62
78
 
63
79
  def start
64
- return if @thread
80
+ if @request_thread and !@request_thread.alive?
81
+ Framed.logger.error("Starting request thread due to dead thread")
82
+ end
65
83
 
66
- @thread = Thread.new do
84
+ @request_thread = Thread.new do
67
85
  while true
68
- begin
69
- process_events
70
- rescue StandardError => exc
71
- Framed.logger.error("framed_rails: run_thread failed: #{exc}")
72
- stop
86
+ pending = @request_queue.pop
87
+
88
+ @request_pending.synchronize do
89
+ transmit(pending)
73
90
  end
74
- sleep(0.5)
91
+
92
+ start_request
75
93
  end
76
94
  end
77
95
  end
78
96
 
79
97
  def stop(drain = false)
80
- if @thread
81
- @thread.kill
98
+ if drain
99
+ # start batch requests if needed
100
+ while @event_queue.length > 0
101
+ start_request
102
+ end
103
+
104
+ # wait for pending requests if needed:
105
+ while @request_queue.length > 0
106
+ sleep(0.1)
107
+ end
108
+
109
+ # and wait for the final request, if needed
110
+ @request_pending.synchronize do
111
+ stop_request_thread
112
+ end
113
+ else
114
+ stop_request_thread
82
115
  end
83
- @thread = nil
116
+ end
117
+
118
+ def warn_full(event)
119
+ Framed.logger.error("Queued #{event} to framed, but queue is full. Dropping event.")
120
+ end
121
+
122
+ def enqueue(event)
123
+ queue_full = false
124
+ @batch_lock.synchronize do
125
+ # To avoid logging inside the lock (since loggers can block)
126
+ # we remember if the queue is full and log outside the lock.
127
+ queue_full = @event_queue.length >= MAX_QUEUE_SIZE
128
+
129
+ if !queue_full
130
+ @event_queue << event
131
+ end
84
132
 
85
- if drain && @queue.length
86
- process_events
133
+ # don't start a new request if one is already in progress.
134
+ if @request_pending.locked?
135
+ return
136
+ end
87
137
  end
138
+
139
+ warn_full if queue_full
140
+
141
+ start_request
88
142
  end
89
143
 
90
144
  private
91
145
 
92
- def dequeue
93
- @queue.pop
94
- end
146
+ def ensure_request_thread
147
+ return if @request_thread && @request_thread.alive?
95
148
 
96
- def process_events
97
- while @queue.length > 0
98
- transmit(dequeue)
149
+ @request_pending.synchronize do
150
+ start
99
151
  end
100
152
  end
101
- end
102
153
 
103
- class Blocking < Base
104
- def start
154
+ def stop_request_thread
155
+ if @request_thread
156
+ @request_thread.kill
157
+ @request_thread = nil
158
+ end
105
159
  end
106
- def stop
160
+
161
+ def start_request
162
+ ensure_request_thread
163
+
164
+ @batch_lock.synchronize do
165
+ return if @event_queue.empty?
166
+
167
+ pending = []
168
+ while pending.length < MAX_REQUEST_BATCH_SIZE && @event_queue.length > 0
169
+ pending << @event_queue.pop
170
+ end
171
+
172
+ @request_queue << pending
173
+ end
107
174
  end
108
- def enqueue(event)
109
- transmit(event)
175
+
176
+ def transmit(events)
177
+ return unless events && events.length > 0
178
+
179
+ begin
180
+ @client.track(events)
181
+ rescue Exception => exc
182
+ Framed.logger.error("framed_rails: transmit failed: #{exc}")
183
+ end
110
184
  end
111
185
  end
112
186
  end
@@ -10,4 +10,4 @@ module Framed
10
10
 
11
11
  class RequestError < Error
12
12
  end
13
- end
13
+ end
data/lib/framed/rails.rb CHANGED
@@ -1,27 +1,37 @@
1
1
  ActionController::Base.class_eval do
2
2
 
3
- after_filter :framed_report_page_view
3
+ after_filter :framed_report_request
4
4
 
5
- def pv_event_name
5
+ def framed_event_name
6
6
  "#{request.method}_#{params[:controller]}\##{params[:action]}"
7
7
  end
8
8
 
9
- def framed_report_page_view
9
+ def framed_included?(request)
10
+ return true if Framed.configuration[:include_xhr]
11
+ # include Turbolinks requests (which are a special kind of XHR)
12
+ return true if request.headers.include?('X-XHR-Referer')
13
+
14
+ !request.xhr?
15
+ end
16
+
17
+ def framed_report_request
10
18
  begin
11
19
  anonymous_id = cookies.signed[Framed.anonymous_cookie]
12
20
  user_id = send(Framed.user_id_controller_method)
13
21
 
14
22
  if user_id.nil? && anonymous_id.nil?
15
23
  anonymous_id = Framed.new_anonymous_id
16
- cookies.signed.permanent[Framed.anonymous_cookie] = { :value => anonymous_id, :httponly => true}
24
+ cookies.signed.permanent[Framed.anonymous_cookie] = {:value => anonymous_id, :httponly => true}
17
25
  end
18
26
 
27
+ return unless framed_included?(request)
28
+
19
29
  cleaned_params = params.except(:controller, :action).to_h
20
- Framed.report({
30
+ event = {
21
31
  :type => :track,
22
32
  :anonymous_id => anonymous_id,
23
33
  :user_id => user_id,
24
- :event => pv_event_name,
34
+ :event => framed_event_name,
25
35
  :context => {
26
36
  :path => request.path,
27
37
  :request_method => request.method,
@@ -31,13 +41,19 @@ ActionController::Base.class_eval do
31
41
  :properties => Framed::Utils.flattened_hash({
32
42
  :params => cleaned_params
33
43
  })
34
- })
35
- rescue StandardError => exc
36
- Framed.logger.error("Failed to report page_view #{exc}")
44
+ }
45
+
46
+ Framed.report(event)
47
+ rescue Exception => exc
48
+ Framed.logger.error("Failed to report request #{exc}")
37
49
  end
38
50
  end
39
51
 
40
- def framed_devise_user_id
41
- current_user.try(:id)
52
+ def framed_current_user_id
53
+ begin
54
+ current_user.try(:id)
55
+ rescue
56
+ nil
57
+ end
42
58
  end
43
- end
59
+ end
data/lib/framed/utils.rb CHANGED
@@ -8,47 +8,47 @@ end
8
8
 
9
9
  module Framed
10
10
  module Utils
11
- class << self
12
- def uuid
13
- begin
14
- UUID.new.generate
15
- rescue NameError
16
- SecureRandom.uuid
17
- end
18
- end
11
+ extend self
19
12
 
20
- # Adapted from Rails in case it isn't available.
21
- def try(o, *a, &b)
22
- try!(o, *a, &b) if a.empty? || o.respond_to?(a.first)
13
+ def uuid
14
+ begin
15
+ UUID.new.generate
16
+ rescue NameError
17
+ SecureRandom.uuid
23
18
  end
19
+ end
24
20
 
25
- def try!(o, *a, &b)
26
- if a.empty? && block_given?
27
- if b.arity.zero?
28
- o.instance_eval(&b)
29
- else
30
- yield o
31
- end
21
+ # Adapted from Rails in case it isn't available.
22
+ def try(o, *a, &b)
23
+ try!(o, *a, &b) if a.empty? || o.respond_to?(a.first)
24
+ end
25
+
26
+ def try!(o, *a, &b)
27
+ if a.empty? && block_given?
28
+ if b.arity.zero?
29
+ o.instance_eval(&b)
32
30
  else
33
- o.public_send(*a, &b)
31
+ yield o
34
32
  end
33
+ else
34
+ o.public_send(*a, &b)
35
35
  end
36
+ end
36
37
 
37
- def serialize_date(dt)
38
- dt.utc.iso8601
39
- end
38
+ def serialize_date(dt)
39
+ dt.utc.iso8601
40
+ end
40
41
 
41
- def flattened_hash(h, namespace = '', memo = {})
42
- h.reduce(memo) { |memo, (key, value)|
43
- value = value.to_h if value.respond_to?(:to_h)
44
- if value.instance_of?(Hash)
45
- memo.merge!(flattened_hash(value, "#{namespace}#{key}_", memo))
46
- else
47
- memo["#{namespace}#{key}"] = value
48
- end
49
- memo
50
- }
51
- end
42
+ def flattened_hash(h, namespace = '', memo = {})
43
+ h.reduce(memo) { |memo, (key, value)|
44
+ value = value.to_h if value.respond_to?(:to_h)
45
+ if value.instance_of?(Hash)
46
+ memo.merge!(flattened_hash(value, "#{namespace}#{key}_", memo))
47
+ else
48
+ memo["#{namespace}#{key}"] = value
49
+ end
50
+ memo
51
+ }
52
52
  end
53
53
  end
54
- end
54
+ end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Framed
3
- VERSION = "0.1.3"
3
+ VERSION = "0.1.4"
4
4
  end
data/lib/framed_rails.rb CHANGED
@@ -19,10 +19,11 @@ module Framed
19
19
  def configuration
20
20
  @configuration ||= {
21
21
  :consumer => Framed::Emitters::Blocking,
22
- :user_id_controller_method => 'framed_devise_user_id',
22
+ :user_id_controller_method => 'framed_current_user_id',
23
23
  :endpoint => Framed::FRAMED_API_ENDPOINT,
24
24
  :logger => Logger.new(STDERR),
25
- :anonymous_cookie => Framed::COOKIE_NAME
25
+ :anonymous_cookie => Framed::COOKIE_NAME,
26
+ :include_xhr => false
26
27
  }
27
28
  end
28
29
 
@@ -30,21 +31,23 @@ module Framed
30
31
  yield configuration
31
32
  self.client = Client.new(configuration)
32
33
 
33
- @consumer.stop if @consumer
34
+ @consumer.stop(true) if @consumer
34
35
  @consumer = configuration[:consumer].new(self.client)
35
36
  end
36
37
 
37
38
  def report(event)
38
39
  event[:lib] = "framed_ruby"
39
40
  event[:lib_version] = Framed::VERSION
41
+ event[:type] ||= :track
40
42
  event[:context] ||= {}
41
43
  event[:context].merge!({
42
44
  :channel => 'server',
43
45
  })
44
46
 
47
+ event[:properties] ||= {}
48
+
45
49
  # fill in if needed, in case it sits in queue for a while.
46
50
  event[:timestamp] ||= Framed::Utils.serialize_date(Time.now)
47
-
48
51
  @consumer.enqueue(event)
49
52
  end
50
53
 
@@ -53,7 +56,7 @@ module Framed
53
56
  end
54
57
 
55
58
  def drain
56
- @consumer.stop(true)
59
+ @consumer.stop(true) if @consumer
57
60
  end
58
61
 
59
62
  def user_id_controller_method
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: framed_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-08-13 00:00:00.000000000 Z
12
+ date: 2015-08-26 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! 'TK
15
15
 
@@ -31,7 +31,6 @@ files:
31
31
  - framed_rails.gemspec
32
32
  - lib/framed/client.rb
33
33
  - lib/framed/emitters.rb
34
- - lib/framed/example.rb
35
34
  - lib/framed/exceptions.rb
36
35
  - lib/framed/okjson.rb
37
36
  - lib/framed/rails.rb
@@ -1,17 +0,0 @@
1
- require 'framed_rails'
2
-
3
- Framed.configure do |config|
4
- config[:api_key] = 'XWkzILLq5gLUKXQUhAl4DJej1wkxqiBy'
5
- # config[:consumer] = Framed::Blocking
6
- end
7
-
8
- data = {
9
- anonymousId: "anon1",
10
- userId: "user1",
11
- event: "signup",
12
- properties: {
13
- name: "value"
14
- }
15
- }
16
-
17
- Framed.report(data)