appsignal 4.10.0-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 +42 -0
- data/Rakefile +87 -0
- data/appsignal.gemspec +8 -0
- data/build_matrix.yml +14 -1
- data/ext/appsignal_extension.c +14 -0
- data/ext/base.rb +8 -1
- 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
|
@@ -4,24 +4,48 @@ module Appsignal
|
|
|
4
4
|
module Integrations
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module DryMonitorIntegration
|
|
7
|
+
# The event's formatter says what kind of work the event is, such as ROM
|
|
8
|
+
# reporting a SQL query as a dry-monitor `"sql"` event, and which library
|
|
9
|
+
# the instrumentation is for. Both are immutable once the span exists, so
|
|
10
|
+
# they have to be set here at event start.
|
|
11
|
+
#
|
|
12
|
+
# dry-monitor is a notification bus, so an event arriving over it is not
|
|
13
|
+
# necessarily dry-monitor's own work. A formatter that knows better says
|
|
14
|
+
# so; anything else is attributed to dry-monitor.
|
|
7
15
|
def instrument(event_id, payload = {}, &block)
|
|
8
|
-
Appsignal::Transaction.current.start_event
|
|
9
|
-
|
|
10
|
-
super
|
|
11
|
-
ensure
|
|
12
16
|
name = "#{event_id}.dry"
|
|
13
|
-
|
|
17
|
+
# An event a dedicated integration already records is not recorded a
|
|
18
|
+
# second time here.
|
|
19
|
+
return super unless Appsignal::EventFormatter.record?(name)
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
Appsignal::Transaction.current.start_event(
|
|
23
|
+
:opentelemetry_kind => Appsignal::EventFormatter.opentelemetry_kind(name),
|
|
24
|
+
:opentelemetry_scope =>
|
|
25
|
+
Appsignal::EventFormatter.opentelemetry_scope(name) ||
|
|
26
|
+
["appsignal-ruby/dry_monitor", Appsignal::VERSION]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
super
|
|
30
|
+
ensure
|
|
31
|
+
event_name, body, body_format = Appsignal::EventFormatter.format(name, payload)
|
|
32
|
+
# Set while the event's span is still open, so the attributes land
|
|
33
|
+
# on the event rather than on the transaction.
|
|
34
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
35
|
+
Appsignal::EventFormatter.opentelemetry_attributes(name, payload)
|
|
36
|
+
)
|
|
14
37
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
38
|
+
# dry-monitor reports an event under an id, such as `sql`, rather than
|
|
39
|
+
# a name. A formatter names the event it knows about, and an event
|
|
40
|
+
# without one is named after its id in the dry-monitor group. Either
|
|
41
|
+
# way the name has a group, which is what an event is listed under.
|
|
42
|
+
Appsignal::Transaction.current.finish_event(
|
|
43
|
+
event_name || name,
|
|
44
|
+
nil,
|
|
45
|
+
body,
|
|
46
|
+
body_format
|
|
47
|
+
)
|
|
48
|
+
end
|
|
25
49
|
end
|
|
26
50
|
end
|
|
27
51
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
module Integrations
|
|
5
|
+
# Excon middleware that writes trace context onto the outgoing request, so
|
|
6
|
+
# the called service joins this trace. The integration on the connection
|
|
7
|
+
# records the span; this middleware only injects.
|
|
8
|
+
#
|
|
9
|
+
# @!visibility private
|
|
10
|
+
class ExconMiddleware < ::Excon::Middleware::Base
|
|
11
|
+
def request_call(datum)
|
|
12
|
+
datum[:headers] ||= {}
|
|
13
|
+
# Inject from whatever span is current. The connection's client span is
|
|
14
|
+
# open around the whole request, so the written `traceparent` reflects
|
|
15
|
+
# the Excon client event. No-op outside collector mode.
|
|
16
|
+
Appsignal::OpenTelemetry.inject_context(datum[:headers])
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -4,16 +4,20 @@ module Appsignal
|
|
|
4
4
|
module Integrations
|
|
5
5
|
# @!visibility private
|
|
6
6
|
module ExconIntegration
|
|
7
|
+
# The method of a request. Excon defaults it to GET further down, so the
|
|
8
|
+
# same default is applied here.
|
|
9
|
+
def self.method_for(datum)
|
|
10
|
+
datum[:method] || :get
|
|
11
|
+
end
|
|
12
|
+
|
|
7
13
|
# The title of the event, built the way the Net::HTTP integration builds
|
|
8
14
|
# its own: the request method and where the request went, without the
|
|
9
15
|
# path, so paths stay out of event titles.
|
|
10
16
|
#
|
|
11
17
|
# Excon splits a request's data between the connection it is made on and
|
|
12
|
-
# the call that makes it, so both are read to build this.
|
|
13
|
-
# the method to GET itself, so the same default is applied here.
|
|
18
|
+
# the call that makes it, so both are read to build this.
|
|
14
19
|
def self.title_for(datum)
|
|
15
|
-
|
|
16
|
-
"#{method} #{datum[:scheme]}://#{datum[:host]}"
|
|
20
|
+
"#{method_for(datum).to_s.upcase} #{datum[:scheme]}://#{datum[:host]}"
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def request(params = {})
|
|
@@ -38,18 +42,51 @@ module Appsignal
|
|
|
38
42
|
# A pipelined request is the exception to this method being the whole of
|
|
39
43
|
# a request. It returns before the response is read, so its event covers
|
|
40
44
|
# only the sending.
|
|
41
|
-
|
|
45
|
+
datum = data.merge(params)
|
|
46
|
+
|
|
47
|
+
Appsignal.instrument(
|
|
48
|
+
"request.excon",
|
|
49
|
+
ExconIntegration.title_for(datum),
|
|
50
|
+
:opentelemetry_kind => :client,
|
|
51
|
+
:opentelemetry_scope => ["appsignal-ruby/excon", Appsignal::VERSION]
|
|
52
|
+
) do
|
|
53
|
+
# Describes the span as an outgoing HTTP request. Together with the
|
|
54
|
+
# CLIENT kind, this is what the trace timeline reads to recognize it
|
|
55
|
+
# as one.
|
|
56
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
57
|
+
Appsignal::OpenTelemetry::HttpClientRequest.attributes_for(
|
|
58
|
+
:method => ExconIntegration.method_for(datum),
|
|
59
|
+
:scheme => datum[:scheme],
|
|
60
|
+
:host => datum[:host],
|
|
61
|
+
:port => datum[:port],
|
|
62
|
+
:path => datum[:path]
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
response =
|
|
67
|
+
if Appsignal::Transaction.current?
|
|
68
|
+
# Excon retries a request, and follows a redirect, by calling this
|
|
69
|
+
# method again from inside the request it is retrying or
|
|
70
|
+
# following. Suppressing those means they count towards this event
|
|
71
|
+
# rather than becoming events of their own, so one request stays
|
|
72
|
+
# one event.
|
|
73
|
+
Appsignal::Transaction.current.suppress_http_client_events { super }
|
|
74
|
+
else
|
|
75
|
+
super
|
|
76
|
+
end
|
|
42
77
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
# Describes the response on the same span as the request, which the
|
|
79
|
+
# semantic conventions ask for whenever one was received.
|
|
80
|
+
#
|
|
81
|
+
# A pipelined request returns the request data rather than a response,
|
|
82
|
+
# because its response has not been read yet, so there is no status to
|
|
83
|
+
# report for it.
|
|
84
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
85
|
+
Appsignal::OpenTelemetry::HttpResponse.attributes_for(
|
|
86
|
+
response.respond_to?(:status) ? response.status : nil
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
response
|
|
53
90
|
end
|
|
54
91
|
end
|
|
55
92
|
end
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Appsignal
|
|
4
4
|
module Integrations
|
|
5
|
-
# Faraday middleware that records each request as a `request.faraday`
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
5
|
+
# Faraday middleware that records each request as a `request.faraday` client
|
|
6
|
+
# event, writes trace context onto the outgoing request so the called service
|
|
7
|
+
# joins this trace, and suppresses the downstream HTTP client's own
|
|
8
|
+
# instrumentation, so the request is recorded once rather than as nested
|
|
9
|
+
# Faraday + Net::HTTP (or Excon) client events.
|
|
9
10
|
#
|
|
10
11
|
# @!visibility private
|
|
11
12
|
class FaradayMiddleware < ::Faraday::Middleware
|
|
@@ -16,17 +17,50 @@ module Appsignal
|
|
|
16
17
|
# Net::HTTP's (scheme and host only), keeping paths out of event titles.
|
|
17
18
|
Appsignal.instrument(
|
|
18
19
|
"request.faraday",
|
|
19
|
-
"#{http_method} #{uri.scheme}://#{uri.host}"
|
|
20
|
+
"#{http_method} #{uri.scheme}://#{uri.host}",
|
|
21
|
+
:opentelemetry_kind => :client,
|
|
22
|
+
:opentelemetry_scope => ["appsignal-ruby/faraday", Appsignal::VERSION]
|
|
20
23
|
) do
|
|
24
|
+
# Describes the span as an outgoing HTTP request. Together with the
|
|
25
|
+
# CLIENT kind, this is what the trace timeline reads to recognize it
|
|
26
|
+
# as one.
|
|
27
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
28
|
+
Appsignal::OpenTelemetry::HttpClientRequest.attributes_for(
|
|
29
|
+
:method => env[:method],
|
|
30
|
+
:scheme => uri.scheme,
|
|
31
|
+
:host => uri.host,
|
|
32
|
+
:port => uri.port,
|
|
33
|
+
:path => uri.path
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
# Write trace context onto the outgoing request so the called service
|
|
37
|
+
# joins this trace. Injected inside the instrument block, so the written
|
|
38
|
+
# `traceparent` reflects the Faraday client event's span. No-op outside
|
|
39
|
+
# collector mode. `env.request_headers` is the live outgoing header set
|
|
40
|
+
# and a valid carrier (it responds to `[]=`).
|
|
41
|
+
Appsignal::OpenTelemetry.inject_context(env.request_headers)
|
|
42
|
+
|
|
21
43
|
# Faraday's default adapter is Net::HTTP, which AppSignal also
|
|
22
44
|
# instruments. Suppress the adapter's own instrumentation so the
|
|
23
45
|
# request appears once (as the Faraday event) rather than as nested
|
|
24
46
|
# Faraday + Net::HTTP client events.
|
|
25
|
-
|
|
26
|
-
Appsignal::Transaction.current
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
47
|
+
response =
|
|
48
|
+
if Appsignal::Transaction.current?
|
|
49
|
+
Appsignal::Transaction.current.suppress_http_client_events { @app.call(env) }
|
|
50
|
+
else
|
|
51
|
+
@app.call(env)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Describes the response on the same span, which the semantic
|
|
55
|
+
# conventions ask for whenever one was received. The event is still
|
|
56
|
+
# open here, so it lands on the request's own span. The status is read
|
|
57
|
+
# off the environment rather than the returned response, because an
|
|
58
|
+
# adapter fills the response in later than it fills in the environment.
|
|
59
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
60
|
+
Appsignal::OpenTelemetry::HttpResponse.attributes_for(env[:status])
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
response
|
|
30
64
|
end
|
|
31
65
|
end
|
|
32
66
|
end
|
|
@@ -37,8 +71,9 @@ module Appsignal
|
|
|
37
71
|
# the build path is the only way to instrument every connection automatically.
|
|
38
72
|
#
|
|
39
73
|
# Just before the adapter (the innermost handler, where the request is sent)
|
|
40
|
-
# it inserts `FaradayMiddleware`, which records the `request.faraday` event
|
|
41
|
-
# and suppresses the downstream client. Skipped if it's
|
|
74
|
+
# it inserts `FaradayMiddleware`, which records the `request.faraday` event,
|
|
75
|
+
# injects trace context, and suppresses the downstream client. Skipped if it's
|
|
76
|
+
# already present.
|
|
42
77
|
#
|
|
43
78
|
# @!visibility private
|
|
44
79
|
module FaradayRackBuilderPatch
|
|
@@ -9,7 +9,34 @@ module Appsignal
|
|
|
9
9
|
parsed_request_uri = uri.is_a?(URI) ? uri : uri_module.parse(uri.to_s)
|
|
10
10
|
request_uri = "#{parsed_request_uri.scheme}://#{parsed_request_uri.host}"
|
|
11
11
|
|
|
12
|
-
Appsignal.instrument(
|
|
12
|
+
Appsignal.instrument(
|
|
13
|
+
"request.http_rb",
|
|
14
|
+
"#{verb.to_s.upcase} #{request_uri}",
|
|
15
|
+
:opentelemetry_kind => :client,
|
|
16
|
+
:opentelemetry_scope => ["appsignal-ruby/http_rb", Appsignal::VERSION]
|
|
17
|
+
) do
|
|
18
|
+
# Describes the span as an outgoing HTTP request. Together with the
|
|
19
|
+
# CLIENT kind, this is what the trace timeline reads to recognize it
|
|
20
|
+
# as one.
|
|
21
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
22
|
+
Appsignal::OpenTelemetry::HttpClientRequest.attributes_for(
|
|
23
|
+
:method => verb,
|
|
24
|
+
:scheme => parsed_request_uri.scheme,
|
|
25
|
+
:host => parsed_request_uri.host,
|
|
26
|
+
:port => parsed_request_uri.port,
|
|
27
|
+
:path => parsed_request_uri.path
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
# Describes the response on the same span, which the semantic
|
|
31
|
+
# conventions ask for whenever one was received. The event is still
|
|
32
|
+
# open here, so it lands on the request's own span. A request that
|
|
33
|
+
# followed redirects reports the status of the response it ended on.
|
|
34
|
+
block.call.tap do |response|
|
|
35
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
36
|
+
Appsignal::OpenTelemetry::HttpResponse.attributes_for(response&.code)
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
13
40
|
end
|
|
14
41
|
|
|
15
42
|
# The event is recorded at the request boundary, so a redirected request
|
|
@@ -32,6 +59,21 @@ module Appsignal
|
|
|
32
59
|
HttpIntegration.instrument(verb, uri) { super }
|
|
33
60
|
end
|
|
34
61
|
end
|
|
62
|
+
|
|
63
|
+
# Trace context has to ride on each outgoing hop's headers, so it's
|
|
64
|
+
# injected at `HTTP::Client#perform` -- the single send chokepoint in both
|
|
65
|
+
# http5 and http6, called once per request and once per redirect hop --
|
|
66
|
+
# where the live request headers are reachable. The event stays at the
|
|
67
|
+
# request boundary above, so a redirected request is still a single event;
|
|
68
|
+
# this only propagates context, and every hop carries it. No-op outside
|
|
69
|
+
# collector mode. `req.headers` is the live outgoing header set and a valid
|
|
70
|
+
# carrier (it responds to `[]=`).
|
|
71
|
+
module ContextInjection
|
|
72
|
+
def perform(req, options)
|
|
73
|
+
Appsignal::OpenTelemetry.inject_context(req.headers)
|
|
74
|
+
super
|
|
75
|
+
end
|
|
76
|
+
end
|
|
35
77
|
end
|
|
36
78
|
end
|
|
37
79
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
module Appsignal
|
|
4
6
|
class Hooks
|
|
5
7
|
# @!visibility private
|
|
@@ -19,8 +21,25 @@ module Appsignal
|
|
|
19
21
|
store = transaction.store("mongo_driver")
|
|
20
22
|
store[event.request_id] = command
|
|
21
23
|
|
|
22
|
-
# Start this event
|
|
23
|
-
transaction.start_event
|
|
24
|
+
# Start this event. The query is an outgoing client call.
|
|
25
|
+
transaction.start_event(
|
|
26
|
+
:opentelemetry_kind => :client,
|
|
27
|
+
:opentelemetry_scope => ["appsignal-ruby/mongo", Appsignal::VERSION]
|
|
28
|
+
)
|
|
29
|
+
# Names the datastore this span talks to, which is what the trace
|
|
30
|
+
# timeline reads to recognize a database call, along with the command
|
|
31
|
+
# that ran, what it ran against, and the server it went to. Set here,
|
|
32
|
+
# where the event span it describes is the open one.
|
|
33
|
+
transaction.add_opentelemetry_attributes(
|
|
34
|
+
{
|
|
35
|
+
"db.system.name" => "mongodb",
|
|
36
|
+
"db.operation.name" => event.command_name,
|
|
37
|
+
"db.collection.name" => collection_name(event),
|
|
38
|
+
"db.namespace" => event.database_name,
|
|
39
|
+
"server.address" => event.address&.host,
|
|
40
|
+
"server.port" => event.address&.port
|
|
41
|
+
}.compact
|
|
42
|
+
)
|
|
24
43
|
end
|
|
25
44
|
|
|
26
45
|
# Called by Mongo::Monitor when query succeeds
|
|
@@ -46,9 +65,29 @@ module Appsignal
|
|
|
46
65
|
store = transaction.store("mongo_driver")
|
|
47
66
|
command = store.delete(event.request_id) || {}
|
|
48
67
|
|
|
68
|
+
# Say what kind of failure ended the query, which the OpenTelemetry
|
|
69
|
+
# semantic conventions ask for on a span whose operation failed. The
|
|
70
|
+
# driver reports a failure by calling us rather than by raising, so this
|
|
71
|
+
# is the only place it can be read. Set before the event is finished, so
|
|
72
|
+
# it lands on the query's own span.
|
|
73
|
+
#
|
|
74
|
+
# Only a failure event carries a failure, which is what tells the two
|
|
75
|
+
# apart here.
|
|
76
|
+
if event.respond_to?(:failure)
|
|
77
|
+
transaction.add_opentelemetry_attributes(
|
|
78
|
+
Appsignal::OpenTelemetry::ErrorType.attributes_for(error_name(event.failure))
|
|
79
|
+
)
|
|
80
|
+
# MongoDB's own code for the error, which the conventions ask for
|
|
81
|
+
# whenever the database reported one.
|
|
82
|
+
transaction.add_opentelemetry_attributes(
|
|
83
|
+
{ "db.response.status_code" => error_code(event.failure) }.compact
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
49
87
|
# Finish the event. The sanitized command is a (nested) Hash; emit it
|
|
50
|
-
# as a JSON string
|
|
51
|
-
# anyway, so this is
|
|
88
|
+
# as a JSON string so it works with both transaction backends. The
|
|
89
|
+
# agent serializes structured bodies to JSON anyway, so this is
|
|
90
|
+
# equivalent output there, and the collector receives a plain string.
|
|
52
91
|
transaction.finish_event(
|
|
53
92
|
"query.mongodb",
|
|
54
93
|
"#{event.command_name} | #{event.database_name} | #{result}",
|
|
@@ -63,6 +102,36 @@ module Appsignal
|
|
|
63
102
|
:database => event.database_name
|
|
64
103
|
)
|
|
65
104
|
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# The collection a command worked on. MongoDB puts it in the field named
|
|
109
|
+
# after the command itself, as in `{ "find" => "users" }`. A command that
|
|
110
|
+
# works on the database as a whole rather than on one collection has
|
|
111
|
+
# something else in that field, such as the number 1, so only a String
|
|
112
|
+
# counts as a collection name.
|
|
113
|
+
def collection_name(event)
|
|
114
|
+
return unless event.command.respond_to?(:[])
|
|
115
|
+
|
|
116
|
+
collection = event.command[event.command_name]
|
|
117
|
+
collection if collection.is_a?(String)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# MongoDB's own code for an error, which it puts in the `code` field of the
|
|
121
|
+
# error document it replies with. Reported as a String, which is what the
|
|
122
|
+
# conventions ask for.
|
|
123
|
+
def error_code(failure)
|
|
124
|
+
return unless failure.respond_to?(:[])
|
|
125
|
+
|
|
126
|
+
failure["code"]&.to_s
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# MongoDB names an error in the `codeName` field of the error document it
|
|
130
|
+
# replies with. A failure the driver never got a document for, such as a
|
|
131
|
+
# connection that dropped, has no name.
|
|
132
|
+
def error_name(failure)
|
|
133
|
+
failure["codeName"] if failure.respond_to?(:[])
|
|
134
|
+
end
|
|
66
135
|
end
|
|
67
136
|
end
|
|
68
137
|
end
|
|
@@ -14,9 +14,38 @@ module Appsignal
|
|
|
14
14
|
|
|
15
15
|
Appsignal.instrument(
|
|
16
16
|
"request.net_http",
|
|
17
|
-
"#{request.method} #{use_ssl? ? "https" : "http"}://#{request["host"] || address}"
|
|
17
|
+
"#{request.method} #{use_ssl? ? "https" : "http"}://#{request["host"] || address}",
|
|
18
|
+
:opentelemetry_kind => :client,
|
|
19
|
+
:opentelemetry_scope => ["appsignal-ruby/net_http", Appsignal::VERSION]
|
|
18
20
|
) do
|
|
19
|
-
|
|
21
|
+
# Describes the span as an outgoing HTTP request. Together with the
|
|
22
|
+
# CLIENT kind, this is what the trace timeline reads to recognize it
|
|
23
|
+
# as one.
|
|
24
|
+
#
|
|
25
|
+
# The client's own `address` and `port` name the host being called.
|
|
26
|
+
# The request's `path` is a request target, so it can carry a query
|
|
27
|
+
# string, which the attribute builder cuts off.
|
|
28
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
29
|
+
Appsignal::OpenTelemetry::HttpClientRequest.attributes_for(
|
|
30
|
+
:method => request.method,
|
|
31
|
+
:scheme => use_ssl? ? "https" : "http",
|
|
32
|
+
:host => address,
|
|
33
|
+
:port => port,
|
|
34
|
+
:path => request.path
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
# Write trace context onto the outgoing request so the called service
|
|
38
|
+
# joins this trace. No-op outside collector mode. The request object
|
|
39
|
+
# is a valid carrier (it responds to `[]=`).
|
|
40
|
+
Appsignal::OpenTelemetry.inject_context(request)
|
|
41
|
+
# Describes the response on the same span, which the semantic
|
|
42
|
+
# conventions ask for whenever one was received. The event is still
|
|
43
|
+
# open here, so it lands on the request's own span.
|
|
44
|
+
super.tap do |response|
|
|
45
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
46
|
+
Appsignal::OpenTelemetry::HttpResponse.attributes_for(response&.code)
|
|
47
|
+
)
|
|
48
|
+
end
|
|
20
49
|
end
|
|
21
50
|
end
|
|
22
51
|
end
|
|
@@ -13,7 +13,10 @@ module Appsignal
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
unless PumaServerHelper.ignored_error?(error)
|
|
16
|
-
Appsignal.report_error(
|
|
16
|
+
Appsignal.report_error(
|
|
17
|
+
error,
|
|
18
|
+
:opentelemetry_scope => ["appsignal-ruby/puma", Appsignal::VERSION]
|
|
19
|
+
) do |transaction|
|
|
17
20
|
Appsignal::Rack::ApplyRackRequest
|
|
18
21
|
.new(::Rack::Request.new(env))
|
|
19
22
|
.apply_to(transaction)
|