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 +35 -40
- data/lib/framed/client.rb +2 -0
- data/lib/framed/emitters.rb +108 -34
- data/lib/framed/exceptions.rb +1 -1
- data/lib/framed/rails.rb +28 -12
- data/lib/framed/utils.rb +34 -34
- data/lib/framed/version.rb +1 -1
- data/lib/framed_rails.rb +8 -5
- metadata +2 -3
- data/lib/framed/example.rb +0 -17
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
|
5
|
-
app.
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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.
|
81
|
-
<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,
|
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
|
-
|
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.
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
data/lib/framed/emitters.rb
CHANGED
@@ -21,10 +21,12 @@ module Framed
|
|
21
21
|
|
22
22
|
private
|
23
23
|
|
24
|
-
def transmit(
|
24
|
+
def transmit(events)
|
25
|
+
return unless events && events.length > 0
|
26
|
+
|
25
27
|
begin
|
26
|
-
@client.track(
|
27
|
-
rescue
|
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
|
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
|
-
@
|
56
|
-
|
70
|
+
@event_queue = Queue.new
|
71
|
+
@batch_lock = Mutex.new
|
57
72
|
|
58
|
-
|
59
|
-
@
|
60
|
-
|
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
|
-
|
80
|
+
if @request_thread and !@request_thread.alive?
|
81
|
+
Framed.logger.error("Starting request thread due to dead thread")
|
82
|
+
end
|
65
83
|
|
66
|
-
@
|
84
|
+
@request_thread = Thread.new do
|
67
85
|
while true
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
stop
|
86
|
+
pending = @request_queue.pop
|
87
|
+
|
88
|
+
@request_pending.synchronize do
|
89
|
+
transmit(pending)
|
73
90
|
end
|
74
|
-
|
91
|
+
|
92
|
+
start_request
|
75
93
|
end
|
76
94
|
end
|
77
95
|
end
|
78
96
|
|
79
97
|
def stop(drain = false)
|
80
|
-
if
|
81
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
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
|
93
|
-
@
|
94
|
-
end
|
146
|
+
def ensure_request_thread
|
147
|
+
return if @request_thread && @request_thread.alive?
|
95
148
|
|
96
|
-
|
97
|
-
|
98
|
-
transmit(dequeue)
|
149
|
+
@request_pending.synchronize do
|
150
|
+
start
|
99
151
|
end
|
100
152
|
end
|
101
|
-
end
|
102
153
|
|
103
|
-
|
104
|
-
|
154
|
+
def stop_request_thread
|
155
|
+
if @request_thread
|
156
|
+
@request_thread.kill
|
157
|
+
@request_thread = nil
|
158
|
+
end
|
105
159
|
end
|
106
|
-
|
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
|
-
|
109
|
-
|
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
|
data/lib/framed/exceptions.rb
CHANGED
data/lib/framed/rails.rb
CHANGED
@@ -1,27 +1,37 @@
|
|
1
1
|
ActionController::Base.class_eval do
|
2
2
|
|
3
|
-
after_filter :
|
3
|
+
after_filter :framed_report_request
|
4
4
|
|
5
|
-
def
|
5
|
+
def framed_event_name
|
6
6
|
"#{request.method}_#{params[:controller]}\##{params[:action]}"
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
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] = {
|
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
|
-
|
30
|
+
event = {
|
21
31
|
:type => :track,
|
22
32
|
:anonymous_id => anonymous_id,
|
23
33
|
:user_id => user_id,
|
24
|
-
:event =>
|
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
|
-
|
36
|
-
Framed.
|
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
|
41
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
13
|
+
def uuid
|
14
|
+
begin
|
15
|
+
UUID.new.generate
|
16
|
+
rescue NameError
|
17
|
+
SecureRandom.uuid
|
23
18
|
end
|
19
|
+
end
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
31
|
+
yield o
|
34
32
|
end
|
33
|
+
else
|
34
|
+
o.public_send(*a, &b)
|
35
35
|
end
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def serialize_date(dt)
|
39
|
+
dt.utc.iso8601
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
data/lib/framed/version.rb
CHANGED
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 => '
|
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.
|
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-
|
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
|
data/lib/framed/example.rb
DELETED
@@ -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)
|