appsignal 3.0.3-java → 3.0.7-java
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 +4 -4
- data/.semaphore/semaphore.yml +64 -199
- data/CHANGELOG.md +47 -19
- data/README.md +9 -3
- data/appsignal.gemspec +16 -3
- data/build_matrix.yml +16 -24
- data/ext/agent.yml +17 -17
- data/ext/base.rb +12 -1
- data/gemfiles/capistrano2.gemfile +0 -1
- data/gemfiles/capistrano3.gemfile +0 -1
- data/gemfiles/grape.gemfile +0 -1
- data/gemfiles/no_dependencies.gemfile +4 -1
- data/gemfiles/rails-3.2.gemfile +2 -0
- data/gemfiles/rails-4.2.gemfile +6 -0
- data/gemfiles/resque-2.gemfile +0 -4
- data/gemfiles/sequel-435.gemfile +0 -1
- data/gemfiles/sequel.gemfile +0 -1
- data/gemfiles/sinatra.gemfile +0 -1
- data/lib/appsignal/config.rb +1 -0
- data/lib/appsignal/hooks.rb +2 -1
- data/lib/appsignal/hooks/excon.rb +19 -0
- data/lib/appsignal/hooks/puma.rb +1 -16
- data/lib/appsignal/integrations/excon.rb +20 -0
- data/lib/appsignal/integrations/padrino.rb +1 -1
- data/lib/appsignal/integrations/railtie.rb +1 -1
- data/lib/appsignal/integrations/redis.rb +8 -5
- data/lib/appsignal/integrations/sinatra.rb +1 -1
- data/lib/appsignal/probes.rb +0 -1
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +146 -17
- data/mono.yml +16 -0
- data/spec/lib/appsignal/cli/diagnose_spec.rb +1 -0
- data/spec/lib/appsignal/hooks/excon_spec.rb +74 -0
- data/spec/lib/appsignal/hooks/puma_spec.rb +0 -46
- data/spec/lib/appsignal/hooks/redis_spec.rb +34 -10
- data/spec/lib/appsignal/hooks_spec.rb +4 -1
- data/spec/lib/puma/appsignal_spec.rb +244 -68
- data/support/install_deps +9 -8
- metadata +8 -6
- data/lib/appsignal/probes/puma.rb +0 -61
- data/spec/lib/appsignal/probes/puma_spec.rb +0 -180
data/gemfiles/resque-2.gemfile
CHANGED
data/gemfiles/sequel-435.gemfile
CHANGED
data/gemfiles/sequel.gemfile
CHANGED
data/gemfiles/sinatra.gemfile
CHANGED
data/lib/appsignal/config.rb
CHANGED
@@ -290,6 +290,7 @@ module Appsignal
|
|
290
290
|
ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = config_hash[:files_world_accessible].to_s
|
291
291
|
ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"] = config_hash[:transaction_debug_mode].to_s
|
292
292
|
ENV["_APPSIGNAL_SEND_ENVIRONMENT_METADATA"] = config_hash[:send_environment_metadata].to_s
|
293
|
+
ENV["_APPSIGNAL_ENABLE_STATSD"] = "true"
|
293
294
|
ENV["_APP_REVISION"] = config_hash[:revision].to_s
|
294
295
|
end
|
295
296
|
|
data/lib/appsignal/hooks.rb
CHANGED
@@ -32,7 +32,7 @@ module Appsignal
|
|
32
32
|
return unless dependencies_present?
|
33
33
|
return if installed?
|
34
34
|
|
35
|
-
Appsignal.logger.
|
35
|
+
Appsignal.logger.debug("Installing #{name} hook")
|
36
36
|
begin
|
37
37
|
install
|
38
38
|
@installed = true
|
@@ -108,3 +108,4 @@ require "appsignal/hooks/mongo_ruby_driver"
|
|
108
108
|
require "appsignal/hooks/webmachine"
|
109
109
|
require "appsignal/hooks/data_mapper"
|
110
110
|
require "appsignal/hooks/que"
|
111
|
+
require "appsignal/hooks/excon"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
class Hooks
|
5
|
+
# @api private
|
6
|
+
class ExconHook < Appsignal::Hooks::Hook
|
7
|
+
register :excon
|
8
|
+
|
9
|
+
def dependencies_present?
|
10
|
+
Appsignal.config && defined?(::Excon)
|
11
|
+
end
|
12
|
+
|
13
|
+
def install
|
14
|
+
require "appsignal/integrations/excon"
|
15
|
+
::Excon.defaults[:instrumentor] = Appsignal::Integrations::ExconIntegration
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/appsignal/hooks/puma.rb
CHANGED
@@ -11,23 +11,8 @@ module Appsignal
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def install
|
14
|
-
if ::Puma.respond_to?(:stats) && !defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)
|
15
|
-
# Only install the minutely probe if a user isn't using our Puma
|
16
|
-
# plugin, which lives in `lib/puma/appsignal.rb`. This plugin defines
|
17
|
-
# the {APPSIGNAL_PUMA_PLUGIN_LOADED} constant.
|
18
|
-
#
|
19
|
-
# We prefer people use the AppSignal Puma plugin. This fallback is
|
20
|
-
# only there when users relied on our *magic* integration.
|
21
|
-
#
|
22
|
-
# Using the Puma plugin, the minutely probe thread will still run in
|
23
|
-
# Puma workers, for other non-Puma probes, but the Puma probe only
|
24
|
-
# runs in the Puma main process.
|
25
|
-
# For more information:
|
26
|
-
# https://docs.appsignal.com/ruby/integrations/puma.html
|
27
|
-
Appsignal::Minutely.probes.register :puma, ::Appsignal::Probes::PumaProbe
|
28
|
-
end
|
29
|
-
|
30
14
|
return unless defined?(::Puma::Cluster)
|
15
|
+
|
31
16
|
# For clustered mode with multiple workers
|
32
17
|
::Puma::Cluster.send(:prepend, Module.new do
|
33
18
|
def stop_workers
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
module Integrations
|
5
|
+
module ExconIntegration
|
6
|
+
def self.instrument(name, data, &block)
|
7
|
+
namespace, *event = name.split(".")
|
8
|
+
rails_name = [event, namespace].flatten.join(".")
|
9
|
+
|
10
|
+
title =
|
11
|
+
if rails_name == "response.excon"
|
12
|
+
data[:host]
|
13
|
+
else
|
14
|
+
"#{data[:method].to_s.upcase} #{data[:scheme]}://#{data[:host]}"
|
15
|
+
end
|
16
|
+
Appsignal.instrument(rails_name, title, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -7,7 +7,7 @@ module Appsignal
|
|
7
7
|
# @api private
|
8
8
|
module PadrinoPlugin
|
9
9
|
def self.init
|
10
|
-
Appsignal.logger.
|
10
|
+
Appsignal.logger.debug("Loading Padrino (#{Padrino::VERSION}) integration")
|
11
11
|
|
12
12
|
root = Padrino.mounted_root
|
13
13
|
Appsignal.config = Appsignal::Config.new(root, Padrino.env)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
Appsignal.logger.
|
3
|
+
Appsignal.logger.debug("Loading Rails (#{Rails.version}) integration")
|
4
4
|
|
5
5
|
require "appsignal/utils/rails_helper"
|
6
6
|
require "appsignal/rack/rails_instrumentation"
|
@@ -3,12 +3,15 @@
|
|
3
3
|
module Appsignal
|
4
4
|
module Integrations
|
5
5
|
module RedisIntegration
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
def write(command)
|
7
|
+
sanitized_command =
|
8
|
+
if command[0] == :eval
|
9
|
+
"#{command[1]}#{" ?" * (command.size - 3)}"
|
10
|
+
else
|
11
|
+
"#{command[0]}#{" ?" * (command.size - 1)}"
|
12
|
+
end
|
10
13
|
|
11
|
-
Appsignal.instrument "query.redis", id,
|
14
|
+
Appsignal.instrument "query.redis", id, sanitized_command do
|
12
15
|
super
|
13
16
|
end
|
14
17
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require "appsignal"
|
4
4
|
require "appsignal/rack/sinatra_instrumentation"
|
5
5
|
|
6
|
-
Appsignal.logger.
|
6
|
+
Appsignal.logger.debug("Loading Sinatra (#{Sinatra::VERSION}) integration")
|
7
7
|
|
8
8
|
app_settings = ::Sinatra::Application.settings
|
9
9
|
Appsignal.config = Appsignal::Config.new(
|
data/lib/appsignal/probes.rb
CHANGED
data/lib/appsignal/version.rb
CHANGED
@@ -1,27 +1,156 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
2
4
|
|
3
5
|
# AppSignal Puma plugin
|
4
6
|
#
|
5
|
-
# This plugin ensures
|
6
|
-
# minutely probe in the Puma master process.
|
7
|
-
#
|
8
|
-
# The constant {APPSIGNAL_PUMA_PLUGIN_LOADED} is here to mark the Plugin as
|
9
|
-
# loaded by the rest of the AppSignal gem. This ensures that the Puma minutely
|
10
|
-
# probe is not also started in every Puma workers, which was the old behavior.
|
11
|
-
# See {Appsignal::Hooks::PumaHook#install} for more information.
|
7
|
+
# This plugin ensures Puma metrics are sent to the AppSignal agent using StatsD.
|
12
8
|
#
|
13
9
|
# For even more information:
|
14
10
|
# https://docs.appsignal.com/ruby/integrations/puma.html
|
15
|
-
Puma::Plugin.create do
|
16
|
-
def start(launcher
|
17
|
-
launcher
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
Puma::Plugin.create do # rubocop:disable Metrics/BlockLength
|
12
|
+
def start(launcher)
|
13
|
+
@launcher = launcher
|
14
|
+
@launcher.events.debug "AppSignal: Puma plugin start."
|
15
|
+
in_background do
|
16
|
+
@launcher.events.debug "AppSignal: Start Puma stats collection loop."
|
17
|
+
plugin = AppsignalPumaPlugin.new
|
18
|
+
|
19
|
+
loop do
|
20
|
+
begin
|
21
|
+
# Implement similar behavior to minutely probes.
|
22
|
+
# Initial sleep to wait until the app is fully initalized.
|
23
|
+
# Then loop every 60 seconds and collect the Puma stats as AppSignal
|
24
|
+
# metrics.
|
25
|
+
sleep sleep_time
|
26
|
+
|
27
|
+
@launcher.events.debug "AppSignal: Collecting Puma stats."
|
28
|
+
stats = fetch_puma_stats
|
29
|
+
if stats
|
30
|
+
plugin.call(stats)
|
31
|
+
else
|
32
|
+
@launcher.events.log "AppSignal: No Puma stats to report."
|
33
|
+
end
|
34
|
+
rescue StandardError => error
|
35
|
+
log_error "Error while processing metrics.", error
|
36
|
+
end
|
22
37
|
end
|
23
|
-
|
24
|
-
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def sleep_time
|
44
|
+
60 # seconds
|
45
|
+
end
|
46
|
+
|
47
|
+
def log_error(message, error)
|
48
|
+
@launcher.events.log "AppSignal: #{message}\n" \
|
49
|
+
"#{error.class}: #{error.message}\n#{error.backtrace.join("\n")}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_puma_stats
|
53
|
+
if Puma.respond_to? :stats_hash # Puma >= 5.0.0
|
54
|
+
Puma.stats_hash
|
55
|
+
elsif Puma.respond_to? :stats # Puma < 5.0.0
|
56
|
+
# Puma.stats_hash returns symbolized keys as well
|
57
|
+
JSON.parse Puma.stats, :symbolize_names => true
|
58
|
+
end
|
59
|
+
rescue StandardError => error
|
60
|
+
log_error "Error while parsing Puma stats.", error
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# AppsignalPumaPlugin
|
66
|
+
#
|
67
|
+
# Class to handle the logic of translating the Puma stats to AppSignal metrics.
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
class AppsignalPumaPlugin
|
71
|
+
def initialize
|
72
|
+
@hostname = fetch_hostname
|
73
|
+
@statsd = Statsd.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def call(stats)
|
77
|
+
counts = {}
|
78
|
+
count_keys = [:backlog, :running, :pool_capacity, :max_threads]
|
79
|
+
|
80
|
+
if stats[:worker_status] # Clustered mode - Multiple workers
|
81
|
+
stats[:worker_status].each do |worker|
|
82
|
+
stat = worker[:last_status]
|
83
|
+
count_keys.each do |key|
|
84
|
+
count_if_present counts, key, stat
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
gauge(:workers, stats[:workers], :type => :count)
|
89
|
+
gauge(:workers, stats[:booted_workers], :type => :booted)
|
90
|
+
gauge(:workers, stats[:old_workers], :type => :old)
|
91
|
+
else # Single mode - Single worker
|
92
|
+
count_keys.each do |key|
|
93
|
+
count_if_present counts, key, stats
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
gauge(:connection_backlog, counts[:backlog]) if counts[:backlog]
|
98
|
+
gauge(:pool_capacity, counts[:pool_capacity]) if counts[:pool_capacity]
|
99
|
+
gauge(:threads, counts[:running], :type => :running) if counts[:running]
|
100
|
+
gauge(:threads, counts[:max_threads], :type => :max) if counts[:max_threads]
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
attr_reader :hostname
|
106
|
+
|
107
|
+
def fetch_hostname
|
108
|
+
# Configure hostname as reported for the Puma metrics with the
|
109
|
+
# APPSIGNAL_HOSTNAME environment variable.
|
110
|
+
env_hostname = ENV["APPSIGNAL_HOSTNAME"]
|
111
|
+
return env_hostname if env_hostname
|
112
|
+
|
113
|
+
# Auto detect hostname as fallback. May be inaccurate.
|
114
|
+
Socket.gethostname
|
115
|
+
end
|
116
|
+
|
117
|
+
def gauge(field, count, tags = {})
|
118
|
+
@statsd.gauge("puma_#{field}", count, tags.merge(:hostname => hostname))
|
119
|
+
end
|
120
|
+
|
121
|
+
def count_if_present(counts, key, stats)
|
122
|
+
stat_value = stats[key]
|
123
|
+
return unless stat_value
|
124
|
+
|
125
|
+
counts[key] ||= 0
|
126
|
+
counts[key] += stat_value
|
127
|
+
end
|
128
|
+
|
129
|
+
class Statsd
|
130
|
+
def initialize
|
131
|
+
# StatsD server location as configured in AppSignal agent StatsD server.
|
132
|
+
@host = "127.0.0.1"
|
133
|
+
@port = 8125
|
134
|
+
end
|
135
|
+
|
136
|
+
def gauge(metric_name, value, tags)
|
137
|
+
send_metric "g", metric_name, value, tags
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
attr_reader :host, :port
|
143
|
+
|
144
|
+
def send_metric(type, metric_name, metric_value, tags_hash)
|
145
|
+
tags = tags_hash.map { |key, value| "#{key}:#{value}" }.join(",")
|
146
|
+
data = "#{metric_name}:#{metric_value}|#{type}|##{tags}"
|
147
|
+
|
148
|
+
# Open (and close) a new socket every time because we don't know when the
|
149
|
+
# plugin will exit and when to cleanly close the socket connection.
|
150
|
+
socket = UDPSocket.new
|
151
|
+
socket.send(data, 0, host, port)
|
152
|
+
ensure
|
153
|
+
socket && socket.close
|
25
154
|
end
|
26
155
|
end
|
27
156
|
end
|
data/mono.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
language: ruby
|
3
|
+
repo: "https://github.com/appsignal/appsignal-ruby"
|
4
|
+
bootstrap:
|
5
|
+
post:
|
6
|
+
- "rake extension:install"
|
7
|
+
clean:
|
8
|
+
post:
|
9
|
+
- "bundle exec rake extension:clean"
|
10
|
+
- "rm -rf pkg"
|
11
|
+
build:
|
12
|
+
command: "bundle exec rake build:all"
|
13
|
+
publish:
|
14
|
+
gem_files_dir: pkg/
|
15
|
+
test:
|
16
|
+
command: "bundle exec rake test"
|
@@ -115,6 +115,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
|
|
115
115
|
it "logs to the log file" do
|
116
116
|
run
|
117
117
|
log_contents = File.read(config.log_file_path)
|
118
|
+
p log_contents
|
118
119
|
expect(log_contents).to contains_log :info, "Starting AppSignal diagnose"
|
119
120
|
end
|
120
121
|
|
@@ -0,0 +1,74 @@
|
|
1
|
+
describe Appsignal::Hooks::ExconHook do
|
2
|
+
before :context do
|
3
|
+
start_agent
|
4
|
+
end
|
5
|
+
|
6
|
+
context "with Excon" do
|
7
|
+
before(:context) do
|
8
|
+
class Excon
|
9
|
+
def self.defaults
|
10
|
+
@defaults ||= {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
Appsignal::Hooks::ExconHook.new.install
|
14
|
+
end
|
15
|
+
after(:context) { Object.send(:remove_const, :Excon) }
|
16
|
+
|
17
|
+
describe "#dependencies_present?" do
|
18
|
+
subject { described_class.new.dependencies_present? }
|
19
|
+
|
20
|
+
it { is_expected.to be_truthy }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#install" do
|
24
|
+
it "adds the AppSignal instrumentor to Excon" do
|
25
|
+
expect(Excon.defaults[:instrumentor]).to eql(Appsignal::Integrations::ExconIntegration)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "instrumentation" do
|
30
|
+
let!(:transaction) do
|
31
|
+
Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
|
32
|
+
end
|
33
|
+
around { |example| keep_transactions { example.run } }
|
34
|
+
|
35
|
+
it "instruments a http request" do
|
36
|
+
data = {
|
37
|
+
:host => "www.google.com",
|
38
|
+
:method => :get,
|
39
|
+
:scheme => "http"
|
40
|
+
}
|
41
|
+
Excon.defaults[:instrumentor].instrument("excon.request", data) {}
|
42
|
+
|
43
|
+
expect(transaction.to_h["events"]).to include(
|
44
|
+
hash_including(
|
45
|
+
"name" => "request.excon",
|
46
|
+
"title" => "GET http://www.google.com",
|
47
|
+
"body" => ""
|
48
|
+
)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "instruments a http response" do
|
53
|
+
data = { :host => "www.google.com" }
|
54
|
+
Excon.defaults[:instrumentor].instrument("excon.response", data) {}
|
55
|
+
|
56
|
+
expect(transaction.to_h["events"]).to include(
|
57
|
+
hash_including(
|
58
|
+
"name" => "response.excon",
|
59
|
+
"title" => "www.google.com",
|
60
|
+
"body" => ""
|
61
|
+
)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "without Excon" do
|
68
|
+
describe "#dependencies_present?" do
|
69
|
+
subject { described_class.new.dependencies_present? }
|
70
|
+
|
71
|
+
it { is_expected.to be_falsy }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|