appsignal 5.0.0.rc.1-java → 5.0.0.rc.2-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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +140 -0
  3. data/README.md +2 -1
  4. data/Rakefile +110 -6
  5. data/appsignal.gemspec +7 -0
  6. data/build_matrix.yml +7 -2
  7. data/ext/agent.rb +27 -27
  8. data/lib/appsignal/cli/demo.rb +5 -0
  9. data/lib/appsignal/cli/diagnose.rb +8 -48
  10. data/lib/appsignal/cli/helpers.rb +45 -0
  11. data/lib/appsignal/config.rb +262 -37
  12. data/lib/appsignal/demo.rb +11 -9
  13. data/lib/appsignal/helpers/instrumentation.rb +88 -0
  14. data/lib/appsignal/hooks/action_cable.rb +8 -2
  15. data/lib/appsignal/hooks/active_job.rb +189 -1
  16. data/lib/appsignal/integrations/delayed_job_plugin.rb +172 -32
  17. data/lib/appsignal/integrations/que.rb +41 -9
  18. data/lib/appsignal/integrations/railtie.rb +4 -2
  19. data/lib/appsignal/integrations/resque.rb +26 -3
  20. data/lib/appsignal/integrations/shoryuken.rb +21 -2
  21. data/lib/appsignal/integrations/sidekiq.rb +23 -2
  22. data/lib/appsignal/integrations/webmachine.rb +13 -5
  23. data/lib/appsignal/opentelemetry/http_server_request.rb +35 -1
  24. data/lib/appsignal/opentelemetry/proxied_exporter.rb +83 -0
  25. data/lib/appsignal/opentelemetry.rb +182 -24
  26. data/lib/appsignal/rack/abstract_middleware.rb +4 -1
  27. data/lib/appsignal/rack/event_handler.rb +9 -2
  28. data/lib/appsignal/rack/grape_middleware.rb +37 -7
  29. data/lib/appsignal/rack.rb +29 -1
  30. data/lib/appsignal/transaction/base_backend.rb +21 -0
  31. data/lib/appsignal/transaction/extension_backend.rb +26 -0
  32. data/lib/appsignal/transaction/opentelemetry_backend.rb +90 -39
  33. data/lib/appsignal/transaction.rb +189 -32
  34. data/lib/appsignal/utils/request_headers.rb +78 -0
  35. data/lib/appsignal/utils.rb +1 -0
  36. data/lib/appsignal/version.rb +1 -1
  37. data/lib/appsignal.rb +1 -0
  38. data/sig/appsignal.rbi +205 -1
  39. data/sig/appsignal.rbs +196 -0
  40. metadata +3 -1
@@ -189,13 +189,19 @@ module Appsignal
189
189
 
190
190
  def configure_appsignal(options)
191
191
  env_option = options.fetch(:environment, nil)
192
+ # Mark app as Rails app
193
+ data[:app][:rails] = true if rails_present?
192
194
  # Try and load the Rails app, if any.
193
195
  # This will configure AppSignal through the config file or an
194
196
  # initializer.
195
- require_rails_app_if_present(env_option)
197
+ require_rails_app_if_present(env_option) do |error|
198
+ data[:app][:load_error] =
199
+ "#{error.class}: #{error.message}\n#{error.backtrace.join("\n")}"
200
+ end
196
201
 
197
202
  # No config loaded yet, try loading as normal
198
203
  Appsignal._load_config!(env_option) unless Appsignal.config
204
+ Appsignal.config.apply_overrides
199
205
  Appsignal._start_logger
200
206
  Appsignal.config.write_to_environment
201
207
  Appsignal.internal_logger.info("Starting AppSignal diagnose")
@@ -467,16 +473,7 @@ module Appsignal
467
473
  config = Appsignal.config
468
474
  data[:config] = {
469
475
  :options => config.config_hash.merge(:env => config.env),
470
- :sources => {
471
- :default => Appsignal::Config::DEFAULT_CONFIG,
472
- :system => config.system_config,
473
- :loaders => config.loaders_config,
474
- :initial => config.initial_config,
475
- :file => config.file_config,
476
- :env => config.env_config,
477
- :override => config.override_config,
478
- :dsl => config.dsl_config
479
- }
476
+ :sources => config.config_sources
480
477
  }
481
478
  print_config_options(config)
482
479
  end
@@ -627,43 +624,6 @@ module Appsignal
627
624
  puts " Read error: #{path[:read_error]}"
628
625
  print_empty_line
629
626
  end
630
-
631
- def print_empty_line
632
- puts "\n"
633
- end
634
-
635
- def require_rails_app_if_present(env_option)
636
- return unless rails_present?
637
-
638
- # Set the environment given as an option to the diagnose CLI so the
639
- # Rails app uses it when loaded.
640
- ENV["_APPSIGNAL_CONFIG_FILE_ENV"] = env_option
641
- # Mark app as Rails app
642
- data[:app][:rails] = true
643
- # Manually require the railtie, because it wasn't loaded when the CLI
644
- # started and AppSignal loaded, because the `Rails` constant wasn't
645
- # present.
646
- require "appsignal/integrations/railtie"
647
- # Start the Rails app, including railties and initializers.
648
- require Appsignal::Utils::RailsHelper.environment_config_path
649
- rescue LoadError, StandardError => error
650
- print_empty_line
651
- puts "ERROR: Error encountered while loading the Rails app"
652
- puts "#{error.class}: #{error.message}"
653
- puts error.backtrace
654
- data[:app][:load_error] =
655
- "#{error.class}: #{error.message}\n#{error.backtrace.join("\n")}"
656
- ensure
657
- ENV.delete("_APPSIGNAL_CONFIG_FILE_ENV")
658
- end
659
-
660
- def rails_present?
661
- # Try and load the Rails gem
662
- require "rails"
663
- true
664
- rescue LoadError
665
- false
666
- end
667
627
  end
668
628
  end
669
629
  end
@@ -36,6 +36,51 @@ module Appsignal
36
36
  "\e[#{color_code}m#{text}\e[#{reset_color_code}m"
37
37
  end
38
38
 
39
+ def print_empty_line
40
+ puts "\n"
41
+ end
42
+
43
+ def rails_present?
44
+ require "rails"
45
+ true
46
+ rescue LoadError
47
+ false
48
+ end
49
+
50
+ # The Rails gem being loadable says nothing about the directory the
51
+ # command runs in, so check for the app itself as well.
52
+ def rails_app_present?
53
+ rails_present? &&
54
+ File.exist?(Appsignal::Utils::RailsHelper.environment_config_path)
55
+ end
56
+
57
+ def load_rails_app(environment)
58
+ # Pass the environment given as a command line option to the app, so
59
+ # that the AppSignal config file uses it when the app loads it.
60
+ ENV["_APPSIGNAL_CONFIG_FILE_ENV"] = environment
61
+ # Require the railtie manually. It was not loaded when AppSignal
62
+ # loaded, because the `Rails` constant was not present at that point.
63
+ require "appsignal/integrations/railtie"
64
+ # Start the Rails app, including its railties and initializers.
65
+ require Appsignal::Utils::RailsHelper.environment_config_path
66
+ ensure
67
+ ENV.delete("_APPSIGNAL_CONFIG_FILE_ENV")
68
+ end
69
+
70
+ # Yields the error when the app fails to load, so that a command can
71
+ # report it in its own way.
72
+ def require_rails_app_if_present(environment)
73
+ return unless rails_app_present?
74
+
75
+ load_rails_app(environment)
76
+ rescue LoadError, StandardError => error
77
+ print_empty_line
78
+ puts "ERROR: Error encountered while loading the Rails app"
79
+ puts "#{error.class}: #{error.message}"
80
+ puts error.backtrace
81
+ yield error if block_given?
82
+ end
83
+
39
84
  def periods
40
85
  3.times do
41
86
  print "."
@@ -134,6 +134,11 @@ module Appsignal
134
134
  :instrument_sequel => true,
135
135
  :instrument_shoryuken => true,
136
136
  :instrument_sidekiq => true,
137
+ :keep_request_environment => [],
138
+ :keep_request_headers => %w[
139
+ accept accept-charset accept-encoding accept-language cache-control
140
+ connection content-length range
141
+ ],
137
142
  :log => "file",
138
143
  :logging_endpoint => "https://appsignal-endpoint.net",
139
144
  :ownership_set_namespace => false,
@@ -146,10 +151,10 @@ module Appsignal
146
151
  ],
147
152
  :response_headers => [],
148
153
  :send_environment_metadata => true,
149
- :send_function_parameters => nil,
154
+ :send_function_parameters => true,
150
155
  :send_params => true,
151
- :send_request_payload => nil,
152
- :send_request_query_parameters => nil,
156
+ :send_request_payload => true,
157
+ :send_request_query_parameters => true,
153
158
  :send_session_data => true,
154
159
  :service_name => nil,
155
160
  :sidekiq_report_errors => "all",
@@ -171,6 +176,18 @@ module Appsignal
171
176
  "trace" => ::Logger::DEBUG
172
177
  }.freeze
173
178
 
179
+ # Environment variables set by deployment platforms that name the
180
+ # revision that is being deployed, in the order the agent reads them.
181
+ # The agent detects the revision this way as well, but only for the data
182
+ # it sends itself, so the gem has to do it for collector mode.
183
+ # @!visibility private
184
+ PLATFORM_REVISION_ENV_VARS = [
185
+ "HEROKU_SLUG_COMMIT",
186
+ "RENDER_GIT_COMMIT",
187
+ "KAMAL_VERSION",
188
+ "CONTAINER_VERSION" # Scalingo
189
+ ].freeze
190
+
174
191
  # @!visibility private
175
192
  STRING_OPTIONS = {
176
193
  :activejob_report_errors => "APPSIGNAL_ACTIVEJOB_REPORT_ERRORS",
@@ -254,6 +271,8 @@ module Appsignal
254
271
  :ignore_errors => "APPSIGNAL_IGNORE_ERRORS",
255
272
  :ignore_logs => "APPSIGNAL_IGNORE_LOGS",
256
273
  :ignore_namespaces => "APPSIGNAL_IGNORE_NAMESPACES",
274
+ :keep_request_environment => "APPSIGNAL_KEEP_REQUEST_ENVIRONMENT",
275
+ :keep_request_headers => "APPSIGNAL_KEEP_REQUEST_HEADERS",
257
276
  :request_headers => "APPSIGNAL_REQUEST_HEADERS",
258
277
  :response_headers => "APPSIGNAL_RESPONSE_HEADERS"
259
278
  }.freeze
@@ -275,15 +294,14 @@ module Appsignal
275
294
  # @!visibility private
276
295
  MIN_RUBY_VERSION_FOR_COLLECTOR_MODE = "3.1"
277
296
 
278
- # Configuration options that only have an effect when the integration is
279
- # in collector mode. When the agent is in use, setting any of these emits
280
- # a warning at startup.
281
297
  # @!visibility private
282
298
  COLLECTOR_ONLY_OPTIONS = [
283
299
  :filter_attributes,
284
300
  :filter_function_parameters,
285
301
  :filter_request_payload,
286
302
  :filter_request_query_parameters,
303
+ :keep_request_environment,
304
+ :keep_request_headers,
287
305
  :response_headers,
288
306
  :send_function_parameters,
289
307
  :send_request_payload,
@@ -291,16 +309,45 @@ module Appsignal
291
309
  :service_name
292
310
  ].freeze
293
311
 
294
- # Existing AppSignal options that only affect the agent's handling of
295
- # trace data. In collector mode these don't filter anything; users need
296
- # their collector-mode equivalents (see COLLECTOR_ONLY_OPTIONS).
297
312
  # @!visibility private
298
- AGENT_ONLY_TRACE_OPTIONS = [
299
- :filter_metadata,
300
- :filter_parameters,
301
- :send_params
313
+ COLLECTOR_ONLY_REPLACED_OPTIONS = {
314
+ :filter_function_parameters => :filter_parameters,
315
+ :filter_request_payload => :filter_parameters,
316
+ :filter_request_query_parameters => :filter_parameters,
317
+ :keep_request_environment => :request_headers,
318
+ :keep_request_headers => :request_headers,
319
+ :send_function_parameters => :send_params,
320
+ :send_request_payload => :send_params,
321
+ :send_request_query_parameters => :send_params
322
+ }.freeze
323
+
324
+ # @!visibility private
325
+ DEPRECATED_COLLECTOR_OPTIONS = {
326
+ :filter_parameters => {
327
+ :filter_request_payload => :derived_as_is,
328
+ :filter_function_parameters => :derived_as_is,
329
+ :filter_request_query_parameters => :derived_as_is
330
+ },
331
+ :request_headers => {
332
+ :keep_request_headers => :derived_header_names,
333
+ :keep_request_environment => :derived_environment_keys
334
+ },
335
+ :send_params => {
336
+ :send_request_payload => :derived_as_is,
337
+ :send_request_query_parameters => :derived_as_is,
338
+ :send_function_parameters => :derived_as_is
339
+ }
340
+ }.freeze
341
+
342
+ # @!visibility private
343
+ SOURCE_ORDER = [
344
+ :default, :derived, :system, :loaders, :initial, :file, :env, :override,
345
+ :dsl
302
346
  ].freeze
303
347
 
348
+ # @!visibility private
349
+ APPLICATION_SOURCES = [:initial, :file, :env, :dsl].freeze
350
+
304
351
  # @!visibility private
305
352
  attr_reader :root_path, :env, :config_hash
306
353
 
@@ -312,8 +359,23 @@ module Appsignal
312
359
  #
313
360
  # Used by the diagnose report to list which value was read from which source.
314
361
  # @!visibility private
315
- attr_reader :system_config, :loaders_config, :initial_config, :file_config,
316
- :env_config, :override_config, :dsl_config
362
+ attr_reader :derived_config, :system_config, :loaders_config,
363
+ :initial_config, :file_config, :env_config, :override_config, :dsl_config
364
+
365
+ # @!visibility private
366
+ def config_sources
367
+ {
368
+ :default => DEFAULT_CONFIG,
369
+ :derived => derived_config,
370
+ :system => system_config,
371
+ :loaders => loaders_config,
372
+ :initial => initial_config,
373
+ :file => file_config,
374
+ :env => env_config,
375
+ :override => override_config,
376
+ :dsl => dsl_config
377
+ }
378
+ end
317
379
 
318
380
  # Initialize a new AppSignal configuration object.
319
381
  #
@@ -349,6 +411,7 @@ module Appsignal
349
411
  @initial_config = {}
350
412
  @file_config = {}
351
413
  @env_config = {}
414
+ @derived_config = {}
352
415
  @override_config = {}
353
416
  @dsl_config = {} # Can be set using `Appsignal.configure`
354
417
 
@@ -569,6 +632,10 @@ module Appsignal
569
632
  ENV["_APPSIGNAL_ENABLE_HOST_METRICS"] = config_hash[:enable_host_metrics].to_s
570
633
  ENV["_APPSIGNAL_ENABLE_STATSD"] = config_hash[:enable_statsd].to_s
571
634
  ENV["_APPSIGNAL_ENABLE_NGINX_METRICS"] = config_hash[:enable_nginx_metrics].to_s
635
+ # The gem never sends OpenTelemetry data to the agent, so it has no
636
+ # reason to listen for it. Written out rather than left to the agent's
637
+ # own default, so a value set in the environment cannot turn it on.
638
+ ENV["_APPSIGNAL_ENABLE_OPENTELEMETRY_HTTP"] = "false"
572
639
  ENV["_APPSIGNAL_APP_ENV"] = env
573
640
  ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = config_hash[:files_world_accessible].to_s
574
641
  ENV["_APPSIGNAL_FILTER_PARAMETERS"] = config_hash[:filter_parameters].join(",")
@@ -585,6 +652,7 @@ module Appsignal
585
652
  ENV["_APPSIGNAL_LOG_LEVEL"] = config_hash[:log_level]
586
653
  ENV["_APPSIGNAL_LOG_FILE_PATH"] = log_file_path.to_s if log_file_path
587
654
  ENV["_APPSIGNAL_LOGGING_ENDPOINT"] = config_hash[:logging_endpoint]
655
+ ENV["_APPSIGNAL_PLATFORM"] = config_hash[:platform].to_s
588
656
  ENV["_APPSIGNAL_PROCESS_NAME"] = $PROGRAM_NAME
589
657
  ENV["_APPSIGNAL_PUSH_API_ENDPOINT"] = config_hash[:endpoint]
590
658
  ENV["_APPSIGNAL_PUSH_API_KEY"] = config_hash[:push_api_key]
@@ -604,9 +672,11 @@ module Appsignal
604
672
  merge(options)
605
673
  end
606
674
 
607
- # Apply any overrides for invalid settings.
608
675
  # @!visibility private
609
676
  def apply_overrides
677
+ @derived_config = determine_derived
678
+ merge(derived_config)
679
+
610
680
  @override_config = determine_overrides
611
681
  merge(override_config)
612
682
  end
@@ -645,16 +715,11 @@ module Appsignal
645
715
  # @!visibility private
646
716
  def warn_for_mode_mismatch
647
717
  if collector_mode_configured?
648
- warn_user_modified(AGENT_ONLY_TRACE_OPTIONS) do |option|
649
- "The collector is in use. The '#{option}' configuration option is " \
650
- "only used by the agent for trace data and will be ignored."
718
+ warn_user_modified(DEPRECATED_COLLECTOR_OPTIONS.keys) do |option|
719
+ deprecated_collector_option_message(option)
651
720
  end
652
721
  else
653
- warn_user_modified(COLLECTOR_ONLY_OPTIONS) do |option|
654
- "The agent is in use. The '#{option}' configuration option is " \
655
- "only used by the collector and will be ignored. Set " \
656
- "'collector_endpoint' to use the collector."
657
- end
722
+ warn_collector_only_options
658
723
  end
659
724
  end
660
725
 
@@ -679,12 +744,49 @@ module Appsignal
679
744
 
680
745
  private
681
746
 
682
- # Yield a warning for each option in `options` whose effective value
683
- # differs from the default. Setting an option to its default value is
684
- # a no-op, so we don't warn about it.
747
+ def deprecated_collector_option_message(option)
748
+ replacements = DEPRECATED_COLLECTOR_OPTIONS.fetch(option).keys
749
+ message = "The collector is in use. The '#{option}' configuration " \
750
+ "option is deprecated in collector mode. It is replaced by " \
751
+ "#{quoted_option_list(replacements)}."
752
+
753
+ derived = replacements.select { |name| derived_config.key?(name) }
754
+ return message if derived.empty?
755
+
756
+ values = derived.map { |name| "\n #{name}: #{derived_config[name].inspect}" }
757
+ "#{message} Set these options to keep reporting what this application " \
758
+ "reports now:#{values.join}"
759
+ end
760
+
761
+ def quoted_option_list(names)
762
+ names = names.map { |name| "'#{name}'" }
763
+ return names.first if names.length == 1
764
+
765
+ "#{names[0..-2].join(", ")} and #{names.last}"
766
+ end
767
+
768
+ def warn_collector_only_options
769
+ options = COLLECTOR_ONLY_OPTIONS.select { |option| configured?(option) }
770
+ return if options.empty?
771
+
772
+ options.each do |option|
773
+ logger.warn(
774
+ "The agent is in use. The '#{option}' configuration option is " \
775
+ "only used by the collector and will be ignored."
776
+ )
777
+
778
+ replacement = COLLECTOR_ONLY_REPLACED_OPTIONS[option]
779
+ logger.warn("Use the '#{replacement}' option instead.") if replacement
780
+ end
781
+
782
+ logger.info(
783
+ "To use the collector, set the 'collector_endpoint' configuration option."
784
+ )
785
+ end
786
+
685
787
  def warn_user_modified(options)
686
788
  options.each do |option|
687
- next if config_hash[option] == DEFAULT_CONFIG[option]
789
+ next unless configured?(option)
688
790
 
689
791
  logger.warn(yield(option))
690
792
  end
@@ -710,13 +812,57 @@ module Appsignal
710
812
 
711
813
  hash[:enable_at_exit_hook] = "always" if Appsignal::Extension.running_in_container?
712
814
 
713
- # Set revision from REVISION file if present in project root
714
- # This helps with Capistrano and Hatchbox.io deployments
715
- revision_from_file = detect_revision_from_file
716
- hash[:revision] = revision_from_file if revision_from_file
815
+ # Set the revision from a REVISION file in the project root, which
816
+ # helps with Capistrano and Hatchbox.io deployments, or from the
817
+ # environment variable the deployment platform sets.
818
+ revision = detect_revision_from_file || detect_revision_from_platform
819
+ hash[:revision] = revision if revision
820
+
821
+ hostname = detect_hostname
822
+ hash[:hostname] = hostname if hostname
823
+
824
+ platform = detect_platform
825
+ hash[:platform] = platform if platform
717
826
  end
718
827
  end
719
828
 
829
+ # Detect the platform the application is deployed on, the way the agent
830
+ # does. The agent only detects it for the data it reports itself, so the
831
+ # gem has to do it for collector mode. There is no config option for it,
832
+ # because these are the only two platforms that can be recognized.
833
+ def detect_platform
834
+ return "dokku" unless ENV.fetch("DOKKU_ROOT", nil).to_s.empty?
835
+ return "heroku" unless ENV.fetch("DYNO", nil).to_s.empty?
836
+
837
+ nil
838
+ end
839
+
840
+ # Detect the hostname the way the agent does: the Heroku dyno name first,
841
+ # then the name the host reports for itself. The agent only detects it for
842
+ # the data it reports itself, so the gem has to do it for collector mode.
843
+ def detect_hostname
844
+ dyno = ENV.fetch("DYNO", nil)
845
+ return dyno unless dyno.to_s.empty?
846
+
847
+ hostname = Socket.gethostname
848
+ hostname unless hostname.to_s.empty?
849
+ rescue SystemCallError => e
850
+ logger.debug "Unable to detect the hostname: #{e.class}: #{e.message}"
851
+ nil
852
+ end
853
+
854
+ def detect_revision_from_platform
855
+ PLATFORM_REVISION_ENV_VARS.each do |env_var|
856
+ revision = ENV.fetch(env_var, nil)
857
+ next if revision.to_s.empty?
858
+
859
+ logger.debug "Detected revision from the #{env_var} environment variable"
860
+ return revision
861
+ end
862
+
863
+ nil
864
+ end
865
+
720
866
  def detect_revision_from_file
721
867
  return unless root_path
722
868
 
@@ -854,9 +1000,68 @@ module Appsignal
854
1000
  config[:sidekiq_report_errors] = "all"
855
1001
  end
856
1002
 
1003
+ # A config file holding a YAML null gives an array option a `nil`, which
1004
+ # nothing downstream expects. Correct it to the empty list the option means.
1005
+ ARRAY_OPTIONS.each_key do |option|
1006
+ config[option] = [] if config_hash[option].nil?
1007
+ end
1008
+
857
1009
  config
858
1010
  end
859
1011
 
1012
+ def determine_derived
1013
+ derived = {}
1014
+
1015
+ DEPRECATED_COLLECTOR_OPTIONS.each do |option, replacements|
1016
+ next unless configured?(option)
1017
+
1018
+ replacements.each do |replacement, derivation|
1019
+ next if set_above_derived?(replacement)
1020
+
1021
+ derived[replacement] = send(derivation, config_hash[option])
1022
+ end
1023
+ end
1024
+
1025
+ derived
1026
+ end
1027
+
1028
+ def derived_as_is(value)
1029
+ value
1030
+ end
1031
+
1032
+ def derived_header_names(value)
1033
+ Array(value).filter_map do |key|
1034
+ Appsignal::Utils::RequestHeaders.header_name(key)
1035
+ end
1036
+ end
1037
+
1038
+ def derived_environment_keys(value)
1039
+ Array(value).reject do |key|
1040
+ Appsignal::Utils::RequestHeaders.header_name(key) ||
1041
+ Appsignal::Utils::RequestHeaders::TRANSLATED_ENV_KEYS.include?(key)
1042
+ end
1043
+ end
1044
+
1045
+ def configured?(option)
1046
+ config_hash[option] != DEFAULT_CONFIG[option] && user_set?(option)
1047
+ end
1048
+
1049
+ def user_set?(option)
1050
+ set_by_any?(APPLICATION_SOURCES, option)
1051
+ end
1052
+
1053
+ def set_above_derived?(option)
1054
+ set_by_any?(sources_above_derived, option)
1055
+ end
1056
+
1057
+ def sources_above_derived
1058
+ SOURCE_ORDER[(SOURCE_ORDER.index(:derived) + 1)..] - [:override]
1059
+ end
1060
+
1061
+ def set_by_any?(sources, option)
1062
+ sources.any? { |source| config_sources.fetch(source).key?(option) }
1063
+ end
1064
+
860
1065
  def merge(new_config)
861
1066
  new_config.each do |key, value|
862
1067
  logger.debug("Config key '#{key}' is being overwritten") unless config_hash[key].nil?
@@ -881,14 +1086,19 @@ module Appsignal
881
1086
  # @see AppSignal Ruby gem configuration
882
1087
  # https://docs.appsignal.com/ruby/configuration.html
883
1088
  class ConfigDSL
884
- # @!visibility private
885
- # @return [Hash] Hash containing the DSL option values
886
- attr_reader :dsl_options
887
-
888
1089
  # @!visibility private
889
1090
  def initialize(config)
890
1091
  @config = config
891
1092
  @dsl_options = {}
1093
+ @assigned_options = Set.new
1094
+ end
1095
+
1096
+ # @!visibility private
1097
+ # @return [Hash] Hash containing the DSL option values
1098
+ def dsl_options
1099
+ @dsl_options.reject do |key, value|
1100
+ !@assigned_options.include?(key) && unchanged_option?(key, value)
1101
+ end
892
1102
  end
893
1103
 
894
1104
  # Returns the application's root path.
@@ -1074,6 +1284,12 @@ module Appsignal
1074
1284
  # @return [Array<String>] Ignore log messages by substrings
1075
1285
  # @!attribute [rw] ignore_namespaces
1076
1286
  # @return [Array<String>] Ignore traces by namespaces
1287
+ # @!attribute [rw] keep_request_environment
1288
+ # @return [Array<String>] Rack environment keys to report in collector
1289
+ # mode, named the way Rack names them
1290
+ # @!attribute [rw] keep_request_headers
1291
+ # @return [Array<String>] HTTP request headers to report in collector
1292
+ # mode, named the way OpenTelemetry names them
1077
1293
  # @!attribute [rw] request_headers
1078
1294
  # @return [Array<String>] HTTP request headers to include in error reports
1079
1295
 
@@ -1128,14 +1344,23 @@ module Appsignal
1128
1344
  if @dsl_options.key?(key)
1129
1345
  @dsl_options[key]
1130
1346
  else
1131
- @dsl_options[key] = @config[key].dup
1347
+ @dsl_options[key] = initial_option_value(key)
1132
1348
  end
1133
1349
  end
1134
1350
 
1135
1351
  def update_option(key, value)
1352
+ @assigned_options << key
1136
1353
  @dsl_options[key] = value
1137
1354
  end
1138
1355
 
1356
+ def initial_option_value(key)
1357
+ @config[key].dup
1358
+ end
1359
+
1360
+ def unchanged_option?(key, value)
1361
+ value == initial_option_value(key)
1362
+ end
1363
+
1139
1364
  # Parse tags from various input formats and validate values
1140
1365
  # @param value [Hash, nil] input tags
1141
1366
  # @return [Hash] validated tags with string keys
@@ -77,21 +77,23 @@ module Appsignal
77
77
  end
78
78
 
79
79
  def add_headers_to(transaction)
80
- transaction.add_headers(
80
+ transaction.add_request_headers(
81
+ "accept" => "text/html,application/xhtml+xml",
82
+ "accept-encoding" => "gzip, deflate, sdch",
83
+ "accept-language" => "en-US,en;q=0.8,nl;q=0.6",
84
+ "cache-control" => "max-age=0",
85
+ "connection" => "keep-alive",
86
+ "user-agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0)",
87
+ "referer" => "http://appsignal.com/accounts"
88
+ )
89
+ transaction.add_request_environment(
81
90
  "REMOTE_ADDR" => "127.0.0.1",
82
91
  "REQUEST_METHOD" => "GET",
83
92
  "SERVER_NAME" => "localhost",
84
93
  "SERVER_PORT" => "80",
85
94
  "SERVER_PROTOCOL" => "HTTP/1.1",
86
95
  "REQUEST_PATH" => "/hello",
87
- "PATH_INFO" => "/hello",
88
- "HTTP_ACCEPT" => "text/html,application/xhtml+xml",
89
- "HTTP_ACCEPT_ENCODING" => "gzip, deflate, sdch",
90
- "HTTP_ACCEPT_LANGUAGE" => "en-US,en;q=0.8,nl;q=0.6",
91
- "HTTP_CACHE_CONTROL" => "max-age=0",
92
- "HTTP_CONNECTION" => "keep-alive",
93
- "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0)",
94
- "HTTP_REFERER" => "http://appsignal.com/accounts"
96
+ "PATH_INFO" => "/hello"
95
97
  )
96
98
  end
97
99
  end