appsignal 5.0.0.rc.1-java → 5.0.0.rc.2-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +140 -0
- data/README.md +2 -1
- data/Rakefile +110 -6
- data/appsignal.gemspec +7 -0
- data/build_matrix.yml +7 -2
- data/ext/agent.rb +27 -27
- data/lib/appsignal/cli/demo.rb +5 -0
- data/lib/appsignal/cli/diagnose.rb +8 -48
- data/lib/appsignal/cli/helpers.rb +45 -0
- data/lib/appsignal/config.rb +262 -37
- data/lib/appsignal/demo.rb +11 -9
- data/lib/appsignal/helpers/instrumentation.rb +88 -0
- data/lib/appsignal/hooks/action_cable.rb +8 -2
- data/lib/appsignal/hooks/active_job.rb +189 -1
- data/lib/appsignal/integrations/delayed_job_plugin.rb +172 -32
- data/lib/appsignal/integrations/que.rb +41 -9
- data/lib/appsignal/integrations/railtie.rb +4 -2
- data/lib/appsignal/integrations/resque.rb +26 -3
- data/lib/appsignal/integrations/shoryuken.rb +21 -2
- data/lib/appsignal/integrations/sidekiq.rb +23 -2
- data/lib/appsignal/integrations/webmachine.rb +13 -5
- data/lib/appsignal/opentelemetry/http_server_request.rb +35 -1
- data/lib/appsignal/opentelemetry/proxied_exporter.rb +83 -0
- data/lib/appsignal/opentelemetry.rb +182 -24
- data/lib/appsignal/rack/abstract_middleware.rb +4 -1
- data/lib/appsignal/rack/event_handler.rb +9 -2
- data/lib/appsignal/rack/grape_middleware.rb +37 -7
- data/lib/appsignal/rack.rb +29 -1
- data/lib/appsignal/transaction/base_backend.rb +21 -0
- data/lib/appsignal/transaction/extension_backend.rb +26 -0
- data/lib/appsignal/transaction/opentelemetry_backend.rb +90 -39
- data/lib/appsignal/transaction.rb +189 -32
- data/lib/appsignal/utils/request_headers.rb +78 -0
- data/lib/appsignal/utils.rb +1 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +1 -0
- data/sig/appsignal.rbi +205 -1
- data/sig/appsignal.rbs +196 -0
- metadata +3 -1
|
@@ -16,26 +16,56 @@ module Appsignal
|
|
|
16
16
|
|
|
17
17
|
def add_transaction_metadata_after(transaction, request)
|
|
18
18
|
endpoint = request.env["api.endpoint"]
|
|
19
|
-
|
|
19
|
+
request_method, klass, path = endpoint && endpoint_action(endpoint)
|
|
20
|
+
unless path
|
|
20
21
|
super
|
|
21
22
|
return
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
transaction.set_action_if_nil("#{request_method}::#{klass}##{path}")
|
|
26
|
+
|
|
27
|
+
super
|
|
28
|
+
|
|
29
|
+
transaction.set_metadata("path", path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the HTTP method, API class and path that make up the action
|
|
33
|
+
# name, or nil when the endpoint does not describe a route.
|
|
34
|
+
def endpoint_action(endpoint)
|
|
24
35
|
options = endpoint.options
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
return unless options
|
|
37
|
+
|
|
38
|
+
if options.key?(:path)
|
|
39
|
+
# Only Grape 3 and older populate `options[:path]`. Their route is
|
|
40
|
+
# readable too, but its path is the full route template, so reading
|
|
41
|
+
# it would rename the actions these applications already report.
|
|
42
|
+
endpoint_action_from_options(endpoint, options)
|
|
43
|
+
else
|
|
44
|
+
# Grape 4 keeps these three in a value object behind a protected
|
|
45
|
+
# reader, leaving the route as the only public source.
|
|
46
|
+
endpoint_action_from_route(endpoint)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def endpoint_action_from_options(endpoint, options)
|
|
27
51
|
namespace = endpoint.namespace
|
|
28
52
|
namespace = "" if namespace == "/"
|
|
29
53
|
|
|
30
54
|
path = options[:path].first.to_s
|
|
31
55
|
path = "/#{path}" if path[0] != "/"
|
|
32
|
-
path = "#{namespace}#{path}"
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
[
|
|
58
|
+
options[:method].first.to_s.upcase,
|
|
59
|
+
options[:for],
|
|
60
|
+
"#{namespace}#{path}"
|
|
61
|
+
]
|
|
62
|
+
end
|
|
35
63
|
|
|
36
|
-
|
|
64
|
+
def endpoint_action_from_route(endpoint)
|
|
65
|
+
route = endpoint.routes.first
|
|
66
|
+
return unless route
|
|
37
67
|
|
|
38
|
-
|
|
68
|
+
[route.request_method.to_s.upcase, endpoint.api, route.origin]
|
|
39
69
|
end
|
|
40
70
|
end
|
|
41
71
|
end
|
data/lib/appsignal/rack.rb
CHANGED
|
@@ -47,6 +47,32 @@ module Appsignal
|
|
|
47
47
|
nil
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# Fetch a value from the request environment, for the values that are only
|
|
51
|
+
# available there.
|
|
52
|
+
#
|
|
53
|
+
# The request class is configurable, so it may have no environment at all,
|
|
54
|
+
# which is not worth logging about. Reading from one can still raise, and
|
|
55
|
+
# that is logged. Either way the caller gets nil and skips whatever it
|
|
56
|
+
# needed the value for.
|
|
57
|
+
#
|
|
58
|
+
# @param request [Rack::Request] Request object.
|
|
59
|
+
# @param key [String] Name of the environment key to read.
|
|
60
|
+
# @return [Object, NilClass]
|
|
61
|
+
def self.request_env_value_from(request, key)
|
|
62
|
+
return unless request.respond_to?(:env)
|
|
63
|
+
|
|
64
|
+
env = request.env
|
|
65
|
+
return unless env
|
|
66
|
+
|
|
67
|
+
env[key]
|
|
68
|
+
rescue => error
|
|
69
|
+
Appsignal.internal_logger.error(
|
|
70
|
+
"Exception while fetching the HTTP request environment #{key}: " \
|
|
71
|
+
"#{error.class}: #{error}"
|
|
72
|
+
)
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
50
76
|
# Fetch the queue start time from the request environment.
|
|
51
77
|
#
|
|
52
78
|
# @since 3.11.0
|
|
@@ -99,9 +125,11 @@ module Appsignal
|
|
|
99
125
|
|
|
100
126
|
transaction.add_request_payload { params_for(request) }
|
|
101
127
|
transaction.add_session_data { session_data_for(request) }
|
|
102
|
-
|
|
128
|
+
headers, environment = Appsignal::Utils::RequestHeaders.split_lazily do
|
|
103
129
|
request.env if request.respond_to?(:env)
|
|
104
130
|
end
|
|
131
|
+
transaction.add_request_headers(&headers)
|
|
132
|
+
transaction.add_request_environment(&environment)
|
|
105
133
|
|
|
106
134
|
queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
|
|
107
135
|
transaction.set_queue_start(queue_start) if queue_start
|
|
@@ -59,6 +59,27 @@ module Appsignal
|
|
|
59
59
|
raise NotImplementedError
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
# Maps each params bucket to the configuration options that decide what
|
|
63
|
+
# it reports: `:filter`, naming the keys to filter out of it, and
|
|
64
|
+
# `:send`, deciding whether to report it at all.
|
|
65
|
+
def params_options
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Maps each logical header channel (`:request_headers`,
|
|
70
|
+
# `:request_environment`) to the storage bucket it lands in and the
|
|
71
|
+
# transform to apply to each key and value added on it, as
|
|
72
|
+
# `[bucket, transform]`. A `nil` transform leaves the value alone.
|
|
73
|
+
def headers_mapping
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Maps each header bucket to the configuration option that lists the
|
|
78
|
+
# keys to keep in it. An option holding `nil` keeps every key.
|
|
79
|
+
def headers_allowlist
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
end
|
|
82
|
+
|
|
62
83
|
# Sample data (params, session, tags, ...), breadcrumbs and errors.
|
|
63
84
|
def set_sample_data(_key, _data)
|
|
64
85
|
raise NotImplementedError
|
|
@@ -96,6 +96,32 @@ module Appsignal
|
|
|
96
96
|
PARAMS_MAPPING
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
PARAMS_OPTIONS = {
|
|
100
|
+
:params => { :filter => :filter_parameters, :send => :send_params }
|
|
101
|
+
}.freeze
|
|
102
|
+
|
|
103
|
+
def params_options
|
|
104
|
+
PARAMS_OPTIONS
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
HEADERS_MAPPING = {
|
|
108
|
+
:request_headers => [
|
|
109
|
+
:environment,
|
|
110
|
+
lambda { |name, value| [Appsignal::Utils::RequestHeaders.rack_name(name), value] }
|
|
111
|
+
],
|
|
112
|
+
:request_environment => [:environment, nil]
|
|
113
|
+
}.freeze
|
|
114
|
+
|
|
115
|
+
def headers_mapping
|
|
116
|
+
HEADERS_MAPPING
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
HEADERS_ALLOWLIST = { :environment => [:request_headers, false] }.freeze
|
|
120
|
+
|
|
121
|
+
def headers_allowlist
|
|
122
|
+
HEADERS_ALLOWLIST
|
|
123
|
+
end
|
|
124
|
+
|
|
99
125
|
# `data` is a raw Ruby Hash/Array; the C extension wants a `Data` object,
|
|
100
126
|
# so serialize it here (mirrors how `set_error` serializes its backtrace).
|
|
101
127
|
def set_sample_data(key, data)
|
|
@@ -14,6 +14,12 @@ module Appsignal
|
|
|
14
14
|
class OpenTelemetryBackend < BaseBackend
|
|
15
15
|
TRACER_NAME = "appsignal-ruby"
|
|
16
16
|
|
|
17
|
+
# The action name given to a transaction that recorded an error without
|
|
18
|
+
# ever setting one. Bracketed so it reads as a marker rather than as a
|
|
19
|
+
# class the application defines, and so it sorts apart from real action
|
|
20
|
+
# names.
|
|
21
|
+
UNNAMED_ACTION = "[unnamed action]"
|
|
22
|
+
|
|
17
23
|
# Guards the process-wide warn-once state, which transactions touch
|
|
18
24
|
# concurrently on threaded servers. A constant so it is created once at
|
|
19
25
|
# load time rather than lazily (which would race).
|
|
@@ -125,6 +131,7 @@ module Appsignal
|
|
|
125
131
|
@start_time = Time.now
|
|
126
132
|
@action_set = false
|
|
127
133
|
@action = nil
|
|
134
|
+
@error_set = false
|
|
128
135
|
@allocation_start = current_allocation_count
|
|
129
136
|
@root_child_allocation_count = 0
|
|
130
137
|
|
|
@@ -277,6 +284,43 @@ module Appsignal
|
|
|
277
284
|
PARAMS_MAPPING
|
|
278
285
|
end
|
|
279
286
|
|
|
287
|
+
PARAMS_OPTIONS = {
|
|
288
|
+
:request_payload => {
|
|
289
|
+
:filter => :filter_request_payload,
|
|
290
|
+
:send => :send_request_payload
|
|
291
|
+
},
|
|
292
|
+
:function_parameters => {
|
|
293
|
+
:filter => :filter_function_parameters,
|
|
294
|
+
:send => :send_function_parameters
|
|
295
|
+
},
|
|
296
|
+
:query_parameters => {
|
|
297
|
+
:filter => :filter_request_query_parameters,
|
|
298
|
+
:send => :send_request_query_parameters
|
|
299
|
+
}
|
|
300
|
+
}.freeze
|
|
301
|
+
|
|
302
|
+
def params_options
|
|
303
|
+
PARAMS_OPTIONS
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
HEADERS_MAPPING = {
|
|
307
|
+
:request_headers => [:request_headers, nil],
|
|
308
|
+
:request_environment => [:environment, nil]
|
|
309
|
+
}.freeze
|
|
310
|
+
|
|
311
|
+
def headers_mapping
|
|
312
|
+
HEADERS_MAPPING
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
HEADERS_ALLOWLIST = {
|
|
316
|
+
:request_headers => [:keep_request_headers, true],
|
|
317
|
+
:environment => [:keep_request_environment, false]
|
|
318
|
+
}.freeze
|
|
319
|
+
|
|
320
|
+
def headers_allowlist
|
|
321
|
+
HEADERS_ALLOWLIST
|
|
322
|
+
end
|
|
323
|
+
|
|
280
324
|
# Routes each sample-data category to the attribute the collector reads.
|
|
281
325
|
# The params arrive on one of three channels: `request_payload` (web),
|
|
282
326
|
# `function_parameters` (jobs) and `query_parameters` (a request's query
|
|
@@ -298,8 +342,10 @@ module Appsignal
|
|
|
298
342
|
@span.set_attribute("appsignal.request.session_data", JSON.generate(data))
|
|
299
343
|
when "custom_data"
|
|
300
344
|
@span.set_attribute("appsignal.custom_data", JSON.generate(data))
|
|
301
|
-
when "
|
|
345
|
+
when "request_headers"
|
|
302
346
|
write_request_headers(data)
|
|
347
|
+
when "environment"
|
|
348
|
+
write_request_environment(data)
|
|
303
349
|
when "tags"
|
|
304
350
|
write_tags(data)
|
|
305
351
|
else
|
|
@@ -317,6 +363,7 @@ module Appsignal
|
|
|
317
363
|
# become their own incident. Each cause carries only the part of its
|
|
318
364
|
# backtrace that is not shared (see `trim_shared_tail`).
|
|
319
365
|
def set_error(class_name, message, backtrace, causes, _root_cause_missing)
|
|
366
|
+
@error_set = true
|
|
320
367
|
span = current_span
|
|
321
368
|
error_lines = Array(backtrace)
|
|
322
369
|
|
|
@@ -392,12 +439,12 @@ module Appsignal
|
|
|
392
439
|
# `teardown` sets `@completed`, so this guard also makes the body
|
|
393
440
|
# idempotent across a double `complete`, and skips it on `discard`.
|
|
394
441
|
unless @completed
|
|
395
|
-
#
|
|
396
|
-
# action
|
|
397
|
-
#
|
|
398
|
-
|
|
442
|
+
# Settle the action first, because the allocation count metric below
|
|
443
|
+
# tags by `@action`, and this is what fills it in for a transaction
|
|
444
|
+
# the application never named.
|
|
445
|
+
resolve_missing_action
|
|
446
|
+
emit_queue_duration_metric if should_report?
|
|
399
447
|
report_allocation_count
|
|
400
|
-
ignore_subtrace_without_action
|
|
401
448
|
end
|
|
402
449
|
teardown
|
|
403
450
|
end
|
|
@@ -450,15 +497,38 @@ module Appsignal
|
|
|
450
497
|
@span&.finish
|
|
451
498
|
end
|
|
452
499
|
|
|
453
|
-
#
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
def
|
|
500
|
+
# Whether this transaction is reported at all. One that set an action is
|
|
501
|
+
# reported so it can be grouped under it. One that recorded an error is
|
|
502
|
+
# reported even without an action, because the failure is worth reporting
|
|
503
|
+
# even when we cannot say what failed. The agent draws the line in the
|
|
504
|
+
# same place: it drops a transaction only when it has neither.
|
|
505
|
+
def should_report?
|
|
506
|
+
@action_set || @error_set
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Decides what to do about a transaction that never set an action, which
|
|
510
|
+
# has nothing to group by. Collector mode cannot represent "no action", so
|
|
511
|
+
# it needs a name for the ones it reports and a way to drop the rest.
|
|
512
|
+
#
|
|
513
|
+
# A reported one is named after the marker. Leaving the action unset would
|
|
514
|
+
# not report nothing: the collector fills an empty action in from the span
|
|
515
|
+
# name, which here is the placeholder this backend opened the span with,
|
|
516
|
+
# and that reads as though it were the application's own action.
|
|
517
|
+
#
|
|
518
|
+
# The rest are noise, such as serving assets in development, so their
|
|
519
|
+
# subtrace is flagged for the collector to drop, the same way `discard`
|
|
520
|
+
# does.
|
|
521
|
+
#
|
|
522
|
+
# Both branches have to run before `teardown` finishes the span, because
|
|
523
|
+
# attributes set on an ended span are dropped.
|
|
524
|
+
def resolve_missing_action
|
|
459
525
|
return if @action_set
|
|
460
526
|
|
|
461
|
-
|
|
527
|
+
if should_report?
|
|
528
|
+
set_action(UNNAMED_ACTION)
|
|
529
|
+
else
|
|
530
|
+
@span&.set_attribute("appsignal.ignore_subtrace", true)
|
|
531
|
+
end
|
|
462
532
|
end
|
|
463
533
|
|
|
464
534
|
# Emits the queue duration as a distribution metric in both the
|
|
@@ -505,7 +575,7 @@ module Appsignal
|
|
|
505
575
|
count - @root_child_allocation_count
|
|
506
576
|
)
|
|
507
577
|
|
|
508
|
-
return unless
|
|
578
|
+
return unless should_report? && count.positive?
|
|
509
579
|
|
|
510
580
|
namespace = display_namespace(@namespace)
|
|
511
581
|
Appsignal::Metrics::OpenTelemetryBackend.increment_counter(
|
|
@@ -633,7 +703,7 @@ module Appsignal
|
|
|
633
703
|
# to a plain root span, since there is nothing to parent or link to.
|
|
634
704
|
def start_transaction_span(namespace, kind, relationship, opentelemetry_context)
|
|
635
705
|
name = placeholder_span_name(namespace)
|
|
636
|
-
remote = remote_span_context(opentelemetry_context)
|
|
706
|
+
remote = Appsignal::OpenTelemetry.remote_span_context(opentelemetry_context)
|
|
637
707
|
tracer = tracer_for(@scope)
|
|
638
708
|
|
|
639
709
|
# With no incoming context (or an invalid remote span) there is nothing
|
|
@@ -682,38 +752,19 @@ module Appsignal
|
|
|
682
752
|
frames.find { |frame| !frame.include?("/lib/appsignal/") } || frames.first
|
|
683
753
|
end
|
|
684
754
|
|
|
685
|
-
# The remote parent's SpanContext from an incoming OTel context, or nil
|
|
686
|
-
# when there is no context or the remote span is invalid -- in which case
|
|
687
|
-
# callers fall back to a plain root span.
|
|
688
|
-
def remote_span_context(opentelemetry_context)
|
|
689
|
-
return unless opentelemetry_context
|
|
690
|
-
|
|
691
|
-
context = ::OpenTelemetry::Trace.current_span(opentelemetry_context).context
|
|
692
|
-
context if context.valid?
|
|
693
|
-
end
|
|
694
|
-
|
|
695
755
|
def display_namespace(namespace)
|
|
696
756
|
DISPLAY_NAMESPACE.fetch(namespace, namespace)
|
|
697
757
|
end
|
|
698
758
|
|
|
699
|
-
# The transaction's "environment" sample data is a Rack/CGI env allowlist
|
|
700
|
-
# mixing true HTTP headers (HTTP_*, plus CONTENT_LENGTH/CONTENT_TYPE) with
|
|
701
|
-
# non-header CGI vars (REQUEST_METHOD, REQUEST_PATH, PATH_INFO, SERVER_*).
|
|
702
|
-
# Only the true headers map to the OTel `http.request.header.*` convention
|
|
703
|
-
# the collector and trace UI read, so emit those (normalized to lowercase,
|
|
704
|
-
# dashed header names) and drop everything else.
|
|
705
759
|
def write_request_headers(headers)
|
|
706
|
-
headers.each do |
|
|
707
|
-
name
|
|
708
|
-
@span.set_attribute("http.request.header.#{name}", value.to_s) if name
|
|
760
|
+
headers.each do |name, value|
|
|
761
|
+
@span.set_attribute("http.request.header.#{name}", value.to_s)
|
|
709
762
|
end
|
|
710
763
|
end
|
|
711
764
|
|
|
712
|
-
def
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
elsif env_key.start_with?("CONTENT_")
|
|
716
|
-
env_key.downcase.tr("_", "-")
|
|
765
|
+
def write_request_environment(environment)
|
|
766
|
+
environment.each do |key, value|
|
|
767
|
+
@span.set_attribute("appsignal.environment.#{key}", value.to_s)
|
|
717
768
|
end
|
|
718
769
|
end
|
|
719
770
|
|