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
|
@@ -2,20 +2,118 @@
|
|
|
2
2
|
|
|
3
3
|
module Appsignal
|
|
4
4
|
module Integrations
|
|
5
|
+
# @!visibility private
|
|
6
|
+
#
|
|
7
|
+
# Reads and writes W3C trace context the way OpenTelemetry's aws-sdk
|
|
8
|
+
# instrumentation does: as SQS message attributes, using the global
|
|
9
|
+
# propagator. Staying wire-equivalent means that if both AppSignal and
|
|
10
|
+
# OpenTelemetry's aws-sdk instrumentation are active, one simply shadows the
|
|
11
|
+
# other rather than corrupting the carrier. Collector mode only.
|
|
12
|
+
module ShoryukenTraceContext
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# SQS allows at most 10 message attributes per message. Mirror
|
|
16
|
+
# OpenTelemetry and skip propagation rather than risk the enqueue failing
|
|
17
|
+
# when the user already fills the slots.
|
|
18
|
+
MAX_MESSAGE_ATTRIBUTES = 10
|
|
19
|
+
|
|
20
|
+
# Writes each trace header as an SQS message attribute, matching the shape
|
|
21
|
+
# OpenTelemetry's aws-sdk instrumentation injects on send.
|
|
22
|
+
module MessageAttributeSetter
|
|
23
|
+
def self.set(carrier, key, value)
|
|
24
|
+
return if carrier.length >= MAX_MESSAGE_ATTRIBUTES
|
|
25
|
+
|
|
26
|
+
carrier[key] = { :string_value => value, :data_type => "String" }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Reads a trace header back out of a message attribute. Works both for the
|
|
31
|
+
# plain hash we inject and for the `Aws::SQS::Types::MessageAttributeValue`
|
|
32
|
+
# struct delivered on receive, since both respond to `[:string_value]` /
|
|
33
|
+
# `[:data_type]`.
|
|
34
|
+
module MessageAttributeGetter
|
|
35
|
+
def self.get(carrier, key)
|
|
36
|
+
attribute = carrier[key]
|
|
37
|
+
attribute[:string_value] if attribute && attribute[:data_type] == "String"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Read the incoming context off a message's SQS message attributes so the
|
|
42
|
+
# transaction links back to the enqueuer. Returns an
|
|
43
|
+
# `OpenTelemetry::Context`, or `nil` outside collector mode.
|
|
44
|
+
def extract(message_attributes)
|
|
45
|
+
Appsignal::OpenTelemetry.if_started do
|
|
46
|
+
::OpenTelemetry.propagation.extract(
|
|
47
|
+
message_attributes || {},
|
|
48
|
+
:getter => MessageAttributeGetter
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Write the current trace context into the outgoing send `options`.
|
|
54
|
+
# Injects into a scratch carrier first and merges it into the message
|
|
55
|
+
# attributes only when something was written, so an enqueue with no active
|
|
56
|
+
# span (no transaction, or outside collector mode) leaves the options
|
|
57
|
+
# untouched -- a transparent pass-through.
|
|
58
|
+
def inject(options)
|
|
59
|
+
Appsignal::OpenTelemetry.if_started do
|
|
60
|
+
carrier = {}
|
|
61
|
+
::OpenTelemetry.propagation.inject(carrier, :setter => MessageAttributeSetter)
|
|
62
|
+
next if carrier.empty?
|
|
63
|
+
|
|
64
|
+
options[:message_attributes] = (options[:message_attributes] || {}).merge(carrier)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
5
69
|
# @!visibility private
|
|
6
70
|
class ShoryukenMiddleware
|
|
7
71
|
def call(worker_instance, queue, sqs_msg, body, &block)
|
|
8
|
-
|
|
72
|
+
batch = sqs_msg.is_a?(Array)
|
|
73
|
+
# How many messages this call covers, which is only reported for a batch.
|
|
74
|
+
batch_size = sqs_msg.size if batch
|
|
75
|
+
|
|
76
|
+
# Read the incoming trace context off the message so the transaction
|
|
77
|
+
# links back to the enqueuer. A batch carries messages from multiple
|
|
78
|
+
# traces with no single parent, so only single messages link back.
|
|
79
|
+
# No-op outside collector mode.
|
|
80
|
+
context = ShoryukenTraceContext.extract(sqs_msg.message_attributes) unless batch
|
|
81
|
+
|
|
82
|
+
transaction = Appsignal::Transaction.create(
|
|
83
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
|
84
|
+
:opentelemetry_context => context,
|
|
85
|
+
:opentelemetry_scope => ["appsignal-ruby/shoryuken", Appsignal::VERSION],
|
|
86
|
+
:opentelemetry_kind => :consumer,
|
|
87
|
+
:opentelemetry_relationship => :both
|
|
88
|
+
)
|
|
89
|
+
# Describes this span as a job being performed. The messaging system is
|
|
90
|
+
# what the trace timeline reads to recognize background job work.
|
|
91
|
+
# Shoryuken runs on Amazon SQS, so it takes the `aws_sqs` value the
|
|
92
|
+
# OpenTelemetry semantic conventions define for it.
|
|
93
|
+
transaction.add_opentelemetry_attributes(
|
|
94
|
+
Appsignal::OpenTelemetry::Messaging.perform_attributes(
|
|
95
|
+
"aws_sqs", :destination => queue, :batch_size => batch_size
|
|
96
|
+
)
|
|
97
|
+
)
|
|
9
98
|
|
|
10
|
-
Appsignal.instrument(
|
|
99
|
+
Appsignal.instrument(
|
|
100
|
+
"perform_job.shoryuken",
|
|
101
|
+
:opentelemetry_scope => ["appsignal-ruby/shoryuken", Appsignal::VERSION]
|
|
102
|
+
) do
|
|
103
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
104
|
+
Appsignal::OpenTelemetry::Messaging.perform_attributes(
|
|
105
|
+
"aws_sqs", :destination => queue, :batch_size => batch_size
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
block.call
|
|
109
|
+
end
|
|
11
110
|
rescue Exception => error
|
|
12
111
|
transaction.set_error(error)
|
|
13
112
|
raise
|
|
14
113
|
ensure
|
|
15
|
-
batch = sqs_msg.is_a?(Array)
|
|
16
114
|
attributes = fetch_attributes(batch, sqs_msg)
|
|
17
115
|
transaction.set_action_if_nil("#{worker_instance.class.name}#perform")
|
|
18
|
-
transaction.
|
|
116
|
+
transaction.add_function_parameters_if_nil { fetch_args(batch, sqs_msg, body) }
|
|
19
117
|
transaction.add_tags(attributes)
|
|
20
118
|
transaction.add_tags("queue" => queue)
|
|
21
119
|
transaction.add_tags("batch" => true) if batch
|
|
@@ -73,7 +171,9 @@ module Appsignal
|
|
|
73
171
|
end
|
|
74
172
|
|
|
75
173
|
# Shoryuken client middleware that records an `enqueue.shoryuken` event so
|
|
76
|
-
# the enqueue shows up under the active transaction
|
|
174
|
+
# the enqueue shows up under the active transaction (both modes), and in
|
|
175
|
+
# collector mode writes the current trace context onto the outgoing message
|
|
176
|
+
# so the job that later performs links back to it.
|
|
77
177
|
#
|
|
78
178
|
# Like all AppSignal events, this only records when there's an active
|
|
79
179
|
# transaction (e.g. enqueuing from within a web request or another job). An
|
|
@@ -81,13 +181,36 @@ module Appsignal
|
|
|
81
181
|
#
|
|
82
182
|
# @!visibility private
|
|
83
183
|
class ShoryukenClientMiddleware
|
|
84
|
-
def call(options
|
|
184
|
+
def call(options)
|
|
185
|
+
# When enqueue instrumentation is disabled, drop the trace context along
|
|
186
|
+
# with the event. Without an enqueue event there is no producer span, so
|
|
187
|
+
# the context we would write is that of whatever span is current, such as
|
|
188
|
+
# the surrounding web request. The job that performs later would then
|
|
189
|
+
# link back to a span that is not a producer.
|
|
190
|
+
return yield if Appsignal.config && !Appsignal.config[:enable_job_enqueue_instrumentation]
|
|
191
|
+
|
|
85
192
|
# Under Active Job the enqueue is already recorded as an
|
|
86
|
-
# `enqueue.active_job` event, so skip recording it again here.
|
|
87
|
-
|
|
88
|
-
|
|
193
|
+
# `enqueue.active_job` event, so skip recording it again here. The trace
|
|
194
|
+
# context is still injected so the performed job links back.
|
|
195
|
+
if Appsignal::Transaction.current? &&
|
|
196
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
197
|
+
ShoryukenTraceContext.inject(options)
|
|
198
|
+
return yield
|
|
199
|
+
end
|
|
89
200
|
|
|
90
|
-
Appsignal.instrument(
|
|
201
|
+
Appsignal.instrument(
|
|
202
|
+
"enqueue.shoryuken",
|
|
203
|
+
enqueue_title(options),
|
|
204
|
+
:opentelemetry_kind => :producer,
|
|
205
|
+
:opentelemetry_scope => ["appsignal-ruby/shoryuken", Appsignal::VERSION]
|
|
206
|
+
) do
|
|
207
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
208
|
+
Appsignal::OpenTelemetry::Messaging
|
|
209
|
+
.enqueue_attributes("aws_sqs", :destination => queue_name(options))
|
|
210
|
+
)
|
|
211
|
+
ShoryukenTraceContext.inject(options)
|
|
212
|
+
yield
|
|
213
|
+
end
|
|
91
214
|
end
|
|
92
215
|
|
|
93
216
|
private
|
|
@@ -99,8 +222,13 @@ module Appsignal
|
|
|
99
222
|
worker_class = options.dig(:message_attributes, "shoryuken_class", :string_value)
|
|
100
223
|
return "enqueue #{worker_class} job" if worker_class
|
|
101
224
|
|
|
102
|
-
|
|
103
|
-
|
|
225
|
+
"enqueue on #{queue_name(options)}"
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# The queue a message is being sent to, which SQS identifies by a URL whose
|
|
229
|
+
# last segment is the queue's name.
|
|
230
|
+
def queue_name(options)
|
|
231
|
+
options[:queue_url].to_s.split("/").last
|
|
104
232
|
end
|
|
105
233
|
end
|
|
106
234
|
end
|
|
@@ -38,10 +38,18 @@ module Appsignal
|
|
|
38
38
|
# Sidekiq error outside of the middleware scope.
|
|
39
39
|
# Can be a job JSON parse error or some other error happening in
|
|
40
40
|
# Sidekiq.
|
|
41
|
-
transaction = Appsignal::Transaction.create(
|
|
41
|
+
transaction = Appsignal::Transaction.create(
|
|
42
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
|
43
|
+
:opentelemetry_scope => ["appsignal-ruby/sidekiq", Appsignal::VERSION],
|
|
44
|
+
:opentelemetry_kind => :consumer,
|
|
45
|
+
:opentelemetry_relationship => :both
|
|
46
|
+
)
|
|
47
|
+
transaction.add_opentelemetry_attributes(
|
|
48
|
+
Appsignal::OpenTelemetry::Messaging.perform_attributes("sidekiq")
|
|
49
|
+
)
|
|
42
50
|
transaction.set_action_if_nil("SidekiqInternal")
|
|
43
51
|
transaction.set_metadata("sidekiq_error", sidekiq_context[:context])
|
|
44
|
-
transaction.
|
|
52
|
+
transaction.add_function_parameters_if_nil(:jobstr => sidekiq_context[:jobstr])
|
|
45
53
|
transaction.set_error(exception)
|
|
46
54
|
end
|
|
47
55
|
|
|
@@ -98,24 +106,52 @@ module Appsignal
|
|
|
98
106
|
end
|
|
99
107
|
end
|
|
100
108
|
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
109
|
+
# Client middleware that runs on enqueue. Records an `enqueue.sidekiq`
|
|
110
|
+
# event so the enqueue shows up under the active transaction (both modes),
|
|
111
|
+
# and in collector mode writes the trace context onto the job hash so the
|
|
112
|
+
# job that later performs links back to it.
|
|
104
113
|
#
|
|
105
114
|
# Like all AppSignal events, this only records when there's an active
|
|
106
|
-
# transaction (e.g. enqueuing from within a web request or another job).
|
|
107
|
-
# enqueue with no transaction is a transparent pass-through.
|
|
115
|
+
# transaction (e.g. enqueuing from within a web request or another job).
|
|
116
|
+
# An enqueue with no transaction is a transparent pass-through.
|
|
108
117
|
#
|
|
109
118
|
# @!visibility private
|
|
110
119
|
class SidekiqClientMiddleware
|
|
111
|
-
def call(_worker_class, job, _queue, _redis_pool
|
|
120
|
+
def call(_worker_class, job, _queue, _redis_pool)
|
|
121
|
+
# When enqueue instrumentation is disabled, drop the trace context along
|
|
122
|
+
# with the event. Without an enqueue event there is no producer span, so
|
|
123
|
+
# the context we would write is that of whatever span is current, such as
|
|
124
|
+
# the surrounding web request. The job that performs later would then
|
|
125
|
+
# link back to a span that is not a producer.
|
|
126
|
+
return yield if Appsignal.config && !Appsignal.config[:enable_job_enqueue_instrumentation]
|
|
127
|
+
|
|
112
128
|
# Under Active Job the enqueue is already recorded as an
|
|
113
|
-
# `enqueue.active_job` event, so skip recording it again here.
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
# `enqueue.active_job` event, so skip recording it again here. The trace
|
|
130
|
+
# context is still injected so the performed job links back.
|
|
131
|
+
if Appsignal::Transaction.current? &&
|
|
132
|
+
Appsignal::Transaction.current.job_enqueue_events_suppressed?
|
|
133
|
+
Appsignal::OpenTelemetry.inject_context(job)
|
|
134
|
+
return yield
|
|
135
|
+
end
|
|
116
136
|
|
|
117
137
|
title = "enqueue #{SidekiqActionName.parse_action_name(job)} job"
|
|
118
|
-
Appsignal.instrument(
|
|
138
|
+
Appsignal.instrument(
|
|
139
|
+
"enqueue.sidekiq",
|
|
140
|
+
title,
|
|
141
|
+
:opentelemetry_kind => :producer,
|
|
142
|
+
:opentelemetry_scope => ["appsignal-ruby/sidekiq", Appsignal::VERSION]
|
|
143
|
+
) do
|
|
144
|
+
# Describes this span as a job being enqueued. The messaging system
|
|
145
|
+
# is what the trace timeline reads to recognize background job work,
|
|
146
|
+
# and `sidekiq` is the value OpenTelemetry's own Sidekiq
|
|
147
|
+
# instrumentation uses.
|
|
148
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
149
|
+
Appsignal::OpenTelemetry::Messaging
|
|
150
|
+
.enqueue_attributes("sidekiq", :destination => job["queue"])
|
|
151
|
+
)
|
|
152
|
+
Appsignal::OpenTelemetry.inject_context(job)
|
|
153
|
+
yield
|
|
154
|
+
end
|
|
119
155
|
end
|
|
120
156
|
end
|
|
121
157
|
|
|
@@ -126,7 +162,7 @@ module Appsignal
|
|
|
126
162
|
EXCLUDED_JOB_KEYS = %w[
|
|
127
163
|
args backtrace class created_at enqueued_at error_backtrace error_class
|
|
128
164
|
error_message failed_at jid retried_at retry wrapped cattr tags retry_for
|
|
129
|
-
unique_for
|
|
165
|
+
unique_for traceparent tracestate __otel_headers
|
|
130
166
|
].freeze
|
|
131
167
|
|
|
132
168
|
def self.sidekiq8?
|
|
@@ -138,7 +174,19 @@ module Appsignal
|
|
|
138
174
|
def call(_worker, item, _queue, &block)
|
|
139
175
|
job_status = nil
|
|
140
176
|
action_name = formatted_action_name(item)
|
|
141
|
-
transaction
|
|
177
|
+
# Read trace context off the job so the transaction links back to the
|
|
178
|
+
# enqueuer. No-op outside collector mode.
|
|
179
|
+
transaction = Appsignal::Transaction.create(
|
|
180
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
|
181
|
+
:opentelemetry_context => Appsignal::OpenTelemetry.extract_job_context(item),
|
|
182
|
+
:opentelemetry_scope => ["appsignal-ruby/sidekiq", Appsignal::VERSION],
|
|
183
|
+
:opentelemetry_kind => :consumer,
|
|
184
|
+
:opentelemetry_relationship => :both
|
|
185
|
+
)
|
|
186
|
+
transaction.add_opentelemetry_attributes(
|
|
187
|
+
Appsignal::OpenTelemetry::Messaging
|
|
188
|
+
.perform_attributes("sidekiq", :destination => item["queue"])
|
|
189
|
+
)
|
|
142
190
|
transaction.set_action_if_nil(action_name)
|
|
143
191
|
|
|
144
192
|
formatted_metadata(item).each do |key, value|
|
|
@@ -146,13 +194,22 @@ module Appsignal
|
|
|
146
194
|
end
|
|
147
195
|
|
|
148
196
|
begin
|
|
149
|
-
Appsignal.instrument
|
|
197
|
+
Appsignal.instrument(
|
|
198
|
+
"perform_job.sidekiq",
|
|
199
|
+
:opentelemetry_scope => ["appsignal-ruby/sidekiq", Appsignal::VERSION]
|
|
200
|
+
) do
|
|
201
|
+
Appsignal::Transaction.current.add_opentelemetry_attributes(
|
|
202
|
+
Appsignal::OpenTelemetry::Messaging
|
|
203
|
+
.perform_attributes("sidekiq", :destination => item["queue"])
|
|
204
|
+
)
|
|
205
|
+
block.call
|
|
206
|
+
end
|
|
150
207
|
rescue Exception => exception
|
|
151
208
|
job_status = :failed
|
|
152
209
|
raise exception
|
|
153
210
|
ensure
|
|
154
211
|
if transaction
|
|
155
|
-
transaction.
|
|
212
|
+
transaction.add_function_parameters_if_nil { parse_arguments(item) }
|
|
156
213
|
enqueued_at = item["enqueued_at"]
|
|
157
214
|
queue_start =
|
|
158
215
|
if self.class.sidekiq8?
|
|
@@ -10,20 +10,63 @@ module Appsignal
|
|
|
10
10
|
if has_parent_transaction
|
|
11
11
|
Appsignal::Transaction.current
|
|
12
12
|
else
|
|
13
|
-
|
|
13
|
+
# Read the incoming trace context off the request headers so the
|
|
14
|
+
# transaction continues the upstream trace. No-op outside collector
|
|
15
|
+
# mode. Webmachine isn't Rack: `request.headers` is a case-insensitive
|
|
16
|
+
# `Webmachine::Headers`, so the default getter reads it directly.
|
|
17
|
+
Appsignal::Transaction.create(
|
|
18
|
+
Appsignal::Transaction::HTTP_REQUEST,
|
|
19
|
+
:opentelemetry_context => Appsignal::OpenTelemetry.if_started do
|
|
20
|
+
::OpenTelemetry.propagation.extract(request.headers)
|
|
21
|
+
end,
|
|
22
|
+
:opentelemetry_scope => ["appsignal-ruby/webmachine", Appsignal::VERSION]
|
|
23
|
+
)
|
|
14
24
|
end
|
|
15
25
|
|
|
26
|
+
unless has_parent_transaction
|
|
27
|
+
# Describes the transaction's span as an incoming HTTP request.
|
|
28
|
+
# Together with the SERVER span kind the transaction already carries,
|
|
29
|
+
# this is what the trace timeline reads to recognize a web request.
|
|
30
|
+
# Set here, where the transaction is created, so they land on the
|
|
31
|
+
# transaction's own span rather than on the event started below.
|
|
32
|
+
#
|
|
33
|
+
# Webmachine isn't Rack: the path, scheme and query string come off the
|
|
34
|
+
# request's `URI` rather than from Rack's readers. The request's own
|
|
35
|
+
# `query` is the parsed form, so the URI is where the string itself is.
|
|
36
|
+
transaction.add_opentelemetry_attributes(
|
|
37
|
+
Appsignal::OpenTelemetry::HttpServerRequest.attributes_for(
|
|
38
|
+
:method => request.method,
|
|
39
|
+
:path => request.uri&.path,
|
|
40
|
+
:scheme => request.uri&.scheme,
|
|
41
|
+
:query => request.uri&.query
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
16
46
|
begin
|
|
17
|
-
transaction.
|
|
47
|
+
transaction.add_query_parameters_if_nil { request.query }
|
|
18
48
|
transaction.add_headers_if_nil { request.headers if request.respond_to?(:headers) }
|
|
19
49
|
|
|
20
|
-
Appsignal.instrument(
|
|
50
|
+
Appsignal.instrument(
|
|
51
|
+
"process_action.webmachine",
|
|
52
|
+
:opentelemetry_scope => ["appsignal-ruby/webmachine", Appsignal::VERSION]
|
|
53
|
+
) do
|
|
21
54
|
super
|
|
22
55
|
end
|
|
23
56
|
ensure
|
|
24
57
|
transaction.set_action_if_nil("#{resource.class.name}##{request.method}")
|
|
25
58
|
|
|
26
|
-
|
|
59
|
+
unless has_parent_transaction
|
|
60
|
+
# Describes the response on the transaction's span, which the
|
|
61
|
+
# semantic conventions ask for whenever a response was sent. The
|
|
62
|
+
# event above has closed by now, so this lands on the transaction's
|
|
63
|
+
# own span.
|
|
64
|
+
transaction.add_opentelemetry_attributes(
|
|
65
|
+
Appsignal::OpenTelemetry::HttpResponse.attributes_for(response.code)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
Appsignal::Transaction.complete_current!
|
|
69
|
+
end
|
|
27
70
|
end
|
|
28
71
|
end
|
|
29
72
|
|
|
@@ -20,7 +20,8 @@ module Appsignal
|
|
|
20
20
|
Padrino.before_load do
|
|
21
21
|
Padrino.use Appsignal::Rack::EventMiddleware
|
|
22
22
|
Padrino.use Appsignal::Rack::SinatraBaseInstrumentation,
|
|
23
|
-
:instrument_event_name => "process_action.padrino"
|
|
23
|
+
:instrument_event_name => "process_action.padrino",
|
|
24
|
+
:opentelemetry_scope => ["appsignal-ruby/padrino", Appsignal::VERSION]
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
class Logger < ::Logger
|
|
5
|
+
# @!visibility private
|
|
6
|
+
#
|
|
7
|
+
# Routes Appsignal::Logger emits through the AppSignal C-extension,
|
|
8
|
+
# which forwards them to the agent. This is the default backend used
|
|
9
|
+
# when collector mode is not active.
|
|
10
|
+
module ExtensionBackend
|
|
11
|
+
class << self
|
|
12
|
+
def emit(group, severity, format, message, attributes)
|
|
13
|
+
Appsignal::Extension.log(
|
|
14
|
+
group,
|
|
15
|
+
SEVERITY_MAP.fetch(severity, 0),
|
|
16
|
+
format,
|
|
17
|
+
message,
|
|
18
|
+
Appsignal::Utils::Data.generate(attributes)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "appsignal/opentelemetry/attributes"
|
|
4
|
+
|
|
5
|
+
module Appsignal
|
|
6
|
+
class Logger < ::Logger
|
|
7
|
+
# @!visibility private
|
|
8
|
+
#
|
|
9
|
+
# Routes Appsignal::Logger emits through the OpenTelemetry logs SDK
|
|
10
|
+
# using the logger provider configured at `Appsignal.start` time when
|
|
11
|
+
# collector mode is active.
|
|
12
|
+
#
|
|
13
|
+
# Each emit attaches two well-known attributes that the AppSignal
|
|
14
|
+
# collector consumes:
|
|
15
|
+
#
|
|
16
|
+
# - `appsignal.group` — overrides the collector's default
|
|
17
|
+
# `service.name`-based grouping with the logger's `group` argument.
|
|
18
|
+
# - `appsignal.format` — the lowercase parse-format name
|
|
19
|
+
# (`plaintext`/`logfmt`/`json`/`autodetect`) the processor uses to
|
|
20
|
+
# extract structured attributes from the message body.
|
|
21
|
+
module OpenTelemetryBackend
|
|
22
|
+
# Maps Ruby `::Logger` severities to OTel SeverityNumber + the
|
|
23
|
+
# human-readable severity text.
|
|
24
|
+
OTEL_SEVERITY_MAP = {
|
|
25
|
+
::Logger::DEBUG => [5, "DEBUG"],
|
|
26
|
+
::Logger::INFO => [9, "INFO"],
|
|
27
|
+
::Logger::WARN => [13, "WARN"],
|
|
28
|
+
::Logger::ERROR => [17, "ERROR"],
|
|
29
|
+
::Logger::FATAL => [21, "FATAL"]
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
# Maps the integer parse-format flag on `Appsignal::Logger` to the
|
|
33
|
+
# lowercase string the AppSignal collector and processor share.
|
|
34
|
+
FORMAT_NAMES = {
|
|
35
|
+
Appsignal::Logger::PLAINTEXT => "plaintext",
|
|
36
|
+
Appsignal::Logger::LOGFMT => "logfmt",
|
|
37
|
+
Appsignal::Logger::JSON => "json",
|
|
38
|
+
Appsignal::Logger::AUTODETECT => "autodetect"
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
def emit(group, severity, format, message, attributes)
|
|
43
|
+
number, text = OTEL_SEVERITY_MAP.fetch(severity, [0, nil])
|
|
44
|
+
otel_attributes = Appsignal::OpenTelemetry::Attributes.format(attributes)
|
|
45
|
+
otel_attributes["appsignal.group"] = group.to_s
|
|
46
|
+
otel_attributes["appsignal.format"] = FORMAT_NAMES.fetch(format, "autodetect")
|
|
47
|
+
logger.on_emit(
|
|
48
|
+
:severity_number => number,
|
|
49
|
+
:severity_text => text,
|
|
50
|
+
:body => message,
|
|
51
|
+
:attributes => otel_attributes
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Resolve the OTel logger on each emit. The logger provider caches it
|
|
58
|
+
# by name, so this is a registry lookup rather than a rebuild, and it
|
|
59
|
+
# always reflects the currently configured provider.
|
|
60
|
+
def logger
|
|
61
|
+
::OpenTelemetry.logger_provider.logger(:name => "appsignal-logger")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/appsignal/logger.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "logger"
|
|
4
|
-
require "set"
|
|
5
4
|
|
|
6
5
|
module Appsignal
|
|
7
6
|
# Logger that flushes logs to the AppSignal logging service.
|
|
@@ -55,6 +54,8 @@ module Appsignal
|
|
|
55
54
|
# @!visibility private
|
|
56
55
|
AUTODETECT = 3
|
|
57
56
|
# @!visibility private
|
|
57
|
+
FORMATS = [PLAINTEXT, LOGFMT, JSON, AUTODETECT].freeze
|
|
58
|
+
# @!visibility private
|
|
58
59
|
SEVERITY_MAP = {
|
|
59
60
|
DEBUG => 2,
|
|
60
61
|
INFO => 3,
|
|
@@ -83,7 +84,7 @@ module Appsignal
|
|
|
83
84
|
@group = group
|
|
84
85
|
@level = level
|
|
85
86
|
@silenced = false
|
|
86
|
-
@format = format
|
|
87
|
+
@format = validated_format(format)
|
|
87
88
|
@mutex = Mutex.new
|
|
88
89
|
@default_attributes = attributes
|
|
89
90
|
@appsignal_attributes = attributes
|
|
@@ -145,13 +146,7 @@ module Appsignal
|
|
|
145
146
|
|
|
146
147
|
message = formatter.call(severity, Time.now, group, message) if formatter
|
|
147
148
|
|
|
148
|
-
Appsignal::
|
|
149
|
-
group,
|
|
150
|
-
SEVERITY_MAP.fetch(severity, 0),
|
|
151
|
-
@format,
|
|
152
|
-
message.to_s,
|
|
153
|
-
Appsignal::Utils::Data.generate(appsignal_attributes)
|
|
154
|
-
)
|
|
149
|
+
Appsignal::Backends.logger.emit(group, severity, @format, message.to_s, appsignal_attributes)
|
|
155
150
|
|
|
156
151
|
false
|
|
157
152
|
end
|
|
@@ -299,5 +294,14 @@ module Appsignal
|
|
|
299
294
|
ensure
|
|
300
295
|
@appsignal_attributes = default_attributes
|
|
301
296
|
end
|
|
297
|
+
|
|
298
|
+
def validated_format(format)
|
|
299
|
+
return format if FORMATS.include?(format)
|
|
300
|
+
|
|
301
|
+
Appsignal.internal_logger.warn(
|
|
302
|
+
"Unknown Appsignal::Logger format #{format.inspect}; falling back to AUTODETECT"
|
|
303
|
+
)
|
|
304
|
+
AUTODETECT
|
|
305
|
+
end
|
|
302
306
|
end
|
|
303
307
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Appsignal
|
|
4
|
+
module Metrics
|
|
5
|
+
# @!visibility private
|
|
6
|
+
#
|
|
7
|
+
# Routes custom metric helper calls through the AppSignal C-extension,
|
|
8
|
+
# which forwards them to the agent. This is the default backend used
|
|
9
|
+
# when collector mode is not active.
|
|
10
|
+
module ExtensionBackend
|
|
11
|
+
class << self
|
|
12
|
+
def set_gauge(name, value, tags)
|
|
13
|
+
Appsignal::Extension.set_gauge(
|
|
14
|
+
name.to_s,
|
|
15
|
+
value.to_f,
|
|
16
|
+
Appsignal::Utils::Data.generate(tags)
|
|
17
|
+
)
|
|
18
|
+
rescue RangeError
|
|
19
|
+
Appsignal.internal_logger
|
|
20
|
+
.warn("The gauge value '#{value}' for metric '#{name}' is too big")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def increment_counter(name, value, tags)
|
|
24
|
+
Appsignal::Extension.increment_counter(
|
|
25
|
+
name.to_s,
|
|
26
|
+
value.to_f,
|
|
27
|
+
Appsignal::Utils::Data.generate(tags)
|
|
28
|
+
)
|
|
29
|
+
rescue RangeError
|
|
30
|
+
Appsignal.internal_logger
|
|
31
|
+
.warn("The counter value '#{value}' for metric '#{name}' is too big")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def add_distribution_value(name, value, tags)
|
|
35
|
+
Appsignal::Extension.add_distribution_value(
|
|
36
|
+
name.to_s,
|
|
37
|
+
value.to_f,
|
|
38
|
+
Appsignal::Utils::Data.generate(tags)
|
|
39
|
+
)
|
|
40
|
+
rescue RangeError
|
|
41
|
+
Appsignal.internal_logger
|
|
42
|
+
.warn("The distribution value '#{value}' for metric '#{name}' is too big")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|