appsignal 4.10.2-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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -38
- data/README.md +1 -2
- data/Rakefile +87 -0
- data/appsignal.gemspec +8 -7
- data/build_matrix.yml +14 -7
- data/ext/agent.rb +27 -27
- data/ext/appsignal_extension.c +14 -0
- data/lib/appsignal/backends.rb +55 -0
- data/lib/appsignal/cli/demo.rb +0 -5
- data/lib/appsignal/cli/diagnose.rb +38 -6
- data/lib/appsignal/cli/helpers.rb +0 -45
- data/lib/appsignal/config.rb +140 -1
- data/lib/appsignal/demo.rb +1 -1
- data/lib/appsignal/event_formatter/action_view/render_formatter.rb +34 -22
- data/lib/appsignal/event_formatter/active_job/perform_formatter.rb +35 -0
- data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +19 -0
- data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +27 -0
- data/lib/appsignal/event_formatter/recorded_elsewhere.rb +17 -0
- data/lib/appsignal/event_formatter/rom/sql_formatter.rb +24 -0
- data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +5 -0
- data/lib/appsignal/event_formatter/view_component/render_formatter.rb +21 -10
- data/lib/appsignal/event_formatter.rb +78 -0
- data/lib/appsignal/extension.rb +4 -0
- data/lib/appsignal/helpers/instrumentation.rb +236 -20
- data/lib/appsignal/helpers/metrics.rb +3 -24
- data/lib/appsignal/hooks/action_cable.rb +18 -6
- data/lib/appsignal/hooks/active_job.rb +128 -179
- data/lib/appsignal/hooks/at_exit.rb +4 -1
- data/lib/appsignal/hooks/excon.rb +20 -0
- data/lib/appsignal/hooks/faraday.rb +16 -0
- data/lib/appsignal/hooks/http.rb +5 -0
- data/lib/appsignal/hooks/resque.rb +1 -1
- data/lib/appsignal/hooks/sequel.rb +32 -2
- data/lib/appsignal/hooks/shoryuken.rb +3 -3
- data/lib/appsignal/hooks/sidekiq.rb +1 -1
- data/lib/appsignal/integrations/action_cable.rb +5 -2
- data/lib/appsignal/integrations/active_support_notifications.rb +59 -19
- data/lib/appsignal/integrations/data_mapper.rb +14 -2
- data/lib/appsignal/integrations/delayed_job_plugin.rb +64 -131
- data/lib/appsignal/integrations/dry_monitor.rb +39 -15
- data/lib/appsignal/integrations/excon/appsignal_middleware.rb +21 -0
- data/lib/appsignal/integrations/excon.rb +52 -15
- data/lib/appsignal/integrations/faraday.rb +47 -12
- data/lib/appsignal/integrations/http.rb +43 -1
- data/lib/appsignal/integrations/mongo_ruby_driver.rb +73 -4
- data/lib/appsignal/integrations/net_http.rb +31 -2
- data/lib/appsignal/integrations/puma.rb +4 -1
- data/lib/appsignal/integrations/que.rb +224 -37
- data/lib/appsignal/integrations/railtie.rb +4 -1
- data/lib/appsignal/integrations/rake.rb +9 -3
- data/lib/appsignal/integrations/redis.rb +22 -1
- data/lib/appsignal/integrations/redis_client.rb +22 -1
- data/lib/appsignal/integrations/resque.rb +57 -10
- data/lib/appsignal/integrations/shoryuken.rb +140 -12
- data/lib/appsignal/integrations/sidekiq.rb +73 -16
- data/lib/appsignal/integrations/webmachine.rb +47 -4
- data/lib/appsignal/loaders/padrino.rb +2 -1
- data/lib/appsignal/logger/extension_backend.rb +24 -0
- data/lib/appsignal/logger/opentelemetry_backend.rb +66 -0
- data/lib/appsignal/logger.rb +13 -9
- data/lib/appsignal/metrics/extension_backend.rb +47 -0
- data/lib/appsignal/metrics/opentelemetry_backend.rb +89 -0
- data/lib/appsignal/opentelemetry/attributes.rb +31 -0
- data/lib/appsignal/opentelemetry/dependencies.rb +35 -0
- data/lib/appsignal/opentelemetry/error_type.rb +37 -0
- data/lib/appsignal/opentelemetry/http_client_request.rb +83 -0
- data/lib/appsignal/opentelemetry/http_method.rb +59 -0
- data/lib/appsignal/opentelemetry/http_response.rb +30 -0
- data/lib/appsignal/opentelemetry/http_server_request.rb +45 -0
- data/lib/appsignal/opentelemetry/messaging.rb +82 -0
- data/lib/appsignal/opentelemetry/rendering.rb +29 -0
- data/lib/appsignal/opentelemetry/sql_db_system.rb +89 -0
- data/lib/appsignal/opentelemetry.rb +337 -0
- data/lib/appsignal/rack/abstract_middleware.rb +63 -4
- data/lib/appsignal/rack/body_wrapper.rb +18 -5
- data/lib/appsignal/rack/event_handler.rb +36 -3
- data/lib/appsignal/rack/grape_middleware.rb +8 -37
- data/lib/appsignal/rack/hanami_middleware.rb +2 -1
- data/lib/appsignal/rack/instrumentation_middleware.rb +1 -0
- data/lib/appsignal/rack/rails_instrumentation.rb +1 -0
- data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -0
- data/lib/appsignal/rack.rb +39 -11
- data/lib/appsignal/sample_data.rb +4 -0
- data/lib/appsignal/transaction/base_backend.rb +107 -0
- data/lib/appsignal/transaction/extension_backend.rb +203 -0
- data/lib/appsignal/transaction/opentelemetry_backend.rb +796 -0
- data/lib/appsignal/transaction.rb +537 -144
- data/lib/appsignal/utils/stdout_and_logger_message.rb +9 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +9 -0
- data/sig/appsignal.rbi +426 -37
- data/sig/appsignal.rbs +386 -27
- metadata +23 -1
|
@@ -36,51 +36,6 @@ 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
|
-
|
|
84
39
|
def periods
|
|
85
40
|
3.times do
|
|
86
41
|
print "."
|
data/lib/appsignal/config.rb
CHANGED
|
@@ -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
|
data/lib/appsignal/demo.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
)
|
|
@@ -5,6 +5,25 @@ module Appsignal
|
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module ActiveRecord
|
|
7
7
|
class SqlFormatter < Appsignal::EventFormatter
|
|
8
|
+
# A query is an outgoing call to a datastore.
|
|
9
|
+
def opentelemetry_kind
|
|
10
|
+
:client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The payload carries the connection the query ran on (Rails 6.0+),
|
|
14
|
+
# whose `adapter_name` is each adapter's own name for itself, such as
|
|
15
|
+
# `"PostgreSQL"` or `"Mysql2"`. A name the mapping does not recognise
|
|
16
|
+
# is left to the SQL sentinel, same as an adapter this gem has never
|
|
17
|
+
# heard of.
|
|
18
|
+
def opentelemetry_attributes(payload)
|
|
19
|
+
name = Appsignal::OpenTelemetry::SqlDbSystem.name_for_active_record(
|
|
20
|
+
payload[:connection]&.adapter_name
|
|
21
|
+
)
|
|
22
|
+
return unless name
|
|
23
|
+
|
|
24
|
+
{ "db.system.name" => name }
|
|
25
|
+
end
|
|
26
|
+
|
|
8
27
|
def format(payload)
|
|
9
28
|
[payload[:name], payload[:sql], SQL_BODY_FORMAT]
|
|
10
29
|
end
|
|
@@ -5,6 +5,21 @@ module Appsignal
|
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module ElasticSearch
|
|
7
7
|
class SearchFormatter < Appsignal::EventFormatter
|
|
8
|
+
# A search is an outgoing call to an Elasticsearch cluster.
|
|
9
|
+
def opentelemetry_kind
|
|
10
|
+
:client
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def opentelemetry_attributes(payload)
|
|
14
|
+
{
|
|
15
|
+
"db.system.name" => "elasticsearch",
|
|
16
|
+
# This notification is only emitted for a search, so that is the
|
|
17
|
+
# operation every one of these spans describes.
|
|
18
|
+
"db.operation.name" => "search",
|
|
19
|
+
"db.collection.name" => search_index(payload)
|
|
20
|
+
}.compact
|
|
21
|
+
end
|
|
22
|
+
|
|
8
23
|
def format(payload)
|
|
9
24
|
[
|
|
10
25
|
"#{payload[:name]}: #{payload[:klass]}",
|
|
@@ -12,6 +27,18 @@ module Appsignal
|
|
|
12
27
|
]
|
|
13
28
|
end
|
|
14
29
|
|
|
30
|
+
# The index a search ran against, which the notification carries in the
|
|
31
|
+
# search it describes. A search that names more than one index, or none
|
|
32
|
+
# at all, is left without this attribute rather than described with a
|
|
33
|
+
# value that is not an index name.
|
|
34
|
+
def search_index(payload)
|
|
35
|
+
search = payload[:search]
|
|
36
|
+
return unless search.respond_to?(:[])
|
|
37
|
+
|
|
38
|
+
index = search[:index]
|
|
39
|
+
index if index.is_a?(String)
|
|
40
|
+
end
|
|
41
|
+
|
|
15
42
|
def sanitized_search(search)
|
|
16
43
|
return unless search.is_a?(Hash)
|
|
17
44
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
class EventFormatter
|
|
5
|
+
# Registered for an event that a dedicated AppSignal integration already
|
|
6
|
+
# records with richer semantics. The generic instrumentation paths ask the
|
|
7
|
+
# registry whether to record an event, so registering this for an event
|
|
8
|
+
# name is how that integration claims it.
|
|
9
|
+
#
|
|
10
|
+
# @!visibility private
|
|
11
|
+
class RecordedElsewhere < Appsignal::EventFormatter
|
|
12
|
+
def record?
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -5,6 +5,18 @@ module Appsignal
|
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module Rom
|
|
7
7
|
class SqlFormatter < Appsignal::EventFormatter
|
|
8
|
+
# These events arrive over dry-monitor, which is a notification bus
|
|
9
|
+
# rather than a library that queries a database. ROM is what emits
|
|
10
|
+
# them, so that is what the scope names.
|
|
11
|
+
def opentelemetry_scope
|
|
12
|
+
["appsignal-ruby/rom", Appsignal::VERSION]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# A query is an outgoing call to a datastore.
|
|
16
|
+
def opentelemetry_kind
|
|
17
|
+
:client
|
|
18
|
+
end
|
|
19
|
+
|
|
8
20
|
# dry-monitor reports an event under an id rather than a name, so the
|
|
9
21
|
# first value here names the event. Naming it after ROM keeps every ROM
|
|
10
22
|
# query in one group.
|
|
@@ -17,6 +29,18 @@ module Appsignal
|
|
|
17
29
|
def format(payload)
|
|
18
30
|
["query.rom", payload[:query], SQL_BODY_FORMAT]
|
|
19
31
|
end
|
|
32
|
+
|
|
33
|
+
# The payload's `name` is Sequel's `database_type` symbol for the
|
|
34
|
+
# database ROM is talking to, so this uses Sequel's own lookup
|
|
35
|
+
# rather than a coincidentally similar one. A symbol the mapping does
|
|
36
|
+
# not recognise is left to the SQL sentinel, same as an engine this
|
|
37
|
+
# gem has never heard of.
|
|
38
|
+
def opentelemetry_attributes(payload)
|
|
39
|
+
name = Appsignal::OpenTelemetry::SqlDbSystem.name_for_sequel(payload[:name])
|
|
40
|
+
return unless name
|
|
41
|
+
|
|
42
|
+
{ "db.system.name" => name }
|
|
43
|
+
end
|
|
20
44
|
end
|
|
21
45
|
end
|
|
22
46
|
end
|
|
@@ -10,6 +10,11 @@ module Appsignal
|
|
|
10
10
|
# formatter the sequel-rails events are recorded without the SQL query
|
|
11
11
|
# that's being executed.
|
|
12
12
|
class SqlFormatter < Appsignal::EventFormatter
|
|
13
|
+
# A query is an outgoing call to a datastore.
|
|
14
|
+
def opentelemetry_kind
|
|
15
|
+
:client
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
def format(payload)
|
|
14
19
|
[payload[:name].to_s, payload[:sql], SQL_BODY_FORMAT]
|
|
15
20
|
end
|
|
@@ -7,11 +7,28 @@ 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)
|
|
15
|
+
# The body is the component's path made relative to the application's
|
|
16
|
+
# root, so a component rendered outside an application gets no title
|
|
17
|
+
# and no body.
|
|
18
|
+
return unless root_path
|
|
19
|
+
|
|
11
20
|
[payload[:name], payload[:identifier].sub(root_path, BLANK)]
|
|
12
21
|
end
|
|
13
22
|
|
|
23
|
+
# The application's root, which a component's path is made relative to.
|
|
24
|
+
#
|
|
25
|
+
# Whether there is an application is decided here, when the event is
|
|
26
|
+
# formatted, rather than when this file is loaded. AppSignal can be
|
|
27
|
+
# required before Rails is, and deciding it at load time would leave
|
|
28
|
+
# every component render in the application without a title.
|
|
14
29
|
def root_path
|
|
30
|
+
return unless defined?(Rails)
|
|
31
|
+
|
|
15
32
|
@root_path ||= "#{Rails.root}/"
|
|
16
33
|
end
|
|
17
34
|
end
|
|
@@ -19,13 +36,7 @@ module Appsignal
|
|
|
19
36
|
end
|
|
20
37
|
end
|
|
21
38
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
)
|
|
27
|
-
Appsignal::EventFormatter.register(
|
|
28
|
-
"!render.view_component",
|
|
29
|
-
Appsignal::EventFormatter::ViewComponent::RenderFormatter
|
|
30
|
-
)
|
|
31
|
-
end
|
|
39
|
+
Appsignal::EventFormatter.register(
|
|
40
|
+
"render.view_component",
|
|
41
|
+
Appsignal::EventFormatter::ViewComponent::RenderFormatter
|
|
42
|
+
)
|
|
@@ -102,8 +102,77 @@ module Appsignal
|
|
|
102
102
|
formatter&.format(payload)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
# The OpenTelemetry span kind for an event, which its formatter can
|
|
106
|
+
# declare. An event with no formatter, or whose formatter declares
|
|
107
|
+
# nothing, has no kind of its own and falls back to the default.
|
|
108
|
+
#
|
|
109
|
+
# A formatter written against the documented interface only implements
|
|
110
|
+
# `format`, so ask whether this one answers to the method at all rather
|
|
111
|
+
# than assuming every formatter does.
|
|
112
|
+
#
|
|
113
|
+
# @!visibility private
|
|
114
|
+
def opentelemetry_kind(name)
|
|
115
|
+
formatter = formatter_for(name)
|
|
116
|
+
return unless formatter.respond_to?(:opentelemetry_kind)
|
|
117
|
+
|
|
118
|
+
formatter.opentelemetry_kind
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# The OpenTelemetry attributes describing an event, which its formatter
|
|
122
|
+
# can build from the event's payload. An event with no formatter, or
|
|
123
|
+
# whose formatter describes nothing, gets no attributes of its own.
|
|
124
|
+
#
|
|
125
|
+
# @!visibility private
|
|
126
|
+
def opentelemetry_attributes(name, payload)
|
|
127
|
+
formatter = formatter_for(name)
|
|
128
|
+
return unless formatter.respond_to?(:opentelemetry_attributes)
|
|
129
|
+
|
|
130
|
+
formatter.opentelemetry_attributes(payload)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The OpenTelemetry instrumentation scope for an event, which its
|
|
134
|
+
# formatter can declare. It names the library the instrumentation is for,
|
|
135
|
+
# which is not always the library the event arrived through.
|
|
136
|
+
#
|
|
137
|
+
# An event with no formatter, or whose formatter declares nothing, is
|
|
138
|
+
# left to the scope the recording path derives for it.
|
|
139
|
+
#
|
|
140
|
+
# @!visibility private
|
|
141
|
+
def opentelemetry_scope(name)
|
|
142
|
+
formatter = formatter_for(name)
|
|
143
|
+
return unless formatter.respond_to?(:opentelemetry_scope)
|
|
144
|
+
|
|
145
|
+
formatter.opentelemetry_scope
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Whether the generic instrumentation paths should record an event, which
|
|
149
|
+
# its formatter can answer. An event a dedicated integration already
|
|
150
|
+
# records says no, so that it is not recorded a second time. An event
|
|
151
|
+
# with no formatter, or whose formatter says nothing, is recorded.
|
|
152
|
+
#
|
|
153
|
+
# @!visibility private
|
|
154
|
+
def record?(name)
|
|
155
|
+
formatter = formatter_for(name)
|
|
156
|
+
return true unless formatter.respond_to?(:record?)
|
|
157
|
+
|
|
158
|
+
formatter.record?
|
|
159
|
+
end
|
|
160
|
+
|
|
105
161
|
private
|
|
106
162
|
|
|
163
|
+
# The formatter registered for an event name.
|
|
164
|
+
#
|
|
165
|
+
# A formatter is registered under whatever key was given to `register`,
|
|
166
|
+
# which is a String for every formatter in this gem. An event can be
|
|
167
|
+
# instrumented under a Symbol name, so fall back to the String form of it.
|
|
168
|
+
#
|
|
169
|
+
# `format` does not do this, on purpose. It has always looked a name up
|
|
170
|
+
# exactly as given, so making it match a Symbol name would start giving a
|
|
171
|
+
# title to events that have never had one.
|
|
172
|
+
def formatter_for(name)
|
|
173
|
+
formatters[name] || formatters[name.to_s]
|
|
174
|
+
end
|
|
175
|
+
|
|
107
176
|
def initialize_formatter(name, formatter)
|
|
108
177
|
format_method = formatter.instance_method(:format)
|
|
109
178
|
if !format_method || format_method.arity != 1
|
|
@@ -123,6 +192,15 @@ module Appsignal
|
|
|
123
192
|
end
|
|
124
193
|
end
|
|
125
194
|
|
|
195
|
+
# The title and the body to show for an event, as an array. A formatter
|
|
196
|
+
# that is registered to describe an event in some other way, rather than to
|
|
197
|
+
# name it, does not have to implement this.
|
|
198
|
+
#
|
|
199
|
+
# @!visibility private
|
|
200
|
+
def format(_payload)
|
|
201
|
+
nil
|
|
202
|
+
end
|
|
203
|
+
|
|
126
204
|
# @return [Integer]
|
|
127
205
|
# @api public
|
|
128
206
|
DEFAULT = 0
|