grifter 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 272206b2927fd805a4d0eebfaeb12aa9bb5544b8
4
- data.tar.gz: 811ddf5f1872f2c8a82a0ccf7fb5082d7d5537ac
3
+ metadata.gz: b3c5acde96e3f1e8b052b55aa73c4c26e955d50e
4
+ data.tar.gz: 855578ed7e193ba868a043e3cc8c99b93451bbb4
5
5
  SHA512:
6
- metadata.gz: e687565405a56497e50a449267895dc594006df2ae3679034003fc24132937fab025a76f3204e1707af89a8456c661b50a9766bec55d50b02343221262874759
7
- data.tar.gz: 4aaf0eda705b509714ffbcb3b02d2d06df44dbf3645ebb412ccc6bb20dcef047bfac5693815bedfa4b26421689480cc432080e150931896031bf9a146418914f
6
+ metadata.gz: 49c8a5c1f5529e7f144921987a7032b26dff13e6b482c3a5095b458e940e7c6eb1085fd0ee120ff04de6a7479e93528edb760c635bc91cbcf2f036adbc9dba80
7
+ data.tar.gz: 1aad905c89ba51705b0aa203e0e5ad7fe3e93f324cea6c79bbdeb15604ee7e9e9dea8aa0dcba7dec1e6f11211f41d0eb66312fcc0b969db102db31cdb2e85583
data/Gemfile CHANGED
@@ -2,7 +2,9 @@ source :rubygems
2
2
 
3
3
  #gemspec
4
4
 
5
+ gem 'faraday'
5
6
  gem 'json'
7
+ gem 'typhoeus'
6
8
 
7
9
  group :development do
8
10
  gem 'rspec'
data/Gemfile.lock CHANGED
@@ -6,8 +6,12 @@ GEM
6
6
  builder (3.2.2)
7
7
  coderay (1.0.9)
8
8
  diff-lcs (1.2.4)
9
+ ethon (0.6.1)
10
+ ffi (>= 1.3.0)
11
+ mime-types (~> 1.18)
9
12
  faraday (0.8.8)
10
13
  multipart-post (~> 1.2.0)
14
+ ffi (1.9.3)
11
15
  git (1.2.5)
12
16
  github_api (0.10.1)
13
17
  addressable
@@ -32,6 +36,7 @@ GEM
32
36
  jwt (0.1.8)
33
37
  multi_json (>= 1.5)
34
38
  method_source (0.8.2)
39
+ mime-types (1.25.1)
35
40
  multi_json (1.7.8)
36
41
  multi_xml (0.5.4)
37
42
  multipart-post (1.2.0)
@@ -60,13 +65,17 @@ GEM
60
65
  diff-lcs (>= 1.1.3, < 2.0)
61
66
  rspec-mocks (2.14.2)
62
67
  slop (3.4.6)
68
+ typhoeus (0.6.6)
69
+ ethon (~> 0.6.1)
63
70
 
64
71
  PLATFORMS
65
72
  ruby
66
73
 
67
74
  DEPENDENCIES
68
75
  awesome_print
76
+ faraday
69
77
  jeweler
70
78
  json
71
79
  pry
72
80
  rspec
81
+ typhoeus
data/grifter.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "grifter"
8
- s.version = "0.2.0"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Robert Schultheis"]
12
- s.date = "2013-11-05"
12
+ s.date = "2013-12-04"
13
13
  s.description = "convention based approach to interfacing with an HTTP JSON API."
14
14
  s.email = "rob@knewton.com"
15
15
  s.executables = ["grift"]
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  "lib/grifter/configuration.rb",
29
29
  "lib/grifter/helpers.rb",
30
30
  "lib/grifter/http_service.rb",
31
+ "lib/grifter/instrumentation.rb",
31
32
  "lib/grifter/json_helpers.rb",
32
33
  "lib/grifter/log.rb"
33
34
  ]
@@ -41,20 +42,26 @@ Gem::Specification.new do |s|
41
42
  s.specification_version = 4
42
43
 
43
44
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
45
+ s.add_runtime_dependency(%q<faraday>, [">= 0"])
44
46
  s.add_runtime_dependency(%q<json>, [">= 0"])
47
+ s.add_runtime_dependency(%q<typhoeus>, [">= 0"])
45
48
  s.add_development_dependency(%q<rspec>, [">= 0"])
46
49
  s.add_development_dependency(%q<pry>, [">= 0"])
47
50
  s.add_development_dependency(%q<awesome_print>, [">= 0"])
48
51
  s.add_development_dependency(%q<jeweler>, [">= 0"])
49
52
  else
53
+ s.add_dependency(%q<faraday>, [">= 0"])
50
54
  s.add_dependency(%q<json>, [">= 0"])
55
+ s.add_dependency(%q<typhoeus>, [">= 0"])
51
56
  s.add_dependency(%q<rspec>, [">= 0"])
52
57
  s.add_dependency(%q<pry>, [">= 0"])
53
58
  s.add_dependency(%q<awesome_print>, [">= 0"])
54
59
  s.add_dependency(%q<jeweler>, [">= 0"])
55
60
  end
56
61
  else
62
+ s.add_dependency(%q<faraday>, [">= 0"])
57
63
  s.add_dependency(%q<json>, [">= 0"])
64
+ s.add_dependency(%q<typhoeus>, [">= 0"])
58
65
  s.add_dependency(%q<rspec>, [">= 0"])
59
66
  s.add_dependency(%q<pry>, [">= 0"])
60
67
  s.add_dependency(%q<awesome_print>, [">= 0"])
data/lib/grifter.rb CHANGED
@@ -2,9 +2,11 @@ require_relative 'grifter/http_service'
2
2
  require_relative 'grifter/configuration'
3
3
  require_relative 'grifter/log'
4
4
  require_relative 'grifter/blankslate'
5
+ require_relative 'grifter/instrumentation'
5
6
 
6
7
  class Grifter
7
8
  include Grifter::Configuration
9
+ include Grifter::Instrumentation
8
10
 
9
11
  DefaultConfigOptions = {
10
12
  #TODO: service_config: nil,
@@ -43,6 +45,8 @@ class Grifter
43
45
  if @config[:authenticate]
44
46
  self.grifter_authenticate_do
45
47
  end
48
+
49
+ start_instrumentation
46
50
  end
47
51
 
48
52
  attr_reader :services
@@ -23,6 +23,7 @@ class Grifter
23
23
  raise GrifterConfigurationError.new "url is not a proper aboslute URL: #{url}"
24
24
  end
25
25
  parsed = URI.parse url
26
+ #make the url faraday is configured with
26
27
  {
27
28
  :hostname => parsed.host,
28
29
  :port => parsed.port,
@@ -36,7 +37,7 @@ class Grifter
36
37
  config_file: ENV['GRIFTER_CONFIG_FILE'] ? ENV['GRIFTER_CONFIG_FILE'] : 'grifter.yml',
37
38
  environment: ENV['GRIFTER_ENVIRONMENT'],
38
39
  }.merge(options)
39
- Log.debug "Loading config file '#{options[:config_file]}'"
40
+ Grifter::Log.debug "Loading config file '#{options[:config_file]}'"
40
41
  unless File.exist?(options[:config_file])
41
42
  raise GrifterConfigFileMissing.new "No such config file: '#{options[:config_file]}'"
42
43
  end
@@ -68,7 +69,6 @@ class Grifter
68
69
  base_uri: '',
69
70
  port: (service_config[:ssl] == true ? 443 : 80),
70
71
  }.merge(service_config))
71
-
72
72
  end
73
73
 
74
74
  #merge any environment overrides into the service block
@@ -95,6 +95,12 @@ class Grifter
95
95
  end
96
96
  end
97
97
 
98
+ #add in the faraday url as the final thing after figuring everything else out
99
+ config[:services].each_pair do |service_name, service_config|
100
+ #set the url we'll use to start faraday
101
+ service_config[:faraday_url] = "#{service_config[:ssl] ? 'https':'http'}://#{service_config[:hostname]}:#{service_config[:port].to_s}"
102
+ end
103
+
98
104
  #join the grift globs with the relative path to config file
99
105
  if config[:grift_globs] && options[:config_file]
100
106
  config_file_dir = File.dirname options[:config_file]
@@ -1,5 +1,6 @@
1
- require 'net/http'
2
- require 'openssl'
1
+ require 'faraday'
2
+ require 'typhoeus'
3
+ require 'typhoeus/adapters/faraday' #https://github.com/typhoeus/typhoeus/issues/226#issuecomment-9919517
3
4
 
4
5
  require_relative 'json_helpers'
5
6
  require_relative 'log'
@@ -7,6 +8,7 @@ require_relative 'log'
7
8
  class Grifter
8
9
  class HTTPService
9
10
 
11
+ include Grifter::Log
10
12
  include Grifter::JsonHelpers
11
13
 
12
14
  def initialize config
@@ -14,30 +16,50 @@ class Grifter
14
16
  @config = config
15
17
  @name = config[:name]
16
18
  @base_uri = config[:base_uri]
17
-
18
- Log.debug "Configuring service '#{@name}' with:\n\t#{@config.inspect}"
19
-
20
- @http = Net::HTTP.new(@config[:hostname], @config[:port])
21
- @http.use_ssl = @config[:ssl]
22
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @config[:ignore_ssl_cert]
23
- @http.read_timeout = @config[:timeout] if @config[:timeout]
19
+ @log_headers = config.fetch(:log_headers, true)
20
+ @log_bodies = config.fetch(:log_bodies, true)
21
+
22
+ logger.debug "Configuring service '#{@name}' with:\n\t#{@config.inspect}"
23
+
24
+ #@conn = Net::HTTP.new(@config[:hostname], @config[:port])
25
+ #@conn.use_ssl = @config[:ssl]
26
+ @conn = Faraday.new @config[:faraday_url] do |conn_builder|
27
+ #do our own logging
28
+ #conn_builder.response logger: logger
29
+ #conn_builder.adapter Faraday.default_adapter # make requests with Net::HTTP
30
+ conn_builder.adapter :typhoeus
31
+ conn_builder.ssl[:verify] = false if @config[:ignore_ssl_cert]
32
+
33
+ #this nonsense dont work?! https://github.com/lostisland/faraday_middleware/issues/76
34
+ #conn_builder.use :instrumentation
35
+ end
24
36
 
25
37
  @headers = {
26
38
  'accept' => 'application/json',
27
39
  'content-type' => 'application/json',
28
40
  }
29
41
  if @config[:default_headers]
30
- Log.debug "Default headers configured: " + @config[:default_headers].inspect
42
+ logger.debug "Default headers configured: " + @config[:default_headers].inspect
31
43
  @config[:default_headers].each_pair do |k, v|
32
44
  @headers[k.to_s] = v.to_s
33
45
  end
34
46
  end
47
+ @default_timeout = @config.fetch(:timeout, 10)
48
+ logger.info "Initialized grifter service '#{@name}'"
49
+ end
50
+
51
+ def stubs &blk
52
+ stubs = Faraday::Adapter::Test::Stubs.new
53
+ @conn = Faraday.new @config[:faraday_url] do |conn_builder|
54
+ conn_builder.adapter :test, stubs
55
+ end
56
+ stubs
35
57
  end
36
58
 
37
59
  #allow stubbing http if we are testing
38
60
  attr_reader :http if defined?(RSpec)
39
61
 
40
- attr_reader :headers, :name, :config
62
+ attr_reader :headers, :name, :config, :conn
41
63
 
42
64
  #this is useful for testing apis, and other times
43
65
  #you want to interrogate the http details of a response
@@ -49,34 +71,74 @@ class Grifter
49
71
  # options can include:
50
72
  # - :timeout, which specifies num secs the request should timeout in
51
73
  # (this turns out to be kind of annoying to implement)
52
- def do_request req, options={}
53
- Log.debug RequestLogSeperator
54
- Log.debug "#{req.class} #{req.path}"
55
- Log.debug "HEADERS: #{req.to_hash}"
56
- Log.debug "BODY:\n#{req.body}" if req.request_body_permitted?
57
-
58
- if options.has_key? :timeout
59
- cur_timeout = @http.read_timeout
60
- Log.debug "Overriding timeout to: #{options[:timeout]}"
61
- @http.read_timeout = options[:timeout]
74
+ def do_request method, path, obj=nil, options={}
75
+
76
+ #grifter clients pass in path possibly including query params.
77
+ #Faraday needs the query and path seperately.
78
+ parsed = URI.parse make_path(path)
79
+ #faraday needs the request params as a hash.
80
+ #this turns out to be non-trivial
81
+ query_hash = if parsed.query
82
+ cgi_hash = CGI.parse(parsed.query)
83
+ #make to account for one param having multiple values
84
+ cgi_hash.inject({}) { |h,(k,v)| h[k] = v[1] ? v : v.first; h }
85
+ else
86
+ nil
87
+ end
88
+
89
+ req_headers = make_headers(options)
90
+
91
+ body = if options[:form]
92
+ URI.encode_www_form obj
93
+ else
94
+ jsonify(obj)
62
95
  end
63
96
 
64
- response = @http.request(req)
65
-
66
- if cur_timeout
67
- @http.read_timeout = cur_timeout
97
+ #log the request
98
+ logger.debug [
99
+ "Doing request: #{@name}: #{method.to_s.upcase} #{path}",
100
+ @log_headers ? ["Request Headers:",
101
+ req_headers.map{ |k, v| "#{k}: #{v.inspect}" }] : nil,
102
+ @log_bodies ? ["Request Body:", body] : nil,
103
+ ].flatten.compact.join("\n")
104
+
105
+ #doing it this way avoids problem with OPTIONS method: https://github.com/lostisland/faraday/issues/305
106
+ response = nil
107
+ metrics_obj = { method: method, service: @name, path: path }
108
+ ActiveSupport::Notifications.instrument('request.grifter', metrics_obj) do
109
+ response = @conn.run_request(method, nil, nil, nil) do |req|
110
+ req.path = parsed.path
111
+ req.params = metrics_obj[:params] = query_hash if query_hash
112
+
113
+ req.headers = req_headers
114
+ req.body = body
115
+ req.options[:timeout] = options.fetch(:timeout, @default_timeout)
116
+ end
117
+ metrics_obj[:response] = response
68
118
  end
69
119
 
70
- Log.debug "RESPONSE CODE: #{response.code}"
71
- Log.debug "RESPONSE HEADERS: #{response.to_hash}"
72
- Log.debug "RESPONSE BODY:\n#{jsonify response.body}\n"
73
-
74
- @last_request = req
120
+ logger.info "Request status: (#{response.status}) #{@name}: #{method.to_s.upcase} #{path}"
121
+ #@last_request = req
75
122
  @last_response = response
76
123
 
77
- raise RequestException.new(req, response) unless response.kind_of? Net::HTTPSuccess
124
+ raise RequestException.new(nil, response) unless response.status >= 200 and response.status < 300
125
+
126
+ response_obj = objectify response.body
127
+ if response.headers['content-type'] =~ /json/
128
+ logger.debug [
129
+ "Response Details:",
130
+ @log_headers ? ["Response Headers:",
131
+ response.headers.map { |k, v| "#{k}: #{v.inspect}" }] : nil,
132
+ @log_bodies ? [ "Response Body:", jsonify(response_obj)] : nil,
133
+ ''
134
+ ].flatten.compact.join("\n")
135
+ end
136
+ return response_obj
137
+ end
78
138
 
79
- objectify response.body
139
+ def in_parallel &blk
140
+ @conn.headers = @headers
141
+ @conn.in_parallel &blk
80
142
  end
81
143
 
82
144
  #add base uri to request
@@ -90,13 +152,15 @@ class Grifter
90
152
  end
91
153
 
92
154
  def make_headers options
93
- if options[:additional_headers]
155
+ headers = if options[:additional_headers]
94
156
  @headers.merge options[:additional_headers]
95
157
  elsif options[:headers]
96
158
  options[:headers]
97
159
  else
98
- @headers
160
+ @headers.clone
99
161
  end
162
+ headers['content-type'] = 'application/x-www-form-urlencoded' if options[:form]
163
+ headers
100
164
  end
101
165
 
102
166
  def req_args path, options
@@ -104,47 +168,38 @@ class Grifter
104
168
  end
105
169
 
106
170
  def get path, options={}
107
- req = Net::HTTP::Get.new(*req_args(path, options))
108
- do_request req, options
171
+ do_request :get, path, nil, options
109
172
  end
110
173
 
111
174
  def head path, options={}
112
- req = Net::HTTP::Head.new(*req_args(path, options))
113
- do_request req, options
175
+ do_request :head, path, nil, options
114
176
  end
115
177
 
116
178
  def options path, options={}
117
- req = Net::HTTP::Options.new(*req_args(path, options))
118
- do_request req, options
179
+ do_request :options, path, nil, options
119
180
  end
120
181
 
121
182
  def delete path, options={}
122
- req = Net::HTTP::Delete.new(*req_args(path, options))
123
- do_request req, options
183
+ do_request :delete, path, nil, options
124
184
  end
125
185
 
126
186
  def post path, obj, options={}
127
- req = Net::HTTP::Post.new(*req_args(path, options))
128
- req.body = jsonify(obj)
129
- do_request req, options
187
+ do_request :post, path, obj, options
130
188
  end
131
189
 
132
190
  def put path, obj, options={}
133
- req = Net::HTTP::Put.new(*req_args(path, options))
134
- req.body = jsonify(obj)
135
- do_request req, options
191
+ do_request :put, path, obj, options
136
192
  end
137
193
 
138
194
  def patch path, obj, options={}
139
- req = Net::HTTP::Patch.new(*req_args(path, options))
140
- req.body = jsonify(obj)
141
- do_request req, options
195
+ do_request :patch, path, obj, options
142
196
  end
143
197
 
144
198
  def post_form path, params, options={}
145
- request_obj = Net::HTTP::Post.new(*req_args(path, options))
146
- request_obj.set_form_data params
147
- do_request request_obj, options
199
+ do_request :post, path, params, options.merge(form: true)
200
+ #request_obj = Net::HTTP::Post.new(*req_args(path, options))
201
+ #request_obj.set_form_data params
202
+ #do_request request_obj, options
148
203
  end
149
204
  end
150
205
 
@@ -155,12 +210,12 @@ class Grifter
155
210
 
156
211
  #this makes good info show up in rspec reports
157
212
  def to_s
158
- "#{self.class}\nResponseCode: #{@response.code}\nResponseBody:\n#{@response.body}"
213
+ "#{self.class}\nResponseCode: #{self.code}\nResponseBody:\n#{self.body}"
159
214
  end
160
215
 
161
216
  #shortcut methods
162
217
  def code
163
- @response.code
218
+ @response.status
164
219
  end
165
220
 
166
221
  def body
@@ -0,0 +1,30 @@
1
+ require 'active_support/notifications'
2
+ class Grifter
3
+ module Instrumentation
4
+
5
+ Sample = Struct.new(:service_name, :method, :path, :status, :duration_ms, :end_time)
6
+
7
+ def start_instrumentation
8
+ @all_requests ||= []
9
+ ActiveSupport::Notifications.subscribe('request.grifter') do |name, start_time, end_time, _, data|
10
+ #do nothing if exception happened, else we might interfere with exception handling
11
+ unless data[:exception]
12
+ duration_ms = ((end_time.to_f - start_time.to_f) * 1000).to_i
13
+ #$stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
14
+ @all_requests << Sample.new(
15
+ data[:service],
16
+ data[:method].intern,
17
+ data[:path],
18
+ data[:response].status.to_s.intern,
19
+ duration_ms,
20
+ end_time
21
+ )
22
+ end
23
+ end
24
+ end
25
+
26
+ def metrics_all_requests
27
+ @all_requests
28
+ end
29
+ end
30
+ end
@@ -21,14 +21,14 @@ class Grifter
21
21
 
22
22
  #attempts to parse json strings into native ruby objects
23
23
  def objectify json_string
24
+ return nil if json_string.nil? or json_string==''
24
25
  case json_string
25
26
  when Hash, Array
26
27
  return json_string
27
28
  else
28
29
  JSON.parse(json_string.to_s)
29
30
  end
30
- rescue Exception => e
31
- Log.debug "Unable to parse non-json object: #{e.to_s}"
31
+ rescue Exception
32
32
  json_string
33
33
  end
34
34
 
data/lib/grifter/log.rb CHANGED
@@ -1,34 +1,44 @@
1
1
  require 'logger'
2
2
 
3
3
  class Grifter
4
- class Log
4
+ module Log
5
+ extend self
6
+
5
7
  GrifterFormatter = proc do |severity, datetime, progname, msg|
6
8
  "#{severity[0]}: [#{datetime.strftime('%m/%d/%y %H:%M:%S')}][#{progname}] - #{msg}\n"
7
9
  end
8
10
 
9
11
  @@loggers = []
10
- def self.add_logger handle
12
+ def loggers
13
+ @@loggers
14
+ end
15
+
16
+ def add_logger handle
11
17
  new_logger = Logger.new handle
12
18
  new_logger.progname = 'grifter'
13
19
  new_logger.formatter = GrifterFormatter
14
20
  @@loggers << new_logger
15
21
  end
16
22
 
17
- self.add_logger(STDOUT)
23
+ add_logger(STDOUT)
18
24
 
19
- def self.level= log_level
25
+ def level= log_level
20
26
  @@loggers.each { |logger| logger.level = log_level}
21
27
  end
22
28
 
23
- def self.log level, msg
29
+ def log level, msg
24
30
  @@loggers.each {|logger| logger.send(level, msg)}
25
31
  end
26
32
 
27
33
  [:fatal, :error, :warn, :info,:debug].each do |log_method|
28
- define_singleton_method log_method do |msg|
29
- log(log_method, msg)
34
+ define_method log_method do |msg|
35
+ self.log(log_method, msg)
30
36
  end
31
37
  end
38
+
39
+ def logger
40
+ self
41
+ end
32
42
  end
33
43
  end
34
44
 
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grifter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Schultheis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-05 00:00:00.000000000 Z
11
+ date: 2013-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: json
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - '>='
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: typhoeus
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rspec
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -99,6 +127,7 @@ files:
99
127
  - lib/grifter/configuration.rb
100
128
  - lib/grifter/helpers.rb
101
129
  - lib/grifter/http_service.rb
130
+ - lib/grifter/instrumentation.rb
102
131
  - lib/grifter/json_helpers.rb
103
132
  - lib/grifter/log.rb
104
133
  homepage: http://github.com/knewton/grifter