appsignal 3.1.5-java → 3.2.0-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.
@@ -3,17 +3,59 @@ module Appsignal
3
3
  class SidekiqProbe
4
4
  include Helpers
5
5
 
6
+ class Sidekiq7Adapter
7
+ def self.redis_info
8
+ redis_info = nil
9
+ ::Sidekiq.redis { |c| redis_info = c.info }
10
+ redis_info
11
+ end
12
+
13
+ def self.hostname
14
+ host = nil
15
+ ::Sidekiq.redis do |c|
16
+ host = c.config.host
17
+ end
18
+ host
19
+ end
20
+ end
21
+
22
+ class Sidekiq6Adapter
23
+ def self.redis_info
24
+ return unless ::Sidekiq.respond_to?(:redis_info)
25
+
26
+ ::Sidekiq.redis_info
27
+ end
28
+
29
+ def self.hostname
30
+ host = nil
31
+ ::Sidekiq.redis do |c|
32
+ host = c.connection[:host] if c.respond_to? :connection
33
+ end
34
+ host
35
+ end
36
+ end
37
+
6
38
  # @api private
7
39
  attr_reader :config
8
40
 
41
+ def self.sidekiq7_and_greater?
42
+ Gem::Version.new(::Sidekiq::VERSION) >= Gem::Version.new("7.0.0")
43
+ end
44
+
9
45
  # @api private
10
46
  def self.dependencies_present?
47
+ return true if sidekiq7_and_greater?
48
+ return unless defined?(::Redis::VERSION) # Sidekiq <= 6
49
+
11
50
  Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("3.3.5")
12
51
  end
13
52
 
14
53
  def initialize(config = {})
15
54
  @config = config
16
55
  @cache = {}
56
+ is_sidekiq7 = self.class.sidekiq7_and_greater?
57
+ @adapter = is_sidekiq7 ? Sidekiq7Adapter : Sidekiq6Adapter
58
+
17
59
  config_string = " with config: #{config}" unless config.empty?
18
60
  Appsignal.logger.debug("Initializing Sidekiq probe#{config_string}")
19
61
  require "sidekiq/api"
@@ -28,11 +70,11 @@ module Appsignal
28
70
 
29
71
  private
30
72
 
31
- attr_reader :cache
73
+ attr_reader :adapter, :cache
32
74
 
33
75
  def track_redis_info
34
- return unless ::Sidekiq.respond_to?(:redis_info)
35
- redis_info = ::Sidekiq.redis_info
76
+ redis_info = adapter.redis_info
77
+ return unless redis_info
36
78
 
37
79
  gauge "connection_count", redis_info.fetch("connected_clients")
38
80
  gauge "memory_usage", redis_info.fetch("used_memory")
@@ -81,8 +123,7 @@ module Appsignal
81
123
  return @hostname
82
124
  end
83
125
 
84
- host = nil
85
- ::Sidekiq.redis { |c| host = c.connection[:host] }
126
+ host = adapter.hostname
86
127
  Appsignal.logger.debug "Sidekiq probe: Using Redis server hostname " \
87
128
  "#{host.inspect} as hostname"
88
129
  @hostname = host
@@ -0,0 +1,19 @@
1
+ module Appsignal
2
+ module Utils
3
+ # Subclass of logger with method to only log a warning once
4
+ # prevents the local log from filling up with repeated messages.
5
+ class IntegrationLogger < ::Logger
6
+ def seen_keys
7
+ @seen_keys ||= Set.new
8
+ end
9
+
10
+ def warn_once_then_debug(key, message)
11
+ if !seen_keys.add?(key).nil?
12
+ warn message
13
+ else
14
+ debug message
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -3,5 +3,6 @@
3
3
  require "appsignal/utils/deprecation_message"
4
4
  require "appsignal/utils/data"
5
5
  require "appsignal/utils/hash_sanitizer"
6
+ require "appsignal/utils/integration_logger"
6
7
  require "appsignal/utils/json"
7
8
  require "appsignal/utils/query_params_sanitizer"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.1.5".freeze
4
+ VERSION = "3.2.0".freeze
5
5
  end
data/lib/appsignal.rb CHANGED
@@ -175,8 +175,8 @@ module Appsignal
175
175
  end
176
176
 
177
177
  def logger
178
- @logger ||= Appsignal::Logger.new(in_memory_log).tap do |l|
179
- l.level = Logger::INFO
178
+ @logger ||= Appsignal::Utils::IntegrationLogger.new(in_memory_log).tap do |l|
179
+ l.level = ::Logger::INFO
180
180
  l.formatter = log_formatter("appsignal")
181
181
  end
182
182
  end
@@ -254,12 +254,12 @@ module Appsignal
254
254
  private
255
255
 
256
256
  def start_stdout_logger
257
- @logger = Appsignal::Logger.new($stdout)
257
+ @logger = Appsignal::Utils::IntegrationLogger.new($stdout)
258
258
  logger.formatter = log_formatter("appsignal")
259
259
  end
260
260
 
261
261
  def start_file_logger(path)
262
- @logger = Appsignal::Logger.new(path)
262
+ @logger = Appsignal::Utils::IntegrationLogger.new(path)
263
263
  logger.formatter = log_formatter
264
264
  rescue SystemCallError => error
265
265
  start_stdout_logger
@@ -166,10 +166,12 @@ describe Appsignal::Config do
166
166
  :ignore_actions => [],
167
167
  :ignore_errors => [],
168
168
  :ignore_namespaces => [],
169
+ :instrument_http_rb => true,
169
170
  :instrument_net_http => true,
170
171
  :instrument_redis => true,
171
172
  :instrument_sequel => true,
172
173
  :log => "file",
174
+ :logging_endpoint => "https://appsignal-endpoint.net",
173
175
  :name => "TestApp",
174
176
  :push_api_key => "abc",
175
177
  :request_headers => [],
@@ -536,6 +538,18 @@ describe Appsignal::Config do
536
538
  end
537
539
  end
538
540
  end
541
+
542
+ describe ":logging_endpoint" do
543
+ subject { config[:logging_endpoint] }
544
+
545
+ context "with a non-standard port" do
546
+ let(:config) { project_fixture_config("production", :logging_endpoint => "http://localhost:4567") }
547
+
548
+ it "keeps the port" do
549
+ expect(subject).to eq "http://localhost:4567"
550
+ end
551
+ end
552
+ end
539
553
  end
540
554
 
541
555
  describe "#[]" do
@@ -577,6 +591,7 @@ describe Appsignal::Config do
577
591
  describe "#write_to_environment" do
578
592
  let(:config) { project_fixture_config(:production) }
579
593
  before do
594
+ config[:logging_endpoint] = "http://localhost:123"
580
595
  config[:http_proxy] = "http://localhost"
581
596
  config[:ignore_actions] = %w[action1 action2]
582
597
  config[:ignore_errors] = %w[ExampleStandardError AnotherError]
@@ -600,6 +615,7 @@ describe Appsignal::Config do
600
615
  expect(ENV["_APPSIGNAL_DEBUG_LOGGING"]).to eq "false"
601
616
  expect(ENV["_APPSIGNAL_LOG"]).to eq "stdout"
602
617
  expect(ENV["_APPSIGNAL_LOG_FILE_PATH"]).to end_with("/tmp/appsignal.log")
618
+ expect(ENV["_APPSIGNAL_LOGGING_ENDPOINT"]).to eq "http://localhost:123"
603
619
  expect(ENV["_APPSIGNAL_PUSH_API_ENDPOINT"]).to eq "https://push.appsignal.com"
604
620
  expect(ENV["_APPSIGNAL_PUSH_API_KEY"]).to eq "abc"
605
621
  expect(ENV["_APPSIGNAL_APP_NAME"]).to eq "TestApp"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Appsignal::Hooks::HttpHook do
4
+ before :context do
5
+ start_agent
6
+ end
7
+
8
+ if DependencyHelper.http_present?
9
+ context "with instrument_http_rb set to true" do
10
+ describe "#dependencies_present?" do
11
+ subject { described_class.new.dependencies_present? }
12
+
13
+ it { is_expected.to be_truthy }
14
+ end
15
+
16
+ it "installs the HTTP plugin" do
17
+ expect(HTTP::Client.included_modules)
18
+ .to include(Appsignal::Integrations::HttpIntegration)
19
+ end
20
+ end
21
+
22
+ context "with instrument_http_rb set to false" do
23
+ before { Appsignal.config.config_hash[:instrument_http_rb] = false }
24
+ after { Appsignal.config.config_hash[:instrument_http_rb] = true }
25
+
26
+ describe "#dependencies_present?" do
27
+ subject { described_class.new.dependencies_present? }
28
+
29
+ it { is_expected.to be_falsy }
30
+ end
31
+ end
32
+ else
33
+ describe "#dependencies_present?" do
34
+ subject { described_class.new.dependencies_present? }
35
+
36
+ it { is_expected.to be_falsy }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ if DependencyHelper.http_present?
4
+ require "appsignal/integrations/http"
5
+
6
+ describe Appsignal::Integrations::HttpIntegration do
7
+ let(:transaction) { http_request_transaction }
8
+
9
+ around do |example|
10
+ keep_transactions { example.run }
11
+ end
12
+
13
+ before :context do
14
+ start_agent
15
+ end
16
+
17
+ before do
18
+ set_current_transaction(transaction)
19
+ end
20
+
21
+ it "instruments a HTTP request" do
22
+ stub_request(:get, "http://www.google.com")
23
+
24
+ HTTP.get("http://www.google.com")
25
+
26
+ transaction_hash = transaction.to_h
27
+ expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST)
28
+ expect(transaction_hash["events"].first).to include(
29
+ "body" => "",
30
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
31
+ "name" => "request.http_rb",
32
+ "title" => "GET http://www.google.com"
33
+ )
34
+ end
35
+
36
+ it "instruments a HTTPS request" do
37
+ stub_request(:get, "https://www.google.com")
38
+
39
+ HTTP.get("https://www.google.com")
40
+
41
+ transaction_hash = transaction.to_h
42
+ expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST)
43
+ expect(transaction_hash["events"].first).to include(
44
+ "body" => "",
45
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
46
+ "name" => "request.http_rb",
47
+ "title" => "GET https://www.google.com"
48
+ )
49
+ end
50
+
51
+ context "with request parameters" do
52
+ it "does not include the query parameters in the title" do
53
+ stub_request(:get, "https://www.google.com?q=Appsignal")
54
+
55
+ HTTP.get("https://www.google.com", :params => { :q => "Appsignal" })
56
+
57
+ expect(transaction.to_h["events"].first).to include(
58
+ "body" => "",
59
+ "title" => "GET https://www.google.com"
60
+ )
61
+ end
62
+
63
+ it "does not include the request body in the title" do
64
+ stub_request(:post, "https://www.google.com")
65
+ .with(:body => { :q => "Appsignal" }.to_json)
66
+
67
+ HTTP.post("https://www.google.com", :json => { :q => "Appsignal" })
68
+
69
+ expect(transaction.to_h["events"].first).to include(
70
+ "body" => "",
71
+ "title" => "POST https://www.google.com"
72
+ )
73
+ end
74
+ end
75
+
76
+ context "with an HTTP exception" do
77
+ let(:error) { ExampleException.new("oh no!") }
78
+
79
+ it "reports the exception and re-raises it" do
80
+ stub_request(:get, "https://www.google.com").and_raise(error)
81
+
82
+ expect do
83
+ HTTP.get("https://www.google.com")
84
+ end.to raise_error(ExampleException)
85
+
86
+ transaction_hash = transaction.to_h
87
+ expect(transaction_hash).to include("namespace" => Appsignal::Transaction::HTTP_REQUEST)
88
+ expect(transaction_hash["events"].first).to include(
89
+ "body" => "",
90
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
91
+ "name" => "request.http_rb",
92
+ "title" => "GET https://www.google.com"
93
+ )
94
+
95
+ expect(transaction_hash["error"]).to include(
96
+ "backtrace" => kind_of(String),
97
+ "name" => error.class.name,
98
+ "message" => error.message
99
+ )
100
+ end
101
+ end
102
+ end
103
+ end
@@ -1,25 +1,94 @@
1
1
  describe Appsignal::Logger do
2
- let(:log) { std_stream }
3
- let(:logger) do
4
- Appsignal::Logger.new(log).tap do |l|
5
- l.formatter = logger_formatter
6
- end
2
+ let(:level) { ::Logger::DEBUG }
3
+ let(:logger) { Appsignal::Logger.new("group", level) }
4
+
5
+ it "should not create a logger with a nil group" do
6
+ expect do
7
+ Appsignal::Logger.new(nil, level)
8
+ end.to raise_error(TypeError)
7
9
  end
8
10
 
9
- describe "#seen_keys" do
10
- it "returns a Set" do
11
- expect(logger.seen_keys).to be_a(Set)
11
+ describe "#add" do
12
+ it "should log with a level and message" do
13
+ expect(Appsignal::Extension).to receive(:log)
14
+ .with("group", 3, "Log message", instance_of(Appsignal::Extension::Data))
15
+ logger.add(::Logger::INFO, "Log message")
16
+ end
17
+
18
+ it "should log with a block" do
19
+ expect(Appsignal::Extension).to receive(:log)
20
+ .with("group", 3, "Log message", instance_of(Appsignal::Extension::Data))
21
+ logger.add(::Logger::INFO) do
22
+ "Log message"
23
+ end
24
+ end
25
+
26
+ it "should log with a level, message and group" do
27
+ expect(Appsignal::Extension).to receive(:log)
28
+ .with("other_group", 3, "Log message", instance_of(Appsignal::Extension::Data))
29
+ logger.add(::Logger::INFO, "Log message", "other_group")
30
+ end
31
+
32
+ it "should return with a nil message" do
33
+ expect(Appsignal::Extension).not_to receive(:log)
34
+ logger.add(::Logger::INFO, nil)
35
+ end
36
+
37
+ context "with debug log level" do
38
+ let(:level) { ::Logger::INFO }
39
+
40
+ it "should skip logging if the level is too low" do
41
+ expect(Appsignal::Extension).not_to receive(:log)
42
+ logger.add(::Logger::DEBUG, "Log message")
43
+ end
12
44
  end
13
45
  end
14
46
 
15
- describe "#warn_once_then_debug" do
16
- it "only warns once, then uses debug" do
17
- message = "This is a log line"
18
- 3.times { logger.warn_once_then_debug(:key, message) }
47
+ [
48
+ ["debug", 2, ::Logger::INFO],
49
+ ["info", 3, ::Logger::WARN],
50
+ ["warn", 5, ::Logger::ERROR],
51
+ ["error", 6, ::Logger::FATAL],
52
+ ["fatal", 7, nil]
53
+ ].each do |method|
54
+ describe "##{method[0]}" do
55
+ it "should log with a message" do
56
+ expect(Appsignal::Utils::Data).to receive(:generate)
57
+ .with({})
58
+ .and_call_original
59
+ expect(Appsignal::Extension).to receive(:log)
60
+ .with("group", method[1], "Log message", instance_of(Appsignal::Extension::Data))
61
+
62
+ logger.send(method[0], "Log message")
63
+ end
64
+
65
+ it "should log with a block" do
66
+ expect(Appsignal::Utils::Data).to receive(:generate)
67
+ .with({})
68
+ .and_call_original
69
+ expect(Appsignal::Extension).to receive(:log)
70
+ .with("group", method[1], "Log message", instance_of(Appsignal::Extension::Data))
71
+
72
+ logger.send(method[0]) do
73
+ "Log message"
74
+ end
75
+ end
76
+
77
+ it "should return with a nil message" do
78
+ expect(Appsignal::Extension).not_to receive(:log)
79
+ logger.send(method[0])
80
+ end
81
+
82
+ if method[2]
83
+ context "with a lower log level" do
84
+ let(:level) { method[2] }
19
85
 
20
- logs = log_contents(log)
21
- expect(logs.scan(/#{Regexp.escape(log_line(:WARN, message))}/).count).to eql(1)
22
- expect(logs.scan(/#{Regexp.escape(log_line(:DEBUG, message))}/).count).to eql(2)
86
+ it "should skip logging if the level is too low" do
87
+ expect(Appsignal::Extension).not_to receive(:log)
88
+ logger.send(method[0], "Log message")
89
+ end
90
+ end
91
+ end
23
92
  end
24
93
  end
25
94
  end