raygun-apm 1.1.15.pre3
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 +7 -0
- data/COPYING.rax +9 -0
- data/LICENSE +27 -0
- data/LICENSE.bipbuffer +24 -0
- data/README.rdoc +118 -0
- data/bin/console +14 -0
- data/bin/rake +27 -0
- data/bin/rake.cmd +30 -0
- data/bin/raygun-diagnostics +6 -0
- data/bin/setup +8 -0
- data/ext/raygun/extconf.rb +114 -0
- data/lib/raygun/apm/blacklist/parser.rb +49 -0
- data/lib/raygun/apm/blacklist/translator.rb +79 -0
- data/lib/raygun/apm/blacklist.rb +476 -0
- data/lib/raygun/apm/config.rb +104 -0
- data/lib/raygun/apm/diagnostics.rb +54 -0
- data/lib/raygun/apm/event.rb +49 -0
- data/lib/raygun/apm/hooks/excon.rb +36 -0
- data/lib/raygun/apm/hooks/httpclient.rb +43 -0
- data/lib/raygun/apm/hooks/internals.rb +95 -0
- data/lib/raygun/apm/hooks/mongodb.rb +45 -0
- data/lib/raygun/apm/hooks/net_http.rb +44 -0
- data/lib/raygun/apm/hooks/redis.rb +46 -0
- data/lib/raygun/apm/tracer.rb +131 -0
- data/lib/raygun/apm/version.rb +6 -0
- data/lib/raygun/apm.rb +18 -0
- data/raygun-apm.gemspec +43 -0
- metadata +282 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'httpclient'
|
|
2
|
+
|
|
3
|
+
module Raygun
|
|
4
|
+
module Apm
|
|
5
|
+
module Hooks
|
|
6
|
+
module HTTPClient
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def do_request(method, uri, query, body, header, &filtered_block)
|
|
10
|
+
if tracer = Raygun::Apm::Tracer.instance
|
|
11
|
+
started = tracer.now
|
|
12
|
+
response = super
|
|
13
|
+
ended = tracer.now
|
|
14
|
+
event = raygun_apm_http_out_event
|
|
15
|
+
event[:pid] = Process.pid
|
|
16
|
+
event[:url] = raygun_apm_url(uri, query)
|
|
17
|
+
event[:verb] = method.to_s.upcase
|
|
18
|
+
event[:status] = response.code.to_i
|
|
19
|
+
event[:duration] = ended - started
|
|
20
|
+
event[:timestamp] = started
|
|
21
|
+
event[:tid] = tracer.get_thread_id(Thread.current)
|
|
22
|
+
tracer.emit(event)
|
|
23
|
+
response
|
|
24
|
+
else
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def raygun_apm_url(uri, query)
|
|
30
|
+
uri = uri.to_param if uri.respond_to?(:to_param)
|
|
31
|
+
query = query.to_param if query.respond_to?(:to_param)
|
|
32
|
+
query && uri ? URI.join(uri, query).to_s : uri.to_s
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def raygun_apm_http_out_event
|
|
36
|
+
@_raygun_apm_http_out_event ||= Raygun::Apm::Event::HttpOut.new
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Raygun::Apm::Tracer.patch(HTTPClient, Raygun::Apm::Hooks::HTTPClient)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
|
|
3
|
+
module Raygun
|
|
4
|
+
module Apm
|
|
5
|
+
module Hooks
|
|
6
|
+
module Object
|
|
7
|
+
def system(*args)
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def sleep(*args)
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def exec(*args)
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def spawn(*args)
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fork(*args)
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module IO
|
|
29
|
+
def sycall(*args)
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def open(*args)
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def puts(*args)
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def gets(*args)
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def readline(*args)
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def readlines(*args)
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
module Random
|
|
55
|
+
def srand(*args)
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def rand(*args)
|
|
60
|
+
super
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module Signal
|
|
65
|
+
def trap(*args)
|
|
66
|
+
super
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module Mutex
|
|
71
|
+
def synchronize(*args)
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def lock(*args)
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def unlock(*args)
|
|
80
|
+
super
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def sleep(*args)
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Raygun::Apm::Tracer.patch(Object, Raygun::Apm::Hooks::Object)
|
|
92
|
+
Raygun::Apm::Tracer.patch(IO, Raygun::Apm::Hooks::IO)
|
|
93
|
+
Raygun::Apm::Tracer.patch(Random, Raygun::Apm::Hooks::Random)
|
|
94
|
+
Raygun::Apm::Tracer.patch(Signal, Raygun::Apm::Hooks::Signal)
|
|
95
|
+
Raygun::Apm::Tracer.patch(Thread::Mutex, Raygun::Apm::Hooks::Mutex)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'mongo'
|
|
2
|
+
|
|
3
|
+
module Raygun
|
|
4
|
+
module Apm
|
|
5
|
+
module Hooks
|
|
6
|
+
module MongoDB
|
|
7
|
+
def do_execute(connection, client, options = {})
|
|
8
|
+
result = nil
|
|
9
|
+
if tracer = Raygun::Apm::Tracer.instance
|
|
10
|
+
started = tracer.now
|
|
11
|
+
result = super
|
|
12
|
+
ended = tracer.now
|
|
13
|
+
event = raygun_apm_sql_event
|
|
14
|
+
event[:pid] = Process.pid
|
|
15
|
+
event[:query] = raygun_format_query(connection)
|
|
16
|
+
event[:provider] = "mongodb"
|
|
17
|
+
event[:host] = connection.address.to_s
|
|
18
|
+
event[:database] = client.database.name
|
|
19
|
+
event[:duration] = ended - started
|
|
20
|
+
event[:timestamp] = started
|
|
21
|
+
event[:tid] = tracer.get_thread_id(Thread.current)
|
|
22
|
+
tracer.emit(event)
|
|
23
|
+
result
|
|
24
|
+
else
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
def raygun_format_query(connection)
|
|
31
|
+
payload = message(connection).payload
|
|
32
|
+
return "#{payload["database_name"]}.#{payload["command_name"]} #{payload["command"]}}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def raygun_apm_sql_event
|
|
36
|
+
@_raygun_apm_sql_event ||= Raygun::Apm::Event::Sql.new
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Mongo::Operation.constants.each do |operation|
|
|
44
|
+
Raygun::Apm::Tracer.patch(Mongo::Operation.const_get(operation), Raygun::Apm::Hooks::MongoDB) rescue nil
|
|
45
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
|
|
3
|
+
module Raygun
|
|
4
|
+
module Apm
|
|
5
|
+
module Hooks
|
|
6
|
+
module Net
|
|
7
|
+
module HTTP
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def transport_request(request)
|
|
11
|
+
if tracer = Raygun::Apm::Tracer.instance
|
|
12
|
+
started = tracer.now
|
|
13
|
+
response = super
|
|
14
|
+
ended = tracer.now
|
|
15
|
+
event = raygun_apm_http_out_event
|
|
16
|
+
event[:pid] = Process.pid
|
|
17
|
+
event[:url] = raygun_apm_url(request)
|
|
18
|
+
event[:verb] = request.method
|
|
19
|
+
event[:status] = response.code.to_i
|
|
20
|
+
event[:duration] = ended - started
|
|
21
|
+
event[:timestamp] = started
|
|
22
|
+
event[:tid] = tracer.get_thread_id(Thread.current)
|
|
23
|
+
tracer.emit(event)
|
|
24
|
+
response
|
|
25
|
+
else
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def raygun_apm_url(request)
|
|
31
|
+
return request.uri.to_s if request.uri
|
|
32
|
+
"#{use_ssl? ? "https" : "http"}://#{request["host"] || address}#{request.path}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def raygun_apm_http_out_event
|
|
36
|
+
@_raygun_apm_http_out_event ||= Raygun::Apm::Event::HttpOut.new
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
Raygun::Apm::Tracer.patch(::Net::HTTP, Raygun::Apm::Hooks::Net::HTTP)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Raygun
|
|
2
|
+
module Apm
|
|
3
|
+
module Hooks
|
|
4
|
+
module Redis
|
|
5
|
+
def process(commands)
|
|
6
|
+
result = nil
|
|
7
|
+
if tracer = Raygun::Apm::Tracer.instance
|
|
8
|
+
started = tracer.now
|
|
9
|
+
result = super
|
|
10
|
+
ended = tracer.now
|
|
11
|
+
event = raygun_apm_sql_event
|
|
12
|
+
event[:pid] = Process.pid
|
|
13
|
+
event[:query] = raygun_format_query(commands)
|
|
14
|
+
event[:provider] = "redis"
|
|
15
|
+
event[:host] = "#{host}:#{port}"
|
|
16
|
+
event[:database] = db.to_s
|
|
17
|
+
event[:duration] = ended - started
|
|
18
|
+
event[:timestamp] = started
|
|
19
|
+
event[:tid] = tracer.get_thread_id(Thread.current)
|
|
20
|
+
tracer.emit(event)
|
|
21
|
+
result
|
|
22
|
+
else
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
def raygun_format_query(commands)
|
|
29
|
+
commands.map do |command|
|
|
30
|
+
command.map(&:to_s).join(" ")
|
|
31
|
+
end.join(", ")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def raygun_apm_sql_event
|
|
35
|
+
@_raygun_apm_sql_event ||= Raygun::Apm::Event::Sql.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def raygun_redis_call
|
|
39
|
+
yield
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Raygun::Apm::Tracer.patch(::Redis::Client, Raygun::Apm::Hooks::Redis)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require 'raygun/apm/blacklist'
|
|
2
|
+
require 'rbconfig'
|
|
3
|
+
|
|
4
|
+
module Raygun
|
|
5
|
+
module Apm
|
|
6
|
+
class Tracer
|
|
7
|
+
@__mutex = Mutex.new
|
|
8
|
+
|
|
9
|
+
@__pids ||= {}
|
|
10
|
+
class << self
|
|
11
|
+
def synchronize(&block)
|
|
12
|
+
@__mutex.synchronize { block.call }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def instance
|
|
16
|
+
@__pids[Process.pid]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def instance=(tracer)
|
|
20
|
+
@__pids[Process.pid] = tracer
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def patch(concern, hook)
|
|
24
|
+
concern.prepend(hook) unless concern.ancestors.include?(hook)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
attr_accessor :config
|
|
29
|
+
|
|
30
|
+
def initialize(env=ENV)
|
|
31
|
+
configure(env)
|
|
32
|
+
initialize_blacklist
|
|
33
|
+
register_known_library_paths
|
|
34
|
+
run_agent_connectivity_diagnostics
|
|
35
|
+
require_hooks
|
|
36
|
+
ObjectSpace.define_finalizer(self, proc{ disable_tracepoints })
|
|
37
|
+
# Any fails here is kamikaze for the tracer
|
|
38
|
+
rescue => e
|
|
39
|
+
# XXX works for the middleware wrapped case, not for standalone - revisit
|
|
40
|
+
raise Raygun::Apm::FatalError, "Raygun APM tracer could not be initialized: #{e.message} #{e.backtrace.join("\n")}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def udp_sink!
|
|
44
|
+
sock = UDPSocket.new
|
|
45
|
+
# For UDP sockets, SO_SNDBUF is the max packet size and NOT send buffer as with a connection oriented transport
|
|
46
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, Tracer::BATCH_PACKET_SIZE)
|
|
47
|
+
self.udp_sink(
|
|
48
|
+
socket: sock,
|
|
49
|
+
host: config.proton_udp_host,
|
|
50
|
+
port: config.proton_udp_port,
|
|
51
|
+
receive_buffer_size: sock.getsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF).int
|
|
52
|
+
)
|
|
53
|
+
rescue => e
|
|
54
|
+
# XXX works for the middleware wrapped case, not for standalone - revisit
|
|
55
|
+
raise Raygun::Apm::FatalError, "Raygun APM UDP sink could not be initialized: #{e.message} #{e.backtrace.join("\n")}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def tcp_sink!
|
|
59
|
+
self.tcp_sink(
|
|
60
|
+
host: config.proton_tcp_host,
|
|
61
|
+
port: config.proton_tcp_port
|
|
62
|
+
)
|
|
63
|
+
rescue => e
|
|
64
|
+
# XXX works for the middleware wrapped case, not for standalone - revisit
|
|
65
|
+
raise Raygun::Apm::FatalError, "Raygun APM TCP sink could not be initialized: #{e.message} #{e.backtrace.join("\n")}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def enable_sink!
|
|
69
|
+
if config.proton_network_mode == "Udp"
|
|
70
|
+
udp_sink!
|
|
71
|
+
elsif config.proton_network_mode == "Tcp"
|
|
72
|
+
tcp_sink!
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
def configure(env)
|
|
78
|
+
@config = Config.new(env)
|
|
79
|
+
# Special assignments from config to the Tracer
|
|
80
|
+
self.log_level = config.loglevel
|
|
81
|
+
self.environment = config.environment
|
|
82
|
+
self.api_key = config.proton_api_key
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def initialize_blacklist
|
|
86
|
+
@blacklist_parser = Raygun::Apm::Blacklist::Parser.new(self)
|
|
87
|
+
file = @config.blacklist_file
|
|
88
|
+
@blacklist = if file && File.exist?(file)
|
|
89
|
+
File.readlines(file)
|
|
90
|
+
else
|
|
91
|
+
[]
|
|
92
|
+
end
|
|
93
|
+
# Defaults
|
|
94
|
+
@blacklist_parser.add_filters Raygun::Apm::Blacklist.resolve_entries
|
|
95
|
+
# From file
|
|
96
|
+
@blacklist_parser.add_filters @blacklist
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def register_known_library_paths
|
|
100
|
+
if defined?(Bundler)
|
|
101
|
+
libs = Bundler.load.specs.map(&:full_gem_path).sort << RbConfig::CONFIG['rubylibdir']
|
|
102
|
+
libs.delete(Dir.getwd)
|
|
103
|
+
self.register_libraries libs
|
|
104
|
+
else
|
|
105
|
+
self.register_libraries [RbConfig::CONFIG['rubylibdir']]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def run_agent_connectivity_diagnostics
|
|
110
|
+
check = Raygun::Apm::Diagnostics.new
|
|
111
|
+
check.verify_agent(self)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def require_hooks
|
|
115
|
+
require "raygun/apm/hooks/internals" if @config.proton_hook_internals
|
|
116
|
+
require "raygun/apm/hooks/net_http"
|
|
117
|
+
# conditionally required - may not be bundled
|
|
118
|
+
conditional_hooks = %w(httpclient excon mongodb)
|
|
119
|
+
conditional_hooks.each do |hook|
|
|
120
|
+
begin
|
|
121
|
+
require "raygun/apm/hooks/#{hook}"
|
|
122
|
+
rescue LoadError
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
if @config.proton_hook_redis
|
|
126
|
+
require "raygun/apm/hooks/redis" if defined?(Redis::Client)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/raygun/apm.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "raygun/apm/version"
|
|
2
|
+
require "socket"
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
# Attempt to load a precompiled shared object (released gem)
|
|
6
|
+
RUBY_VERSION =~ /(\d+\.\d+)/
|
|
7
|
+
require "raygun/#{$1}/raygun_ext"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# Attempt to load the development specific extension (non-released gem, local dev)
|
|
10
|
+
require "raygun/raygun_ext"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require "raygun/apm/config"
|
|
14
|
+
require "raygun/apm/diagnostics"
|
|
15
|
+
require "raygun/apm/blacklist/parser"
|
|
16
|
+
require "raygun/apm/blacklist/translator"
|
|
17
|
+
require "raygun/apm/tracer"
|
|
18
|
+
require "raygun/apm/event"
|
data/raygun-apm.gemspec
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
require File.expand_path('../lib/raygun/apm/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |spec|
|
|
5
|
+
spec.name = "raygun-apm"
|
|
6
|
+
spec.version = Raygun::Apm::VERSION
|
|
7
|
+
|
|
8
|
+
spec.authors = ["Raygun Limited"]
|
|
9
|
+
spec.email = ["ruby-apm@raygun.io", "support@raygun.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = %q{Raygun application performance monitoring core Profiler}
|
|
12
|
+
spec.homepage = "https://raygun.com/platform/apm"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
|
|
15
|
+
spec.files = Dir['README.rdoc', 'raygun-apm.gemspec', 'LICENSE', 'LICENSE.bipbuffer', 'COPYING.rax', 'lib/**/*', 'bin/**/*'].reject { |f| f.match(/raygun_ext\./) }
|
|
16
|
+
spec.files += Dir['lib/raygun/**/{2}*/raygun_ext.*']
|
|
17
|
+
spec.bindir = "bin"
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ["lib", "ext"]
|
|
20
|
+
|
|
21
|
+
spec.platform = Gem::Platform::RUBY
|
|
22
|
+
|
|
23
|
+
spec.extensions = ["ext/raygun/extconf.rb"]
|
|
24
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
25
|
+
|
|
26
|
+
# Required at runtime for native extension compilation against Ruby VM internals
|
|
27
|
+
spec.add_dependency "debase-ruby_core_source", ">= 3.3.6"
|
|
28
|
+
|
|
29
|
+
spec.add_development_dependency "debase-ruby_core_source", ">= 3.3.6"
|
|
30
|
+
spec.add_development_dependency "bundler", ">= 2.2.15"
|
|
31
|
+
spec.add_development_dependency "rake", "~> 13.0.3"
|
|
32
|
+
spec.add_development_dependency "minitest", "~> 5.16"
|
|
33
|
+
spec.add_development_dependency "rake-compiler", "~> 1.1.1"
|
|
34
|
+
spec.add_development_dependency "rake-compiler-dock", "~> 1.11.0"
|
|
35
|
+
spec.add_development_dependency "benchmark_driver", "~> 0.15.9"
|
|
36
|
+
spec.add_development_dependency "faraday", "~> 1.0.1"
|
|
37
|
+
spec.add_development_dependency "multipart-post", "~> 2.1.1"
|
|
38
|
+
spec.add_development_dependency "rest-client", "~> 2.1.0"
|
|
39
|
+
spec.add_development_dependency "excon", "~> 0.73.0"
|
|
40
|
+
spec.add_development_dependency "httparty", "~> 0.18.0"
|
|
41
|
+
spec.add_development_dependency "httpclient", "~> 2.8.3"
|
|
42
|
+
spec.add_development_dependency "mongoid", "~> 7.1.2"
|
|
43
|
+
end
|