appsignal 4.10.1-java → 5.0.0.rc.1-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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Rakefile +87 -0
  4. data/appsignal.gemspec +8 -0
  5. data/build_matrix.yml +14 -1
  6. data/ext/appsignal_extension.c +14 -0
  7. data/lib/appsignal/backends.rb +55 -0
  8. data/lib/appsignal/config.rb +140 -1
  9. data/lib/appsignal/demo.rb +1 -1
  10. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +34 -22
  11. data/lib/appsignal/event_formatter/active_job/perform_formatter.rb +35 -0
  12. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +19 -0
  13. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +27 -0
  14. data/lib/appsignal/event_formatter/recorded_elsewhere.rb +17 -0
  15. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +24 -0
  16. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +5 -0
  17. data/lib/appsignal/event_formatter/view_component/render_formatter.rb +21 -10
  18. data/lib/appsignal/event_formatter.rb +78 -0
  19. data/lib/appsignal/extension.rb +4 -0
  20. data/lib/appsignal/helpers/instrumentation.rb +236 -20
  21. data/lib/appsignal/helpers/metrics.rb +3 -24
  22. data/lib/appsignal/hooks/action_cable.rb +18 -6
  23. data/lib/appsignal/hooks/active_job.rb +128 -39
  24. data/lib/appsignal/hooks/at_exit.rb +4 -1
  25. data/lib/appsignal/hooks/excon.rb +20 -0
  26. data/lib/appsignal/hooks/faraday.rb +16 -0
  27. data/lib/appsignal/hooks/http.rb +5 -0
  28. data/lib/appsignal/hooks/resque.rb +1 -1
  29. data/lib/appsignal/hooks/sequel.rb +32 -2
  30. data/lib/appsignal/hooks/shoryuken.rb +3 -3
  31. data/lib/appsignal/hooks/sidekiq.rb +1 -1
  32. data/lib/appsignal/integrations/action_cable.rb +5 -2
  33. data/lib/appsignal/integrations/active_support_notifications.rb +59 -14
  34. data/lib/appsignal/integrations/data_mapper.rb +14 -2
  35. data/lib/appsignal/integrations/delayed_job_plugin.rb +45 -5
  36. data/lib/appsignal/integrations/dry_monitor.rb +39 -15
  37. data/lib/appsignal/integrations/excon/appsignal_middleware.rb +21 -0
  38. data/lib/appsignal/integrations/excon.rb +52 -15
  39. data/lib/appsignal/integrations/faraday.rb +47 -12
  40. data/lib/appsignal/integrations/http.rb +43 -1
  41. data/lib/appsignal/integrations/mongo_ruby_driver.rb +73 -4
  42. data/lib/appsignal/integrations/net_http.rb +31 -2
  43. data/lib/appsignal/integrations/puma.rb +4 -1
  44. data/lib/appsignal/integrations/que.rb +224 -37
  45. data/lib/appsignal/integrations/railtie.rb +4 -1
  46. data/lib/appsignal/integrations/rake.rb +9 -3
  47. data/lib/appsignal/integrations/redis.rb +22 -1
  48. data/lib/appsignal/integrations/redis_client.rb +22 -1
  49. data/lib/appsignal/integrations/resque.rb +57 -10
  50. data/lib/appsignal/integrations/shoryuken.rb +140 -12
  51. data/lib/appsignal/integrations/sidekiq.rb +73 -16
  52. data/lib/appsignal/integrations/webmachine.rb +47 -4
  53. data/lib/appsignal/loaders/padrino.rb +2 -1
  54. data/lib/appsignal/logger/extension_backend.rb +24 -0
  55. data/lib/appsignal/logger/opentelemetry_backend.rb +66 -0
  56. data/lib/appsignal/logger.rb +13 -9
  57. data/lib/appsignal/metrics/extension_backend.rb +47 -0
  58. data/lib/appsignal/metrics/opentelemetry_backend.rb +89 -0
  59. data/lib/appsignal/opentelemetry/attributes.rb +31 -0
  60. data/lib/appsignal/opentelemetry/dependencies.rb +35 -0
  61. data/lib/appsignal/opentelemetry/error_type.rb +37 -0
  62. data/lib/appsignal/opentelemetry/http_client_request.rb +83 -0
  63. data/lib/appsignal/opentelemetry/http_method.rb +59 -0
  64. data/lib/appsignal/opentelemetry/http_response.rb +30 -0
  65. data/lib/appsignal/opentelemetry/http_server_request.rb +45 -0
  66. data/lib/appsignal/opentelemetry/messaging.rb +82 -0
  67. data/lib/appsignal/opentelemetry/rendering.rb +29 -0
  68. data/lib/appsignal/opentelemetry/sql_db_system.rb +89 -0
  69. data/lib/appsignal/opentelemetry.rb +337 -0
  70. data/lib/appsignal/rack/abstract_middleware.rb +63 -4
  71. data/lib/appsignal/rack/body_wrapper.rb +18 -5
  72. data/lib/appsignal/rack/event_handler.rb +36 -3
  73. data/lib/appsignal/rack/grape_middleware.rb +1 -0
  74. data/lib/appsignal/rack/hanami_middleware.rb +2 -1
  75. data/lib/appsignal/rack/instrumentation_middleware.rb +1 -0
  76. data/lib/appsignal/rack/rails_instrumentation.rb +1 -0
  77. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -0
  78. data/lib/appsignal/rack.rb +39 -11
  79. data/lib/appsignal/sample_data.rb +4 -0
  80. data/lib/appsignal/transaction/base_backend.rb +107 -0
  81. data/lib/appsignal/transaction/extension_backend.rb +203 -0
  82. data/lib/appsignal/transaction/opentelemetry_backend.rb +796 -0
  83. data/lib/appsignal/transaction.rb +537 -144
  84. data/lib/appsignal/utils/stdout_and_logger_message.rb +9 -0
  85. data/lib/appsignal/version.rb +1 -1
  86. data/lib/appsignal.rb +9 -0
  87. data/sig/appsignal.rbi +426 -37
  88. data/sig/appsignal.rbs +386 -27
  89. metadata +23 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1484edc2b0257539896eed6be1f56407fcaf454ab7c200c6bf798fea67d75c05
4
- data.tar.gz: bc1cd76fce693264350284284af84e86bbc4956e84e36fc30af4b8d9b8ab5811
3
+ metadata.gz: 77d0e22fb5459d6180f21e04d33c74b67fccf48bd536f9d421f2360803336edd
4
+ data.tar.gz: 4080ad48951daa435a0798951a5f36d98e62fe3b9cc62a0a94b04043e0b02b23
5
5
  SHA512:
6
- metadata.gz: ce745eea54bcb98d7c2cf87acd060a1c6e162a25090bc0c653491fd926ef6ef26dd6e4f4a88516d4ac2560d943b7f720c88fade34276481d51c0a1cf277b7318
7
- data.tar.gz: 35f4295a1a9685e44719a1a6723e3933a3777e495ffeb862884c4e1dc61979cb990ef093136ecea5a9040d05790c0925464eb0b9481fdfcd0f66da85747c3f7a
6
+ metadata.gz: 3dcc132106477e0181cff58f8bbed8ed1db7d6c3cebb81dac5b2cc9fc1a8e8b96cfcaf768bf1bfc4fff9c2529ddf6aa920ac30b9fc8367c7687f477f948ab048
7
+ data.tar.gz: e053bcbe1bb2ec6ad4e09efb80cbd86593ab9342acfe16bda373f596bc96e507920a611cff9802fd46a8219851feaf909a59b2e35b5d9e7bce60c4f2eb1716e2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # AppSignal for Ruby gem Changelog
2
2
 
3
+ ## 5.0.0.rc.1
4
+
5
+ _Published on 2026-08-21._
6
+
7
+ ### Added
8
+
9
+ - 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.
10
+
11
+ 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.
12
+
13
+ (major [84d83b65](https://github.com/appsignal/appsignal-ruby/commit/84d83b657d65a93859ba73f295216a3b0ee9cab1), [90768626](https://github.com/appsignal/appsignal-ruby/commit/9076862677444c7772e6f8fbe1e3d5a646a4849d))
14
+ - 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`.
15
+
16
+ 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`.
17
+
18
+ Setting any of these without `collector_endpoint`, or setting `filter_parameters`, `filter_metadata` or `send_params` with it, logs a warning at startup.
19
+
20
+ (minor [84d83b65](https://github.com/appsignal/appsignal-ruby/commit/84d83b657d65a93859ba73f295216a3b0ee9cab1), [90768626](https://github.com/appsignal/appsignal-ruby/commit/9076862677444c7772e6f8fbe1e3d5a646a4849d))
21
+
3
22
  ## 4.10.1
4
23
 
5
24
  _Published on 2026-08-20._
data/Rakefile CHANGED
@@ -72,7 +72,50 @@ GITHUB_ACTION_WORKFLOW_FILE = ".github/workflows/ci.yml"
72
72
  PRIMARY_JOB_GEMSET = "no_dependencies"
73
73
  DEFAULT_RUNS_ON = "ubuntu-latest"
74
74
 
75
+ COLLECTOR_GEMFILE_PARTIAL = "collector.rb"
76
+
75
77
  namespace :build_matrix do
78
+ namespace :gemfiles do
79
+ # Generates a `<base>-collector.gemfile` next to each base gemfile. The
80
+ # variant layers the optional OpenTelemetry gems (`gemfiles/collector.rb`)
81
+ # on top of the base, so collector-mode test runs resolve them while the
82
+ # base gemfiles (and the gemspec) stay OpenTelemetry-free. Regenerate
83
+ # whenever a base gemfile is added or removed.
84
+ task :generate do
85
+ base_gemfiles =
86
+ Dir["gemfiles/*.gemfile"]
87
+ .map { |path| File.basename(path) }
88
+ .reject { |name| name.end_with?("-collector.gemfile") }
89
+ .sort
90
+
91
+ base_gemfiles.each do |base|
92
+ name = base.sub(/\.gemfile\z/, "")
93
+ contents =
94
+ "# DO NOT EDIT\n" \
95
+ "# This is a generated file by the " \
96
+ "`rake build_matrix:gemfiles:generate` task.\n" \
97
+ "# It layers the optional OpenTelemetry gems (gemfiles/" \
98
+ "#{COLLECTOR_GEMFILE_PARTIAL}) on top of #{base}.\n" \
99
+ "\n" \
100
+ "eval_gemfile File.expand_path(#{base.inspect}, __dir__)\n" \
101
+ "eval_gemfile File.expand_path(" \
102
+ "#{COLLECTOR_GEMFILE_PARTIAL.inspect}, __dir__)\n"
103
+ File.write("gemfiles/#{name}-collector.gemfile", contents)
104
+ end
105
+
106
+ puts "Generated #{base_gemfiles.count} `-collector` gemfiles."
107
+ end
108
+
109
+ task :validate => :generate do
110
+ output = `git status --porcelain gemfiles`
111
+ if output.include?("-collector.gemfile")
112
+ puts "The `-collector` gemfiles are out of date. The changes were not committed."
113
+ puts "Please run `rake build_matrix:gemfiles:generate` and commit the changes."
114
+ exit 1
115
+ end
116
+ end
117
+ end
118
+
76
119
  namespace :github do
77
120
  task :generate do
78
121
  yaml = YAML.load_file("build_matrix.yml")
@@ -113,6 +156,21 @@ namespace :build_matrix do
113
156
  job["steps"] << test_step
114
157
  builds[build_matrix_key(ruby["ruby"], :ruby_gem => ruby_gem["gem"])] = job
115
158
  end
159
+
160
+ # On collector-capable Rubies, additionally run the gem's
161
+ # `-collector` gemfile (base gems + optional OpenTelemetry gems) so
162
+ # collector-mode specs are exercised. These always depend on the
163
+ # primary job for the Ruby version and run on Ubuntu only.
164
+ next unless collector_ruby?(matrix, ruby_version)
165
+
166
+ collector_gem = "#{ruby_gem["gem"]}-collector"
167
+ collector_job = build_job(ruby_version, :ruby_gem => collector_gem)
168
+ collector_job["env"] = matrix["env"]
169
+ .merge("BUNDLE_GEMFILE" => "gemfiles/#{collector_gem}.gemfile")
170
+ collector_job["needs"] = build_matrix_key(ruby["ruby"])
171
+ collector_job["steps"] << test_step
172
+ builds[build_matrix_key(ruby["ruby"], :ruby_gem => collector_gem)] =
173
+ collector_job
116
174
  end
117
175
 
118
176
  # Add build for macOS
@@ -191,6 +249,14 @@ namespace :build_matrix do
191
249
  out << "#{bundler_version} #{gemfile_env} ./script/bundler_wrapper install --quiet || { echo 'Bundling failed'; exit 1; }"
192
250
  out << "echo 'Running #{gemfile} in #{ruby_version}'"
193
251
  out << "#{bundler_version} #{gemfile_env} ./script/bundler_wrapper exec rspec || { echo 'Running specs failed'; exit 1; }"
252
+
253
+ next unless collector_ruby?(matrix, ruby_version)
254
+
255
+ collector_env = "env BUNDLE_GEMFILE=gemfiles/#{gemfile}-collector.gemfile"
256
+ out << "echo 'Bundling #{gemfile}-collector in #{ruby_version}'"
257
+ out << "#{bundler_version} #{collector_env} ./script/bundler_wrapper install --quiet || { echo 'Bundling failed'; exit 1; }"
258
+ out << "echo 'Running #{gemfile}-collector in #{ruby_version}'"
259
+ out << "#{bundler_version} #{collector_env} ./script/bundler_wrapper exec rspec || { echo 'Running specs failed'; exit 1; }"
194
260
  end
195
261
  # rubocop:enable Layout/LineLength
196
262
  out << ""
@@ -207,6 +273,10 @@ namespace :build_matrix do
207
273
  end
208
274
  end
209
275
 
276
+ def collector_ruby?(matrix, ruby_version)
277
+ Array(matrix.dig("collector", "ruby")).include?(ruby_version)
278
+ end
279
+
210
280
  def gemset_for_ruby(ruby, matrix)
211
281
  gems = matrix["gems"]
212
282
  if ruby["gems"]
@@ -288,6 +358,23 @@ namespace :build do
288
358
  desc "Build all gem versions"
289
359
  task :all => ["ruby:gem", "jruby:gem"]
290
360
 
361
+ desc "Build every package in this repository"
362
+ task :packages => :all do
363
+ # Build the companion gem by delegating to its own Rakefile rather than
364
+ # duplicating its build logic here. mono builds each package directly, so
365
+ # this task is only a convenience for developers building from the root.
366
+ #
367
+ # Clear the Bundler environment first so the sub-build resolves against the
368
+ # package's own Gemfile instead of inheriting this repository's
369
+ # BUNDLE_GEMFILE. Install its dependencies first, then build it.
370
+ Bundler.with_unbundled_env do
371
+ Dir.chdir("packages/opentelemetry") do
372
+ sh "bundle install"
373
+ sh "bundle exec rake build:all"
374
+ end
375
+ end
376
+ end
377
+
291
378
  desc "Clean up all gem build artifacts"
292
379
  task :clean do
293
380
  FileUtils.rm_rf File.expand_path("pkg", __dir__)
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
  gem.add_development_dependency "rake", ">= 12"
65
73
  gem.add_development_dependency "rspec", "~> 3.8"
data/build_matrix.yml CHANGED
@@ -2,7 +2,7 @@ github:
2
2
  name: Ruby gem CI
3
3
  "on":
4
4
  push:
5
- branches: ["main", "develop"]
5
+ branches: ["main", "develop", "*-stable"]
6
6
  pull_request:
7
7
  types: [opened, reopened, synchronize]
8
8
  schedule:
@@ -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"
@@ -281,6 +293,7 @@ matrix:
281
293
  - "3.4.1"
282
294
  - "3.3.4"
283
295
  - "3.2.5"
296
+ - gem: "mongo"
284
297
  - gem: "resque-2"
285
298
  - gem: "resque-3"
286
299
  only:
@@ -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
@@ -91,6 +91,7 @@ module Appsignal
91
91
  DEFAULT_CONFIG = {
92
92
  :activejob_report_errors => "all",
93
93
  :ca_file_path => File.expand_path(File.join("../../../resources/cacert.pem"), __FILE__),
94
+ :collector_endpoint => nil,
94
95
  :dns_servers => [],
95
96
  :enable_allocation_tracking => true,
96
97
  :enable_at_exit_hook => "on_error",
@@ -107,8 +108,12 @@ module Appsignal
107
108
  :enable_rake_performance_instrumentation => false,
108
109
  :endpoint => "https://push.appsignal.com",
109
110
  :files_world_accessible => true,
111
+ :filter_attributes => [],
112
+ :filter_function_parameters => [],
110
113
  :filter_metadata => [],
111
114
  :filter_parameters => [],
115
+ :filter_request_payload => [],
116
+ :filter_request_query_parameters => [],
112
117
  :filter_session_data => [],
113
118
  :ignore_actions => [],
114
119
  :ignore_errors => [],
@@ -139,9 +144,14 @@ module Appsignal
139
144
  REQUEST_METHOD REQUEST_PATH SERVER_NAME SERVER_PORT
140
145
  SERVER_PROTOCOL
141
146
  ],
147
+ :response_headers => [],
142
148
  :send_environment_metadata => true,
149
+ :send_function_parameters => nil,
143
150
  :send_params => true,
151
+ :send_request_payload => nil,
152
+ :send_request_query_parameters => nil,
144
153
  :send_session_data => true,
154
+ :service_name => nil,
145
155
  :sidekiq_report_errors => "all",
146
156
  :default_tags => {}
147
157
  }.freeze
@@ -167,6 +177,7 @@ module Appsignal
167
177
  :name => "APPSIGNAL_APP_NAME",
168
178
  :bind_address => "APPSIGNAL_BIND_ADDRESS",
169
179
  :ca_file_path => "APPSIGNAL_CA_FILE_PATH",
180
+ :collector_endpoint => "APPSIGNAL_COLLECTOR_ENDPOINT",
170
181
  :enable_at_exit_hook => "APPSIGNAL_ENABLE_AT_EXIT_HOOK",
171
182
  :hostname => "APPSIGNAL_HOSTNAME",
172
183
  :host_role => "APPSIGNAL_HOST_ROLE",
@@ -177,6 +188,7 @@ module Appsignal
177
188
  :logging_endpoint => "APPSIGNAL_LOGGING_ENDPOINT",
178
189
  :endpoint => "APPSIGNAL_PUSH_API_ENDPOINT",
179
190
  :push_api_key => "APPSIGNAL_PUSH_API_KEY",
191
+ :service_name => "APPSIGNAL_SERVICE_NAME",
180
192
  :sidekiq_report_errors => "APPSIGNAL_SIDEKIQ_REPORT_ERRORS",
181
193
  :statsd_port => "APPSIGNAL_STATSD_PORT",
182
194
  :nginx_port => "APPSIGNAL_NGINX_PORT",
@@ -221,21 +233,29 @@ module Appsignal
221
233
  :ownership_set_namespace => "APPSIGNAL_OWNERSHIP_SET_NAMESPACE",
222
234
  :running_in_container => "APPSIGNAL_RUNNING_IN_CONTAINER",
223
235
  :send_environment_metadata => "APPSIGNAL_SEND_ENVIRONMENT_METADATA",
236
+ :send_function_parameters => "APPSIGNAL_SEND_FUNCTION_PARAMETERS",
224
237
  :send_params => "APPSIGNAL_SEND_PARAMS",
238
+ :send_request_payload => "APPSIGNAL_SEND_REQUEST_PAYLOAD",
239
+ :send_request_query_parameters => "APPSIGNAL_SEND_REQUEST_QUERY_PARAMETERS",
225
240
  :send_session_data => "APPSIGNAL_SEND_SESSION_DATA"
226
241
  }.freeze
227
242
 
228
243
  # @!visibility private
229
244
  ARRAY_OPTIONS = {
230
245
  :dns_servers => "APPSIGNAL_DNS_SERVERS",
246
+ :filter_attributes => "APPSIGNAL_FILTER_ATTRIBUTES",
247
+ :filter_function_parameters => "APPSIGNAL_FILTER_FUNCTION_PARAMETERS",
231
248
  :filter_metadata => "APPSIGNAL_FILTER_METADATA",
232
249
  :filter_parameters => "APPSIGNAL_FILTER_PARAMETERS",
250
+ :filter_request_payload => "APPSIGNAL_FILTER_REQUEST_PAYLOAD",
251
+ :filter_request_query_parameters => "APPSIGNAL_FILTER_REQUEST_QUERY_PARAMETERS",
233
252
  :filter_session_data => "APPSIGNAL_FILTER_SESSION_DATA",
234
253
  :ignore_actions => "APPSIGNAL_IGNORE_ACTIONS",
235
254
  :ignore_errors => "APPSIGNAL_IGNORE_ERRORS",
236
255
  :ignore_logs => "APPSIGNAL_IGNORE_LOGS",
237
256
  :ignore_namespaces => "APPSIGNAL_IGNORE_NAMESPACES",
238
- :request_headers => "APPSIGNAL_REQUEST_HEADERS"
257
+ :request_headers => "APPSIGNAL_REQUEST_HEADERS",
258
+ :response_headers => "APPSIGNAL_RESPONSE_HEADERS"
239
259
  }.freeze
240
260
 
241
261
  # @!visibility private
@@ -248,6 +268,39 @@ module Appsignal
248
268
  :default_tags => "APPSIGNAL_DEFAULT_TAGS"
249
269
  }.freeze
250
270
 
271
+ # Collector mode requires Ruby 3.1+. The OpenTelemetry Ruby SDK relies on
272
+ # `Process._fork` (introduced in Ruby 3.1) for its fork hooks, without
273
+ # which background reader threads don't restart in child processes and
274
+ # buffered telemetry is lost after a fork.
275
+ # @!visibility private
276
+ MIN_RUBY_VERSION_FOR_COLLECTOR_MODE = "3.1"
277
+
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
+ # @!visibility private
282
+ COLLECTOR_ONLY_OPTIONS = [
283
+ :filter_attributes,
284
+ :filter_function_parameters,
285
+ :filter_request_payload,
286
+ :filter_request_query_parameters,
287
+ :response_headers,
288
+ :send_function_parameters,
289
+ :send_request_payload,
290
+ :send_request_query_parameters,
291
+ :service_name
292
+ ].freeze
293
+
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
+ # @!visibility private
298
+ AGENT_ONLY_TRACE_OPTIONS = [
299
+ :filter_metadata,
300
+ :filter_parameters,
301
+ :send_params
302
+ ].freeze
303
+
251
304
  # @!visibility private
252
305
  attr_reader :root_path, :env, :config_hash
253
306
 
@@ -452,6 +505,57 @@ module Appsignal
452
505
  valid? && active_for_env?
453
506
  end
454
507
 
508
+ # Check if collector mode is configured.
509
+ #
510
+ # Returns true when a non-empty `collector_endpoint` is set and the
511
+ # running Ruby version is at least {MIN_RUBY_VERSION_FOR_COLLECTOR_MODE}.
512
+ # On older Rubies, `collector_endpoint` is ignored (with a warning) and
513
+ # the AppSignal agent is used instead.
514
+ #
515
+ # This is the *intent* check — it answers "did the user ask for
516
+ # collector mode, and could we honor it?". It does not say whether the
517
+ # OpenTelemetry SDK actually booted. See {#collector_mode?} for that.
518
+ #
519
+ # Memoised: the result is cached on first call so hot paths avoid
520
+ # re-running the string-strip predicate, and so the unsupported-Ruby
521
+ # warning is emitted at most once per `Config` instance.
522
+ #
523
+ # @return [Boolean] True if collector mode is configured.
524
+ def collector_mode_configured?
525
+ return @collector_mode_configured if defined?(@collector_mode_configured)
526
+
527
+ endpoint = config_hash[:collector_endpoint]
528
+ configured = !endpoint.nil? && !endpoint.to_s.strip.empty?
529
+
530
+ if configured && Gem::Version.new(RUBY_VERSION) <
531
+ Gem::Version.new(MIN_RUBY_VERSION_FOR_COLLECTOR_MODE)
532
+ Appsignal::Utils::StdoutAndLoggerMessage.warning(
533
+ "Collector mode requires Ruby #{MIN_RUBY_VERSION_FOR_COLLECTOR_MODE} or higher " \
534
+ "(running Ruby #{RUBY_VERSION}). The `collector_endpoint` option will be " \
535
+ "ignored and the AppSignal agent will be used instead."
536
+ )
537
+ @collector_mode_configured = false
538
+ else
539
+ @collector_mode_configured = configured
540
+ end
541
+ end
542
+
543
+ # Check if AppSignal is actively running in collector mode.
544
+ #
545
+ # True only if collector mode is {#collector_mode_configured? configured}
546
+ # *and* `Appsignal::OpenTelemetry.configure` has successfully booted the
547
+ # SDK in this process. Use this for backend dispatch on hot paths
548
+ # (metric and log emits): if the OTel boot failed, callers fall back to
549
+ # the agent backend rather than silently dropping data into no-op
550
+ # providers.
551
+ #
552
+ # @return [Boolean] True if collector mode is configured and started.
553
+ def collector_mode?
554
+ collector_mode_configured? &&
555
+ defined?(Appsignal::OpenTelemetry) &&
556
+ Appsignal::OpenTelemetry.started?
557
+ end
558
+
455
559
  # @!visibility private
456
560
  def write_to_environment
457
561
  ENV["_APPSIGNAL_ACTIVE"] = active?.to_s
@@ -528,6 +632,30 @@ module Appsignal
528
632
  else
529
633
  @valid = true
530
634
  end
635
+
636
+ warn_for_mode_mismatch
637
+ end
638
+
639
+ # Emit warnings when a configuration option is set that has no effect in
640
+ # the current mode (collector vs. agent).
641
+ #
642
+ # Uses {#collector_mode_configured?} (intent) rather than
643
+ # {#collector_mode?} so the warnings fire based on what the user asked
644
+ # for, independent of whether the OpenTelemetry SDK successfully booted.
645
+ # @!visibility private
646
+ def warn_for_mode_mismatch
647
+ 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."
651
+ end
652
+ 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
658
+ end
531
659
  end
532
660
 
533
661
  # Deep freeze the config object so it cannot be modified during the runtime
@@ -551,6 +679,17 @@ module Appsignal
551
679
 
552
680
  private
553
681
 
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.
685
+ def warn_user_modified(options)
686
+ options.each do |option|
687
+ next if config_hash[option] == DEFAULT_CONFIG[option]
688
+
689
+ logger.warn(yield(option))
690
+ end
691
+ end
692
+
554
693
  def logger
555
694
  Appsignal.internal_logger
556
695
  end
@@ -70,7 +70,7 @@ module Appsignal
70
70
  end
71
71
 
72
72
  def add_params_to(transaction)
73
- transaction.add_params(
73
+ transaction.add_request_payload(
74
74
  "controller" => "demo",
75
75
  "action" => "hello"
76
76
  )
@@ -7,13 +7,27 @@ module Appsignal
7
7
  class RenderFormatter < Appsignal::EventFormatter
8
8
  BLANK = ""
9
9
 
10
+ def opentelemetry_attributes(_payload)
11
+ Appsignal::OpenTelemetry::Rendering.attributes
12
+ end
13
+
10
14
  def format(payload)
11
- return nil unless payload[:identifier]
15
+ # The title is the template's path made relative to the application's
16
+ # root, so a template rendered outside an application gets no title.
17
+ return unless payload[:identifier] && root_path
12
18
 
13
19
  [payload[:identifier].sub(root_path, BLANK), nil]
14
20
  end
15
21
 
22
+ # The application's root, which a template's path is made relative to.
23
+ #
24
+ # Whether there is an application is decided here, when the event is
25
+ # formatted, rather than when this file is loaded. AppSignal can be
26
+ # required before Rails is, and deciding it at load time would leave
27
+ # every template render in the application without a title.
16
28
  def root_path
29
+ return unless defined?(Rails)
30
+
17
31
  @root_path ||= "#{Rails.root}/"
18
32
  end
19
33
  end
@@ -21,24 +35,22 @@ module Appsignal
21
35
  end
22
36
  end
23
37
 
24
- if defined?(Rails)
25
- Appsignal::EventFormatter.register(
26
- "render_partial.action_view",
27
- Appsignal::EventFormatter::ActionView::RenderFormatter
28
- )
29
- Appsignal::EventFormatter.register(
30
- "render_template.action_view",
31
- Appsignal::EventFormatter::ActionView::RenderFormatter
32
- )
33
- # Action View reports the template's path for these two as well, so they are
34
- # titled the same way. A collection reports the partial it rendered for each
35
- # item, and a layout reports itself.
36
- Appsignal::EventFormatter.register(
37
- "render_collection.action_view",
38
- Appsignal::EventFormatter::ActionView::RenderFormatter
39
- )
40
- Appsignal::EventFormatter.register(
41
- "render_layout.action_view",
42
- Appsignal::EventFormatter::ActionView::RenderFormatter
43
- )
44
- end
38
+ Appsignal::EventFormatter.register(
39
+ "render_partial.action_view",
40
+ Appsignal::EventFormatter::ActionView::RenderFormatter
41
+ )
42
+ Appsignal::EventFormatter.register(
43
+ "render_template.action_view",
44
+ Appsignal::EventFormatter::ActionView::RenderFormatter
45
+ )
46
+ # Action View reports the template's path for these two as well, so they are
47
+ # titled the same way. A collection reports the partial it rendered for each
48
+ # item, and a layout reports itself.
49
+ Appsignal::EventFormatter.register(
50
+ "render_collection.action_view",
51
+ Appsignal::EventFormatter::ActionView::RenderFormatter
52
+ )
53
+ Appsignal::EventFormatter.register(
54
+ "render_layout.action_view",
55
+ Appsignal::EventFormatter::ActionView::RenderFormatter
56
+ )
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ class EventFormatter
5
+ # @!visibility private
6
+ module ActiveJob
7
+ # Active Job reports the job it is running as a `perform.active_job`
8
+ # notification. The job's own transaction already carries its title, so
9
+ # this formatter only describes the event as the work of performing a
10
+ # job.
11
+ class PerformFormatter < Appsignal::EventFormatter
12
+ PERFORM_ATTRIBUTES =
13
+ Appsignal::OpenTelemetry::Messaging.perform_attributes("active_job").freeze
14
+
15
+ def opentelemetry_attributes(payload)
16
+ PERFORM_ATTRIBUTES.merge(
17
+ { "messaging.destination.name" => queue_name(payload) }.compact
18
+ )
19
+ end
20
+
21
+ # The queue the job being performed is on, which the notification
22
+ # carries as the job itself.
23
+ def queue_name(payload)
24
+ job = payload[:job]
25
+ job.queue_name if job.respond_to?(:queue_name)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ Appsignal::EventFormatter.register(
33
+ "perform.active_job",
34
+ Appsignal::EventFormatter::ActiveJob::PerformFormatter
35
+ )