appsignal 3.11.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +109 -0
  3. data/Rakefile +1 -1
  4. data/lib/appsignal/cli/diagnose.rb +1 -1
  5. data/lib/appsignal/config.rb +150 -32
  6. data/lib/appsignal/demo.rb +1 -6
  7. data/lib/appsignal/integrations/grape.rb +7 -0
  8. data/lib/appsignal/integrations/hanami.rb +8 -43
  9. data/lib/appsignal/integrations/padrino.rb +8 -73
  10. data/lib/appsignal/integrations/railtie.rb +35 -13
  11. data/lib/appsignal/integrations/sinatra.rb +8 -19
  12. data/lib/appsignal/loaders/grape.rb +13 -0
  13. data/lib/appsignal/loaders/hanami.rb +40 -0
  14. data/lib/appsignal/loaders/padrino.rb +68 -0
  15. data/lib/appsignal/loaders/sinatra.rb +24 -0
  16. data/lib/appsignal/loaders.rb +92 -0
  17. data/lib/appsignal/rack/abstract_middleware.rb +2 -1
  18. data/lib/appsignal/rack/event_handler.rb +5 -5
  19. data/lib/appsignal/rack.rb +6 -0
  20. data/lib/appsignal/version.rb +1 -1
  21. data/lib/appsignal.rb +163 -9
  22. data/spec/lib/appsignal/cli/demo_spec.rb +0 -1
  23. data/spec/lib/appsignal/cli/diagnose/paths_spec.rb +1 -1
  24. data/spec/lib/appsignal/cli/diagnose_spec.rb +0 -1
  25. data/spec/lib/appsignal/config_spec.rb +153 -1
  26. data/spec/lib/appsignal/demo_spec.rb +1 -2
  27. data/spec/lib/appsignal/environment_spec.rb +4 -2
  28. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +3 -6
  29. data/spec/lib/appsignal/hooks/activejob_spec.rb +3 -3
  30. data/spec/lib/appsignal/hooks/dry_monitor_spec.rb +4 -7
  31. data/spec/lib/appsignal/hooks/excon_spec.rb +3 -6
  32. data/spec/lib/appsignal/hooks/gvl_spec.rb +2 -2
  33. data/spec/lib/appsignal/hooks/http_spec.rb +1 -3
  34. data/spec/lib/appsignal/hooks/net_http_spec.rb +1 -1
  35. data/spec/lib/appsignal/hooks/redis_client_spec.rb +5 -8
  36. data/spec/lib/appsignal/hooks/redis_spec.rb +3 -6
  37. data/spec/lib/appsignal/hooks/resque_spec.rb +1 -1
  38. data/spec/lib/appsignal/hooks/sequel_spec.rb +3 -5
  39. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +1 -1
  40. data/spec/lib/appsignal/hooks/webmachine_spec.rb +1 -1
  41. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +2 -2
  42. data/spec/lib/appsignal/integrations/grape_spec.rb +36 -0
  43. data/spec/lib/appsignal/integrations/hanami_spec.rb +9 -178
  44. data/spec/lib/appsignal/integrations/http_spec.rb +1 -5
  45. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +4 -2
  46. data/spec/lib/appsignal/integrations/net_http_spec.rb +1 -1
  47. data/spec/lib/appsignal/integrations/object_spec.rb +1 -3
  48. data/spec/lib/appsignal/integrations/padrino_spec.rb +8 -330
  49. data/spec/lib/appsignal/integrations/railtie_spec.rb +275 -191
  50. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +1 -1
  51. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +11 -9
  52. data/spec/lib/appsignal/integrations/sinatra_spec.rb +9 -104
  53. data/spec/lib/appsignal/loaders/grape_spec.rb +12 -0
  54. data/spec/lib/appsignal/loaders/hanami_spec.rb +95 -0
  55. data/spec/lib/appsignal/loaders/padrino_spec.rb +277 -0
  56. data/spec/lib/appsignal/loaders/sinatra_spec.rb +47 -0
  57. data/spec/lib/appsignal/loaders_spec.rb +137 -0
  58. data/spec/lib/appsignal/probes/sidekiq_spec.rb +1 -1
  59. data/spec/lib/appsignal/probes_spec.rb +6 -5
  60. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +3 -2
  61. data/spec/lib/appsignal/rack/event_handler_spec.rb +33 -0
  62. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +1 -1
  63. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +2 -35
  64. data/spec/lib/appsignal/rack/hanami_middleware_spec.rb +1 -1
  65. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +3 -3
  66. data/spec/lib/appsignal/span_spec.rb +1 -3
  67. data/spec/lib/appsignal/transaction_spec.rb +4 -2
  68. data/spec/lib/appsignal_spec.rb +278 -26
  69. data/spec/lib/puma/appsignal_spec.rb +0 -3
  70. data/spec/spec_helper.rb +5 -4
  71. data/spec/support/helpers/config_helpers.rb +2 -1
  72. data/spec/support/helpers/loader_helper.rb +21 -0
  73. data/spec/support/stubs/appsignal/loaders/loader_stub.rb +7 -0
  74. data/spec/support/testing.rb +46 -0
  75. metadata +15 -2
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Loaders
5
+ class GrapeLoader < Loader
6
+ register :grape
7
+
8
+ def on_load
9
+ require "appsignal/rack/grape_middleware"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Loaders
5
+ class HanamiLoader < Loader
6
+ register :hanami
7
+
8
+ def on_load
9
+ hanami_app_config = ::Hanami.app.config
10
+ register_config_defaults(
11
+ :root_path => hanami_app_config.root.to_s,
12
+ :env => hanami_app_config.env
13
+ )
14
+ end
15
+
16
+ def on_start
17
+ require "appsignal/rack/hanami_middleware"
18
+
19
+ hanami_app_config = ::Hanami.app.config
20
+ hanami_app_config.middleware.use(
21
+ ::Rack::Events,
22
+ [Appsignal::Rack::EventHandler.new]
23
+ )
24
+ hanami_app_config.middleware.use(Appsignal::Rack::HanamiMiddleware)
25
+
26
+ ::Hanami::Action.prepend Appsignal::Loaders::HanamiLoader::HanamiIntegration
27
+ end
28
+
29
+ module HanamiIntegration
30
+ def call(env)
31
+ super
32
+ ensure
33
+ transaction = env[::Appsignal::Rack::APPSIGNAL_TRANSACTION]
34
+
35
+ transaction&.set_action_if_nil(self.class.name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Loaders
5
+ class PadrinoLoader < Loader
6
+ register :padrino
7
+
8
+ def on_load
9
+ register_config_defaults(
10
+ :root_path => Padrino.mounted_root,
11
+ :env => Padrino.env
12
+ )
13
+ end
14
+
15
+ def on_start
16
+ require "appsignal/rack/sinatra_instrumentation"
17
+
18
+ Padrino::Application.prepend(Appsignal::Loaders::PadrinoLoader::PadrinoIntegration)
19
+
20
+ Padrino.before_load do
21
+ Padrino.use ::Rack::Events, [Appsignal::Rack::EventHandler.new]
22
+ Padrino.use Appsignal::Rack::SinatraBaseInstrumentation,
23
+ :instrument_event_name => "process_action.padrino"
24
+ end
25
+ end
26
+
27
+ module PadrinoIntegration
28
+ def route!(base = settings, pass_block = nil)
29
+ return super if !Appsignal.active? || env["sinatra.static_file"]
30
+
31
+ begin
32
+ super
33
+ ensure
34
+ transaction = Appsignal::Transaction.current
35
+ transaction.set_action_if_nil(get_payload_action(request))
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def get_payload_action(request)
42
+ # Short-circuit is there's no request object to obtain information from
43
+ return settings.name.to_s unless request
44
+
45
+ # Newer versions expose the action / controller on the request class.
46
+ # Newer versions also still expose a route_obj so we must prioritize the
47
+ # action/fullpath methods.
48
+ # The `request.action` and `request.controller` values are `nil` when a
49
+ # endpoint is not found, `""` if not specified by the user.
50
+ controller_name = request.controller if request.respond_to?(:controller)
51
+ action_name = request.action if request.respond_to?(:action)
52
+ action_name ||= ""
53
+
54
+ return "#{settings.name}:#{controller_name}##{action_name}" unless action_name.empty?
55
+
56
+ # Older versions of Padrino work with a route object
57
+ if request.respond_to?(:route_obj) && request.route_obj
58
+ return "#{settings.name}:#{request.route_obj.original_path}"
59
+ end
60
+
61
+ # Fall back to the application name if we haven't found an action name in
62
+ # any previous methods.
63
+ "#{settings.name}#unknown"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Loaders
5
+ class SinatraLoader < Loader
6
+ register :sinatra
7
+
8
+ def on_load
9
+ app_settings = ::Sinatra::Application.settings
10
+ register_config_defaults(
11
+ :root_path => app_settings.root,
12
+ :env => app_settings.environment
13
+ )
14
+ end
15
+
16
+ def on_start
17
+ require "appsignal/rack/sinatra_instrumentation"
18
+
19
+ ::Sinatra::Base.use(::Rack::Events, [Appsignal::Rack::EventHandler.new])
20
+ ::Sinatra::Base.use(Appsignal::Rack::SinatraBaseInstrumentation)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ # @api private
5
+ module Loaders
6
+ class << self
7
+ def loaders
8
+ @loaders ||= {}
9
+ end
10
+
11
+ def instances
12
+ @instances ||= {}
13
+ end
14
+
15
+ def register(name, klass)
16
+ loaders[name.to_sym] = klass
17
+ end
18
+
19
+ def registered?(name)
20
+ loaders.key?(name)
21
+ end
22
+
23
+ def unregister(name)
24
+ loaders.delete(name)
25
+ end
26
+
27
+ def load(name_str)
28
+ name = name_str.to_sym
29
+
30
+ unless registered?(name)
31
+ require_loader(name)
32
+ unless registered?(name)
33
+ Appsignal.internal_logger
34
+ .warn("No loader found with the name '#{name}'.")
35
+ return
36
+ end
37
+ end
38
+
39
+ Appsignal.internal_logger.debug("Loading '#{name}' loader")
40
+
41
+ begin
42
+ loader_klass = loaders[name]
43
+ loader = loader_klass.new
44
+ instances[name] = loader
45
+ loader.on_load if loader.respond_to?(:on_load)
46
+ rescue => e
47
+ Appsignal.internal_logger.error(
48
+ "An error occurred while loading the '#{name}' loader: " \
49
+ "#{e.class}: #{e.message}\n#{e.backtrace}"
50
+ )
51
+ end
52
+ end
53
+
54
+ def start
55
+ instances.each do |name, instance|
56
+ Appsignal.internal_logger.debug("Starting '#{name}' loader")
57
+ begin
58
+ instance.on_start if instance.respond_to?(:on_start)
59
+ rescue => e
60
+ Appsignal.internal_logger.error(
61
+ "An error occurred while starting the '#{name}' loader: " \
62
+ "#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
63
+ )
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def require_loader(name)
71
+ require "appsignal/loaders/#{name}"
72
+ rescue LoadError
73
+ nil
74
+ end
75
+ end
76
+
77
+ class Loader
78
+ class << self
79
+ attr_reader :loader_name
80
+
81
+ def register(name)
82
+ @loader_name = name
83
+ Loaders.register(name, self)
84
+ end
85
+ end
86
+
87
+ def register_config_defaults(options)
88
+ Appsignal::Config.add_loader_defaults(self.class.loader_name, options)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -91,9 +91,10 @@ module Appsignal
91
91
  def call_app(env, transaction)
92
92
  status, headers, obody = @app.call(env)
93
93
  body =
94
- if obody.is_a? Appsignal::Rack::BodyWrapper
94
+ if env[Appsignal::Rack::APPSIGNAL_RESPONSE_INSTRUMENTED]
95
95
  obody
96
96
  else
97
+ env[Appsignal::Rack::APPSIGNAL_RESPONSE_INSTRUMENTED] = true
97
98
  # Instrument response body and closing of the response body
98
99
  Appsignal::Rack::BodyWrapper.wrap(obody, transaction)
99
100
  end
@@ -2,11 +2,6 @@
2
2
 
3
3
  module Appsignal
4
4
  module Rack
5
- APPSIGNAL_TRANSACTION = "appsignal.transaction"
6
- APPSIGNAL_EVENT_HANDLER_ID = "appsignal.event_handler_id"
7
- APPSIGNAL_EVENT_HANDLER_HAS_ERROR = "appsignal.event_handler.error"
8
- RACK_AFTER_REPLY = "rack.after_reply"
9
-
10
5
  # Instrumentation middleware using Rack's Events module.
11
6
  #
12
7
  # We recommend using this in combination with the
@@ -56,6 +51,8 @@ module Appsignal
56
51
 
57
52
  # @api private
58
53
  def on_start(request, _response)
54
+ return unless Appsignal.active?
55
+
59
56
  event_handler = self
60
57
  self.class.safe_execution("Appsignal::Rack::EventHandler#on_start") do
61
58
  request.env[APPSIGNAL_EVENT_HANDLER_ID] ||= id
@@ -90,6 +87,8 @@ module Appsignal
90
87
 
91
88
  # @api private
92
89
  def on_error(request, _response, error)
90
+ return unless Appsignal.active?
91
+
93
92
  self.class.safe_execution("Appsignal::Rack::EventHandler#on_error") do
94
93
  return unless request_handler?(request.env[APPSIGNAL_EVENT_HANDLER_ID])
95
94
 
@@ -103,6 +102,7 @@ module Appsignal
103
102
 
104
103
  # @api private
105
104
  def on_finish(request, response)
105
+ return unless Appsignal.active?
106
106
  return unless request_handler?(request.env[APPSIGNAL_EVENT_HANDLER_ID])
107
107
 
108
108
  transaction = request.env[APPSIGNAL_TRANSACTION]
@@ -3,6 +3,12 @@
3
3
  module Appsignal
4
4
  # @api private
5
5
  module Rack
6
+ APPSIGNAL_TRANSACTION = "appsignal.transaction"
7
+ APPSIGNAL_EVENT_HANDLER_ID = "appsignal.event_handler_id"
8
+ APPSIGNAL_EVENT_HANDLER_HAS_ERROR = "appsignal.event_handler.error"
9
+ APPSIGNAL_RESPONSE_INSTRUMENTED = "appsignal.response_instrumentation_active"
10
+ RACK_AFTER_REPLY = "rack.after_reply"
11
+
6
12
  class Utils
7
13
  # Fetch the queue start time from the request environment.
8
14
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.11.0"
4
+ VERSION = "3.12.0"
5
5
  end
data/lib/appsignal.rb CHANGED
@@ -22,8 +22,8 @@ module Appsignal
22
22
  include Helpers::Instrumentation
23
23
  include Helpers::Metrics
24
24
 
25
- # Accessor for the AppSignal configuration.
26
- # Return the current AppSignal configuration.
25
+ # The loaded AppSignal configuration.
26
+ # Returns the current AppSignal configuration.
27
27
  #
28
28
  # Can return `nil` if no configuration has been set or automatically loaded
29
29
  # by an automatic integration or by calling {.start}.
@@ -31,12 +31,31 @@ module Appsignal
31
31
  # @example
32
32
  # Appsignal.config
33
33
  #
34
- # @example Setting the configuration
35
- # Appsignal.config = Appsignal::Config.new(Dir.pwd, "production")
36
- #
37
34
  # @return [Config, nil]
35
+ # @see configure
36
+ # @see Config
37
+ attr_reader :config
38
+
39
+ # Set the AppSignal config.
40
+ #
41
+ # @deprecated Use {Appsignal.configure} instead.
42
+ # @param conf [Appsignal::Config]
43
+ # @return [void]
38
44
  # @see Config
39
- attr_accessor :config
45
+ def config=(conf)
46
+ Appsignal::Utils::StdoutAndLoggerMessage.warning \
47
+ "Configuring AppSignal with `Appsignal.config=` is deprecated. " \
48
+ "Use `Appsignal.configure { |config| ... }` to configure AppSignal. " \
49
+ "https://docs.appsignal.com/ruby/configuration.html\n" \
50
+ "#{caller.first}"
51
+ @config = conf
52
+ end
53
+
54
+ # @api private
55
+ def _config=(conf)
56
+ @config = conf
57
+ end
58
+
40
59
  # Accessor for toggle if the AppSignal C-extension is loaded.
41
60
  #
42
61
  # Can be `nil` if extension has not been loaded yet. See
@@ -87,7 +106,9 @@ module Appsignal
87
106
  # Appsignal.start
88
107
  #
89
108
  # @example with custom loaded configuration
90
- # Appsignal.config = Appsignal::Config.new(Dir.pwd, "production")
109
+ # Appsignal.configure(:production) do |config|
110
+ # config.ignore_actions = ["My action"]
111
+ # end
91
112
  # Appsignal.start
92
113
  #
93
114
  # @return [void]
@@ -109,11 +130,13 @@ module Appsignal
109
130
 
110
131
  if config.valid?
111
132
  if config.active?
133
+ @started = true
112
134
  internal_logger.info "Starting AppSignal #{Appsignal::VERSION} " \
113
135
  "(#{$PROGRAM_NAME}, Ruby #{RUBY_VERSION}, #{RUBY_PLATFORM})"
114
136
  config.write_to_environment
115
137
  Appsignal::Extension.start
116
138
  Appsignal::Hooks.load_hooks
139
+ Appsignal::Loaders.start
117
140
 
118
141
  if config[:enable_allocation_tracking] && !Appsignal::System.jruby?
119
142
  Appsignal::Extension.install_allocation_event_hook
@@ -147,14 +170,102 @@ module Appsignal
147
170
  # @since 1.0.0
148
171
  def stop(called_by = nil)
149
172
  if called_by
150
- internal_logger.debug("Stopping appsignal (#{called_by})")
173
+ internal_logger.debug("Stopping AppSignal (#{called_by})")
151
174
  else
152
- internal_logger.debug("Stopping appsignal")
175
+ internal_logger.debug("Stopping AppSignal")
153
176
  end
154
177
  Appsignal::Extension.stop
155
178
  Appsignal::Probes.stop
156
179
  end
157
180
 
181
+ # Configure the AppSignal Ruby gem using a DSL.
182
+ #
183
+ # Pass a block to the configure method to configure the Ruby gem.
184
+ #
185
+ # Each config option defined in our docs can be fetched, set and modified
186
+ # via a helper method in the given block.
187
+ #
188
+ # After AppSignal has started using {start}, the configuration can not be
189
+ # modified. Any calls to this helper will be ignored.
190
+ #
191
+ # This helper should not be used to configure multiple environments, like
192
+ # done in the YAML file. Configure the environment you want active when the
193
+ # application starts.
194
+ #
195
+ # @example Configure AppSignal for the application
196
+ # Appsignal.configure do |config|
197
+ # config.path = "/the/app/path"
198
+ # config.active = ENV["APP_ACTIVE"] == "true"
199
+ # config.push_api_key = File.read("appsignal_key.txt").chomp
200
+ # config.ignore_actions = ENDPOINTS.select { |e| e.public? }.map(&:name)
201
+ # config.request_headers << "MY_CUSTOM_HEADER"
202
+ # end
203
+ #
204
+ # @example Configure AppSignal for the application and select the environment
205
+ # Appsignal.configure(:production) do |config|
206
+ # config.active = true
207
+ # end
208
+ #
209
+ # @example Automatically detects the app environment
210
+ # # Tries to determine the app environment automatically from the
211
+ # # environment and the libraries it integrates with.
212
+ # ENV["RACK_ENV"] = "production"
213
+ #
214
+ # Appsignal.configure do |config|
215
+ # config.env # => "production"
216
+ # end
217
+ #
218
+ # @example Calling configure multiple times for different environments resets the configuration
219
+ # Appsignal.configure(:development) do |config|
220
+ # config.ignore_actions = ["My action"]
221
+ # end
222
+ #
223
+ # Appsignal.configure(:production) do |config|
224
+ # config.ignore_actions # => []
225
+ # end
226
+ #
227
+ # @example Load config without a block
228
+ # # This will require either ENV vars being set
229
+ # # or the config/appsignal.yml being present
230
+ # Appsignal.configure
231
+ # # Or for the environment given as an argument
232
+ # Appsignal.configure(:production)
233
+ #
234
+ # @yield [Config] Gives the {Config} instance to the block.
235
+ # @return [void]
236
+ # @see config
237
+ # @see Config
238
+ # @see https://docs.appsignal.com/ruby/configuration.html Configuration guide
239
+ # @see https://docs.appsignal.com/ruby/configuration/options.html Configuration options
240
+ def configure(env = nil)
241
+ if Appsignal.started?
242
+ Appsignal.internal_logger
243
+ .warn("AppSignal is already started. Ignoring `Appsignal.configure` call.")
244
+ return
245
+ end
246
+
247
+ if config && config.env == env.to_s
248
+ config
249
+ else
250
+ self._config = Appsignal::Config.new(
251
+ Dir.pwd,
252
+ env || ENV["APPSIGNAL_APP_ENV"] || ENV["RAILS_ENV"] || ENV.fetch("RACK_ENV", nil),
253
+ {},
254
+ Appsignal.internal_logger,
255
+ nil,
256
+ false
257
+ )
258
+ config.load_config
259
+ end
260
+
261
+ config_dsl = Appsignal::Config::ConfigDSL.new(config)
262
+ if block_given?
263
+ yield config_dsl
264
+ config.merge_dsl_options(config_dsl.dsl_options)
265
+ end
266
+ config.validate
267
+ end
268
+
158
269
  def forked
159
270
  return unless active?
160
271
 
@@ -163,6 +274,38 @@ module Appsignal
163
274
  Appsignal::Extension.start
164
275
  end
165
276
 
277
+ # Load an AppSignal integration.
278
+ #
279
+ # Load one of the supported integrations via our loader system.
280
+ # This will set config defaults and integratie with the library if
281
+ # AppSignal is active upon start.
282
+ #
283
+ # @example Load Sinatra integrations
284
+ # # First load the integration
285
+ # Appsignal.load(:sinatra)
286
+ # # Start AppSignal
287
+ # Appsignal.start
288
+ #
289
+ # @example Load Sinatra integrations and define custom config
290
+ # # First load the integration
291
+ # Appsignal.load(:sinatra)
292
+ #
293
+ # # Customize config
294
+ # Appsignal.configure do |config|
295
+ # config.ignore_actions = ["GET /ping"]
296
+ # end
297
+ #
298
+ #
299
+ # # Start AppSignal
300
+ # Appsignal.start
301
+ #
302
+ # @param integration_name [String, Symbol] Name of the integration to load.
303
+ # @return [void]
304
+ # @since 3.12.0
305
+ def load(integration_name)
306
+ Loaders.load(integration_name)
307
+ end
308
+
166
309
  # @api private
167
310
  def get_server_state(key)
168
311
  Appsignal::Extension.get_server_state(key)
@@ -238,6 +381,16 @@ module Appsignal
238
381
  !!extension_loaded
239
382
  end
240
383
 
384
+ # Returns if {.start} has been called before with a valid config to start
385
+ # AppSignal.
386
+ #
387
+ # @return [Boolean]
388
+ # @see Extension
389
+ # @since 3.12.0
390
+ def started?
391
+ defined?(@started) ? @started : false
392
+ end
393
+
241
394
  # Returns the active state of the AppSignal integration.
242
395
  #
243
396
  # Conditions apply for AppSignal to be marked as active:
@@ -314,6 +467,7 @@ module Appsignal
314
467
  end
315
468
  end
316
469
 
470
+ require "appsignal/loaders"
317
471
  require "appsignal/environment"
318
472
  require "appsignal/system"
319
473
  require "appsignal/utils"
@@ -13,7 +13,6 @@ describe Appsignal::CLI::Demo do
13
13
  ENV.delete("RACK_ENV")
14
14
  stub_api_request config, "auth"
15
15
  end
16
- after { Appsignal.config = nil }
17
16
 
18
17
  def run
19
18
  run_within_dir project_fixture_path
@@ -4,7 +4,7 @@ require "appsignal/cli/diagnose/paths"
4
4
 
5
5
  describe Appsignal::CLI::Diagnose::Paths do
6
6
  describe "#paths" do
7
- before { Appsignal.config = project_fixture_config }
7
+ before { start_agent }
8
8
 
9
9
  it "returns gem installation path as package_install_path" do
10
10
  expect(described_class.new.paths[:package_install_path]).to eq(
@@ -67,7 +67,6 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
67
67
  capture_diagnatics_report_request
68
68
  end
69
69
  before(:send_report => :no_cli_option) { options["no-send-report"] = nil }
70
- after { Appsignal.config = nil }
71
70
 
72
71
  def capture_diagnatics_report_request
73
72
  stub_diagnostics_report_request.to_rack(DiagnosticsReportEndpoint)