Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OTel Railtie #1111

Merged
merged 4 commits into from
Mar 23, 2022

Conversation

arielvalentin
Copy link
Contributor

@arielvalentin arielvalentin commented Jan 29, 2022

This gem provides a minimal Railtie to use in lieu of manual configuration.

Operators may customize SDK settings using environment variables but this gem will provide some defaults:

  1. Sets a minimal set of values for OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES when left unset
  2. Users may override SDK configurations using standard OTel SDK environment variables
  3. Users may override Instrumentation libraries are configurable using OTEL_RUBY_*_CONFIG_OPTS

@arielvalentin arielvalentin self-assigned this Jan 29, 2022
@arielvalentin arielvalentin force-pushed the arielvalentin/railtie branch 2 times, most recently from e1204ce to 71ba3ed Compare February 4, 2022 23:43
@arielvalentin
Copy link
Contributor Author

@SomalianIvan @robertlaurin This is rather perplexing and I am curious if you all have run into this error if the course of testing auto-instrumentation with JRuby.

Tests are failing for Linux JRuby 9.3.3.0 + Rails/ActiveRecord 6.1.4.4 with a NameError: undefined local variable or method primary_abstract_class' for #Class:0x1deca369`, however I am unable to reproduce this issue locally.

https://github.com/open-telemetry/opentelemetry-ruby/runs/5074238711?check_suite_focus=true

2022-02-05T00:10:41.9725650Z NameError: undefined local variable or method `primary_abstract_class' for #<Class:0x1deca369>
2022-02-05T00:10:41.9726194Z Did you mean?  primary_class?
2022-02-05T00:10:41.9726558Z                           method_missing at org/jruby/RubyBasicObject.java:1694
2022-02-05T00:10:41.9727405Z                           method_missing at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activerecord-6.1.4.4/lib/active_record/dynamic_matchers.rb:22
2022-02-05T00:10:41.9728243Z                <class:ApplicationRecord> at /home/runner/work/opentelemetry-ruby/opentelemetry-ruby/opentelemetry-rails/test/dummy/app/models/application_record.rb:2
2022-02-05T00:10:41.9729046Z                                   <main> at /home/runner/work/opentelemetry-ruby/opentelemetry-ruby/opentelemetry-rails/test/dummy/app/models/application_record.rb:1
2022-02-05T00:10:41.9729543Z                                  require at org/jruby/RubyKernel.java:1017
2022-02-05T00:10:41.9730202Z                                  require at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:332
2022-02-05T00:10:41.9731341Z                          load_dependency at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:299
2022-02-05T00:10:41.9732144Z                                  require at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:332
2022-02-05T00:10:41.9732898Z                          require_or_load at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:424
2022-02-05T00:10:41.9733641Z                           load_interlock at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:39
2022-02-05T00:10:41.9734425Z                                  loading at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies/interlock.rb:14
2022-02-05T00:10:41.9735203Z                                exclusive at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/concurrency/share_lock.rb:151
2022-02-05T00:10:41.9735976Z                                  loading at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies/interlock.rb:13
2022-02-05T00:10:41.9736714Z                           load_interlock at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:39
2022-02-05T00:10:41.9737465Z                          require_or_load at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:402
2022-02-05T00:10:41.9738210Z                                depend_on at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:375
2022-02-05T00:10:41.9738953Z                       require_dependency at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/activesupport-6.1.4.4/lib/active_support/dependencies.rb:288
2022-02-05T00:10:41.9739667Z                              eager_load! at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/engine.rb:493
2022-02-05T00:10:41.9740105Z                                     each at org/jruby/RubyArray.java:1865
2022-02-05T00:10:41.9740682Z                              eager_load! at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/engine.rb:492
2022-02-05T00:10:41.9741114Z                                     each at org/jruby/RubyArray.java:1865
2022-02-05T00:10:41.9741702Z                              eager_load! at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/engine.rb:489
2022-02-05T00:10:41.9742492Z                              eager_load! at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/application.rb:519
2022-02-05T00:10:41.9743169Z                              eager_load! at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/engine.rb:358
2022-02-05T00:10:41.9743603Z                                     each at org/jruby/RubyArray.java:1865
2022-02-05T00:10:41.9744211Z                                 Finisher at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/application/finisher.rb:134
2022-02-05T00:10:41.9744697Z                            instance_exec at org/jruby/RubyBasicObject.java:2673
2022-02-05T00:10:41.9745468Z                                      run at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/initializable.rb:32
2022-02-05T00:10:41.9746149Z                         run_initializers at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/initializable.rb:61
2022-02-05T00:10:41.9746829Z                               tsort_each at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/stdlib/tsort.rb:228
2022-02-05T00:10:41.9747419Z        each_strongly_connected_component at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/stdlib/tsort.rb:350
2022-02-05T00:10:41.9748010Z   each_strongly_connected_component_from at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/stdlib/tsort.rb:431
2022-02-05T00:10:41.9748611Z        each_strongly_connected_component at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/stdlib/tsort.rb:349
2022-02-05T00:10:41.9749612Z                                     each at org/jruby/RubyArray.java:1865
2022-02-05T00:10:41.9750412Z                                     call at org/jruby/RubyMethod.java:131
2022-02-05T00:10:41.9750896Z        each_strongly_connected_component at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/stdlib/tsort.rb:347
2022-02-05T00:10:41.9751399Z                               tsort_each at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/stdlib/tsort.rb:226
2022-02-05T00:10:41.9751859Z                               tsort_each at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/stdlib/tsort.rb:205
2022-02-05T00:10:41.9752431Z                         run_initializers at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/initializable.rb:60
2022-02-05T00:10:41.9753056Z                              initialize! at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/railties-6.1.4.4/lib/rails/application.rb:391
2022-02-05T00:10:41.9753682Z                                   <main> at /home/runner/work/opentelemetry-ruby/opentelemetry-ruby/opentelemetry-rails/test/dummy/config/environment.rb:5
2022-02-05T00:10:41.9754088Z                                  require at org/jruby/RubyKernel.java:1017
2022-02-05T00:10:41.9754379Z                         require_relative at org/jruby/RubyKernel.java:1045
2022-02-05T00:10:41.9754927Z                                   <main> at /home/runner/work/opentelemetry-ruby/opentelemetry-ruby/opentelemetry-rails/test/test_helper.rb:10
2022-02-05T00:10:41.9755316Z                                  require at org/jruby/RubyKernel.java:1017
2022-02-05T00:10:41.9755859Z                                   <main> at /home/runner/work/opentelemetry-ruby/opentelemetry-ruby/opentelemetry-rails/test/opentelemetry/rails_test.rb:6
2022-02-05T00:10:41.9756266Z                                  require at org/jruby/RubyKernel.java:1017
2022-02-05T00:10:41.9756781Z                                   <main> at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/rake-12.3.3/lib/rake/rake_test_loader.rb:17
2022-02-05T00:10:41.9757124Z                                   select at org/jruby/RubyArray.java:2721
2022-02-05T00:10:41.9757641Z                                   <main> at /home/runner/.rubies/jruby-9.3.3.0/lib/ruby/gems/shared/gems/rake-12.3.3/lib/rake/rake_test_loader.rb:5
2022-02-05T00:10:42.0152960Z rake aborted!

@arielvalentin
Copy link
Contributor Author

DOAH Never mind this is JRuby being stuck at Rails 6 && Ruby 2.6.8 :table-flip:

It still makes 0 sense to me that my testes do not fail locally

@robertlaurin
Copy link
Contributor

Been thinking about this PR and where this railtie could or should live.

  • Standalone, like this PR. It would be a standalone gem that a consumer could include in the gemfile of their rails project and it would run the configuration for the SDK for them.

    • In this scenario we would need to decide whether or not this should be included as part of instrumentation all, I'm leaning towards no, simply because this is not instrumentation really. It's more of an auto configuration package.
  • It could be part of the rails instrumentation gem, so anyone who adds the rails instrumentation gem gets all the rails related instrumentation and it auto configures the SDK for them.

    • If we do this, than instrumentation-all means that it also configures the sdk for you. Is this confusing or unexpected?

The more I think about the more I think that this should probably be part of the rails instrumentation gem, the user experience then becomes:

  • I don't want to curate my instrumentation or I just want a quick way to get started, so I use "instrumentation-all" on my rails app and everything possible is there, and I don't have to configure anything (other than add the appropriate exporter gem).
  • I just want rails instrumentation to start, I add that gem, the exporter I want, and it just works.
  • I want to curate everything, and run my own configuration, I don't use instrumentation-rails but instead I select each rails specific instrumentation package I want and run my own configuration block in an init hook. This is the last one that sits a little funny, I would expect to be able to add instrumentation rails without it auto configuring (or running config twice if I've done it explicitly). I may just be biased on that last concern though.

Would be good to discuss this on the sig today.

Copy link
Contributor

@ericmustin ericmustin left a comment

Choose a reason for hiding this comment

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

Firstly, great stuff as usual. The actual implementation I don't have much feedback on, lgtm broadly, i think pulling in a few rails specific defaults for resource attributes and service name "makes sense". I had one small point of feedback on the initializer order but its pretty minor.

As far as the tests, my opinion is that you've taken a good direction here although it's a lot of boilerplate. If we're worried about the test suite taking too long to run, i think we can probably hack that by having any auto-instrumentation tests be "off by default" and enabled via env var, and we can choose to selectively enable those tests on certain ci steps (perhaps just on latest ruby and on jruby, or something along those lines).

as @robertlaurin pointed out i think the uncertain thing is packaging.

I think the simplest way to get this out the door, and manage it long term without boxing ourselves in, is to simply include it as a file within opentelemetry-instrumentation-rails that a user can require from theGemfile.

gem 'opentelemetry-instrumentation-rails', require: 'lib/opentelemetry-railtie'

I haven't actually tested this specific incantation but I "think" it should "just work". This was the approach we took with dd-trace-rb(which was, to be fair, structured differently, as 1 big gem and not a bunch of small ones), and it seemed to be well received in terms of user adoption and also in terms of not having a ton of angry support tickets :)

Thinking about it for different types of users:

  • For existing users, or those who don't want the magical OOTB experience, there's no breaking changes because they aren't going to be requiring lib/opentelemetry-railtie
  • For new users, they get a magical OOTB experience via both "quickstart documentation" and perhaps eventually the otel operator, if they want it.
gem 'opentelemetry-instrumentation-all
gem 'opentelemetry-instrumentation-rails', require: 'lib/opentelemetry-railtie'

New rails users (the vast majority of our new users) can blindly c/p that code snippet and get everything instrumented and lots of control via Env var. And If new users eventually want to start tuning what instrumentation packages they want to include they can still do it via just their gemfile(by switching out instrumentation-all for specific subset of packages they want) and env vars.

By packaging it within the instrumentation-rails gem, we ensure that there's never going to be a scenario where the railtie "doesn't work", or the user expects a no-code auto-instrumentation experience and doesn't get it. and I also think we offering the right message that this only works for rails.

Also, we aren't boxing ourselves into maintaining a new gem, releasing that gem, etc etc, which i think is practically speaking a good thing.

class Railtie < ::Rails::Railtie
railtie_name :opentelemetry
config.opentelemetry = ActiveSupport::OrderedOptions.new
initializer 'opentelemetry.configure' do |app|
Copy link
Contributor

Choose a reason for hiding this comment

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

The before and after options lets us determine where we want to insert the railtie in the initializer process. New Relic inserts before load_config_initializers , which makes sense to me since this would ensure our railtie runs before anything in config/initializers/

Suggested change
initializer 'opentelemetry.configure' do |app|
initializer 'opentelemetry.configure', before: :load_config_initializers do |app|

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmmm... I need to dig into this a bit more to understand what effect this will have.

It seems New Relic is dealing with an issue where their instrumentation is inadvertently loading the framework but I have not looked closely enough to see why they are running into this problem:

newrelic/newrelic-ruby-agent#662

class Railtie < ::Rails::Railtie
railtie_name :opentelemetry
config.opentelemetry = ActiveSupport::OrderedOptions.new
initializer 'opentelemetry.configure' do |app|
Copy link
Contributor

Choose a reason for hiding this comment

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

kinda scope creep but how do we feel about wrapping this in an on/off environment variable?

If the goal is "just update the gemfile once and control everything with env vars" it might be nice to have a break-glass env-var option to completely disable instrumentation.

@arielvalentin
Copy link
Contributor Author

I will move this into the Rails auto-instrumentation gem.

As far as the tests, my opinion is that you've taken a good direction here although it's a lot of boilerplate.

Are you saying there's too much structural duplication and you me to consolidate them?

If we're worried about the test suite taking too long to run, i think we can probably hack that by having any auto-instrumentation tests be "off by default" and enabled via env var, and we can choose to selectively enable those tests on certain ci steps (perhaps just on latest ruby and on jruby, or something along those lines).

What gives you that impression?

@arielvalentin
Copy link
Contributor Author

In the spirit of making things easier to do for our users, I'm inclined to include the Railtie if the class is present and then add an option that could be set to skip it entirely both in code and via envar.

wdyt? @robertlaurin @ericmustin

@ahayworth
Copy link
Contributor

In the spirit of making things easier to do for our users, I'm inclined to include the Railtie if the class is present and then add an option that could be set to skip it entirely both in code and via envar.

This is how I generally expect rails-related things to work: add the gem to my Gemfile, and I don't need to otherwise require anything to use the functionality the gem brings. There are exceptions, of course - some gems do require an explicit require 'foo-gem/railtie', and some require explicit setup in an initializer - but my gut feeling is that the "magic" approach is probably better.

Rails is magic, as its detractors enjoy reminding everyone!

I also like the idea of including a config option to disable the auto-configuration, if you need. I suspect some big users of Rails with highly specialized initialization routines might benefit from disabling that in certain monolithic applications, while still taking advantage of the easy autoconfig in other Rails apps they run. Hypothetically, of course.

@ericmustin
Copy link
Contributor

if the railtie is invoked by default and we include it in instrumentation-rails, wouldn't this be a breaking change for most users on a minor upgrade? we've suddenly snuck in a .use_all config block and called configure on their behalf. I would argue we should have the railtie off by-default in that case and make it easy to opt in (env var i guess).

Re, the tests, i don't think there's too much duplication, and i am not sure if the tests take too long or not, i just recall @arielvalentin mentioning the tests took awhile in the SIG mtg, but it's possible i have my wires crossed.

@arielvalentin
Copy link
Contributor Author

if the railtie is invoked by default and we include it in instrumentation-rails, wouldn't this be a breaking change for most users on a minor upgrade? we've suddenly snuck in a .use_all config block and called configure on their behalf. I would argue we should have the railtie off by-default in that case and make it easy to opt in (env var i guess).

We have not concerned ourselves too much with stability guarantees in the past for any gems that are pre-1.0 or GA. Are we changing our position on this?

Re, the tests, i don't think there's too much duplication, and i am not sure if the tests take too long or not, i just recall @arielvalentin mentioning the tests took awhile in the SIG mtg, but it's possible i have my wires crossed.

I was referring to our existing GH actions setup and it is not specific to these changes. The current test suite completes in a little under 10 minutes per matrix version.

@ericmustin
Copy link
Contributor

We have not concerned ourselves too much with stability guarantees in the past for any gems that are pre-1.0 or GA. Are we changing our position on this?

I don't think that's accurate. We have multiple "deprecated" configuration options, for example, and I think this represents a significant change in behavior, certainly the most significant since we've 1.0'd the SDK. As @robertlaurin pointed out, since instrumentation-all pulls this in, it would effect practically everyone (some huge % of users are rails apps).

Multiple vendors are also informing their users to rely on this package so i think a bit of care is warranted since we'd be breaking real folks out there's real monitoring for real stuff, and, i'd like to avoid that if possible? I don't think that's unreasonable.

hc

@arielvalentin
Copy link
Contributor Author

I agree that I don't want to surprise our users in a negative way but I'm also keen on pushing towards making as much available to our users by default as we possibly can.

w/r/t breaking changes... IIRC the one option we did that for was DB statement obfuscation and that was because it posed a risk of leaking PII so we wanted to be careful about it. If there are others then yeah I will concede that we need a less disruptive rollout but we should also make that an explicit rule as opposed to exception.

As far as vendors are concerned, they should probably defer to our documentation or (as we've discussed in side bars) manage their own distros and pin to specific versions so they can mitigate upstream changes.

If we worry about vendor documentation being stale all of the time we'll slow down our ability to make significant changes especially around auto-instrumentation, which is moving target.

This gem provides a minimal Railtie to use in lieu of manual configuration.

Operators may customize SDK settings using environment variables but this gem will provide some defaults:

Sets a minimal set of values for OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES when left unset
Users may override SDK configurations using standard OTel SDK environment variables
Users may override Instrumentation libraries are configurable using OTEL_RUBY_*_CONFIG_OPTS
@arielvalentin arielvalentin force-pushed the arielvalentin/railtie branch 2 times, most recently from 117c6e3 to 955089a Compare March 20, 2022 18:02
This commit moves the Railtie from its own gem into opentelemetry-instrumentation-rails.

Users will have to explicitly require the railtie in their bundler file or as part of Rails bootstrap after bundler
has required dependencies.

It also reverses the decision to set default OTEL_RESOURCES_ATTRIBUTES based on the Rails.env since we have run into
some issues in production.

cc: @robertlaurin @ericmustin
@arielvalentin
Copy link
Contributor Author

@robertlaurin @ericmustin looking for some feedback before I proceed with writing up docs.

Copy link
Contributor

@robertlaurin robertlaurin left a comment

Choose a reason for hiding this comment

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

Hey @arielvalentin we did a mob review on this PR during the SIG.

The only contentious point (raised by me) was whether or not it is worth adding all the test scaffolding to support testing the railtie itself.

We landed on if you think it is worth keeping, and because you've already put in the work to test it. We can keep it.

If in the future it becomes a maintenance burden, we may reserve the right to do away with the railtie tests.

Thanks for pushing this through.

@arielvalentin
Copy link
Contributor Author

If in the future it becomes a maintenance burden, we may reserve the right to do away with the railtie tests.

Sounds reasonable to me.

@robertlaurin robertlaurin merged commit dc25f73 into open-telemetry:main Mar 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants