opentelemetry-instrumentation-active_job 0.6.0 → 0.7.0
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 +14 -0
- data/README.md +58 -0
- data/lib/opentelemetry/instrumentation/active_job/handlers/default.rb +114 -0
- data/lib/opentelemetry/instrumentation/active_job/handlers/enqueue.rb +40 -0
- data/lib/opentelemetry/instrumentation/active_job/handlers/perform.rb +68 -0
- data/lib/opentelemetry/instrumentation/active_job/handlers.rb +73 -0
- data/lib/opentelemetry/instrumentation/active_job/instrumentation.rb +6 -5
- data/lib/opentelemetry/instrumentation/active_job/mappers/attribute.rb +43 -0
- data/lib/opentelemetry/instrumentation/active_job/patches/base.rb +17 -6
- data/lib/opentelemetry/instrumentation/active_job/version.rb +1 -1
- data/lib/opentelemetry/instrumentation/active_job.rb +33 -0
- metadata +13 -23
- data/lib/opentelemetry/instrumentation/active_job/patches/active_job_callbacks.rb +0 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: beffc5272c390fbbd9ea98797106691f5e48d45b4f82cbd41c40856e051bab2b
|
4
|
+
data.tar.gz: 98a1656bebf82443f7ba8a7fd6037f05c260ba41d2430fd8376e7ebe073ef113
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49516a6e6edc52fc8c431d3ee99c707e61c4c5437fb3c3c4fe13658e544528a0299ba645e8f5d413dc6bb2297d0d2d5d792b1421ce639aa339ac3aaecc739207
|
7
|
+
data.tar.gz: dc85ceb2997eccf9557fd22b25f90fe78892c0663b6d19ce80355d0b19721970b13a9ce861dc38b26e360eee484601dc7cc93d4c6dd45a842a7f3ef3d6286f5c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Release History: opentelemetry-instrumentation-active_job
|
2
2
|
|
3
|
+
### v0.7.0 / 2023-11-22
|
4
|
+
|
5
|
+
* BREAKING CHANGE: Drop Rails 6.0 EOL
|
6
|
+
|
7
|
+
* ADDED: Drop Rails 6.0 EOL
|
8
|
+
|
9
|
+
* BREAKING CHANGE: Use ActiveSupport Instrumentation instead of Monkey Patches
|
10
|
+
|
11
|
+
* CHANGED: Use ActiveSupport Instrumentation instead of Money Patches [#677](https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/677)
|
12
|
+
|
13
|
+
### v0.6.1 / 2023-10-16
|
14
|
+
|
15
|
+
* FIXED: Add Rails 7.1 compatibility
|
16
|
+
|
3
17
|
### v0.6.0 / 2023-09-07
|
4
18
|
|
5
19
|
* FIXED: Align messaging instrumentation operation names
|
data/README.md
CHANGED
@@ -30,6 +30,64 @@ OpenTelemetry::SDK.configure do |c|
|
|
30
30
|
end
|
31
31
|
```
|
32
32
|
|
33
|
+
## Active Support Instrumentation
|
34
|
+
|
35
|
+
Earlier versions of this instrumentation relied on registering custom `around_perform` hooks in order to deal with limitations
|
36
|
+
in `ActiveSupport::Notifications`, however those patches resulted in error reports and inconsistent behavior when combined with other gems.
|
37
|
+
|
38
|
+
This instrumentation now relies entirely on `ActiveSupport::Notifications` and registers a custom Subscriber that listens to relevant events to report as spans.
|
39
|
+
|
40
|
+
See the table below for details of what [Rails Framework Hook Events](https://guides.rubyonrails.org/active_support_instrumentation.html#active-job) are recorded by this instrumentation:
|
41
|
+
|
42
|
+
| Event Name | Creates Span? | Notes |
|
43
|
+
| - | - | - |
|
44
|
+
| `enqueue_at.active_job` | :white_check_mark: | Creates an egress span with kind `producer` |
|
45
|
+
| `enqueue.active_job` | :white_check_mark: | Creates an egress span with kind `producer` |
|
46
|
+
| `enqueue_retry.active_job` | :white_check_mark: | Creates an `internal` span |
|
47
|
+
| `perform_start.active_job` | :x: | This is invoked prior to the appropriate ingress point and is therefore ignored |
|
48
|
+
| `perform.active_job` | :white_check_mark: | Creates an ingress span with kind `consumer` |
|
49
|
+
| `retry_stopped.active_job` | :white_check_mark: | Creates and `internal` span with an `exception` event |
|
50
|
+
| `discard.active_job` | :white_check_mark: | Creates and `internal` span with an `exception` event |
|
51
|
+
|
52
|
+
## Semantic Conventions
|
53
|
+
|
54
|
+
This instrumentation generally uses [Messaging semantic conventions](https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/) by treating job enqueuers as `producers` and workers as `consumers`.
|
55
|
+
|
56
|
+
Internal spans are named using the name of the `ActiveSupport` event that was provided.
|
57
|
+
|
58
|
+
Attributes that are specific to this instrumentation are recorded under `messaging.active_job.*`:
|
59
|
+
|
60
|
+
| Attribute Name | Type | Notes |
|
61
|
+
| - | - | - |
|
62
|
+
| `code.namespace` | String | `ActiveJob` class name |
|
63
|
+
| `messaging.system` | String | Static value set to `active_job` |
|
64
|
+
| `messaging.destination` | String | Set from `ActiveJob#queue_name` |
|
65
|
+
| `messaging.message.id` | String | Set from `ActiveJob#job_id` |
|
66
|
+
| `messaging.active_job.adapter.name` | String | The name of the `ActiveJob` adapter implementation |
|
67
|
+
| `messaging.active_job.message.priority` | String | Present when set by the client from `ActiveJob#priority` |
|
68
|
+
| `messaging.active_job.message.provider_job_id` | String | Present if the underlying adapter has backend specific message ids |
|
69
|
+
|
70
|
+
## Differences between ActiveJob versions
|
71
|
+
|
72
|
+
### ActiveJob 6.1
|
73
|
+
|
74
|
+
`perform.active_job` events do not include timings for `ActiveJob` callbacks therefore time spent in `before` and `after` hooks will be missing
|
75
|
+
|
76
|
+
### ActiveJob 7+
|
77
|
+
|
78
|
+
`perform.active_job` no longer includes exceptions handled using `rescue_from` in the payload.
|
79
|
+
|
80
|
+
In order to preserve this behavior you will have to update the span yourself, e.g.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
rescue_from MyCustomError do |e|
|
84
|
+
# Custom code to handle the error
|
85
|
+
span = OpenTelemetry::Instrumentation::ActiveJob.current_span
|
86
|
+
span.record_exception(e)
|
87
|
+
span.status = OpenTelemetry::Trace::Status.error('Job failed')
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
33
91
|
## Examples
|
34
92
|
|
35
93
|
Example usage can be seen in the `./example/active_job.rb` file [here](https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/instrumentation/active_job/example/active_job.rb)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Instrumentation
|
9
|
+
module ActiveJob
|
10
|
+
module Handlers
|
11
|
+
# Default handler to create internal spans for events
|
12
|
+
# This class provides default template methods that derived classes may override to generate spans and register contexts.
|
13
|
+
class Default
|
14
|
+
# @param parent_span_provider [Object] provides access to the top most parent span (usually the ingress span)
|
15
|
+
# @param mapper [Callable] converts ActiveSupport::Notifications payloads to span attributes
|
16
|
+
# @param config [Hash] of instrumentation options
|
17
|
+
def initialize(parent_span_provider, mapper, config)
|
18
|
+
@mapper = mapper
|
19
|
+
@config = config
|
20
|
+
@parent_span_provider = parent_span_provider
|
21
|
+
end
|
22
|
+
|
23
|
+
# Invoked by ActiveSupport::Notifications at the start of the instrumentation block
|
24
|
+
# It amends the otel context of a Span and Context tokens to the payload
|
25
|
+
#
|
26
|
+
# @param name [String] of the Event
|
27
|
+
# @param id [String] of the event
|
28
|
+
# @param payload [Hash] containing job run information
|
29
|
+
# @return [Hash] the payload passed as a method argument
|
30
|
+
def start(name, id, payload)
|
31
|
+
payload.merge!(__otel: start_span(name, id, payload))
|
32
|
+
rescue StandardError => e
|
33
|
+
OpenTelemetry.handle_error(exception: e)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a span and registers it with the current context
|
37
|
+
#
|
38
|
+
# @param name [String] of the Event
|
39
|
+
# @param id [String] of the event
|
40
|
+
# @param payload [Hash] containing job run information
|
41
|
+
# @return [Hash] with the span and generated context tokens
|
42
|
+
def start_span(name, _id, payload)
|
43
|
+
span = tracer.start_span(name, attributes: @mapper.call(payload))
|
44
|
+
tokens = [OpenTelemetry::Context.attach(OpenTelemetry::Trace.context_with_span(span))]
|
45
|
+
|
46
|
+
{ span: span, ctx_tokens: tokens }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Creates a span and registers it with the current context
|
50
|
+
#
|
51
|
+
# @param _name [String] of the Event (unused)
|
52
|
+
# @param _id [String] of the event (unused)
|
53
|
+
# @param payload [Hash] containing job run information
|
54
|
+
# @return [Hash] with the span and generated context tokens
|
55
|
+
def finish(_name, _id, payload)
|
56
|
+
otel = payload.delete(:__otel)
|
57
|
+
span = otel&.fetch(:span)
|
58
|
+
tokens = otel&.fetch(:ctx_tokens)
|
59
|
+
|
60
|
+
on_exception((payload[:error] || payload[:exception_object]), span)
|
61
|
+
rescue StandardError => e
|
62
|
+
OpenTelemetry.handle_error(exception: e)
|
63
|
+
ensure
|
64
|
+
finish_span(span, tokens)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Finishes the provided spans and also detaches the associated contexts
|
68
|
+
#
|
69
|
+
# @param span [OpenTelemetry::Trace::Span]
|
70
|
+
# @param tokens [Array] to unregister
|
71
|
+
def finish_span(span, tokens)
|
72
|
+
# closes the span after all attributes have been finalized
|
73
|
+
begin
|
74
|
+
if span&.recording?
|
75
|
+
span.status = OpenTelemetry::Trace::Status.ok if span.status.code == OpenTelemetry::Trace::Status::UNSET
|
76
|
+
span.finish
|
77
|
+
end
|
78
|
+
rescue StandardError => e
|
79
|
+
OpenTelemetry.handle_error(exception: e)
|
80
|
+
end
|
81
|
+
|
82
|
+
# pops the context stack
|
83
|
+
tokens&.reverse&.each do |token|
|
84
|
+
OpenTelemetry::Context.detach(token)
|
85
|
+
rescue StandardError => e
|
86
|
+
OpenTelemetry.handle_error(exception: e)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Records exceptions on spans and sets Span statuses to `Error`
|
91
|
+
#
|
92
|
+
# Handled exceptions are recorded on internal spans related to the event. E.g. `discard` events are recorded on the `discard.active_job` span
|
93
|
+
# Handled exceptions _are not_ copied to the ingress span, but it does set the status to `Error` making it easier to know that a job has failed
|
94
|
+
# Unhandled exceptions bubble up to the ingress span and are recorded there.
|
95
|
+
#
|
96
|
+
# @param [Exception] exception to report as a Span Event
|
97
|
+
# @param [OpenTelemetry::Trace::Span] the currently active span used to record the exception and set the status
|
98
|
+
def on_exception(exception, span)
|
99
|
+
return unless exception && span
|
100
|
+
|
101
|
+
span.record_exception(exception)
|
102
|
+
span.status =
|
103
|
+
@parent_span_provider.current_span.status =
|
104
|
+
OpenTelemetry::Trace::Status.error("Unexpected ActiveJob Error #{exception.class.name}")
|
105
|
+
end
|
106
|
+
|
107
|
+
def tracer
|
108
|
+
OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance.tracer
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Instrumentation
|
9
|
+
module ActiveJob
|
10
|
+
module Handlers
|
11
|
+
# Handles `enqueue.active_job` and `enqueue_at.active_job` to generate egress spans
|
12
|
+
class Enqueue < Default
|
13
|
+
def initialize(...)
|
14
|
+
super
|
15
|
+
@span_name_formatter = if @config[:span_naming] == :job_class
|
16
|
+
->(job) { "#{job.class.name} publish" }
|
17
|
+
else
|
18
|
+
->(job) { "#{job.queue_name} publish" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Overrides the `Default#start_span` method to create an egress span
|
23
|
+
# and registers it with the current context
|
24
|
+
#
|
25
|
+
# @param name [String] of the Event
|
26
|
+
# @param id [String] of the event
|
27
|
+
# @param payload [Hash] containing job run information
|
28
|
+
# @return [Hash] with the span and generated context tokens
|
29
|
+
def start_span(name, _id, payload)
|
30
|
+
job = payload.fetch(:job)
|
31
|
+
span = tracer.start_span(@span_name_formatter.call(job), kind: :producer, attributes: @mapper.call(payload))
|
32
|
+
tokens = [OpenTelemetry::Context.attach(OpenTelemetry::Trace.context_with_span(span))]
|
33
|
+
OpenTelemetry.propagation.inject(job.__otel_headers) # This must be transmitted over the wire
|
34
|
+
{ span: span, ctx_tokens: tokens }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Instrumentation
|
9
|
+
module ActiveJob
|
10
|
+
module Handlers
|
11
|
+
# Handles perform.active_job to generate ingress spans
|
12
|
+
class Perform < Default
|
13
|
+
def initialize(...)
|
14
|
+
super
|
15
|
+
@span_name_formatter = if @config[:span_naming] == :job_class
|
16
|
+
->(job) { "#{job.class.name} process" }
|
17
|
+
else
|
18
|
+
->(job) { "#{job.queue_name} process" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Overrides the `Default#start_span` method to create an ingress span
|
23
|
+
# and registers it with the current context
|
24
|
+
#
|
25
|
+
# @param name [String] of the Event
|
26
|
+
# @param id [String] of the event
|
27
|
+
# @param payload [Hash] containing job run information
|
28
|
+
# @return [Hash] with the span and generated context tokens
|
29
|
+
def start_span(name, _id, payload)
|
30
|
+
tokens = []
|
31
|
+
job = payload.fetch(:job)
|
32
|
+
parent_context = OpenTelemetry.propagation.extract(job.__otel_headers)
|
33
|
+
|
34
|
+
span_name = @span_name_formatter.call(job)
|
35
|
+
|
36
|
+
# TODO: Refactor into a propagation strategy
|
37
|
+
propagation_style = @config[:propagation_style]
|
38
|
+
if propagation_style == :child
|
39
|
+
tokens << OpenTelemetry::Context.attach(parent_context)
|
40
|
+
span = tracer.start_span(span_name, kind: :consumer, attributes: @mapper.call(payload))
|
41
|
+
else
|
42
|
+
span_context = OpenTelemetry::Trace.current_span(parent_context).context
|
43
|
+
links = [OpenTelemetry::Trace::Link.new(span_context)] if span_context.valid? && propagation_style == :link
|
44
|
+
span = tracer.start_root_span(span_name, kind: :consumer, attributes: @mapper.call(payload), links: links)
|
45
|
+
end
|
46
|
+
|
47
|
+
tokens.concat(attach_consumer_context(span))
|
48
|
+
|
49
|
+
{ span: span, ctx_tokens: tokens }
|
50
|
+
end
|
51
|
+
|
52
|
+
# This method attaches a span to multiple contexts:
|
53
|
+
# 1. Registers the ingress span as the top level ActiveJob span.
|
54
|
+
# This is used later to enrich the ingress span in children, e.g. setting span status to error when a child event like `discard` terminates due to an error
|
55
|
+
# 2. Registers the ingress span as the "active" span, which is the default behavior of the SDK.
|
56
|
+
# @param span [OpenTelemetry::Trace::Span] the currently active span used to record the exception and set the status
|
57
|
+
# @return [Array] Context tokens that must be detached when finished
|
58
|
+
def attach_consumer_context(span)
|
59
|
+
consumer_context = OpenTelemetry::Trace.context_with_span(span)
|
60
|
+
internal_context = OpenTelemetry::Instrumentation::ActiveJob.context_with_span(span, parent_context: consumer_context)
|
61
|
+
|
62
|
+
[consumer_context, internal_context].map { |context| OpenTelemetry::Context.attach(context) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
require_relative 'mappers/attribute'
|
8
|
+
require_relative 'handlers/default'
|
9
|
+
require_relative 'handlers/enqueue'
|
10
|
+
require_relative 'handlers/perform'
|
11
|
+
|
12
|
+
module OpenTelemetry
|
13
|
+
module Instrumentation
|
14
|
+
module ActiveJob
|
15
|
+
# Module that contains custom event handlers, which are used to generate spans per event
|
16
|
+
module Handlers
|
17
|
+
module_function
|
18
|
+
|
19
|
+
# Subscribes Event Handlers to relevant ActiveJob notifications
|
20
|
+
#
|
21
|
+
# The following events are recorded as spans:
|
22
|
+
# - enqueue
|
23
|
+
# - enqueue_at
|
24
|
+
# - enqueue_retry
|
25
|
+
# - perform
|
26
|
+
# - retry_stopped
|
27
|
+
# - discard
|
28
|
+
#
|
29
|
+
# Ingress and Egress spans (perform, enqueue, enqueue_at) use Messaging semantic conventions for naming the span,
|
30
|
+
# while internal spans keep their ActiveSupport event name.
|
31
|
+
#
|
32
|
+
# @note this method is not thread safe and should not be used in a multi-threaded context
|
33
|
+
# @note Why no perform_start?
|
34
|
+
# This event causes much heartache as it is the first in a series of events that is triggered.
|
35
|
+
# It should not be the ingress span because it does not measure anything.
|
36
|
+
# https://github.com/rails/rails/blob/v6.1.7.6/activejob/lib/active_job/instrumentation.rb#L14
|
37
|
+
# https://github.com/rails/rails/blob/v7.0.8/activejob/lib/active_job/instrumentation.rb#L19
|
38
|
+
def subscribe
|
39
|
+
return unless Array(@subscriptions).empty?
|
40
|
+
|
41
|
+
mapper = Mappers::Attribute.new
|
42
|
+
config = ActiveJob::Instrumentation.instance.config
|
43
|
+
parent_span_provider = OpenTelemetry::Instrumentation::ActiveJob
|
44
|
+
|
45
|
+
# TODO, use delegation instead of inheritance
|
46
|
+
default_handler = Handlers::Default.new(parent_span_provider, mapper, config)
|
47
|
+
enqueue_handler = Handlers::Enqueue.new(parent_span_provider, mapper, config)
|
48
|
+
perform_handler = Handlers::Perform.new(parent_span_provider, mapper, config)
|
49
|
+
|
50
|
+
handlers_by_pattern = {
|
51
|
+
'enqueue' => enqueue_handler,
|
52
|
+
'enqueue_at' => enqueue_handler,
|
53
|
+
'enqueue_retry' => default_handler,
|
54
|
+
'perform' => perform_handler,
|
55
|
+
'retry_stopped' => default_handler,
|
56
|
+
'discard' => default_handler
|
57
|
+
}
|
58
|
+
|
59
|
+
@subscriptions = handlers_by_pattern.map do |key, handler|
|
60
|
+
::ActiveSupport::Notifications.subscribe("#{key}.active_job", handler)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Removes Event Handler Subscriptions for ActiveJob notifications
|
65
|
+
# @note this method is not thread-safe and should not be used in a multi-threaded context
|
66
|
+
def unsubscribe
|
67
|
+
@subscriptions&.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) }
|
68
|
+
@subscriptions = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -9,7 +9,7 @@ module OpenTelemetry
|
|
9
9
|
module ActiveJob
|
10
10
|
# The Instrumentation class contains logic to detect and install the ActiveJob instrumentation
|
11
11
|
class Instrumentation < OpenTelemetry::Instrumentation::Base
|
12
|
-
MINIMUM_VERSION = Gem::Version.new('6.
|
12
|
+
MINIMUM_VERSION = Gem::Version.new('6.1.0')
|
13
13
|
|
14
14
|
install do |_config|
|
15
15
|
require_dependencies
|
@@ -17,7 +17,7 @@ module OpenTelemetry
|
|
17
17
|
end
|
18
18
|
|
19
19
|
present do
|
20
|
-
defined?(::ActiveJob)
|
20
|
+
defined?(::ActiveJob) && defined?(::ActiveSupport)
|
21
21
|
end
|
22
22
|
|
23
23
|
compatible do
|
@@ -64,12 +64,13 @@ module OpenTelemetry
|
|
64
64
|
|
65
65
|
def require_dependencies
|
66
66
|
require_relative 'patches/base'
|
67
|
-
require_relative '
|
67
|
+
require_relative 'handlers'
|
68
68
|
end
|
69
69
|
|
70
70
|
def patch_activejob
|
71
|
-
::ActiveJob::Base.prepend(Patches::Base)
|
72
|
-
|
71
|
+
::ActiveJob::Base.prepend(Patches::Base) unless ::ActiveJob::Base.ancestors.include?(Patches::Base)
|
72
|
+
|
73
|
+
Handlers.subscribe
|
73
74
|
end
|
74
75
|
end
|
75
76
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright The OpenTelemetry Authors
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: Apache-2.0
|
6
|
+
|
7
|
+
module OpenTelemetry
|
8
|
+
module Instrumentation
|
9
|
+
module ActiveJob
|
10
|
+
module Mappers
|
11
|
+
# Maps ActiveJob Attributes to Semantic Conventions
|
12
|
+
class Attribute
|
13
|
+
# Generates a set of attributes to add to a span using
|
14
|
+
# general and messaging semantic conventions as well as
|
15
|
+
# using `rails.active_job.*` namespace for custom attributes
|
16
|
+
#
|
17
|
+
# @param payload [Hash] of an ActiveSupport::Notifications payload
|
18
|
+
# @return [Hash<String, Object>] of semantic attributes
|
19
|
+
def call(payload)
|
20
|
+
job = payload.fetch(:job)
|
21
|
+
|
22
|
+
otel_attributes = {
|
23
|
+
'code.namespace' => job.class.name,
|
24
|
+
'messaging.system' => 'active_job',
|
25
|
+
'messaging.destination' => job.queue_name,
|
26
|
+
'messaging.message.id' => job.job_id,
|
27
|
+
'messaging.active_job.adapter.name' => job.class.queue_adapter_name
|
28
|
+
}
|
29
|
+
|
30
|
+
# Not all adapters generate or provide back end specific ids for messages
|
31
|
+
otel_attributes['messaging.active_job.message.provider_job_id'] = job.provider_job_id.to_s if job.provider_job_id
|
32
|
+
# This can be problematic if programs use invalid attribute types like Symbols for priority instead of using Integers.
|
33
|
+
otel_attributes['messaging.active_job.message.priority'] = job.priority.to_s if job.priority
|
34
|
+
|
35
|
+
otel_attributes.compact!
|
36
|
+
|
37
|
+
otel_attributes
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -12,22 +12,33 @@ module OpenTelemetry
|
|
12
12
|
module Base
|
13
13
|
def self.prepended(base)
|
14
14
|
base.class_eval do
|
15
|
-
attr_accessor :
|
15
|
+
attr_accessor :__otel_headers
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
def initialize(
|
20
|
-
@
|
19
|
+
def initialize(...)
|
20
|
+
@__otel_headers = {}
|
21
21
|
super
|
22
22
|
end
|
23
|
-
ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
|
24
23
|
|
25
24
|
def serialize
|
26
|
-
|
25
|
+
message = super
|
26
|
+
|
27
|
+
begin
|
28
|
+
message.merge!('__otel_headers' => serialize_arguments(@__otel_headers))
|
29
|
+
rescue StandardError => e
|
30
|
+
OpenTelemetry.handle_error(exception: e)
|
31
|
+
end
|
32
|
+
|
33
|
+
message
|
27
34
|
end
|
28
35
|
|
29
36
|
def deserialize(job_data)
|
30
|
-
|
37
|
+
begin
|
38
|
+
@__otel_headers = deserialize_arguments(job_data.delete('__otel_headers') || []).to_h
|
39
|
+
rescue StandardError => e
|
40
|
+
OpenTelemetry.handle_error(exception: e)
|
41
|
+
end
|
31
42
|
super
|
32
43
|
end
|
33
44
|
end
|
@@ -11,6 +11,39 @@ module OpenTelemetry
|
|
11
11
|
module Instrumentation
|
12
12
|
# Contains the OpenTelemetry instrumentation for the ActiveJob gem
|
13
13
|
module ActiveJob
|
14
|
+
extend self
|
15
|
+
|
16
|
+
CURRENT_SPAN_KEY = Context.create_key('current-span')
|
17
|
+
private_constant :CURRENT_SPAN_KEY
|
18
|
+
|
19
|
+
# Returns the current span from the current or provided context
|
20
|
+
#
|
21
|
+
# @param [optional Context] context The context to lookup the current
|
22
|
+
# {Span} from. Defaults to Context.current
|
23
|
+
def current_span(context = nil)
|
24
|
+
context ||= Context.current
|
25
|
+
context.value(CURRENT_SPAN_KEY) || OpenTelemetry::Trace::Span::INVALID
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a context containing the span, derived from the optional parent
|
29
|
+
# context, or the current context if one was not provided.
|
30
|
+
#
|
31
|
+
# @param [optional Context] context The context to use as the parent for
|
32
|
+
# the returned context
|
33
|
+
def context_with_span(span, parent_context: Context.current)
|
34
|
+
parent_context.set_value(CURRENT_SPAN_KEY, span)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Activates/deactivates the Span within the current Context, which makes the "current span"
|
38
|
+
# available implicitly.
|
39
|
+
#
|
40
|
+
# On exit, the Span that was active before calling this method will be reactivated.
|
41
|
+
#
|
42
|
+
# @param [Span] span the span to activate
|
43
|
+
# @yield [span, context] yields span and a context containing the span to the block.
|
44
|
+
def with_span(span)
|
45
|
+
Context.with_value(CURRENT_SPAN_KEY, span) { |c, s| yield s, c }
|
46
|
+
end
|
14
47
|
end
|
15
48
|
end
|
16
49
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opentelemetry-instrumentation-active_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OpenTelemetry Authors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opentelemetry-api
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 6.
|
47
|
+
version: '6.1'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 6.
|
54
|
+
version: '6.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: appraisal
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,20 +122,6 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0.3'
|
125
|
-
- !ruby/object:Gem::Dependency
|
126
|
-
name: pry
|
127
|
-
requirement: !ruby/object:Gem::Requirement
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
version: '0'
|
132
|
-
type: :development
|
133
|
-
prerelease: false
|
134
|
-
version_requirements: !ruby/object:Gem::Requirement
|
135
|
-
requirements:
|
136
|
-
- - ">="
|
137
|
-
- !ruby/object:Gem::Version
|
138
|
-
version: '0'
|
139
125
|
- !ruby/object:Gem::Dependency
|
140
126
|
name: rake
|
141
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,14 +170,14 @@ dependencies:
|
|
184
170
|
requirements:
|
185
171
|
- - "~>"
|
186
172
|
- !ruby/object:Gem::Version
|
187
|
-
version: 3.
|
173
|
+
version: '3.19'
|
188
174
|
type: :development
|
189
175
|
prerelease: false
|
190
176
|
version_requirements: !ruby/object:Gem::Requirement
|
191
177
|
requirements:
|
192
178
|
- - "~>"
|
193
179
|
- !ruby/object:Gem::Version
|
194
|
-
version: 3.
|
180
|
+
version: '3.19'
|
195
181
|
- !ruby/object:Gem::Dependency
|
196
182
|
name: yard
|
197
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -220,18 +206,22 @@ files:
|
|
220
206
|
- lib/opentelemetry-instrumentation-active_job.rb
|
221
207
|
- lib/opentelemetry/instrumentation.rb
|
222
208
|
- lib/opentelemetry/instrumentation/active_job.rb
|
209
|
+
- lib/opentelemetry/instrumentation/active_job/handlers.rb
|
210
|
+
- lib/opentelemetry/instrumentation/active_job/handlers/default.rb
|
211
|
+
- lib/opentelemetry/instrumentation/active_job/handlers/enqueue.rb
|
212
|
+
- lib/opentelemetry/instrumentation/active_job/handlers/perform.rb
|
223
213
|
- lib/opentelemetry/instrumentation/active_job/instrumentation.rb
|
224
|
-
- lib/opentelemetry/instrumentation/active_job/
|
214
|
+
- lib/opentelemetry/instrumentation/active_job/mappers/attribute.rb
|
225
215
|
- lib/opentelemetry/instrumentation/active_job/patches/base.rb
|
226
216
|
- lib/opentelemetry/instrumentation/active_job/version.rb
|
227
217
|
homepage: https://github.com/open-telemetry/opentelemetry-ruby-contrib
|
228
218
|
licenses:
|
229
219
|
- Apache-2.0
|
230
220
|
metadata:
|
231
|
-
changelog_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-active_job/0.
|
221
|
+
changelog_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-active_job/0.7.0/file/CHANGELOG.md
|
232
222
|
source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/active_job
|
233
223
|
bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues
|
234
|
-
documentation_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-active_job/0.
|
224
|
+
documentation_uri: https://rubydoc.info/gems/opentelemetry-instrumentation-active_job/0.7.0
|
235
225
|
post_install_message:
|
236
226
|
rdoc_options: []
|
237
227
|
require_paths:
|
@@ -1,96 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright The OpenTelemetry Authors
|
4
|
-
#
|
5
|
-
# SPDX-License-Identifier: Apache-2.0
|
6
|
-
|
7
|
-
module OpenTelemetry
|
8
|
-
module Instrumentation
|
9
|
-
module ActiveJob
|
10
|
-
module Patches
|
11
|
-
# Module to prepend to ActiveJob::Base for instrumentation.
|
12
|
-
module ActiveJobCallbacks
|
13
|
-
def self.prepended(base)
|
14
|
-
base.class_eval do
|
15
|
-
around_enqueue do |job, block|
|
16
|
-
span_kind = job.class.queue_adapter_name == 'inline' ? :client : :producer
|
17
|
-
span_name = "#{otel_config[:span_naming] == :job_class ? job.class : job.queue_name} publish"
|
18
|
-
span_attributes = job_attributes(job)
|
19
|
-
otel_tracer.in_span(span_name, attributes: span_attributes, kind: span_kind) do
|
20
|
-
OpenTelemetry.propagation.inject(job.metadata)
|
21
|
-
block.call
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def perform_now
|
28
|
-
span_kind = self.class.queue_adapter_name == 'inline' ? :server : :consumer
|
29
|
-
span_name = "#{otel_config[:span_naming] == :job_class ? self.class : queue_name} process"
|
30
|
-
span_attributes = job_attributes(self).merge('messaging.operation' => 'process', 'code.function' => 'perform_now')
|
31
|
-
executions_count = (executions || 0) + 1 # because we run before the count is incremented in ActiveJob::Execution
|
32
|
-
|
33
|
-
extracted_context = OpenTelemetry.propagation.extract(metadata)
|
34
|
-
OpenTelemetry::Context.with_current(extracted_context) do
|
35
|
-
if otel_config[:propagation_style] == :child
|
36
|
-
otel_tracer.in_span(span_name, attributes: span_attributes, kind: span_kind) do |span|
|
37
|
-
span.set_attribute('messaging.active_job.executions', executions_count)
|
38
|
-
super
|
39
|
-
end
|
40
|
-
else
|
41
|
-
span_links = []
|
42
|
-
if otel_config[:propagation_style] == :link
|
43
|
-
span_context = OpenTelemetry::Trace.current_span(extracted_context).context
|
44
|
-
span_links << OpenTelemetry::Trace::Link.new(span_context) if span_context.valid?
|
45
|
-
end
|
46
|
-
|
47
|
-
root_span = otel_tracer.start_root_span(span_name, attributes: span_attributes, links: span_links, kind: span_kind)
|
48
|
-
OpenTelemetry::Trace.with_span(root_span) do |span|
|
49
|
-
span.set_attribute('messaging.active_job.executions', executions_count)
|
50
|
-
super
|
51
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
52
|
-
span.record_exception(e)
|
53
|
-
span.status = OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{e.class}")
|
54
|
-
raise e
|
55
|
-
ensure
|
56
|
-
root_span.finish
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
ensure
|
61
|
-
# We may be in a job system (eg: resque) that forks and kills worker processes often.
|
62
|
-
# We don't want to lose spans by not flushing any span processors, so we optionally force it here.
|
63
|
-
OpenTelemetry.tracer_provider.force_flush if otel_config[:force_flush]
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def job_attributes(job)
|
69
|
-
otel_attributes = {
|
70
|
-
'code.namespace' => job.class.name,
|
71
|
-
'messaging.destination_kind' => 'queue',
|
72
|
-
'messaging.system' => job.class.queue_adapter_name,
|
73
|
-
'messaging.destination' => job.queue_name,
|
74
|
-
'messaging.message_id' => job.job_id,
|
75
|
-
'messaging.active_job.provider_job_id' => job.provider_job_id,
|
76
|
-
'messaging.active_job.scheduled_at' => job.scheduled_at,
|
77
|
-
'messaging.active_job.priority' => job.priority
|
78
|
-
}
|
79
|
-
|
80
|
-
otel_attributes['net.transport'] = 'inproc' if %w[async inline].include?(job.class.queue_adapter_name)
|
81
|
-
|
82
|
-
otel_attributes.compact
|
83
|
-
end
|
84
|
-
|
85
|
-
def otel_tracer
|
86
|
-
ActiveJob::Instrumentation.instance.tracer
|
87
|
-
end
|
88
|
-
|
89
|
-
def otel_config
|
90
|
-
ActiveJob::Instrumentation.instance.config
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|