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
@@ -243,7 +243,6 @@ module Appsignal
243
243
  @error_set = nil
244
244
 
245
245
  @session_data = Appsignal::SampleData.new(:session_data, Hash)
246
- @headers = Appsignal::SampleData.new(:headers, Hash)
247
246
  @custom_data = Appsignal::SampleData.new(:custom_data)
248
247
 
249
248
  @backend = backend || Appsignal::Backends.transaction.new(
@@ -265,10 +264,19 @@ module Appsignal
265
264
  # That symbol is also the sample-data key the backend receives, so it can
266
265
  # route the bucket to the right storage.
267
266
  @params_mapping = @backend.params_mapping
267
+ @params_options = @backend.params_options
268
268
  @params_buckets = @params_mapping.values.uniq.to_h do |bucket|
269
269
  [bucket, Appsignal::SampleData.new(bucket)]
270
270
  end
271
271
 
272
+ @headers_mapping = @backend.headers_mapping
273
+ @headers_allowlist = @backend.headers_allowlist
274
+ @headers_buckets = @headers_mapping.each_value.map(&:first).uniq.to_h do |bucket|
275
+ [bucket, Appsignal::SampleData.new(bucket, Hash)]
276
+ end
277
+
278
+ @channels_set = []
279
+
272
280
  run_after_create_hooks
273
281
  end
274
282
 
@@ -433,7 +441,7 @@ module Appsignal
433
441
  # Sample data guide
434
442
  def add_params(given_params = nil, &block)
435
443
  warn_params_deprecation
436
- params_data(:params).add(given_params, &block)
444
+ add_params_channel(:params, given_params, &block)
437
445
  end
438
446
  alias set_params add_params
439
447
 
@@ -491,7 +499,7 @@ module Appsignal
491
499
  #
492
500
  # @see #add_function_parameters
493
501
  def add_request_payload(given_params = nil, &block)
494
- params_data(:request_payload).add(given_params, &block)
502
+ add_params_channel(:request_payload, given_params, &block)
495
503
  end
496
504
 
497
505
  # Add the request payload to the transaction if not already set.
@@ -527,7 +535,7 @@ module Appsignal
527
535
  #
528
536
  # @see #add_request_payload
529
537
  def add_function_parameters(given_params = nil, &block)
530
- params_data(:function_parameters).add(given_params, &block)
538
+ add_params_channel(:function_parameters, given_params, &block)
531
539
  end
532
540
 
533
541
  # Add the function parameters to the transaction if not already set.
@@ -564,7 +572,7 @@ module Appsignal
564
572
  #
565
573
  # @see #add_request_payload
566
574
  def add_query_parameters(given_params = nil, &block)
567
- params_data(:query_parameters).add(given_params, &block)
575
+ add_params_channel(:query_parameters, given_params, &block)
568
576
  end
569
577
 
570
578
  # Add the query parameters to the transaction if not already set.
@@ -648,6 +656,10 @@ module Appsignal
648
656
 
649
657
  # Add headers to the transaction.
650
658
  #
659
+ # @deprecated Use {#add_request_headers} for request headers and
660
+ # {#add_request_environment} for the values a Rack environment holds that
661
+ # are not request headers. This method takes both kinds at once, so it
662
+ # has to work out which of them each value is.
651
663
  # @since 4.0.0
652
664
  # @param given_headers [Hash<String, Object>] A hash containing headers.
653
665
  # @yield This block is called when the transaction is sampled. The block's
@@ -659,7 +671,19 @@ module Appsignal
659
671
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
660
672
  # Sample data guide
661
673
  def add_headers(given_headers = nil, &block)
662
- @headers.add(given_headers, &block)
674
+ if block
675
+ headers, environment = Appsignal::Utils::RequestHeaders.split_lazily(&block)
676
+
677
+ add_headers_channel(:request_headers, &headers)
678
+ add_headers_channel(:request_environment, &environment)
679
+ elsif given_headers.is_a?(Hash)
680
+ headers, environment = Appsignal::Utils::RequestHeaders.split(given_headers)
681
+
682
+ add_headers_channel(:request_headers, headers)
683
+ add_headers_channel(:request_environment, environment)
684
+ else
685
+ add_headers_channel(:request_environment, given_headers)
686
+ end
663
687
  end
664
688
  alias set_headers add_headers
665
689
 
@@ -668,6 +692,8 @@ module Appsignal
668
692
  # When both the `given_headers` and a block is given to this method,
669
693
  # the block is leading and the argument will _not_ be used.
670
694
  #
695
+ # @deprecated Use {#add_request_headers_if_nil} or
696
+ # {#add_request_environment_if_nil}.
671
697
  # @since 4.0.0
672
698
  # @param given_headers [Hash<String, Object>] A hash containing headers.
673
699
  # @yield This block is called when the transaction is sampled. The block's
@@ -680,10 +706,92 @@ module Appsignal
680
706
  # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
681
707
  # Sample data guide
682
708
  def add_headers_if_nil(given_headers = nil, &block)
683
- add_headers(given_headers, &block) unless @headers.value?
709
+ return if channel_set?(:request_headers) || channel_set?(:request_environment)
710
+
711
+ add_headers(given_headers, &block)
684
712
  end
685
713
  alias set_headers_if_nil add_headers_if_nil
686
714
 
715
+ # Add request headers to the transaction.
716
+ #
717
+ # Name each header the way OpenTelemetry names it, in lowercase and with
718
+ # dashes, such as `accept` and `content-length`. In agent mode the names
719
+ # are converted to the Rack spellings the environment uses, such as
720
+ # `HTTP_ACCEPT`.
721
+ #
722
+ # Behaves like {#add_headers}: merges when called multiple times, and a
723
+ # block takes precedence over the argument.
724
+ #
725
+ # @param given_headers [Hash<String, Object>] A hash containing request
726
+ # headers.
727
+ # @yield This block is called when the transaction is sampled. The block's
728
+ # return value will become the new request headers.
729
+ # @yieldreturn [Hash<String, Object>]
730
+ # @return [void]
731
+ #
732
+ # @see #add_request_environment
733
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
734
+ # Sample data guide
735
+ def add_request_headers(given_headers = nil, &block)
736
+ add_headers_channel(:request_headers, given_headers, &block)
737
+ end
738
+
739
+ # Add request headers to the transaction if none are already set.
740
+ #
741
+ # @param given_headers [Hash<String, Object>] A hash containing request
742
+ # headers to set if none are already set.
743
+ # @yield This block is called when the transaction is sampled. The block's
744
+ # return value will become the new request headers.
745
+ # @yieldreturn [Hash<String, Object>]
746
+ # @return [void]
747
+ # @!visibility private
748
+ #
749
+ # @see #add_request_headers
750
+ def add_request_headers_if_nil(given_headers = nil, &block)
751
+ add_request_headers(given_headers, &block) unless channel_set?(:request_headers)
752
+ end
753
+
754
+ # Add values from the request environment to the transaction.
755
+ #
756
+ # These are the values a Rack environment holds that are not request
757
+ # headers, such as `REMOTE_ADDR` and `QUERY_STRING`. Name each one the way
758
+ # Rack names it. Use {#add_request_headers} for the request headers.
759
+ #
760
+ # Behaves like {#add_headers}: merges when called multiple times, and a
761
+ # block takes precedence over the argument.
762
+ #
763
+ # @param given_environment [Hash<String, Object>] A hash containing request
764
+ # environment values.
765
+ # @yield This block is called when the transaction is sampled. The block's
766
+ # return value will become the new request environment.
767
+ # @yieldreturn [Hash<String, Object>]
768
+ # @return [void]
769
+ #
770
+ # @see #add_request_headers
771
+ # @see https://docs.appsignal.com/guides/custom-data/sample-data.html
772
+ # Sample data guide
773
+ def add_request_environment(given_environment = nil, &block)
774
+ add_headers_channel(:request_environment, given_environment, &block)
775
+ end
776
+
777
+ # Add values from the request environment to the transaction if none are
778
+ # already set.
779
+ #
780
+ # @param given_environment [Hash<String, Object>] A hash containing request
781
+ # environment values to set if none are already set.
782
+ # @yield This block is called when the transaction is sampled. The block's
783
+ # return value will become the new request environment.
784
+ # @yieldreturn [Hash<String, Object>]
785
+ # @return [void]
786
+ # @!visibility private
787
+ #
788
+ # @see #add_request_environment
789
+ def add_request_environment_if_nil(given_environment = nil, &block)
790
+ return if channel_set?(:request_environment)
791
+
792
+ add_request_environment(given_environment, &block)
793
+ end
794
+
687
795
  # Add custom data to the transaction.
688
796
  #
689
797
  # @since 4.0.0
@@ -984,7 +1092,7 @@ module Appsignal
984
1092
 
985
1093
  # @!visibility private
986
1094
  attr_writer :is_duplicate, :tags, :custom_data, :params_buckets,
987
- :session_data, :headers
1095
+ :session_data, :headers_buckets, :channels_set
988
1096
 
989
1097
  # @!visibility private
990
1098
  def internal_set_error(error, &block)
@@ -1037,11 +1145,21 @@ module Appsignal
1037
1145
  @params_buckets.fetch(@params_mapping.fetch(channel))
1038
1146
  end
1039
1147
 
1040
- # Whether a params channel's bucket has had nothing set yet, so the
1041
- # `_if_nil` setters do not overwrite params the caller already provided.
1148
+ PARAMS_CHANNEL_ALIASES = { :params => :request_payload }.freeze
1149
+ private_constant :PARAMS_CHANNEL_ALIASES
1150
+
1151
+ def params_channel(channel)
1152
+ PARAMS_CHANNEL_ALIASES.fetch(channel, channel)
1153
+ end
1154
+
1155
+ def add_params_channel(channel, given_params = nil, &block)
1156
+ sample = params_data(channel)
1157
+ sample.add(given_params, &block)
1158
+ mark_channel_set(params_channel(channel)) if sample.value?
1159
+ end
1160
+
1042
1161
  def params_unset?(channel)
1043
- bucket = params_data(channel)
1044
- !bucket.value? && !bucket.empty?
1162
+ !channel_set?(params_channel(channel)) && !params_data(channel).empty?
1045
1163
  end
1046
1164
 
1047
1165
  # `add_params`/`set_params` don't say whether the params are a request
@@ -1222,18 +1340,21 @@ module Appsignal
1222
1340
  end
1223
1341
 
1224
1342
  def sample_data
1225
- data = {
1226
- :environment => sanitized_request_headers,
1343
+ data = {}
1344
+ @headers_buckets.each do |bucket, sample|
1345
+ data[bucket] = sanitized_headers(bucket, sample)
1346
+ end
1347
+ data.merge!(
1227
1348
  :session_data => sanitized_session_data,
1228
1349
  :tags => sanitized_tags,
1229
1350
  :custom_data => custom_data
1230
- }
1351
+ )
1231
1352
  # Each params bucket is emitted under its own key. The extension backend
1232
1353
  # has a single `:params` bucket; the OpenTelemetry backend has separate
1233
1354
  # `:request_payload` and `:function_parameters` buckets. The backend maps
1234
1355
  # each key to its storage (C-extension slot or OpenTelemetry attribute).
1235
1356
  @params_buckets.each do |bucket, sample|
1236
- data[bucket] = sanitized_params(sample)
1357
+ data[bucket] = sanitized_params(bucket, sample)
1237
1358
  end
1238
1359
  data.each do |key, value|
1239
1360
  set_sample_data(key, value)
@@ -1252,7 +1373,8 @@ module Appsignal
1252
1373
  transaction.custom_data = @custom_data.dup
1253
1374
  transaction.params_buckets = @params_buckets.transform_values(&:dup)
1254
1375
  transaction.session_data = @session_data.dup
1255
- transaction.headers = @headers.dup
1376
+ transaction.headers_buckets = @headers_buckets.transform_values(&:dup)
1377
+ transaction.channels_set = @channels_set.dup
1256
1378
  end
1257
1379
  end
1258
1380
 
@@ -1260,10 +1382,11 @@ module Appsignal
1260
1382
  params_value(params_data(:params))
1261
1383
  end
1262
1384
 
1263
- def sanitized_params(sample = params_data(:params))
1264
- return unless Appsignal.config[:send_params]
1385
+ def sanitized_params(bucket, sample)
1386
+ options = @params_options.fetch(bucket)
1387
+ return if Appsignal.config[options.fetch(:send)] == false
1265
1388
 
1266
- filter_keys = Appsignal.config[:filter_parameters] || []
1389
+ filter_keys = Appsignal.config[options.fetch(:filter)] || []
1267
1390
  Appsignal::Utils::SampleDataSanitizer.sanitize(params_value(sample), filter_keys)
1268
1391
  end
1269
1392
 
@@ -1305,32 +1428,66 @@ module Appsignal
1305
1428
  )
1306
1429
  end
1307
1430
 
1308
- def request_headers
1309
- @headers.value
1431
+ def add_headers_channel(channel, given_headers = nil, &block)
1432
+ bucket, transform = @headers_mapping.fetch(channel)
1433
+ sample = @headers_buckets.fetch(bucket)
1434
+
1435
+ if transform.nil?
1436
+ sample.add(given_headers, &block)
1437
+ elsif block
1438
+ sample.add { transform_headers(transform, block.call) }
1439
+ else
1440
+ sample.add(transform_headers(transform, given_headers))
1441
+ end
1442
+
1443
+ mark_channel_set(channel) if sample.value?
1444
+ end
1445
+
1446
+ def transform_headers(transform, headers)
1447
+ return headers unless headers.is_a?(Hash)
1448
+
1449
+ headers.to_h(&transform)
1450
+ end
1451
+
1452
+ def mark_channel_set(channel)
1453
+ @channels_set << channel unless @channels_set.include?(channel)
1454
+ end
1455
+
1456
+ def channel_set?(channel)
1457
+ @channels_set.include?(channel)
1458
+ end
1459
+
1460
+ def headers_value(sample)
1461
+ sample.value
1310
1462
  rescue => e
1311
1463
  Appsignal.internal_logger.error \
1312
1464
  "Exception while fetching headers: #{e.class}: #{e}"
1313
1465
  nil
1314
1466
  end
1315
1467
 
1316
- # Returns sanitized environment for a transaction.
1317
- #
1318
- # The environment of a transaction can contain a lot of information, not
1319
- # all of it useful for debugging.
1320
- #
1321
- # @return [nil] if no environment is present.
1322
- # @return [Hash<String, Object>]
1323
- def sanitized_request_headers
1324
- headers = request_headers
1468
+ def sanitized_headers(bucket, sample)
1469
+ headers = headers_value(sample)
1325
1470
  return unless headers
1326
1471
 
1472
+ option, header_names = @headers_allowlist.fetch(bucket)
1473
+ allowlist = Appsignal.config[option]
1474
+
1475
+ headers = normalized_headers(headers) if header_names
1476
+
1327
1477
  {}.tap do |out|
1328
- Appsignal.config[:request_headers].each do |key|
1478
+ allowlist.each do |key|
1479
+ key = Appsignal::Utils::RequestHeaders.normalize(key) if header_names
1329
1480
  out[key] = headers[key] if headers[key]
1330
1481
  end
1331
1482
  end
1332
1483
  end
1333
1484
 
1485
+ def normalized_headers(headers)
1486
+ headers.to_h do |key, value|
1487
+ [Appsignal::Utils::RequestHeaders.normalize(key), value]
1488
+ end
1489
+ end
1490
+
1334
1491
  # Only keep tags if they meet the following criteria:
1335
1492
  # * Key is a symbol or string with less then 100 chars
1336
1493
  # * Value is a symbol or string with less then 100 chars
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Utils
5
+ class RequestHeaders
6
+ # The only two request headers Rack passes without the `HTTP_` prefix,
7
+ # because CGI reserves the prefixed spelling of them.
8
+ UNPREFIXED_HEADER_KEYS = %w[CONTENT_LENGTH CONTENT_TYPE].freeze
9
+
10
+ # `HTTP_VERSION` is a CGI variable holding the same value as
11
+ # `SERVER_PROTOCOL`, not a header.
12
+ NON_HEADER_KEYS = %w[HTTP_VERSION].freeze
13
+
14
+ # The environment keys the instrumentation already describes with a semantic
15
+ # convention attribute, read from the request itself.
16
+ TRANSLATED_ENV_KEYS = %w[
17
+ PATH_INFO REQUEST_METHOD REQUEST_PATH SERVER_NAME SERVER_PORT
18
+ SERVER_PROTOCOL
19
+ ].freeze
20
+
21
+ HTTP_PREFIX = "HTTP_"
22
+
23
+ class << self
24
+ def split(env)
25
+ return [{}, env] unless env.is_a?(Hash)
26
+
27
+ headers = {}
28
+ environment = {}
29
+
30
+ env.each do |key, value|
31
+ name = header_name(key)
32
+ if name
33
+ headers[name] = value
34
+ else
35
+ environment[key] = value
36
+ end
37
+ end
38
+
39
+ [headers, environment]
40
+ end
41
+
42
+ def split_lazily(&block)
43
+ split = nil
44
+ splitter = lambda { split ||= split(block.call) }
45
+
46
+ [lambda { splitter.call.first }, lambda { splitter.call.last }]
47
+ end
48
+
49
+ def header_name(env_key)
50
+ env_key = env_key.to_s
51
+ return if NON_HEADER_KEYS.include?(env_key)
52
+
53
+ if env_key.start_with?(HTTP_PREFIX)
54
+ normalize(env_key.delete_prefix(HTTP_PREFIX))
55
+ elsif UNPREFIXED_HEADER_KEYS.include?(env_key)
56
+ normalize(env_key)
57
+ end
58
+ end
59
+
60
+ # A key does not always make the round trip as itself, because two Rack keys
61
+ # can name the same header: a server that sets both `CONTENT_LENGTH` and
62
+ # `HTTP_CONTENT_LENGTH` gets the second one back as the first.
63
+ def rack_name(header)
64
+ env_key = header.to_s.upcase.tr("-", "_")
65
+ return env_key if UNPREFIXED_HEADER_KEYS.include?(env_key)
66
+
67
+ "#{HTTP_PREFIX}#{env_key}"
68
+ end
69
+
70
+ # Several OpenTelemetry SDKs write header attribute names with underscores,
71
+ # where the semantic conventions the collector compares against use dashes.
72
+ def normalize(name)
73
+ name.to_s.downcase.tr("_", "-")
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -10,6 +10,7 @@ require "appsignal/utils/integration_memory_logger"
10
10
  require "appsignal/utils/stdout_and_logger_message"
11
11
  require "appsignal/utils/data"
12
12
  require "appsignal/utils/sample_data_sanitizer"
13
+ require "appsignal/utils/request_headers"
13
14
  require "appsignal/utils/integration_logger"
14
15
  require "appsignal/utils/json"
15
16
  require "appsignal/utils/ndjson"
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Appsignal
4
4
  # @return [String]
5
- VERSION = "5.0.0.rc.1"
5
+ VERSION = "5.0.0.rc.2"
6
6
  end
data/lib/appsignal.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "json"
4
4
  require "securerandom"
5
+ require "set"
5
6
  require "stringio"
6
7
 
7
8
  require "appsignal/logger"