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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f27d88a247a40e687f3b998919d50f9f95927475bc51b3d315a834057848cc59
4
- data.tar.gz: 759252e7ab2353424e542325506c8c663a60683155f1c13393075ba896bfa31a
3
+ metadata.gz: beffc5272c390fbbd9ea98797106691f5e48d45b4f82cbd41c40856e051bab2b
4
+ data.tar.gz: 98a1656bebf82443f7ba8a7fd6037f05c260ba41d2430fd8376e7ebe073ef113
5
5
  SHA512:
6
- metadata.gz: e55626639d46cab948c41b8e393f1989505f2eac46ef37ceac89bd748d7dee22b3144dd2575431b071b06d6a6e7072b3635775c3f0b6f0aa5096db7125ccde40
7
- data.tar.gz: 49cfc91980e64d0b82919375b2c30735db5da47c44fdd67f8aac18d134f1f3fce7a7fcb580fae5816f0f5bf9b7d18a03faa21ad74e9adbfcd719fcbaa7756bfa
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.0.0')
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 'patches/active_job_callbacks'
67
+ require_relative 'handlers'
68
68
  end
69
69
 
70
70
  def patch_activejob
71
- ::ActiveJob::Base.prepend(Patches::Base)
72
- ::ActiveJob::Base.prepend(Patches::ActiveJobCallbacks)
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 :metadata
15
+ attr_accessor :__otel_headers
16
16
  end
17
17
  end
18
18
 
19
- def initialize(*args)
20
- @metadata = {}
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
- super.merge('metadata' => serialize_arguments(metadata))
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
- self.metadata = deserialize_arguments(job_data['metadata'] || []).to_h
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
@@ -7,7 +7,7 @@
7
7
  module OpenTelemetry
8
8
  module Instrumentation
9
9
  module ActiveJob
10
- VERSION = '0.6.0'
10
+ VERSION = '0.7.0'
11
11
  end
12
12
  end
13
13
  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.6.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-09-07 00:00:00.000000000 Z
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.0.0
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.0.0
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.7.6
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.7.6
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/patches/active_job_callbacks.rb
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.6.0/file/CHANGELOG.md
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.6.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