Skip to content

feat: add gem for auto-instrumentation in otel-operator#1384

Closed
xuan-cao-swi wants to merge 111 commits into
open-telemetry:mainfrom
xuan-cao-swi:auto-instrumentation
Closed

feat: add gem for auto-instrumentation in otel-operator#1384
xuan-cao-swi wants to merge 111 commits into
open-telemetry:mainfrom
xuan-cao-swi:auto-instrumentation

Conversation

@xuan-cao-swi

@xuan-cao-swi xuan-cao-swi commented Jan 30, 2025

Copy link
Copy Markdown
Contributor

Description

This PR aims to contribute a script/gem that can load the necessary OpenTelemetry-related gems and initialize the SDK for the opentelemetry-operator. The script/gem also includes a resource detector as part of its features.

The main idea was inspired by new_relic, which prepends the bundler.require function and places the loading script at the end of the loading procedure. For apps or scripts that don't use Bundler or a Gemfile, more details on variations of app loading can be found in the README. Currently, the goal is to ensure that Rails apps can work, with future improvements in mind.

The reason behind creating this gem is to facilitate easy installation and dependency enforcement. Although for the OpenTelemetry-Operator Node.js, it puts the loading script directly in the src folder (and then copy over to binding volume), it still relies on the auto-instrumentation-node package to install all required packages (e.g., API/SDK and instrumentation packages).

The main purpose of the gem is to serve the OpenTelemetry-Operator, which requires more configuration changes (see the configuration section in the README). Users don't need to modify their Ruby app codebase while using the instrumentation from OpenTelemetry-Ruby, but anyone can still use it in any environment.

The gem name, file structure, and implementation need more suggestions to make them more reasonable. I have tested it with rails and sinatra (through rackup), and I currently still in the process of testing against the opentelemetry operator cluster locally. Once the operator testing done, I will mark the pr as ready.

Related to open-telemetry/opentelemetry-operator#3756

@xuan-cao-swi

Copy link
Copy Markdown
Contributor Author

We don't need to push to make the gem ready/release, but the script file need to get some review first.

@github-actions

Copy link
Copy Markdown
Contributor

👋 This pull request has been marked as stale because it has been open with no activity. You can: comment on the issue or remove the stale label to hold stale off for a while, add the keep label to hold stale off permanently, or do nothing. If you do nothing this pull request will be closed eventually by the stale bot

@github-actions github-actions Bot added the stale Marks an issue/PR stale label Mar 29, 2025
@kaylareopelle kaylareopelle removed the stale Marks an issue/PR stale label Apr 2, 2025
@github-actions

github-actions Bot commented May 3, 2025

Copy link
Copy Markdown
Contributor

👋 This pull request has been marked as stale because it has been open with no activity. You can: comment on the issue or remove the stale label to hold stale off for a while, add the keep label to hold stale off permanently, or do nothing. If you do nothing this pull request will be closed eventually by the stale bot

@github-actions github-actions Bot added the stale Marks an issue/PR stale label May 3, 2025
@thompson-tomo

Copy link
Copy Markdown
Contributor

@xuan-cao-swi there appears to be an issue calculating code coverage https://github.com/open-telemetry/opentelemetry-ruby-contrib/actions/runs/24790433512/job/72546108811#step:4:1 it is getting 0 lines hence classifying it as 100% coverage.

Comment thread packages/auto-instrumentation/lib/opentelemetry-auto-instrumentation.rb Outdated

def self._otel_detect_resource_from_env
env = ENV['OTEL_RUBY_RESOURCE_DETECTORS'].to_s
additional_resource = ::OpenTelemetry::SDK::Resources::Resource.create({})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this have been added by the sdk automatically?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, resource detector is not set for sdk by default; also this function allow user to have option to choose what resource detector to use.

@thompson-tomo thompson-tomo Apr 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have raised open-telemetry/opentelemetry-ruby#2109 to address it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the issue is that the resource is currently created by the SDK when the OpenTelemetry::SDK.configure method is called. Before the auto-instrumentation gem, the configure method needed to be called to start the SDK.

When a new Configurator is initialized, the resource is created.
configure call to create a new Configurator:
https://github.com/open-telemetry/opentelemetry-ruby/blob/559e7a0aacfed2b3f866d469c8f38889038d9bba/sdk/lib/opentelemetry/sdk.rb#L69

@resource assignment to configurator during initialization:
https://github.com/open-telemetry/opentelemetry-ruby/blob/aa6ecce6a15df71a5ad3005450d2d30f7b677348/sdk/lib/opentelemetry/sdk/configurator.rb#L42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, could you clarify what resource need to be reordered?
When using c.resource = new_resource, it will merge the new_resource with the default resource

@arielvalentin arielvalentin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 This review was generated with AI assistance (GitHub Copilot CLI). Source code references were verified against the linked repositories.

I looked at how New Relic, Datadog, Elastic APM, and Honeycomb handle zero-code / auto-instrumentation to see how this approach compares. Overall, the Bundler prepend pattern is well-established — New Relic uses the same approach and it is the only pattern in the Ruby ecosystem that achieves truly zero-code instrumentation.

I have a few observations below. I do not want to block progress on this PR but I would like you to strongly consider these changes before we merge.

Thread Safety

The @_otel_initialized flag is checked without synchronization. In a multi-threaded server like Puma, two threads could both read false and double-initialize the SDK.

Both Elastic APM and Datadog protect their initialization with a Mutex. Would it make sense to add one here?

Hardcoded Instrumentation Map

The OTEL_INSTRUMENTATION_MAP duplicates what the SDK registry already knows. Datadog's approach iterates over a self-registering registry instead of maintaining a parallel map. Since this code already calls c.use_all when no filter is set, could we query the SDK's instrumentation registry for the selective case too? This would avoid the map drifting out of sync as new instrumentations are added.

Code Coverage

@thompson-tomo noted that SimpleCov is reporting 0 lines / 100% coverage. The subprocess fork testing is excellent for isolation but the coverage merging needs to be fixed before we merge.

✅ What I liked

  • The warning about bundled OTel gems is great defensive programming — none of the other APM libraries do this
  • The subprocess fork test architecture is really thorough
  • Following the proven New Relic bootstrap pattern was a good choice

module OTelBundlerPatch
# Nested module to handle OpenTelemetry initialization logic
module OTelInitializer
@_otel_initialized = false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both Elastic APM and Datadog use a Mutex for their initialization guards. Would something like this work here?

@_otel_mutex = Mutex.new
@_otel_initialized = false

def self._otel_require_otel
  @_otel_mutex.synchronize do
    return if @_otel_initialized
    @_otel_initialized = true
    # ... rest of init
  end
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the mutex

@kaylareopelle kaylareopelle May 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xuan-cao-swi - I don't see the mutex. Can you show me where it is?
Disregard -- my PR view was scoped to commits without me realizing it.

module OTelInitializer
@_otel_initialized = false

OTEL_INSTRUMENTATION_MAP = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This map will need to be updated every time a new instrumentation is added. Datadog avoids this by iterating over a self-registering registry. Since we already call c.use_all when no filter is set, could we query OpenTelemetry::Instrumentation.registry for the selective case too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use OpenTelemetry::Instrumentation.registry


# Load OpenTelemetry components and their dependencies
# googleapis-common-protos-types and google-protobuf are dependencies for otlp exporters
loaded_library_file_path = Dir.glob("#{gem_path}/gems/*").select do |file_path|

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This writes to $stdout which could pollute application output or break pipe-based workflows. Other APM libraries use loggers or stderr — Honeycomb, Elastic APM. Should we use warn or $stderr.puts instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use warn

file_path.include?('opentelemetry') ||
file_path.include?('googleapis-common-protos-types') ||
file_path.include?('google-protobuf')
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of thoughts here:

  1. The allowlist only covers OTel + protobuf gems. If any OTel gem has transitive dependencies (e.g., faraday for OTLP HTTP export), they won't be on the load path.
  2. file_path.include?('opentelemetry') is a substring match that could match unintended paths.

New Relic's approach is simpler because they only manage their own gem. Since we need to manage a whole ecosystem here, would it make sense to add ALL gems from the operator's gem path instead of an allowlist? Or could we use Gem.use_paths to properly register the operator gem path?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal here is to prevent gem conflicts.

For example, if the path /otel-auto-instrumentation-ruby includes abc-1.2.6, but the user's gem path (e.g., /home/app/.local/share/gem/ruby/3.3.0) includes abc-1.2.4, a conflict will occur if the user's Gemfile.lock specifies abc-1.2.4 while abc-1.2.6 is already activated.

To address this, I made an update to specifically disable google-protobuf or googleapis-common-protos-types if the user already has these gems installed in their original gem path. This ensures that they are not loaded from otel-auto-instrumentation-ruby, thereby avoiding potential conflicts.

Additionally, if the user wants to use Faraday instrumentation, they must ensure that Faraday is installed in their application.

require 'opentelemetry-exporter-otlp-logs'
require 'opentelemetry-instrumentation-all'

resource_detectors = ENV['OTEL_RUBY_RESOURCE_DETECTORS'].to_s

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String#include? is a substring match — 'my-container-app'.include?('container') would also be true. The _otel_detect_resource_from_env method already handles this correctly by splitting on ,. Should we be consistent here?

Suggested change
resource_detectors = ENV['OTEL_RUBY_RESOURCE_DETECTORS'].to_s
resource_detectors = ENV['OTEL_RUBY_RESOURCE_DETECTORS'].to_s.split(',').map(&:strip)
require 'opentelemetry-resource-detector-container' if resource_detectors.include?('container')
require 'opentelemetry-resource-detector-azure' if resource_detectors.include?('azure')
require 'opentelemetry-resource-detector-aws' if resource_detectors.include?('aws')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

spec.require_paths = ['lib']
spec.required_ruby_version = '>= 3.3'

spec.add_dependency 'opentelemetry-api', '~> 1.9.0'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want these to always use strict versions vs pessimistic versioning to allow for bug fixes in dependencies without requiring a new version of this gem? ~> 1.9.0 only allows patch releases (>= 1.9.0, < 1.10.0). Any minor version bump of the SDK will require a coordinated release of this gem.

Is there going to be a process that bumps these versions when the API and SDK versions are updated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want these to always use strict versions vs pessimistic versioning to allow for bug fixes in dependencies without requiring a new version of this gem?

This doesn't matter in operator's autoinstrumentation image because those are pre-build image (similar to lambda layer).

Is there going to be a process that bumps these versions when the API and SDK versions are updated?

Likely will be picked up by dependabot or renovate

Comment thread packages/auto-instrumentation/lib/opentelemetry-auto-instrumentation.rb Outdated
Comment on lines +58 to +69
def self._otel_detect_resource_from_env
resource_map = {
'container' => (defined?(::OpenTelemetry::Resource::Detector::Container) ? ::OpenTelemetry::Resource::Detector::Container : nil),
'azure' => (defined?(::OpenTelemetry::Resource::Detector::Azure) ? ::OpenTelemetry::Resource::Detector::Azure : nil),
'aws' => (defined?(::OpenTelemetry::Resource::Detector::AWS) ? ::OpenTelemetry::Resource::Detector::AWS : nil)
}

ENV['OTEL_RUBY_RESOURCE_DETECTORS'].to_s.split(',').map(&:strip).reject(&:empty?).reduce(::OpenTelemetry::SDK::Resources::Resource.create({})) do |resource, detector|
detector_class = resource_map[detector]
detector_class ? resource.merge(detector_class.detect) : resource
end
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move this into the resource detectors, ie if disabled return empty resource. This way they benefit & the logic here is simplified as well as ease of adding more resource detectors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean adding disable logic in each resource detector?

Currently there is no env var from specification that can disable individual resource detectors.

OTEL_RUBY_RESOURCE_DETECTORS is only used for the operator and for ease-of-use (i.e. this idea is from js-contrib).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct I am suggesting add it in the detectors with a default state of enabled. only if the env variable is present and the detector not listed is it disabled that way we avoid the maps/lookup process.

@kaylareopelle

Copy link
Copy Markdown
Contributor

We chatted about this during the SIG on 12 May 2026. I'm going to look into what it takes to put this in a new repository and whether we can move this PR with all its history and conversation to a possible new repository. Holding off on merging until I learn about the repository options.

If anyone is opposed to the idea of putting this work in a separate repository, please let me know.

@thompson-tomo

thompson-tomo commented May 14, 2026

Copy link
Copy Markdown
Contributor

I like the idea of having a 3rd repo. My suggestion would be either opentelemetry-ruby-distro (my preference) or maybe opentelemetry-ruby-releases. This would follow the php example https://github.com/open-telemetry/opentelemetry-php-distro.

Within the new repo we could also put the all instrumentation gem & the rails instrumentation gem. That would help us to streamline our release process as it would eliminate the need to be manually bumping dependencies during the release process and instead we can rely on renovate.

My impression is that we will need to merge this PR into contrib and when the new repo is created it is a clone of this repo with the history filtered based on paths. A similar thing was done to split semantic conventions.

My main suggestion for this PR would be to:

  • rename the root folder to auto_instrumentation which would enable us to potentially down the track to have multiple auto instrumentation variants ie aws.
  • name this gem auto_instrumentation-all or contrib that way name makes it clear what is included.

This gives us a structure which can grow to include other distro types ie Faas to ensure they are actively maintained etc as well as achieve consistency.

@thompson-tomo

Copy link
Copy Markdown
Contributor

For reference this third repo pattern could address release pr errors we see in #2358

@xuan-cao-swi

Copy link
Copy Markdown
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci keep Ensures stale-bot keeps this issue/PR open

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants