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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/Rakefile +87 -0
- data/appsignal.gemspec +8 -0
- data/build_matrix.yml +14 -1
- data/ext/appsignal_extension.c +14 -0
- data/lib/appsignal/backends.rb +55 -0
- 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 -39
- 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 -14
- data/lib/appsignal/integrations/data_mapper.rb +14 -2
- data/lib/appsignal/integrations/delayed_job_plugin.rb +45 -5
- 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 +1 -0
- 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
data/lib/appsignal/rack.rb
CHANGED
|
@@ -7,9 +7,46 @@ module Appsignal
|
|
|
7
7
|
APPSIGNAL_EVENT_HANDLER_ID = "appsignal.event_handler_id"
|
|
8
8
|
APPSIGNAL_EVENT_HANDLER_HAS_ERROR = "appsignal.event_handler.error"
|
|
9
9
|
APPSIGNAL_RESPONSE_INSTRUMENTED = "appsignal.response_instrumentation_active"
|
|
10
|
+
APPSIGNAL_RESPONSE_STATUS = "appsignal.response_status"
|
|
10
11
|
RACK_AFTER_REPLY = "rack.after_reply"
|
|
11
12
|
|
|
12
13
|
class Utils
|
|
14
|
+
# Fetch the HTTP request method from the request.
|
|
15
|
+
#
|
|
16
|
+
# The request class is configurable, so reading the method can raise.
|
|
17
|
+
# Log and return nil in that case, leaving it to the caller to skip
|
|
18
|
+
# whatever it needed the method for.
|
|
19
|
+
#
|
|
20
|
+
# @param request [Rack::Request] Request object.
|
|
21
|
+
# @return [String, NilClass]
|
|
22
|
+
def self.request_method_from(request)
|
|
23
|
+
request.request_method
|
|
24
|
+
rescue => error
|
|
25
|
+
Appsignal.internal_logger.error(
|
|
26
|
+
"Exception while fetching the HTTP request method: #{error.class}: #{error}"
|
|
27
|
+
)
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Fetch a value that describes the request, named after the method that
|
|
32
|
+
# reads it.
|
|
33
|
+
#
|
|
34
|
+
# The request class is configurable, so reading from the request can
|
|
35
|
+
# raise. Log and return nil in that case, leaving it to the caller to skip
|
|
36
|
+
# whatever it needed the value for.
|
|
37
|
+
#
|
|
38
|
+
# @param request [Rack::Request] Request object.
|
|
39
|
+
# @param name [Symbol] Name of the method that reads the value.
|
|
40
|
+
# @return [Object, NilClass]
|
|
41
|
+
def self.request_value_from(request, name)
|
|
42
|
+
request.public_send(name)
|
|
43
|
+
rescue => error
|
|
44
|
+
Appsignal.internal_logger.error(
|
|
45
|
+
"Exception while fetching the HTTP request #{name}: #{error.class}: #{error}"
|
|
46
|
+
)
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
13
50
|
# Fetch the queue start time from the request environment.
|
|
14
51
|
#
|
|
15
52
|
# @since 3.11.0
|
|
@@ -53,14 +90,14 @@ module Appsignal
|
|
|
53
90
|
# TODO: Remove in next major/minor version
|
|
54
91
|
transaction.set_metadata("path", request_path)
|
|
55
92
|
|
|
56
|
-
request_method =
|
|
93
|
+
request_method = Appsignal::Rack::Utils.request_method_from(request)
|
|
57
94
|
if request_method
|
|
58
95
|
transaction.set_metadata("request_method", request_method)
|
|
59
96
|
# TODO: Remove in next major/minor version
|
|
60
97
|
transaction.set_metadata("method", request_method)
|
|
61
98
|
end
|
|
62
99
|
|
|
63
|
-
transaction.
|
|
100
|
+
transaction.add_request_payload { params_for(request) }
|
|
64
101
|
transaction.add_session_data { session_data_for(request) }
|
|
65
102
|
transaction.add_headers do
|
|
66
103
|
request.env if request.respond_to?(:env)
|
|
@@ -84,15 +121,6 @@ module Appsignal
|
|
|
84
121
|
nil
|
|
85
122
|
end
|
|
86
123
|
|
|
87
|
-
def request_method_for(request)
|
|
88
|
-
request.request_method
|
|
89
|
-
rescue => error
|
|
90
|
-
Appsignal.internal_logger.error(
|
|
91
|
-
"Exception while fetching the HTTP request method: #{error.class}: #{error}"
|
|
92
|
-
)
|
|
93
|
-
nil
|
|
94
|
-
end
|
|
95
|
-
|
|
96
124
|
def session_data_for(request)
|
|
97
125
|
return unless request.respond_to?(:session)
|
|
98
126
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
class Transaction
|
|
5
|
+
# @!visibility private
|
|
6
|
+
#
|
|
7
|
+
# The interface every transaction backend implements. `Appsignal::Backends
|
|
8
|
+
# .transaction` picks a concrete backend per mode -- ExtensionBackend in
|
|
9
|
+
# agent mode, OpenTelemetryBackend in collector mode. This base documents the
|
|
10
|
+
# contract; a backend that leaves a method unimplemented raises here.
|
|
11
|
+
class BaseBackend
|
|
12
|
+
# Instrumented events.
|
|
13
|
+
def start_event(opentelemetry_kind: nil, opentelemetry_scope: nil)
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def finish_event(_name, _title, _body, _body_format)
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def record_event( # rubocop:disable Metrics/ParameterLists
|
|
22
|
+
_name, _title, _body, _body_format, _duration,
|
|
23
|
+
opentelemetry_kind: nil, opentelemetry_scope: nil
|
|
24
|
+
)
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Transaction metadata.
|
|
29
|
+
def set_action(_action)
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def set_namespace(_namespace)
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def set_metadata(_key, _value)
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def set_queue_start(_start)
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# OpenTelemetry span attributes, set on whichever span the backend is
|
|
46
|
+
# currently recording. Only meaningful in collector mode; agent mode has
|
|
47
|
+
# no span to hang them on.
|
|
48
|
+
def set_attributes(_attributes)
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Maps each logical params channel (`:params`, `:request_payload`,
|
|
53
|
+
# `:function_parameters`) to the storage bucket it lands in. Channels that
|
|
54
|
+
# share a bucket merge into one `SampleData` object on the transaction;
|
|
55
|
+
# distinct buckets stay separate. The bucket name is also the sample-data
|
|
56
|
+
# key `set_sample_data` receives for that bucket, so it must be a key this
|
|
57
|
+
# backend's `set_sample_data` knows how to store.
|
|
58
|
+
def params_mapping
|
|
59
|
+
raise NotImplementedError
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Sample data (params, session, tags, ...), breadcrumbs and errors.
|
|
63
|
+
def set_sample_data(_key, _data)
|
|
64
|
+
raise NotImplementedError
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def add_breadcrumb(_breadcrumb)
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_error(_class_name, _message, _backtrace, _causes, _root_cause_missing)
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Whether the backend can hold more than one error on a single
|
|
76
|
+
# transaction. When it can't (agent mode), the Transaction reports the
|
|
77
|
+
# extra errors as duplicate transactions instead.
|
|
78
|
+
def supports_multiple_errors?
|
|
79
|
+
raise NotImplementedError
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Lifecycle.
|
|
83
|
+
def finish
|
|
84
|
+
raise NotImplementedError
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def complete
|
|
88
|
+
raise NotImplementedError
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def discard
|
|
92
|
+
raise NotImplementedError
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Only used when `supports_multiple_errors?` is false (agent mode).
|
|
96
|
+
# Backends that support multiple errors never duplicate and leave this
|
|
97
|
+
# unimplemented.
|
|
98
|
+
def duplicate(_new_transaction_id)
|
|
99
|
+
raise NotImplementedError
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def to_json # rubocop:disable Lint/ToJSON
|
|
103
|
+
raise NotImplementedError
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
class Transaction
|
|
5
|
+
# @!visibility private
|
|
6
|
+
#
|
|
7
|
+
# The transaction backend used in agent mode. Wraps a per-transaction
|
|
8
|
+
# handle on the C extension (`Appsignal::Extension::Transaction`) and
|
|
9
|
+
# forwards every call to it.
|
|
10
|
+
#
|
|
11
|
+
# In agent mode `Appsignal::Backends.transaction` returns this class;
|
|
12
|
+
# `Appsignal::Transaction#initialize` instantiates one and stores it in
|
|
13
|
+
# `@backend`.
|
|
14
|
+
class ExtensionBackend < BaseBackend
|
|
15
|
+
# rubocop:disable Layout/LineLength
|
|
16
|
+
BACKTRACE_REGEX =
|
|
17
|
+
%r{(?<gem>[\w-]+ \(.+\) )?(?<path>:?/?\w+?.+?):(?<line>:?\d+)(?::in `(?<method>.+)')?$}.freeze
|
|
18
|
+
# rubocop:enable Layout/LineLength
|
|
19
|
+
|
|
20
|
+
# @!visibility private
|
|
21
|
+
attr_writer :breadcrumbs
|
|
22
|
+
|
|
23
|
+
# The `opentelemetry_*` keyword arguments (context, scope, kind and
|
|
24
|
+
# relationship) shape the OpenTelemetry span in collector mode. Agent mode
|
|
25
|
+
# has no notion of them, so they are accepted and ignored. They are listed
|
|
26
|
+
# explicitly, rather than swallowed with `**`, so an unexpected keyword
|
|
27
|
+
# still raises, matching the OpenTelemetry backend.
|
|
28
|
+
# rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument
|
|
29
|
+
def initialize(
|
|
30
|
+
transaction_id,
|
|
31
|
+
namespace,
|
|
32
|
+
handle: nil,
|
|
33
|
+
opentelemetry_context: nil,
|
|
34
|
+
opentelemetry_scope: nil,
|
|
35
|
+
opentelemetry_kind: nil,
|
|
36
|
+
opentelemetry_relationship: nil
|
|
37
|
+
)
|
|
38
|
+
super()
|
|
39
|
+
@handle = handle ||
|
|
40
|
+
Appsignal::Extension.start_transaction(transaction_id, namespace, 0) ||
|
|
41
|
+
Appsignal::Extension::MockTransaction.new
|
|
42
|
+
@breadcrumbs = []
|
|
43
|
+
end
|
|
44
|
+
# rubocop:enable Metrics/ParameterLists, Lint/UnusedMethodArgument
|
|
45
|
+
|
|
46
|
+
# Agent mode has no span kind or instrumentation scope;
|
|
47
|
+
# `opentelemetry_kind` and `opentelemetry_scope` are ignored here.
|
|
48
|
+
def start_event(opentelemetry_kind: nil, opentelemetry_scope: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
49
|
+
@handle.start_event(0)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def finish_event(name, title, body, body_format)
|
|
53
|
+
@handle.finish_event(name, title, body, body_format, 0)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Agent mode has no span kind or instrumentation scope;
|
|
57
|
+
# `opentelemetry_kind` and `opentelemetry_scope` are ignored here.
|
|
58
|
+
def record_event(name, title, body, body_format, duration, opentelemetry_kind: nil, opentelemetry_scope: nil, opentelemetry_attributes: nil) # rubocop:disable Lint/UnusedMethodArgument, Metrics/ParameterLists, Layout/LineLength
|
|
59
|
+
@handle.record_event(name, title, body, body_format, duration, 0)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def set_action(action)
|
|
63
|
+
@handle.set_action(action)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set_namespace(namespace)
|
|
67
|
+
@handle.set_namespace(namespace)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def set_queue_start(start)
|
|
71
|
+
@handle.set_queue_start(start)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def set_metadata(key, value)
|
|
75
|
+
@handle.set_metadata(key, value)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Agent mode has no OpenTelemetry spans, so there is nothing to set the
|
|
79
|
+
# attributes on. A transaction event has no attribute equivalent in the
|
|
80
|
+
# agent protocol either, so they are dropped rather than stored somewhere
|
|
81
|
+
# else.
|
|
82
|
+
def set_attributes(_attributes)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The agent has a single params slot, so every params channel maps to one
|
|
86
|
+
# `:params` bucket. The transaction merges the channels into it, and only
|
|
87
|
+
# the `:params` key ever reaches `set_sample_data`.
|
|
88
|
+
PARAMS_MAPPING = {
|
|
89
|
+
:params => :params,
|
|
90
|
+
:request_payload => :params,
|
|
91
|
+
:function_parameters => :params,
|
|
92
|
+
:query_parameters => :params
|
|
93
|
+
}.freeze
|
|
94
|
+
|
|
95
|
+
def params_mapping
|
|
96
|
+
PARAMS_MAPPING
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# `data` is a raw Ruby Hash/Array; the C extension wants a `Data` object,
|
|
100
|
+
# so serialize it here (mirrors how `set_error` serializes its backtrace).
|
|
101
|
+
def set_sample_data(key, data)
|
|
102
|
+
@handle.set_sample_data(key, Appsignal::Utils::Data.generate(data))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Buffer breadcrumbs, keeping the last `BREADCRUMB_LIMIT`, and flush them as
|
|
106
|
+
# sample data on completion.
|
|
107
|
+
def add_breadcrumb(breadcrumb)
|
|
108
|
+
@breadcrumbs.push(breadcrumb)
|
|
109
|
+
@breadcrumbs = @breadcrumbs.last(Appsignal::Transaction::BREADCRUMB_LIMIT)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Serializes the backtrace to a C-extension `Data` object and records the
|
|
113
|
+
# error, then flushes the causes as `error_causes` sample data in the
|
|
114
|
+
# agent's first-line shape.
|
|
115
|
+
def set_error(class_name, message, backtrace, causes, root_cause_missing)
|
|
116
|
+
backtrace_data =
|
|
117
|
+
if backtrace
|
|
118
|
+
Appsignal::Utils::Data.generate(backtrace)
|
|
119
|
+
else
|
|
120
|
+
Appsignal::Extension.data_array_new
|
|
121
|
+
end
|
|
122
|
+
@handle.set_error(class_name, message, backtrace_data)
|
|
123
|
+
|
|
124
|
+
set_sample_data("error_causes", error_causes_sample_data(causes, root_cause_missing))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def finish
|
|
128
|
+
@handle.finish(0)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def complete
|
|
132
|
+
unless @breadcrumbs.empty?
|
|
133
|
+
@handle.set_sample_data("breadcrumbs", Appsignal::Utils::Data.generate(@breadcrumbs))
|
|
134
|
+
end
|
|
135
|
+
@handle.complete
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Discarding in agent mode drops the transaction: the extension handle is
|
|
139
|
+
# simply abandoned and never told to complete, so nothing is sent. There
|
|
140
|
+
# is no `ignore_subtrace` concept on the agent path. This mirrors the
|
|
141
|
+
# pre-backend behavior, where `Transaction#complete` returned before
|
|
142
|
+
# touching the handle on a discarded transaction.
|
|
143
|
+
def discard
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# The extension transaction holds a single error, so the Transaction
|
|
147
|
+
# reports additional errors as duplicate transactions instead.
|
|
148
|
+
def supports_multiple_errors?
|
|
149
|
+
false
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def duplicate(new_transaction_id)
|
|
153
|
+
self.class.new(
|
|
154
|
+
new_transaction_id, nil, :handle => @handle.duplicate(new_transaction_id)
|
|
155
|
+
).tap { |backend| backend.breadcrumbs = @breadcrumbs.dup }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def to_json # rubocop:disable Lint/ToJSON
|
|
159
|
+
@handle.to_json
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# Projects the neutral causes to the agent's first-line shape. A truncated
|
|
165
|
+
# chain marks its last entry as not the root cause.
|
|
166
|
+
def error_causes_sample_data(causes, root_cause_missing)
|
|
167
|
+
sample_data = causes.map do |cause|
|
|
168
|
+
{
|
|
169
|
+
:name => cause[:name],
|
|
170
|
+
:message => cause[:message],
|
|
171
|
+
:first_line => first_formatted_backtrace_line(cause[:backtrace])
|
|
172
|
+
}
|
|
173
|
+
end
|
|
174
|
+
sample_data.last[:is_root_cause] = false if root_cause_missing && sample_data.any?
|
|
175
|
+
sample_data
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Parses the first backtrace line into the fields the UI links on (gem,
|
|
179
|
+
# path, line, method), with the path made relative to the app root.
|
|
180
|
+
def first_formatted_backtrace_line(backtrace)
|
|
181
|
+
first_line = backtrace&.first
|
|
182
|
+
return unless first_line
|
|
183
|
+
|
|
184
|
+
captures = BACKTRACE_REGEX.match(first_line)
|
|
185
|
+
return unless captures
|
|
186
|
+
|
|
187
|
+
captures.named_captures
|
|
188
|
+
.merge("original" => first_line)
|
|
189
|
+
.tap do |c|
|
|
190
|
+
config = Appsignal.config
|
|
191
|
+
c["gem"] = c["gem"]&.strip
|
|
192
|
+
root_path = config.root_path
|
|
193
|
+
if c["path"].start_with?(root_path)
|
|
194
|
+
c["path"].delete_prefix!(root_path)
|
|
195
|
+
c["path"].delete_prefix!("/")
|
|
196
|
+
end
|
|
197
|
+
c["revision"] = config[:revision]
|
|
198
|
+
c["line"] = c["line"].to_i
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|