appsignal 4.10.4-java → 5.0.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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -0
  3. data/Rakefile +195 -4
  4. data/appsignal.gemspec +8 -0
  5. data/build_matrix.yml +12 -0
  6. data/ext/appsignal_extension.c +14 -0
  7. data/lib/appsignal/backends.rb +55 -0
  8. data/lib/appsignal/cli/diagnose.rb +2 -10
  9. data/lib/appsignal/config.rb +377 -13
  10. data/lib/appsignal/demo.rb +12 -10
  11. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +34 -22
  12. data/lib/appsignal/event_formatter/active_job/perform_formatter.rb +35 -0
  13. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +19 -0
  14. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +27 -0
  15. data/lib/appsignal/event_formatter/recorded_elsewhere.rb +17 -0
  16. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +24 -0
  17. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +5 -0
  18. data/lib/appsignal/event_formatter/view_component/render_formatter.rb +21 -10
  19. data/lib/appsignal/event_formatter.rb +78 -0
  20. data/lib/appsignal/extension.rb +4 -0
  21. data/lib/appsignal/helpers/instrumentation.rb +324 -20
  22. data/lib/appsignal/helpers/metrics.rb +3 -24
  23. data/lib/appsignal/hooks/action_cable.rb +26 -8
  24. data/lib/appsignal/hooks/active_job.rb +184 -47
  25. data/lib/appsignal/hooks/at_exit.rb +4 -1
  26. data/lib/appsignal/hooks/excon.rb +20 -0
  27. data/lib/appsignal/hooks/faraday.rb +16 -0
  28. data/lib/appsignal/hooks/http.rb +5 -0
  29. data/lib/appsignal/hooks/resque.rb +1 -1
  30. data/lib/appsignal/hooks/sequel.rb +32 -2
  31. data/lib/appsignal/hooks/shoryuken.rb +3 -3
  32. data/lib/appsignal/hooks/sidekiq.rb +1 -1
  33. data/lib/appsignal/integrations/action_cable.rb +5 -2
  34. data/lib/appsignal/integrations/active_support_notifications.rb +59 -19
  35. data/lib/appsignal/integrations/data_mapper.rb +14 -2
  36. data/lib/appsignal/integrations/delayed_job_plugin.rb +81 -8
  37. data/lib/appsignal/integrations/dry_monitor.rb +39 -15
  38. data/lib/appsignal/integrations/excon/appsignal_middleware.rb +21 -0
  39. data/lib/appsignal/integrations/excon.rb +52 -15
  40. data/lib/appsignal/integrations/faraday.rb +47 -12
  41. data/lib/appsignal/integrations/http.rb +43 -1
  42. data/lib/appsignal/integrations/mongo_ruby_driver.rb +73 -4
  43. data/lib/appsignal/integrations/net_http.rb +31 -2
  44. data/lib/appsignal/integrations/puma.rb +4 -1
  45. data/lib/appsignal/integrations/que.rb +256 -37
  46. data/lib/appsignal/integrations/railtie.rb +4 -1
  47. data/lib/appsignal/integrations/rake.rb +9 -3
  48. data/lib/appsignal/integrations/redis.rb +22 -1
  49. data/lib/appsignal/integrations/redis_client.rb +22 -1
  50. data/lib/appsignal/integrations/resque.rb +81 -11
  51. data/lib/appsignal/integrations/shoryuken.rb +159 -12
  52. data/lib/appsignal/integrations/sidekiq.rb +94 -16
  53. data/lib/appsignal/integrations/webmachine.rb +56 -5
  54. data/lib/appsignal/loaders/padrino.rb +2 -1
  55. data/lib/appsignal/logger/extension_backend.rb +24 -0
  56. data/lib/appsignal/logger/opentelemetry_backend.rb +66 -0
  57. data/lib/appsignal/logger.rb +13 -9
  58. data/lib/appsignal/metrics/extension_backend.rb +47 -0
  59. data/lib/appsignal/metrics/opentelemetry_backend.rb +89 -0
  60. data/lib/appsignal/opentelemetry/attributes.rb +31 -0
  61. data/lib/appsignal/opentelemetry/dependencies.rb +35 -0
  62. data/lib/appsignal/opentelemetry/error_type.rb +37 -0
  63. data/lib/appsignal/opentelemetry/http_client_request.rb +83 -0
  64. data/lib/appsignal/opentelemetry/http_method.rb +59 -0
  65. data/lib/appsignal/opentelemetry/http_response.rb +30 -0
  66. data/lib/appsignal/opentelemetry/http_server_request.rb +79 -0
  67. data/lib/appsignal/opentelemetry/messaging.rb +82 -0
  68. data/lib/appsignal/opentelemetry/proxied_exporter.rb +83 -0
  69. data/lib/appsignal/opentelemetry/rendering.rb +29 -0
  70. data/lib/appsignal/opentelemetry/sql_db_system.rb +89 -0
  71. data/lib/appsignal/opentelemetry.rb +495 -0
  72. data/lib/appsignal/rack/abstract_middleware.rb +66 -4
  73. data/lib/appsignal/rack/body_wrapper.rb +18 -5
  74. data/lib/appsignal/rack/event_handler.rb +44 -4
  75. data/lib/appsignal/rack/grape_middleware.rb +1 -0
  76. data/lib/appsignal/rack/hanami_middleware.rb +2 -1
  77. data/lib/appsignal/rack/instrumentation_middleware.rb +1 -0
  78. data/lib/appsignal/rack/rails_instrumentation.rb +1 -0
  79. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -0
  80. data/lib/appsignal/rack.rb +68 -12
  81. data/lib/appsignal/sample_data.rb +4 -0
  82. data/lib/appsignal/transaction/base_backend.rb +128 -0
  83. data/lib/appsignal/transaction/extension_backend.rb +229 -0
  84. data/lib/appsignal/transaction/opentelemetry_backend.rb +847 -0
  85. data/lib/appsignal/transaction.rb +714 -164
  86. data/lib/appsignal/utils/request_headers.rb +78 -0
  87. data/lib/appsignal/utils/stdout_and_logger_message.rb +9 -0
  88. data/lib/appsignal/utils.rb +1 -0
  89. data/lib/appsignal/version.rb +1 -1
  90. data/lib/appsignal.rb +10 -0
  91. data/sig/appsignal.rbi +630 -37
  92. data/sig/appsignal.rbs +582 -27
  93. metadata +25 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e9130a7fa45f49f8047af34f7fe483456fedef973f9e62ca56bdaad50eb3a1f
4
- data.tar.gz: f7b4bf9aac46c183ce5ff78387e7a4ab56f85f203efcf2f995701d5424517c7c
3
+ metadata.gz: aa6ecd3d80cea2a3d5b0f3e00c40b3248c633e352337a4640843e7255fc84c49
4
+ data.tar.gz: 98d1d112ded643cc555d7e54b1dd14d3a4d564db310386383e772b298697ba4d
5
5
  SHA512:
6
- metadata.gz: d044269ce96bcbef0e9400ee611b41ee762d529ed0f802451974df77576ad27f46e47465f0f66cceae099a2fa34b361f63267c3182eb351d3b2d31ef108bfcbd
7
- data.tar.gz: e3040b1ad97a20dbfe94fe80345489a22d8de81c907df61180d5e247553122c92dcd146be3b7b37cb665b6db28d454bf580e1e05345e52a61270da31d6774360
6
+ metadata.gz: dd8c19f24a975e360e54d133ab29ec6b8143663e2f55acc97f2f3b7b0556db527188beb808259fb427faa9e3b055036deec2c7bc0f9163db0abcee3503e42756
7
+ data.tar.gz: b727d0b3c91e179f332b198107d0bb8b4ab2bcfab929483b017744cd57746b5d2291674447d317fe305856e05df583af2d07bf7a9d61f329bbccc9f806e8beab
data/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 5.0.0
4
+
5
+ _Published on 2026-09-28._
6
+
7
+ ### Changed
8
+
9
+ - Release the final package version. See the pre-release changelog entries for the changes in this version. (major)
10
+
11
+ ## 5.0.0.rc.2
12
+
13
+ _Published on 2026-09-23._
14
+
15
+ This release also includes all the changes from AppSignal for Ruby version 4.10.4 and below.
16
+
17
+ ### Added
18
+
19
+ - Detect the revision that is being deployed from the environment variables set by Heroku, Render, Kamal and Scalingo: `HEROKU_SLUG_COMMIT`, `RENDER_GIT_COMMIT`, `KAMAL_VERSION` and `CONTAINER_VERSION`. Applications deployed on those platforms now report their revision without setting the `revision` configuration option.
20
+
21
+ This affects collector mode, where deploys were reported as `unknown` when the revision was not configured.
22
+
23
+ (patch [bf606432](https://github.com/appsignal/appsignal-ruby/commit/bf606432aab96d3f9b3a20cbb4b0c48aa7748702))
24
+ - Detect the hostname of the machine the application runs on. On Heroku this is the name of the dyno, and everywhere else it is the name the host reports for itself. Set the `hostname` configuration option to report a different name.
25
+
26
+ This affects collector mode, where all data was reported for a host named `unknown` when the hostname was not configured.
27
+
28
+ (patch [bf606432](https://github.com/appsignal/appsignal-ruby/commit/bf606432aab96d3f9b3a20cbb4b0c48aa7748702))
29
+ - Report the host, the port and the HTTP version of an incoming web request in collector mode. These are sent as the `server.address`, `server.port` and `network.protocol.version` OpenTelemetry attributes. The host is read from the `Forwarded` and `X-Forwarded-Host` headers when a proxy sets them, so it is the host the client used rather than the one the proxy connected to. (patch [85460ff3](https://github.com/appsignal/appsignal-ruby/commit/85460ff3d8ae5235515fe291b9a6baea6ad0c47c))
30
+ - Report the request environment in collector mode. The `request_headers` configuration option is an allowlist of Rack environment names, and some of those names are not request headers. Those values are now reported as `appsignal.environment.*` OpenTelemetry attributes and shown in the request's Environment panel, instead of being left out.
31
+
32
+ Values that describe the request itself are not repeated there, because they are already reported as the request's method, path, host, port and protocol version.
33
+
34
+ (patch [85460ff3](https://github.com/appsignal/appsignal-ruby/commit/85460ff3d8ae5235515fe291b9a6baea6ad0c47c))
35
+ - Add the `keep_request_headers` and `keep_request_environment` configuration options, which are used in collector mode. They replace `request_headers`, which is deprecated in collector mode.
36
+
37
+ `keep_request_headers` lists the request headers to report, using the names OpenTelemetry uses for them, such as `accept` and `content-length`. `keep_request_environment` lists the Rack environment values to report that are not request headers, using the names Rack uses for them, such as `REMOTE_ADDR`.
38
+
39
+ Both options default to a value derived from `request_headers`, which lists Rack environment keys and mixes the two kinds together. So an application that only ever set `request_headers` keeps reporting the same values when it moves to collector mode.
40
+
41
+ The `request_headers` configuration option is deprecated in collector mode, and a warning will be emitted at startup, containing the values of `keep_request_headers` and `keep_request_environment` that should be used to replace it.
42
+
43
+ In agent mode, `request_headers` still lists every Rack environment key to report, and the two new options have no effect.
44
+
45
+ (patch [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
46
+ - Add the `Appsignal.add_request_headers` and `Appsignal.add_request_environment` helpers.
47
+
48
+ Use `add_request_headers` to report request headers, naming each header in lowercase and with dashes, such as `content-length`. Use `add_request_environment` to report the values a Rack environment holds that are not request headers, naming each one the way Rack names it, such as `REMOTE_ADDR`.
49
+
50
+ In agent mode, both write to the "Environment" sample data, formatted as Rack environment keys. In collector mode, they write different span attributes.
51
+
52
+ Together they replace `Appsignal.add_headers`, which is now deprecated, as it accepts Rack environment keys and works out which ones are headers.
53
+
54
+ (patch [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
55
+ - Report request headers for Webmachine applications. (patch [fcfb7a0d](https://github.com/appsignal/appsignal-ruby/commit/fcfb7a0d2545a2144aa61efa61d445c0e11c7749), [b47f5bd2](https://github.com/appsignal/appsignal-ruby/commit/b47f5bd2b4611f5a6089bff28f91d47eac94422f), [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
56
+
57
+ ### Changed
58
+
59
+ - Report `app` instead of `unknown` as the OpenTelemetry service name when the `service_name` configuration option is not set and collector mode is in use. (patch [7078181c](https://github.com/appsignal/appsignal-ruby/commit/7078181ccfc5aa5bb1200d99aa7c612c5acbbd00))
60
+ - The `filter_parameters` and `send_params` configuration options are deprecated in collector mode. Use `filter_request_payload`, `filter_function_parameters` and `filter_request_query_parameters` to filter different kinds of parameters, and `send_request_payload`, `send_request_query_parameters` and `send_function_parameters` to choose which kinds of parameters to report.
61
+
62
+ In collector mode, each kind of parameter is now filtered and reported according to its own options, and the deprecated options' values are only used to fill in values when the new options are unset. AppSignal logs which values to set to keep reporting what it reports now.
63
+
64
+ In agent mode, `filter_parameters` and `send_params` still apply to every kind of parameter, and the new options have no effect.
65
+
66
+ (patch [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
67
+
68
+ ### Fixed
69
+
70
+ - The `ignore_logs` option now filters out the log lines that match its patterns in collector mode. (patch [bf606432](https://github.com/appsignal/appsignal-ruby/commit/bf606432aab96d3f9b3a20cbb4b0c48aa7748702))
71
+ - In collector mode, backtrace lines from your own application are now shown as paths relative to your application's root, and are recognized as your application's code. (patch [bf606432](https://github.com/appsignal/appsignal-ruby/commit/bf606432aab96d3f9b3a20cbb4b0c48aa7748702))
72
+ - The `ca_file_path` and `http_proxy` options now apply to the data sent to the collector in collector mode. Before this change they only applied to the data sent by the agent, so a custom certificate authority file or a proxy had no effect in collector mode. (patch [e99c6b68](https://github.com/appsignal/appsignal-ruby/commit/e99c6b689b20fb433ecdcdc9fb8c6173e1ea8cda))
73
+ - Remove an incorrect warning about the `filter_metadata` configuration option being ignored in collector mode. (patch [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
74
+ - Report request headers in collector mode. (patch [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
75
+ - Report no response headers in collector mode until the `response_headers` configuration option names some. The option had no effect at all, so every response header captured by the application's own OpenTelemetry instrumentation was reported whatever the option said. (patch [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
76
+ - Read an array configuration option written as a null in `config/appsignal.yml` as the empty list it means. Options such as `filter_parameters` and `ignore_actions` raised a `NoMethodError` when AppSignal started, and `filter_metadata` and `filter_session_data` raised one while a transaction was sampled. (patch [992a60c8](https://github.com/appsignal/appsignal-ruby/commit/992a60c8d0f68b3edcb00150a1e9f14985619a4f))
77
+
3
78
  ## 4.10.4
4
79
 
5
80
  _Published on 2026-09-22._
@@ -27,6 +102,20 @@ _Published on 2026-09-10._
27
102
 
28
103
  (patch [74f509b0](https://github.com/appsignal/appsignal-ruby/commit/74f509b0e8438140bab79a5189c2ed421bbbdf6d))
29
104
 
105
+ ## 3.13.2
106
+
107
+ _Published on 2026-09-09._
108
+
109
+ ### Changed
110
+
111
+ - Update the agent to handle high traffic apps. On high-traffic apps that would exceed the maximum accepted internal payload size, send data to the Push API more frequently. (patch [c69fbf57](https://github.com/appsignal/appsignal-ruby/commit/c69fbf57242b13c86af2196baae8f295285de947))
112
+ - Update the agent from version 0.35.19 to version 0.37.0. The 3.x series of the Ruby gem was several agent releases behind, so this update also brings the improvements from every agent release in between. Those include better sanitisation of SQL queries, host metric collection that keeps working when a disk mount is frozen, and no more leftover `[timeout]` processes on Alpine Linux containers. (patch [c69fbf57](https://github.com/appsignal/appsignal-ruby/commit/c69fbf57242b13c86af2196baae8f295285de947))
113
+
114
+ ### Fixed
115
+
116
+ - Fix events showing as unknown in long-running applications. An application process that kept running for thirty days without restarting could lose the names and queries of the events it recorded, both in slow traces and in the "Slow events" panel. (patch [c69fbf57](https://github.com/appsignal/appsignal-ruby/commit/c69fbf57242b13c86af2196baae8f295285de947))
117
+ - Prevent `gc` events from appearing in the "Slow events" panel. (patch [c69fbf57](https://github.com/appsignal/appsignal-ruby/commit/c69fbf57242b13c86af2196baae8f295285de947))
118
+
30
119
  ## 4.10.2
31
120
 
32
121
  _Published on 2026-09-09._
@@ -75,6 +164,25 @@ _Published on 2026-09-09._
75
164
 
76
165
  (patch [fe82dc60](https://github.com/appsignal/appsignal-ruby/commit/fe82dc60bc8b52d466c0c9b673adbae29f79449e))
77
166
 
167
+ ## 5.0.0.rc.1
168
+
169
+ _Published on 2026-08-21._
170
+
171
+ ### Added
172
+
173
+ - Add a `collector_endpoint` configuration option (`APPSIGNAL_COLLECTOR_ENDPOINT` environment variable) that puts the integration in _collector mode_. In collector mode AppSignal reports traces, metrics and logs to an AppSignal Collector, over OTLP/HTTP.
174
+
175
+ Collector mode requires Ruby 3.1 or newer, and the OpenTelemetry gems, which are not installed by default. Add the `appsignal-opentelemetry` gem alongside `appsignal` to install them. When they are missing or too old, AppSignal logs a warning and keeps reporting through its agent.
176
+
177
+ (major [84d83b65](https://github.com/appsignal/appsignal-ruby/commit/84d83b657d65a93859ba73f295216a3b0ee9cab1), [90768626](https://github.com/appsignal/appsignal-ruby/commit/9076862677444c7772e6f8fbe1e3d5a646a4849d))
178
+ - Add configuration options that map to OpenTelemetry resource attributes in collector mode: `service_name`, `filter_attributes`, `filter_function_parameters`, `filter_request_query_parameters`, `filter_request_payload`, `response_headers`, `send_function_parameters`, `send_request_query_parameters` and `send_request_payload`.
179
+
180
+ In collector mode, existing options are passed to the collector as resource attributes as well: `name`, environment, `hostname`, `revision`, `ignore_actions`, `ignore_errors`, `ignore_namespaces`, `request_headers`, `filter_session_data` and `send_session_data`.
181
+
182
+ Setting any of these without `collector_endpoint`, or setting `filter_parameters`, `filter_metadata` or `send_params` with it, logs a warning at startup.
183
+
184
+ (minor [84d83b65](https://github.com/appsignal/appsignal-ruby/commit/84d83b657d65a93859ba73f295216a3b0ee9cab1), [90768626](https://github.com/appsignal/appsignal-ruby/commit/9076862677444c7772e6f8fbe1e3d5a646a4849d))
185
+
78
186
  ## 4.10.1
79
187
 
80
188
  _Published on 2026-08-20._
data/Rakefile CHANGED
@@ -20,6 +20,8 @@ VERSION_MANAGERS = {
20
20
  }
21
21
  }.freeze
22
22
 
23
+ SPEC_COVERAGE_AUDIT_JOB_NAME = "Spec coverage audit"
24
+
23
25
  def build_job(ruby_version, ruby_gem: nil, runs_on: DEFAULT_RUNS_ON)
24
26
  name = "Ruby #{ruby_version}"
25
27
  name = "#{name} - #{ruby_gem}" if ruby_gem
@@ -57,6 +59,89 @@ def build_job(ruby_version, ruby_gem: nil, runs_on: DEFAULT_RUNS_ON)
57
59
  }
58
60
  end
59
61
 
62
+ # Every test job uploads the example list its run wrote, named after the job so
63
+ # the audit can say which combination is missing one. Uploaded even when the run
64
+ # fails, because the list is written either way and the audit needs to tell a
65
+ # crashed job apart from a combination that legitimately runs nothing.
66
+ def example_list_upload_step(key)
67
+ {
68
+ "name" => "Upload example list",
69
+ "if" => "always()",
70
+ "uses" => "actions/upload-artifact@v4",
71
+ "with" => {
72
+ "name" => key,
73
+ "path" => "tmp/examples-*.json",
74
+ "if-no-files-found" => "error",
75
+ "retention-days" => 1
76
+ }
77
+ }
78
+ end
79
+
80
+ # Compares the examples defined in the source against the union of every job's
81
+ # example list.
82
+ #
83
+ # It cannot wait for the test jobs through `needs`. Naming all 426 of them puts
84
+ # the workflow over a limit on how many dependency edges GitHub accepts, and
85
+ # GitHub then refuses to create the run without reporting anything: no run
86
+ # appears, the pull request shows no checks, and the runs API stays empty.
87
+ # Grouping the dependencies behind intermediate jobs does not help, because the
88
+ # same number of edges still has to exist. So this job starts with the others
89
+ # and waits by polling the run it belongs to.
90
+ def spec_coverage_audit_job
91
+ {
92
+ "name" => SPEC_COVERAGE_AUDIT_JOB_NAME,
93
+ "needs" => "validation",
94
+ "runs-on" => DEFAULT_RUNS_ON,
95
+ # Reports without failing the build. The examples it currently finds are
96
+ # covered by separate work; removing this line is what makes it enforcing.
97
+ "continue-on-error" => true,
98
+ # Long enough for the whole matrix, and short enough that a stuck job does
99
+ # not hold a runner for the six hours GitHub would otherwise allow.
100
+ "timeout-minutes" => 180,
101
+ "permissions" => {
102
+ "actions" => "read",
103
+ "contents" => "read"
104
+ },
105
+ "steps" => [
106
+ {
107
+ "name" => "Check out repository",
108
+ "uses" => "actions/checkout@v4"
109
+ },
110
+ {
111
+ "name" => "Install Ruby",
112
+ "uses" => "ruby/setup-ruby@v1",
113
+ "with" => { "ruby-version" => "3.4" }
114
+ },
115
+ {
116
+ "name" => "Wait for the test jobs to upload their example lists",
117
+ "env" => { "GH_TOKEN" => "${{ secrets.GITHUB_TOKEN }}" },
118
+ "run" => <<~SHELL
119
+ jobs="repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs"
120
+ filter='.jobs[]
121
+ | select(.name != "#{SPEC_COVERAGE_AUDIT_JOB_NAME}")
122
+ | select(.status != "completed")
123
+ | .name'
124
+ while :; do
125
+ pending=$(gh api --paginate "$jobs?per_page=100" --jq "$filter" | wc -l)
126
+ if [ "$pending" -eq 0 ]; then break; fi
127
+ echo "Waiting for $pending jobs."
128
+ sleep 60
129
+ done
130
+ SHELL
131
+ },
132
+ {
133
+ "name" => "Download example lists",
134
+ "uses" => "actions/download-artifact@v4",
135
+ "with" => { "path" => "artifacts" }
136
+ },
137
+ {
138
+ "name" => "Audit spec coverage",
139
+ "run" => "ruby script/audit_spec_coverage.rb artifacts"
140
+ }
141
+ ]
142
+ }
143
+ end
144
+
60
145
  def build_matrix_key(ruby_version, ruby_gem: nil, runs_on: DEFAULT_RUNS_ON)
61
146
  base = "ruby_#{ruby_version}"
62
147
  base = "#{base}__#{ruby_gem}" if ruby_gem
@@ -72,7 +157,50 @@ GITHUB_ACTION_WORKFLOW_FILE = ".github/workflows/ci.yml"
72
157
  PRIMARY_JOB_GEMSET = "no_dependencies"
73
158
  DEFAULT_RUNS_ON = "ubuntu-latest"
74
159
 
160
+ COLLECTOR_GEMFILE_PARTIAL = "collector.rb"
161
+
75
162
  namespace :build_matrix do
163
+ namespace :gemfiles do
164
+ # Generates a `<base>-collector.gemfile` next to each base gemfile. The
165
+ # variant layers the optional OpenTelemetry gems (`gemfiles/collector.rb`)
166
+ # on top of the base, so collector-mode test runs resolve them while the
167
+ # base gemfiles (and the gemspec) stay OpenTelemetry-free. Regenerate
168
+ # whenever a base gemfile is added or removed.
169
+ task :generate do
170
+ base_gemfiles =
171
+ Dir["gemfiles/*.gemfile"]
172
+ .map { |path| File.basename(path) }
173
+ .reject { |name| name.end_with?("-collector.gemfile") }
174
+ .sort
175
+
176
+ base_gemfiles.each do |base|
177
+ name = base.sub(/\.gemfile\z/, "")
178
+ contents =
179
+ "# DO NOT EDIT\n" \
180
+ "# This is a generated file by the " \
181
+ "`rake build_matrix:gemfiles:generate` task.\n" \
182
+ "# It layers the optional OpenTelemetry gems (gemfiles/" \
183
+ "#{COLLECTOR_GEMFILE_PARTIAL}) on top of #{base}.\n" \
184
+ "\n" \
185
+ "eval_gemfile File.expand_path(#{base.inspect}, __dir__)\n" \
186
+ "eval_gemfile File.expand_path(" \
187
+ "#{COLLECTOR_GEMFILE_PARTIAL.inspect}, __dir__)\n"
188
+ File.write("gemfiles/#{name}-collector.gemfile", contents)
189
+ end
190
+
191
+ puts "Generated #{base_gemfiles.count} `-collector` gemfiles."
192
+ end
193
+
194
+ task :validate => :generate do
195
+ output = `git status --porcelain gemfiles`
196
+ if output.include?("-collector.gemfile")
197
+ puts "The `-collector` gemfiles are out of date. The changes were not committed."
198
+ puts "Please run `rake build_matrix:gemfiles:generate` and commit the changes."
199
+ exit 1
200
+ end
201
+ end
202
+ end
203
+
76
204
  namespace :github do
77
205
  task :generate do
78
206
  yaml = YAML.load_file("build_matrix.yml")
@@ -107,12 +235,31 @@ namespace :build_matrix do
107
235
  "name" => "Run tests without extension",
108
236
  "run" => "./script/bundler_wrapper exec rake test:failure"
109
237
  }
238
+ job["steps"] << example_list_upload_step(build_matrix_key(ruby["ruby"]))
110
239
  builds[build_matrix_key(ruby["ruby"])] = job
111
240
  else
112
241
  job["needs"] = build_matrix_key(ruby["ruby"])
113
242
  job["steps"] << test_step
114
- builds[build_matrix_key(ruby["ruby"], :ruby_gem => ruby_gem["gem"])] = job
243
+ gem_key = build_matrix_key(ruby["ruby"], :ruby_gem => ruby_gem["gem"])
244
+ job["steps"] << example_list_upload_step(gem_key)
245
+ builds[gem_key] = job
115
246
  end
247
+
248
+ # On collector-capable Rubies, additionally run the gem's
249
+ # `-collector` gemfile (base gems + optional OpenTelemetry gems) so
250
+ # collector-mode specs are exercised. These always depend on the
251
+ # primary job for the Ruby version and run on Ubuntu only.
252
+ next unless collector_ruby?(matrix, ruby_version)
253
+
254
+ collector_gem = "#{ruby_gem["gem"]}-collector"
255
+ collector_job = build_job(ruby_version, :ruby_gem => collector_gem)
256
+ collector_job["env"] = matrix["env"]
257
+ .merge("BUNDLE_GEMFILE" => "gemfiles/#{collector_gem}.gemfile")
258
+ collector_job["needs"] = build_matrix_key(ruby["ruby"])
259
+ collector_job["steps"] << test_step
260
+ collector_key = build_matrix_key(ruby["ruby"], :ruby_gem => collector_gem)
261
+ collector_job["steps"] << example_list_upload_step(collector_key)
262
+ builds[collector_key] = collector_job
116
263
  end
117
264
 
118
265
  # Add build for macOS
@@ -130,9 +277,13 @@ namespace :build_matrix do
130
277
  "name" => "Run tests without extension",
131
278
  "run" => "./script/bundler_wrapper exec rake test:failure"
132
279
  }
133
- builds[build_matrix_key(ruby["ruby"], :runs_on => runs_on)] = job
280
+ macos_key = build_matrix_key(ruby["ruby"], :runs_on => runs_on)
281
+ job["steps"] << example_list_upload_step(macos_key)
282
+ builds[macos_key] = job
134
283
  end
135
284
 
285
+ builds["spec_coverage_audit"] = spec_coverage_audit_job
286
+
136
287
  github["jobs"] = github["jobs"].merge(builds)
137
288
 
138
289
  job_count = github["jobs"].count
@@ -191,6 +342,14 @@ namespace :build_matrix do
191
342
  out << "#{bundler_version} #{gemfile_env} ./script/bundler_wrapper install --quiet || { echo 'Bundling failed'; exit 1; }"
192
343
  out << "echo 'Running #{gemfile} in #{ruby_version}'"
193
344
  out << "#{bundler_version} #{gemfile_env} ./script/bundler_wrapper exec rspec || { echo 'Running specs failed'; exit 1; }"
345
+
346
+ next unless collector_ruby?(matrix, ruby_version)
347
+
348
+ collector_env = "env BUNDLE_GEMFILE=gemfiles/#{gemfile}-collector.gemfile"
349
+ out << "echo 'Bundling #{gemfile}-collector in #{ruby_version}'"
350
+ out << "#{bundler_version} #{collector_env} ./script/bundler_wrapper install --quiet || { echo 'Bundling failed'; exit 1; }"
351
+ out << "echo 'Running #{gemfile}-collector in #{ruby_version}'"
352
+ out << "#{bundler_version} #{collector_env} ./script/bundler_wrapper exec rspec || { echo 'Running specs failed'; exit 1; }"
194
353
  end
195
354
  # rubocop:enable Layout/LineLength
196
355
  out << ""
@@ -207,6 +366,10 @@ namespace :build_matrix do
207
366
  end
208
367
  end
209
368
 
369
+ def collector_ruby?(matrix, ruby_version)
370
+ Array(matrix.dig("collector", "ruby")).include?(ruby_version)
371
+ end
372
+
210
373
  def gemset_for_ruby(ruby, matrix)
211
374
  gems = matrix["gems"]
212
375
  if ruby["gems"]
@@ -288,6 +451,23 @@ namespace :build do
288
451
  desc "Build all gem versions"
289
452
  task :all => ["ruby:gem", "jruby:gem"]
290
453
 
454
+ desc "Build every package in this repository"
455
+ task :packages => :all do
456
+ # Build the companion gem by delegating to its own Rakefile rather than
457
+ # duplicating its build logic here. mono builds each package directly, so
458
+ # this task is only a convenience for developers building from the root.
459
+ #
460
+ # Clear the Bundler environment first so the sub-build resolves against the
461
+ # package's own Gemfile instead of inheriting this repository's
462
+ # BUNDLE_GEMFILE. Install its dependencies first, then build it.
463
+ Bundler.with_unbundled_env do
464
+ Dir.chdir("packages/opentelemetry") do
465
+ sh "bundle install"
466
+ sh "bundle exec rake build:all"
467
+ end
468
+ end
469
+ end
470
+
291
471
  desc "Clean up all gem build artifacts"
292
472
  task :clean do
293
473
  FileUtils.rm_rf File.expand_path("pkg", __dir__)
@@ -361,14 +541,25 @@ begin
361
541
  excludes << "spec/lib/appsignal/extension/jruby_spec.rb" unless is_jruby
362
542
  exclude_pattern = "--exclude-pattern=#{excludes.join(",")}" if excludes.any?
363
543
 
544
+ # Written alongside the human-readable output so the coverage audit can tell
545
+ # which examples this Ruby and gemfile combination actually ran. Every CI job
546
+ # uploads its list. Nothing else reveals that a dependency guard is false in
547
+ # every combination, because a guarded example is then never defined at all.
548
+ def example_list_opts(name)
549
+ "--format json --out tmp/examples-#{name}.json"
550
+ end
551
+
364
552
  desc "Run the AppSignal gem test suite."
365
553
  RSpec::Core::RakeTask.new :test do |t|
366
- t.rspec_opts = "#{exclude_pattern} --format documentation"
554
+ t.rspec_opts = "#{exclude_pattern} --format documentation " \
555
+ "#{example_list_opts("test")}"
367
556
  end
368
557
 
369
558
  namespace :test do
370
559
  RSpec::Core::RakeTask.new :rspec_failure do |t|
371
- t.rspec_opts = "#{exclude_pattern} --tag extension_installation_failure"
560
+ t.rspec_opts = "#{exclude_pattern} --format documentation " \
561
+ "--tag extension_installation_failure " \
562
+ "#{example_list_opts("failure")}"
372
563
  end
373
564
 
374
565
  desc "Intentionally fail the extension installation"
data/appsignal.gemspec CHANGED
@@ -7,6 +7,7 @@ IGNORED_PATHS = [
7
7
  ".changesets/",
8
8
  ".github/",
9
9
  "gemfiles/",
10
+ "packages/",
10
11
  "script/",
11
12
  "spec/",
12
13
 
@@ -60,6 +61,13 @@ Gem::Specification.new do |gem|
60
61
  # Needs 2.0+ because we rely on Rack::Events
61
62
  gem.add_dependency "rack", ">= 2.0.0"
62
63
 
64
+ # The OpenTelemetry SDK and OTLP exporters are *optional* and intentionally
65
+ # not declared here: they're only needed in collector mode (Ruby 3.1+), so
66
+ # bundling them would break Ruby 2.7 and burden non-collector users. Apps
67
+ # that enable collector mode add them to their own Gemfile; the minimum
68
+ # versions live in `lib/appsignal/opentelemetry/dependencies.rb` and are
69
+ # enforced at boot by `Appsignal::OpenTelemetry.configure`.
70
+
63
71
  gem.add_development_dependency "pry"
64
72
  # Reached through webmock's addressable. Version 7 requires Ruby 3.2 and
65
73
  # version 6 requires 3.0, so an older Ruby cannot resolve the development
data/build_matrix.yml CHANGED
@@ -107,6 +107,18 @@ matrix:
107
107
  JRUBY_OPTS: ""
108
108
  COV: "1"
109
109
 
110
+ # Ruby versions that additionally run each gem's `-collector` gemfile (the
111
+ # optional OpenTelemetry gems layered on top). Collector mode requires Ruby
112
+ # 3.1+ (the OTel metrics SDK's fork hooks rely on `Process._fork`) and does
113
+ # not support JRuby's forking model, so only these versions are listed.
114
+ collector:
115
+ ruby:
116
+ - "4.0.0"
117
+ - "3.4.1"
118
+ - "3.3.4"
119
+ - "3.2.5"
120
+ - "3.1.6"
121
+
110
122
  gemsets: # By default all gems are tested
111
123
  none:
112
124
  - "no_dependencies"
@@ -834,10 +834,23 @@ static VALUE add_distribution_value(VALUE self, VALUE key, VALUE value, VALUE ta
834
834
  return Qnil;
835
835
  }
836
836
 
837
+ // Per-thread running total of object allocations, incremented on every Ruby
838
+ // NEWOBJ event. Thread-local because MRI maps each Ruby thread to its own OS
839
+ // thread, so this attributes allocations to the thread doing the work, which is
840
+ // the thread the transaction runs on. Collector mode reads it through
841
+ // Appsignal::Extension.allocation_count and diffs two snapshots to get the
842
+ // allocations made during a transaction or an event.
843
+ static __thread unsigned long long appsignal_thread_allocation_count = 0;
844
+
837
845
  static void track_allocation(rb_event_flag_t flag, VALUE arg1, VALUE arg2, ID arg3, VALUE arg4) {
846
+ appsignal_thread_allocation_count++;
838
847
  appsignal_track_allocation();
839
848
  }
840
849
 
850
+ static VALUE allocation_count(VALUE self) {
851
+ return ULL2NUM(appsignal_thread_allocation_count);
852
+ }
853
+
841
854
  static VALUE install_allocation_event_hook(VALUE self) {
842
855
  // This event hook is only available on Ruby 2.1 and 2.2
843
856
  #if defined(RUBY_INTERNAL_EVENT_NEWOBJ)
@@ -956,6 +969,7 @@ void Init_appsignal_extension(void) {
956
969
 
957
970
  // Other helper methods
958
971
  rb_define_singleton_method(Extension, "install_allocation_event_hook", install_allocation_event_hook, 0);
972
+ rb_define_singleton_method(Extension, "allocation_count", allocation_count, 0);
959
973
  rb_define_singleton_method(Extension, "running_in_container?", running_in_container, 0);
960
974
  rb_define_singleton_method(Extension, "set_environment_metadata", set_environment_metadata, 2);
961
975
 
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appsignal/metrics/extension_backend"
4
+ require "appsignal/metrics/opentelemetry_backend"
5
+ require "appsignal/logger/extension_backend"
6
+ require "appsignal/logger/opentelemetry_backend"
7
+ require "appsignal/transaction/base_backend"
8
+ require "appsignal/transaction/extension_backend"
9
+ require "appsignal/transaction/opentelemetry_backend"
10
+
11
+ module Appsignal
12
+ # @!visibility private
13
+ #
14
+ # Looks up the active backend for each AppSignal subsystem. In normal
15
+ # operation, subsystems route through the C-extension (and its agent).
16
+ # When collector mode is configured and the OpenTelemetry SDK has booted
17
+ # successfully, supported subsystems route through OTel instead.
18
+ #
19
+ # Centralizes the mode-check so per-subsystem call sites don't repeat the
20
+ # "if collector? then OTel else Extension" branch. Future subsystems plug
21
+ # in by adding one more lookup method here.
22
+ module Backends
23
+ class << self
24
+ def metrics
25
+ if collector?
26
+ Appsignal::Metrics::OpenTelemetryBackend
27
+ else
28
+ Appsignal::Metrics::ExtensionBackend
29
+ end
30
+ end
31
+
32
+ def logger
33
+ if collector?
34
+ Appsignal::Logger::OpenTelemetryBackend
35
+ else
36
+ Appsignal::Logger::ExtensionBackend
37
+ end
38
+ end
39
+
40
+ def transaction
41
+ if collector?
42
+ Appsignal::Transaction::OpenTelemetryBackend
43
+ else
44
+ Appsignal::Transaction::ExtensionBackend
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def collector?
51
+ Appsignal.config&.collector_mode? || false
52
+ end
53
+ end
54
+ end
55
+ end
@@ -201,6 +201,7 @@ module Appsignal
201
201
 
202
202
  # No config loaded yet, try loading as normal
203
203
  Appsignal._load_config!(env_option) unless Appsignal.config
204
+ Appsignal.config.apply_overrides
204
205
  Appsignal._start_logger
205
206
  Appsignal.config.write_to_environment
206
207
  Appsignal.internal_logger.info("Starting AppSignal diagnose")
@@ -472,16 +473,7 @@ module Appsignal
472
473
  config = Appsignal.config
473
474
  data[:config] = {
474
475
  :options => config.config_hash.merge(:env => config.env),
475
- :sources => {
476
- :default => Appsignal::Config::DEFAULT_CONFIG,
477
- :system => config.system_config,
478
- :loaders => config.loaders_config,
479
- :initial => config.initial_config,
480
- :file => config.file_config,
481
- :env => config.env_config,
482
- :override => config.override_config,
483
- :dsl => config.dsl_config
484
- }
476
+ :sources => config.config_sources
485
477
  }
486
478
  print_config_options(config)
487
479
  end