From 78858b88d2b50b8e4e329ef5e3b97fc395b4f1e3 Mon Sep 17 00:00:00 2001 From: Kyle Ong Date: Wed, 24 Jul 2024 11:22:13 -0700 Subject: [PATCH] Sync with upstream (#50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Remove dependence on activesupport (#687) Instrumentations must not use transitive dependencies used by the library. In this case, relying on the gruf library to load specific ActiveSupport extensions leaves the instrumentation vulnerable to bugs. For this reason I have changed the code to use Enumerable methods instead of ActiveSupport extensions. See /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/686 * fix!: Drop DelayedJob ActiveRecord in Tests (#685) * fix: Add Rails 7.1 compatability (#684) * chore: Add tests for Rails 7.1 * fix: Rails 7.1 incompatabilities * feat!: obfuscation for mysql2, dalli and postgresql as default option for db_statement (#682) * feat!: fuscation for mysql2, dalli and pg * feat!: update readme * feat!: set db.statement option to obfuscate by default for mysql2, pg and dalli Co-authored-by: Ariel Valentin * ci: Update test reset code for GraphQL-Ruby 2.1.3 (#691) Fixes /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/689 * fix: Omit `nil` `net.peer.name` attributes (#693) * ci: upgrade to latest stable version of toys (#694) Fixes errors with incompatible versions defined in the configs: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/actions/runs/6534421626/job/17741560442#step:5:10 * release: Release 12 gems (#695) * release: Release 12 gems * opentelemetry-instrumentation-gruf 0.1.1 (was 0.1.0) * opentelemetry-instrumentation-active_support 0.4.3 (was 0.4.2) * opentelemetry-instrumentation-action_view 0.6.1 (was 0.6.0) * opentelemetry-instrumentation-action_pack 0.7.1 (was 0.7.0) * opentelemetry-instrumentation-active_job 0.6.1 (was 0.6.0) * opentelemetry-instrumentation-active_record 0.6.3 (was 0.6.2) * opentelemetry-instrumentation-dalli 0.25.0 (was 0.24.2) * opentelemetry-instrumentation-delayed_job 0.22.0 (was 0.21.0) * opentelemetry-instrumentation-faraday 0.23.3 (was 0.23.2) * opentelemetry-instrumentation-mysql2 0.25.0 (was 0.24.3) * opentelemetry-instrumentation-pg 0.26.0 (was 0.25.3) * opentelemetry-instrumentation-rails 0.28.1 (was 0.28.0) * ci: Trigger builds * fix: Apply suggestions from code review Co-authored-by: Josef Šimánek * fix: bump gem versions * fix: bump gems --------- Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin Co-authored-by: Josef Šimánek * chore: Update CODEOWNERS (#692) * chore: Update CODEOWNERS Add @simi and @kaylareopelle and approvers to the list * release: Release opentelemetry-instrumentation-all 0.51.0 (was 0.50.1) (#699) * release: Release opentelemetry-instrumentation-all 0.51.0 (was 0.50.1) * docs: Update all gem Changelog --------- Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * feat(trilogy): instrument connect and ping (#704) These can be just as slow as a query if not slower. Co-authored-by: Jean Boussier * fix: Remove dependency on ActiveSupport core extensions from Grape instrumentation (#706) * release: Release opentelemetry-instrumentation-trilogy 0.57.0 (was 0.56.3) (#708) * release: Release opentelemetry-instrumentation-trilogy 0.57.0 (was 0.56.3) * Empty commit * Release instrumentation-all as well. --------- Co-authored-by: Ariel Valentin Co-authored-by: Francis Bogsanyi * chore(deps): update rubocop requirement from ~> 1.56.2 to ~> 1.57.1 (#702) Updates the requirements on [rubocop](/~https://github.com/rubocop/rubocop) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop/releases) - [Changelog](/~https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop/compare/v1.56.2...v1.57.1) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 in /instrumentation/base (#701) chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 Updates the requirements on [rubocop](/~https://github.com/rubocop/rubocop) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop/releases) - [Changelog](/~https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop/compare/v1.56.1...v1.57.1) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 in /resource_detectors (#700) chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 Updates the requirements on [rubocop](/~https://github.com/rubocop/rubocop) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop/releases) - [Changelog](/~https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop/compare/v1.56.1...v1.57.1) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 in /propagator/ottrace (#698) chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 Updates the requirements on [rubocop](/~https://github.com/rubocop/rubocop) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop/releases) - [Changelog](/~https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop/compare/v1.56.1...v1.57.1) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 in /propagator/xray (#697) chore(deps-dev): update rubocop requirement from ~> 1.56.1 to ~> 1.57.1 Updates the requirements on [rubocop](/~https://github.com/rubocop/rubocop) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop/releases) - [Changelog](/~https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop/compare/v1.56.1...v1.57.1) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Remove opentelemetry-resource_detectors all-in-one gem (#659) * fix: remove call to ActiveSupport::Notifications.notifier#synchronize deprecated in Rails 7.2 (#707) * wrap call to depcrecated private API behind version conditional * convert Rails version string to Gem::Version * fix module access? * check for synchronize method instead of Rails version * add comment --------- Co-authored-by: Ariel Valentin * chore: Fix linter issue * release: Release 2 gems (#710) * release: Release 2 gems * opentelemetry-instrumentation-grape 0.1.5 (was 0.1.4) * opentelemetry-instrumentation-active_support 0.4.4 (was 0.4.3) * ci: Force --------- Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * added httpx opentelemetry adapter (#681) * added httpx opentelemetry adapter * Update instrumentation/httpx/CHANGELOG.md Co-authored-by: Ariel Valentin * Update instrumentation/httpx/README.md Co-authored-by: Ariel Valentin * Update instrumentation/httpx/README.md Co-authored-by: Ariel Valentin --------- Co-authored-by: Ariel Valentin Co-authored-by: Josef Šimánek * chore: Fix HTTPx attribute and changelog (#711) * chore: Change release restrictions The toys gem checks that all actions are passing before allowing a release request to be opened, however only the CI builds are actually required. This change limits the release request requirements to look specifically for CI builds * chore: Fix httpx version * chore: bump toys * release: Release opentelemetry-instrumentation-httpx 0.1.0 (initial release) (#713) * release: Release opentelemetry-instrumentation-httpx 0.1.0 (initial release) * Update instrumentation/httpx/CHANGELOG.md * feat!: Drop Rails 6.0 EOL (#680) * feat!: Drop Rails 6.0 EOL 6.0 is no longer receiving maintenance, security, or feature updates as of 01 Jun 2023 Users who want to continue instrumentating Rails applications should pin to earlier versions of the instrumentation. * squash: Fix test * squash: gem version object instead of string * Update README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Inline gemspec dev constraints with Appraisals. (#716) * Removal Sinatra 1.x Appraisal (#715) fix: Removal Sinatra 1.x Appraisal Rack 1.x is not directly supported anymore. Sinatra 1.x in turn "is not". Removal appraisal and add a compatibility note to both Sinatra and Rack for proper instrumentation version usage. Co-authored-by: Ariel Valentin * feat!(active_job): Use ActiveSupport instead of patches (#677) * feat!(active_job): Use ActiveSupport instead of patches * refactor: Register discard job * refactor: independent registration * refactor: Use template methods for children * refactor: some more small fixes * refactor: a little clean up * refactor: a little more clarity * refactor: try to make sense of a few things * refactor: fix test directory structure * refactor: extract to files * add some docs * refactor: clean up and renames * refactor: split tests out per handler * refactor... maybe a little overengineering * fix: add examples of in-line instrumentation * Update README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update default.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update perform.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * squash: Use Singleton Tracer * fix: Use top level namespaces When used in conjunction with the `OpenTelemetry::Instrumenation::ActiveSupport`, the classloader would mistakenly use the wrong namespace and raises a `NameError`. This change updates references to ensure we use top level namespaces to load the appropriate classes. * squash: Update instrumentation/active_job/README.md Co-authored-by: Robb Kidd * squash: Update docs Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * squash: Reduce Local Variable Allocation Co-authored-by: Robb Kidd * squash: Safe navigation no longer required * squash: fix bad commit * squash: Remove possible PII from status message * squash: PR Feedback * squash: Use up to date semconv and remove unused attrs * squash: remove unused file * squash: fix test * squash: fix messaging id * squash: More semconv fixes * squash: fix ids * squash: feedback from Ruby SIG --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> Co-authored-by: Robb Kidd * release: Release 6 gems (#724) * release: Release 6 gems * opentelemetry-instrumentation-active_support 0.5.0 (was 0.4.4) * opentelemetry-instrumentation-action_view 0.7.0 (was 0.6.1) * opentelemetry-instrumentation-action_pack 0.8.0 (was 0.7.1) * opentelemetry-instrumentation-active_job 0.7.0 (was 0.6.1) * opentelemetry-instrumentation-active_record 0.7.0 (was 0.6.3) * opentelemetry-instrumentation-rails 0.29.0 (was 0.28.1) * docs: Update instrumentation/active_job/CHANGELOG.md * chore: bump all dependencies * chore: bump version file * chore: bump rails dependencies --------- Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * fix: Get Rdkafka version from VERSION contant (#726) * fix: Update `Net::HTTP` instrumentation to no-op on untraced contexts (#722) * chore: Add rspec-mocks development dependency * fix: Net::HTTP instrumentation no-op on untraced context Update Net::HTTP instrumentation to no-op on HTTP requests within untraced contexts. Prevents the logging of "called finish on an ended Span" messages. Example scenario: The Resque instrumentation can be configured to call `OpenTelemetry::SDK::TraceProvider#force_flush` at the end of each job. That causes the batch span processor to immediately send everything it has to the OTLP exporter. The exporter then marks the HTTP request used to send the data to the APM server as "untraced", by adding an `untraced` key to the context and setting it to `true`. Before this commit, the `Net::HTTP` instrumentation did not check for the `untraced` flag. So it would try set attributes and call `finish` on a span that was not recording, creating one "called finish on an ended Span" log entry for each batch of exported spans. On a large application, that results in millions of those log entries per minute. --------- Co-authored-by: Ariel Valentin * release: Release 2 gems (#728) * release: Release 2 gems * opentelemetry-instrumentation-rdkafka 0.4.1 (was 0.4.0) * opentelemetry-instrumentation-net_http 0.22.3 (was 0.22.2) * ci: Force --------- Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * chore(dev-deps): Add rubocop-performance (#727) chore(dev-deps): Add and enable rubocop-performance * release: Release opentelemetry-instrumentation-rdkafka 0.4.2 (#731) * release: Release 23 gems (#732) * release: Release 23 gems * opentelemetry-propagator-ottrace 0.21.2 (was 0.21.1) * opentelemetry-propagator-xray 0.22.1 (was 0.22.0) * opentelemetry-instrumentation-base 0.22.3 (was 0.22.2) * opentelemetry-instrumentation-active_job 0.7.1 (was 0.7.0) * opentelemetry-instrumentation-active_support 0.5.1 (was 0.5.0) * opentelemetry-instrumentation-concurrent_ruby 0.21.2 (was 0.21.1) * opentelemetry-instrumentation-delayed_job 0.22.1 (was 0.22.0) * opentelemetry-instrumentation-ethon 0.21.3 (was 0.21.2) * opentelemetry-instrumentation-excon 0.21.3 (was 0.21.2) * opentelemetry-instrumentation-faraday 0.23.4 (was 0.23.3) * opentelemetry-instrumentation-grape 0.1.6 (was 0.1.5) * opentelemetry-instrumentation-graphql 0.26.8 (was 0.26.7) * opentelemetry-instrumentation-http 0.23.2 (was 0.23.1) * opentelemetry-instrumentation-http_client 0.22.3 (was 0.22.2) * opentelemetry-instrumentation-httpx 0.1.1 (was 0.1.0) * opentelemetry-instrumentation-mongo 0.22.2 (was 0.22.1) * opentelemetry-instrumentation-net_http 0.22.4 (was 0.22.3) * opentelemetry-instrumentation-pg 0.26.1 (was 0.26.0) * opentelemetry-instrumentation-que 0.7.1 (was 0.7.0) * opentelemetry-instrumentation-rack 0.23.5 (was 0.23.4) * opentelemetry-instrumentation-restclient 0.22.3 (was 0.22.2) * opentelemetry-instrumentation-rspec 0.3.2 (was 0.3.1) * opentelemetry-instrumentation-rails 0.29.1 (was 0.29.0) * chore: Apply suggestions from code review --------- Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * perf(graphql): cache attribute hashes (#723) * GraphQL: cache attribute hashes * Use caches for GraphQLTracer attributes * Use the same attribute hashes for resolve_type and authorized * revert changes to legacy trace --------- Co-authored-by: Ariel Valentin * release: Release 2 gems (#741) * feat!: Add a connect span to excon (#712) * Add a connect span to excon and add more span attributes to the tracer middleware * Skip matching on the error message as it varies by platform * Rescue the IOError that can occur on accept * Switch to must_be_empty assertion instead of size * Move allowing and disallowing connect to setup and after respectively. * use dig when getting hostname and port of proxy * Call untraced when we hit an untraced host. * Switch to using recording? to test whether we should finish a span. * Switch to handle_error instead of debug logging. * Record the exception on error * Perform the next step in the middleware stack in the context of the current span. * Add assertions on http spans to connect tests. * Fix rubocop lints * Remove interpolation from status message on span now that we capture exceptions as an event. * Switch to attach and detach instead of with_span * Include untraced context into untraced? check for the middleware and patch. * Add test for untraced. * Add a module that centralizes the untraced hosts concern. * Expand doc comment. * Move module to the excon gem. * Add doc comment for untraced? in the concern * Update instrumentation/excon/lib/opentelemetry/instrumentation/excon/middlewares/tracer_middleware.rb Co-authored-by: Ariel Valentin --------- Co-authored-by: Ariel Valentin * release: Release 2 gems (#744) * chore: Fix Rails 7.2 secrets deprecation warning (#747) * feat!(action_pack): Use ActiveSupport instead of patches (#703) * feat!(action_pack): Use ActiveSupport instead of patches * feat!(action_pack): remove comments * feat: drop 6.0 on action_pack to verify the test case * feat: add rails 6.0 back * feat!(action_pack): remove rails 6.0 support + update readme on error handling * Update instrumentation/action_pack/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> --------- Co-authored-by: Ariel Valentin Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * update Approvers list in README (#755) Kayla and Josef are already approvers! This brings the docs up-to-date with reality. * chore(deps-dev): update rubocop to ~> 1.58.0 in multiple gems (#756) * chore(deps-dev): update rubocop to ~> 1.58.0 in multiple gems * appease Rubocop: Lint/RedundantSafeNavigation Redundant safe navigation with default literal detected. prefix = endpoint.routes.first.options[:prefix]&.to_s || '' ^^^^^^^^^^^^ OK. We can stop at safely navigating the string coersion without supplying a default because paths.reject{} below filters nils. * appease Rubocop: Style/RedundantReturn Redundant return detected. return "HTTP #{request_method} #{request_path} miniswan" ^^^^^^ Sure. * appease Rubocop: Style/RedundantParentheses Don't use parentheses around a logical expression. (!gem_version.nil? && gem_version >= MINIMUM_VERSION) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ OK. * chore(deps): bump actions/stale from 8 to 9 (#757) * chore: let stalebot close issues after 30 days of being stale. (#759) * feat!: change db.mysql.instance.address to db.instance.id (#758) * refactor(trilogy): change db.mysql.instance.address to db.instance.id fixes #745 * feat!(trilogy): change db.mysql.instance.address to db.instance.id fixes #745 --------- Co-authored-by: Ariel Valentin Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * chore(deps-dev): update rubocop to ~> 1.59.0 in all gems (#760) * chore(deps): update rubocop requirement from ~> 1.58.0 to ~> 1.59.0 Updates the requirements on [rubocop](/~https://github.com/rubocop/rubocop) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop/releases) - [Changelog](/~https://github.com/rubocop/rubocop/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop/compare/v1.58.0...v1.59.0) --- updated-dependencies: - dependency-name: rubocop dependency-type: direct:production ... Signed-off-by: dependabot[bot] * chore(deps-dev): also update rubocop to ~> 1.59.0 in all the gemspecs --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Robb Kidd * fix: Backport Rack proxy event to middleware (#764) * chore: update codeowners * chore: Add Tests for GraphQL C Parser (#773) Graphql 2.2 introduces an optional C implementation of the Parser as well as optimizations to the pure Ruby parser that result in changes to what is captured during instrumentation. Users of the pure Ruby implementation will no longer see graphql.lex spans emitted as a part of their traces and will instead need to switch to using the C Parser in order to explicitly see time spent in the lexer. This PR introduces updates to the test suite to add coverage for the C Parser and changes to the Ruby implementation. There are no underlying changes in the instrumentation itself. * chore: Experiment with GCP Release Please (#729) * chore: Experiment with GCP Release Please An experiment to replace toys. /~https://github.com/google-github-actions/release-please-action/tree/main * squash: fixes from PR feedback * squash: Give otel bot the credit * squash: pin to v4.0.2 * squash: add steps to execute the release * squash: fix output * squash: release one at a time * chore: fix actions definition * chore: Fixes for release-please * chore: use objects not arrays * chore: Remove invalid config * chore: Remove nested objects in config * chore: Prerelease config * chore: Fix release version paths * chore: try again * chore: Block matrix from running if no release were created * chore: try /~https://github.com/orgs/community/discussions/27096 * chore: remove superflous action config * feat(rack)! Use Rack Events By Default (#709) * feat(rack)! Use Rack Events By Default I have been running the `Rack::Events` based instrumentation in our production workloads since 2023-10-23. There is no noticeable difference in performance, however applications using `Rack::BodyProxy` to stream responses will notice a slight increase in latency since it will include timings writing response output to the socket. * squash: fix invalid HTTP responses in tests * squash: Remove redundant test * squash: Update rack error description * Update instrumentation.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * ci: Use Org OTel Bot Token (#789) The Org Level OTel Bot Token likely a PAT, which will allow PRs opened by GH actions to trigger subsequent actions workflows. This will fix the problem where CI jobs do not run when the bot opens a PR for release requests. * Revert "ci: Use Org OTel Bot Token (#789)" (#790) This reverts commit 586b0fb137d430e2b826f3814723dd61c8d82ff1. * Trilogy: only set `db.instance.id` attribute if there is a value (#792) * fix: Trilogy only set db.instance.id attribute if there is a value (#792) * ci: OTel Bot Token Redux * feat!: Use Rack Events By Default * ci: Revert token again * chore: release main (#793) * chore: release main * feat: Update Trilogy and Rack --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * ci: force latest bundler version * ci: invalid option * ci: Fix toys config * ci: Try again * Revert "chore: release main (#793)" This reverts commit 9d8788b8b27f4492eba9d6f60cafcee95ecad148. * ci: Give Credit to OTel Bot * release: Release 2 gems (#796) * release: Release 2 gems * opentelemetry-instrumentation-trilogy 0.58.0 (was 0.57.0) * opentelemetry-instrumentation-rack 0.24.0 (was 0.23.5) * feat!: All Upgrade Trilogy:0.58 Rack:0.24 --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * release: Release opentelemetry-instrumentation-all 0.55.0 (was 0.54.0) (#799) * release: Release opentelemetry-instrumentation-all 0.55.0 (was 0.54.0) * ci: Update instrumentation/all/CHANGELOG.md --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * release: Release 3 gems (#808) * release: Release 3 gems * opentelemetry-instrumentation-all 056.0 (was 0.55.0) * opentelemetry-instrumentation-action_pack 0.9.0 (was 0.8.0) * opentelemetry-instrumentation-rails 0.30.0 (was 0.29.1) * Apply suggestions from code review * squash: Bump dependencies --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * chore: Bump rubocop-performance 1.20.0 (#780) ```console $> bin/update-dependencies rubocop-performance 1.20.0 ``` * ci: Add Tests For Ruby 3.3 (#797) * ci: Add Tests For Ruby 3.3 Ruby 3.3 is out so this PR adds actions to test against it. One this to note is that bigcommerce/gruf does not yet support Ruby 3.3. Once /~https://github.com/bigcommerce/gruf/pull/197 is merged I will update the test suite to include gruf again. * squash: fix test * squash: fix test * chore(deps): bump ruby/setup-ruby from 1.162.0 to 1.165.1 (#809) * Add xuan-cao-swi to approvers (#810) * ci: Update Release Please Configs * ci: Use appropriate config * ci: fix job names * ci: Use correct job names * ci: update manifest versions * ci: bump rack manifest * ci: Fix Job Refs * ci: remove hyphens * ci: fix reference * chore(deps): update rubocop-performance requirement from ~> 1.19.1 to ~> 1.20.2 (#807) Updates the requirements on [rubocop-performance](/~https://github.com/rubocop/rubocop-performance) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop-performance/releases) - [Changelog](/~https://github.com/rubocop/rubocop-performance/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop-performance/compare/v1.19.1...v1.20.2) --- updated-dependencies: - dependency-name: rubocop-performance dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs: Add note about running update-dependencies in ubuntu-linux (#819) * chore: Update rubocop version to 1.60.0 (#818) * chore: Update rubocop version to 1.60.0 * chore: Update rubocop in main Gemfile * chore: Address Rubocop offenses Incorporate Style/ArgumentsForwarding - Use shorthand syntax ... for arguments forwarding. * chore(deps): bump ruby/setup-ruby from 1.165.1 to 1.167.0 (#820) * fix: "Getting Started" link at README (#822) * docs: ✏️ Sidekiq instrumentation options (#824) * chore: add appraisal for Sinatra 4. (#827) * chore: add appraisal for Sinatra 4. * chore: allow for latest minor version of rack-test. * chore: add rackup to sinatra example Gemfile (#828) Co-authored-by: Sam <370182+plantfansam@users.noreply.github.com> * chore: Update rubocop to 1.60.1 (#823) * chore(deps): bump ruby/setup-ruby from 1.167.0 to 1.169.0 (#829) Bumps [ruby/setup-ruby](/~https://github.com/ruby/setup-ruby) from 1.167.0 to 1.169.0. - [Release notes](/~https://github.com/ruby/setup-ruby/releases) - [Commits](/~https://github.com/ruby/setup-ruby/compare/v1.167.0...v1.169.0) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: Remove disable directives leftover from older version of Rubocop (#835) * docs: relocate Resque config option comments to render in Yard docs (#834) docs: move config option docs up to the Instrumentation class As formatted markdown comments on the class, the information is made visible in the Yard-generated docs without making the options appear to be class or instance variables. * chore(deps): bump ruby/setup-ruby from 1.169.0 to 1.170.0 (#836) Bumps [ruby/setup-ruby](/~https://github.com/ruby/setup-ruby) from 1.169.0 to 1.170.0. - [Release notes](/~https://github.com/ruby/setup-ruby/releases) - [Commits](/~https://github.com/ruby/setup-ruby/compare/v1.169.0...v1.170.0) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump ruby/setup-ruby from 1.170.0 to 1.171.0 (#838) Bumps [ruby/setup-ruby](/~https://github.com/ruby/setup-ruby) from 1.170.0 to 1.171.0. - [Release notes](/~https://github.com/ruby/setup-ruby/releases) - [Commits](/~https://github.com/ruby/setup-ruby/compare/v1.170.0...v1.171.0) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * refactor!: Move shared sql behavior to helper gems (#529) * chore(deps): bump actions/checkout from 3 to 4 (#844) * chore: Update releases path * chore: force initial release version (#851) * chore: force initial release version * squash: temporarily disable version until helpers are released * feat: Propagate context to Vitess (#850) * feat: Propagate context to Vitess * Require base64 * Refactor to extract Vitess propagator * Use in-repo vitess propagator * The actual tests * Remove unnecessary require * Robust against frozen string carrier * Update README * Fix test * Test with mutable strings * Update instrumentation/trilogy/test/opentelemetry/instrumentation/trilogy/instrumentation_test.rb Co-authored-by: Robert * Appease the cop --------- Co-authored-by: Robert * chore: Update toys and toys release script pins to 0.15.5 (#854) * release: Release 3 gems (#856) * release: Release 3 gems * opentelemetry-helpers-mysql 0.1.0 (initial release) * opentelemetry-helpers-sql-obfuscation 0.1.0 (initial release) * opentelemetry-propagator-vitess 0.1.0 (initial release) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * release: Release 8 gems (#857) * release: Release 8 gems * opentelemetry-instrumentation-trilogy 0.59.0 (was 0.58.0) * opentelemetry-instrumentation-resque 0.5.1 (was 0.5.0) * opentelemetry-instrumentation-bunny 0.21.2 (was 0.21.1) * opentelemetry-instrumentation-mysql2 0.26.0 (was 0.25.0) * opentelemetry-instrumentation-pg 0.27.0 (was 0.26.1) * opentelemetry-instrumentation-que 0.8.0 (was 0.7.1) * opentelemetry-instrumentation-sidekiq 0.25.1 (was 0.25.0) * opentelemetry-instrumentation-all 0.57.0 (was 0.56.0) * ci: Force * fix!: Upgrade dependencies --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * docs: Fix doc for sidekiq options (#825) * docs: ✏️ fix yard for sidekiq instrumentation options * docs: Refactor Sidekiq instrumentation and add configuration options * Update instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/instrumentation.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> --------- Co-authored-by: Ariel Valentin Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * ci: Add Conventional Commit Linter (#800) * ci: Add Conventional Commit Linter * squash: remove unused action * squash: Delete commitlint.config.js * squash: PR feedback * squash: Update contributing guide * squash: PR Feedback * fix: add missing requires for sql-helpers to mysql, pg, and trilogy instrumentation (#859) * release: Release 4 gems (#860) * release: Release 4 gems * opentelemetry-instrumentation-trilogy 0.59.1 (was 0.59.0) * opentelemetry-instrumentation-mysql2 0.26.1 (was 0.26.0) * opentelemetry-instrumentation-pg 0.27.1 (was 0.27.0) * opentelemetry-instrumentation-sidekiq 0.25.2 (was 0.25.1) * ci: force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: return nil for non-existant key in AwsSdk::MessageAttributeGetter (#853) Co-authored-by: Ariel Valentin * release: Release opentelemetry-instrumentation-aws_sdk 0.5.1 (was 0.5.0) (#861) * release: Release opentelemetry-instrumentation-aws_sdk 0.5.1 (was 0.5.0) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * feat: instrument mysql2 prepare statement (#862) * feat: instrument prepare for mysql2 lib * revision * release: Release 2 gems (#868) * release: Release 2 gems * opentelemetry-instrumentation-mysql2 0.27.0 (was 0.26.1) * opentelemetry-instrumentation-all 0.58.0 (was 0.57.0) * Update instrumentation/all/CHANGELOG.md * squash: Bump mysql2 gem --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * feat!: Cache GraphQL attributes (#867) * release: Release 2 gems (#871) * release: Release 2 gems * opentelemetry-instrumentation-graphql 0.28.0 (was 0.27.0) * opentelemetry-instrumentation-all 0.59.0 (was 0.58.0) * squash: Apply suggestions from code review * squash: Bump GraphQL --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * feat: Add support gruf 2.19 (#872) * fix: dup string if frozen in trilogy query (#863) * fix: propagate context from trilogy instrumentation for frozen strings * feat: faraday add support for internal spans (#873) * release: Release opentelemetry-instrumentation-trilogy 0.59.2 (was 0.59.1) (#875) Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> * release: Release 3 gems (#881) * release: Release 3 gems * opentelemetry-instrumentation-gruf 0.2.0 (was 0.1.1) * opentelemetry-instrumentation-faraday 0.24.0 (was 0.23.4) * opentelemetry-instrumentation-all 0.60.0 (was 0.59.0) * docs: Update instrumentation/all/CHANGELOG.md * chore: bump dependencies --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore: Update rubocop and rubocop-performance in helper gems (#878) * chore: drop dependency on ruby2_keywords (#885) Now that Ruby below 2.7 is not supported anymore (#389, /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/commit/38b083aa41b93bcc8f900cd65bc922c97fe26e5d) this is not needed anymore * chore: Update stalebot (#889) * chore(deps): bump ruby/setup-ruby from 1.171.0 to 1.172.0 (#883) * chore(deps): bump ruby/setup-ruby from 1.172.0 to 1.173.0 (#911) * fix: Propagate response attributes on Faraday::Error (#912) feat: Propagate response attributes on Faraday::Error The current `Faraday` middleware won't report `http.status_code` in cases where an error is raised via another middleware (such as `:raise_error` or `:json`). This adds a special case for subclasses of `Faraday::Error` which have response information available, so that `http.status_code` the span status are still added to the span. * release: Release opentelemetry-instrumentation-faraday 0.24.1 (was 0.24.0) (#913) * release: Release opentelemetry-instrumentation-faraday 0.24.1 (was 0.24.0) * ci: force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore: Fix typo in event_handler_resiliency_test.rb filename (#914) * chore: Update rubocop to 1.62 (#910) * chore: Update rubocop to 1.62 * Rubocop updates * add an empty line after a magic comment * remove redundant line continuation --------- Co-authored-by: Ariel Valentin * chore(deps): bump google-github-actions/release-please-action from 4.0.2 to 4.1.0 (#908) chore(deps): bump google-github-actions/release-please-action Bumps [google-github-actions/release-please-action](/~https://github.com/google-github-actions/release-please-action) from 4.0.2 to 4.1.0. - [Release notes](/~https://github.com/google-github-actions/release-please-action/releases) - [Changelog](/~https://github.com/google-github-actions/release-please-action/blob/main/CHANGELOG.md) - [Commits](/~https://github.com/google-github-actions/release-please-action/compare/v4.0.2...v4.1.0) --- updated-dependencies: - dependency-name: google-github-actions/release-please-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * docs: fix typo where Rake is mentioned instead of Rack (#922) * chore: Run release-please only on open-telemetry (#917) Currently, release-please attempts to run on forks of opentelemetry-ruby-contrib. The workflow fails with the message "GitHub Actions is not permitted to create or approve pull requests." release-please is not needed on forks. Co-authored-by: Ariel Valentin * fix: instrumentation/active_record: add `:allow_retry` option to `find_by_sql` patch (#915) fix: instrumentation/active_record add :allow_retry option to find_by_sql patch Rails 7.2 (rails/rails@eabcff2) introduces the :allow_retry option for this method, so we need to add it to this patch to maintain compatibility. Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore(deps-ci): upgrade conventional commit check (#923) commit-me@v1.4.0 includes an update[1] that recognizes scopes that contain hyphens. [1] /~https://github.com/dev-build-deploy/commit-me/commit/38d4cb09a1bf468012516a3bfbfad325f3435915 * chore(deps-dev): update rubocop-performance requirement from ~> 1.20.1 to ~> 1.21.0 in /helpers/sql-obfuscation (#921) chore(deps-dev): update rubocop-performance requirement from ~> 1.20.1 to ~> 1.21.0 Updates the requirements on [rubocop-performance](/~https://github.com/rubocop/rubocop-performance) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop-performance/releases) - [Changelog](/~https://github.com/rubocop/rubocop-performance/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop-performance/compare/v1.20.1...v1.21.0) --- updated-dependencies: - dependency-name: rubocop-performance dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore(deps-dev): update rubocop-performance requirement from ~> 1.20.1 to ~> 1.21.0 in /helpers/mysql (#919) chore(deps-dev): update rubocop-performance requirement from ~> 1.20.1 to ~> 1.21.0 Updates the requirements on [rubocop-performance](/~https://github.com/rubocop/rubocop-performance) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop-performance/releases) - [Changelog](/~https://github.com/rubocop/rubocop-performance/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop-performance/compare/v1.20.1...v1.21.0) --- updated-dependencies: - dependency-name: rubocop-performance dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore(deps-dev): update webmock requirement from ~> 3.19.1 to ~> 3.23.0 in /resources/azure (#888) chore(deps-dev): update webmock requirement from ~> 3.19.1 to ~> 3.23.0 Updates the requirements on [webmock](/~https://github.com/bblimke/webmock) to permit the latest version. - [Changelog](/~https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/bblimke/webmock/compare/v3.19.1...v3.23.0) --- updated-dependencies: - dependency-name: webmock dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore(deps): update rubocop-performance requirement from ~> 1.20.2 to ~> 1.21.0 (#920) Updates the requirements on [rubocop-performance](/~https://github.com/rubocop/rubocop-performance) to permit the latest version. - [Release notes](/~https://github.com/rubocop/rubocop-performance/releases) - [Changelog](/~https://github.com/rubocop/rubocop-performance/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/rubocop/rubocop-performance/compare/v1.20.2...v1.21.0) --- updated-dependencies: - dependency-name: rubocop-performance dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore: Move @kaylareopelle to maintainers list (#926) * fix: fix markdown header (#925) * fix: suppress deprecation warning in Rdkafka Instrumentation (#884) * fix: suppress deprecation warning in Rdkafka Instrumentation * memoize getter * Use constant * rubocop fix --------- Co-authored-by: Ariel Valentin * chore: update webmock requirement from ~> 3.19.1 to ~> 3.20.0 in /resources/google_cloud_platform (#847) chore(deps-dev): update webmock requirement from ~> 3.19.1 to ~> 3.20.0 Updates the requirements on [webmock](/~https://github.com/bblimke/webmock) to permit the latest version. - [Changelog](/~https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/bblimke/webmock/compare/v3.19.1...v3.20.0) --- updated-dependencies: - dependency-name: webmock dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> Co-authored-by: Ariel Valentin * release: Release 4 gems (#928) * release: Release 4 gems * opentelemetry-instrumentation-racecar 0.3.1 (was 0.3.0) * opentelemetry-instrumentation-rdkafka 0.4.3 (was 0.4.2) * opentelemetry-instrumentation-active_record 0.7.1 (was 0.7.0) * opentelemetry-instrumentation-rack 0.24.1 (was 0.24.0) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: analyze span names in GraphQL instrumentation (#930) * release: Release opentelemetry-instrumentation-graphql 0.28.1 (was 0.28.0) (#931) * chore: bump dev-build-deploy/commit-me from 1.4.0 to 1.4.1 (#932) * chore: bump ruby/setup-ruby from 1.173.0 to 1.174.0 (#936) * fix: resolve active_record testing issue (#944) * fix: resolve active_record issue * Update instrumentation/active_record/Gemfile Co-authored-by: Ariel Valentin --------- Co-authored-by: Ariel Valentin * chore: bump ruby/setup-ruby from 1.174.0 to 1.175.0 (#943) Bumps [ruby/setup-ruby](/~https://github.com/ruby/setup-ruby) from 1.174.0 to 1.175.0. - [Release notes](/~https://github.com/ruby/setup-ruby/releases) - [Commits](/~https://github.com/ruby/setup-ruby/compare/v1.174.0...v1.175.0) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: update webmock requirement from ~> 3.20.0 to ~> 3.23.0 in /resources/google_cloud_platform (#929) chore: update webmock requirement from ~> 3.20.0 to ~> 3.23.0 Updates the requirements on [webmock](/~https://github.com/bblimke/webmock) to permit the latest version. - [Changelog](/~https://github.com/bblimke/webmock/blob/master/CHANGELOG.md) - [Commits](/~https://github.com/bblimke/webmock/compare/v3.20.0...v3.23.0) --- updated-dependencies: - dependency-name: webmock dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: revert modifications to Apache license (#935) See open-telemetry/community#2056 for background Co-authored-by: Ariel Valentin * chore: bump ruby/setup-ruby from 1.175.0 to 1.175.1 (#945) * ci: Remove unused jobs (#947) * chore: JRuby 9.4.6.0 - Ruby 3.1 (#952) * chore: JRuby 9.4.6.0 - Ruby 3.1 Update CI to run JRuby under Ruby 3.1 * ci: Update Ruby Action * feat: Add baggage span processor (#937) * fix: Cancel duplicate builds (#954) This change cancels any pending builds when someone pushes changes before the tests are complete * ci: Setup Weekly Releases (#949) This job will create a release request every week at least one hour before or during the Ruby SIG. This will ensure are aware of any pending releases during the SIG meeting and choose to submit a release. * chore: include new baggage span processor in release automation (#959) tell toys about the new baggage span processor Follow up to #937 to make the gem releasable with our automation. * fix: Bundler conflict warnings (#951) Fixes /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/938 * release: Release 35 gems (#960) * ci: Add nightly builds (#948) * ci: Add nightly builds This should allow us to get ahead of any gem incompatabilities * squash: remove unused action * chore: Release on Tuesdays * chore: bump ruby/setup-ruby from 1.175.1 to 1.176.0 (#963) * fix: rack event baggage handling (#965) * release: Release opentelemetry-instrumentation-rack 0.24.3 (was 0.24.2) (#967) * release: Release opentelemetry-instrumentation-rack 0.24.3 (was 0.24.2) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: Untrace entire request (#968) * fix: untraced in rack event middleware * chore: bump common across the board * release: Release 13 gems (#970) * release: Release 13 gems * opentelemetry-instrumentation-rdkafka 0.4.5 (was 0.4.4) * opentelemetry-instrumentation-http_client 0.22.5 (was 0.22.4) * opentelemetry-instrumentation-koala 0.20.4 (was 0.20.3) * opentelemetry-instrumentation-dalli 0.25.2 (was 0.25.1) * opentelemetry-instrumentation-ethon 0.21.5 (was 0.21.4) * opentelemetry-instrumentation-excon 0.22.2 (was 0.22.1) * opentelemetry-instrumentation-faraday 0.24.3 (was 0.24.2) * opentelemetry-instrumentation-net_http 0.22.5 (was 0.22.4) * opentelemetry-instrumentation-rack 0.24.4 (was 0.24.3) * opentelemetry-instrumentation-redis 0.25.5 (was 0.25.4) * opentelemetry-instrumentation-restclient 0.22.5 (was 0.22.4) * opentelemetry-instrumentation-sidekiq 0.25.4 (was 0.25.3) * opentelemetry-instrumentation-sinatra 0.23.4 (was 0.23.3) --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Robert Laurin Co-authored-by: Ariel Valentin * feat: add aws Lambda instrumentation (#721) * feat: add aws lambda instrumentation * feat: return original handler response and require the handler file * Update instrumentation/aws_lambda/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/example/Gemfile Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: revision on fix sample test, update readme and add rubocop-performance * feat: update readme * Update instrumentation/aws_lambda/CHANGELOG.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/version.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: add codeowner for aws_lambda * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update CODEOWNERS Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: lambda - update semantic convention and test case * feat: add lambda to workflow * feat: lambda - run test with rake test directly * feat: lambda - add rescue to avoid break user function * feat: aws lambda - make faas.trigger static to http as following spec. add cloud account id * Update instrumentation/aws_lambda/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: lambda - use symbol and str for statusCode * feat: lambda - refactor on status code * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: lambda - update span name calculation period. add non-http trigger and test case * feat: aws_lambda update * feat: aws lambda - update attributes * feat: aws lambda - revision * Update instrumentation/aws_lambda/README.md Co-authored-by: Lin Lin * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Lin Lin * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Lin Lin * feat: aws lambda - revision * Update instrumentation/aws_lambda/example/sample.rb Co-authored-by: Ariel Valentin * Update instrumentation/aws_lambda/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/aws_lambda/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: lambda - update release file * feat: remove ci-instrumentation-canary.yml --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> Co-authored-by: Lin Lin Co-authored-by: Ariel Valentin * feat: Support prepend SQL comment for PG instrumentation (#690) Co-authored-by: Ariel Valentin Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * release: Release 2 gems (#975) * release: Release 2 gems * opentelemetry-instrumentation-pg 0.27.3 (was 0.27.2) * opentelemetry-instrumentation-aws_lambda 0.1.0 (initial release) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * feat: add instrumentation support for action mailer (#887) * feat: add notification support for action mailer * feat: action mailer typo * Update instrumentation/action_mailer/example/app/views/test_mailer/welcome_email.html.erb Co-authored-by: Ariel Valentin * feat: action mailer: omit email address * feat: actionmailer updated readme * feat: actionmailer exclude mail attributes * feat: force email out of attr * feat: actionmailer use ecs email convention * feat: revision * feat: restrict on modifying original payload * feat: revision * Update instrumentation/action_mailer/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_mailer/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: revision * Update instrumentation/action_mailer/example/trace_request_demonstration.ru Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/action_mailer/README.md Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> Co-authored-by: Ariel Valentin * ci: Add ActionMailer CI (#976) * ci: Add ActionMailer CI * ci: Linter fixes * squash: drop pry-byebug * ci: fix action_mailer version Initial versions must be 0.0.0 * release: Release opentelemetry-instrumentation-action_mailer 0.1.0 (initial release) (#979) * release: Release opentelemetry-instrumentation-action_mailer 0.1.0 (initial release) * ci: force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * docs: Instrumentation Authors Guide (#946) * release: Release opentelemetry-instrumentation-grape 0.1.8 (was 0.1.7) (#985) * release: Release opentelemetry-instrumentation-grape 0.1.8 (was 0.1.7) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore: Update release please action to googleapis (#989) The previous location, google-github-actions, is deprecated. All future work will be completed in googleapis. * fix: add action_mailer to rails and all (#984) * fix: add action_mailer to rails and all * fix: lint --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * feat: add aws lambda to instrumentation-all (#986) fix: add aws lambda to instrumentation-all Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * chore: Update Rubocop to 1.64.0 (#997) * chore: Rubocop Style/SuperArguments * chore: Rubocop Style/RedundantParentheses * chore: Rubocop Style/RedundantFreeze Lint/SuppressedException * chore: Rubocop Layout/SpaceInsideHash * chore: Update Rubocop to 1.64.0 * chore: Rubocop Style/SuperArguments * chore: bump ruby/setup-ruby from 1.176.0 to 1.178.0 (#996) * chore: bump ruby/setup-ruby from 1.176.0 to 1.178.0 Bumps [ruby/setup-ruby](/~https://github.com/ruby/setup-ruby) from 1.176.0 to 1.178.0. - [Release notes](/~https://github.com/ruby/setup-ruby/releases) - [Commits](/~https://github.com/ruby/setup-ruby/compare/v1.176.0...v1.178.0) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * squash: bump reusable test action --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * test: Add vitess to the propagators matrix (#998) * release: Release 2 gems (#1000) * release: Release 2 gems * opentelemetry-instrumentation-rails 0.30.2 (was 0.30.1) * opentelemetry-instrumentation-all 0.61.0 (was 0.60.0) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: Add net.peer.name to ethon (#1004) The ethon instrumentation did not include the `net.peer.name` attribute like other HTTP instrumentations and was not ported from ddtrace-rb: This attribute is important to GitHub because we base the `peer.service` extracting the subdomain and host information from `net.peer.name`. In order to avoid parsing the URL multiple times, I inlined the common gem `cleanse_url` function and plan to open a separate PR adding a feature to support returing URI objects instead of Strings. * ci: Updates grape appraisals (#1006) Fixes /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/1005 * release: Release opentelemetry-instrumentation-ethon 0.21.6 (was 0.21.5) (#1008) * release: Release opentelemetry-instrumentation-ethon 0.21.6 (was 0.21.5) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * test: Fix grape test suite (#1017) `builder` was removed as a dependency to `grape` but is used by active support in some versions. See ruby-grape/grape#2443 See ruby-grape/grape#2445 Fixes /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/1015 * chore: bump ruby/setup-ruby from 1.178.0 to 1.180.0 (#1009) * chore: bump ruby/setup-ruby from 1.178.0 to 1.180.0 Bumps [ruby/setup-ruby](/~https://github.com/ruby/setup-ruby) from 1.178.0 to 1.180.0. - [Release notes](/~https://github.com/ruby/setup-ruby/releases) - [Commits](/~https://github.com/ruby/setup-ruby/compare/v1.178.0...v1.180.0) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * squash: update ruby tests --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * chore: bump googleapis/release-please-action from 4.1.1 to 4.1.3 (#1003) Bumps [googleapis/release-please-action](/~https://github.com/googleapis/release-please-action) from 4.1.1 to 4.1.3. - [Release notes](/~https://github.com/googleapis/release-please-action/releases) - [Changelog](/~https://github.com/googleapis/release-please-action/blob/main/CHANGELOG.md) - [Commits](/~https://github.com/googleapis/release-please-action/compare/v4.1.1...v4.1.3) --- updated-dependencies: - dependency-name: googleapis/release-please-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: Relax otel common gem constraints (#1007) Rolling out changes to opentelemetry-common is made difficult because many of the instrumentation gems that rely on it have to all receive minor version bumps. Relaxing constraints allows for individual instrumentations to be able to use new features in the common gem without requiring a minor dependency bump of unrelated instrumentations that share that dependency. * feat!: Add baggage key predicate func to baggage span processor (#990) * update test to add multiple baggage items * add keyfilter constructor parameter * rework into regex * add regex example and test * move new setup & assertions to #on_start test block Co-authored-by: Mike Goldsmith * consistent start_with; no "starts" to confuse readers Co-authored-by: Mike Goldsmith * spec the new new method Co-authored-by: Mike Goldsmith * update doc comments with new usage * update README with new usage * bonus: convenience docker compose service for testing baggage processor * update description to indicate new baggage entries are not added to active span * fix whitespace --------- Co-authored-by: Robb Kidd * docs: add function doc for config_overrides_from_env (#1021) * doc: add function doc for config_overrides_from_env * docs: lint * chore: Update release please configs * chore: fix sinatra version in manifest * chore: bump ruby/setup-ruby from 1.180.0 to 1.180.1 (#1023) * chore: bump ruby/setup-ruby from 1.180.0 to 1.180.1 Bumps [ruby/setup-ruby](/~https://github.com/ruby/setup-ruby) from 1.180.0 to 1.180.1. - [Release notes](/~https://github.com/ruby/setup-ruby/releases) - [Commits](/~https://github.com/ruby/setup-ruby/compare/v1.180.0...v1.180.1) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * squash: bump test files --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin * chore: bump dev-build-deploy/commit-me from 1.4.1 to 1.5.0 (#1022) Bumps [dev-build-deploy/commit-me](/~https://github.com/dev-build-deploy/commit-me) from 1.4.1 to 1.5.0. - [Release notes](/~https://github.com/dev-build-deploy/commit-me/releases) - [Commits](/~https://github.com/dev-build-deploy/commit-me/compare/v1.4.1...v1.5.0) --- updated-dependencies: - dependency-name: dev-build-deploy/commit-me dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: Add `http.url` to Excon instrumentation (#1020) * Sort attributes alphabetically * Verify port equivalence * Add `http.url` to Excon instrumentation --------- Co-authored-by: Ariel Valentin * chore: use bot token * release: Release 17 gems (#1025) * release: Release 17 gems * opentelemetry-helpers-mysql 0.1.1 (was 0.1.0) * opentelemetry-helpers-sql-obfuscation 0.1.1 (was 0.1.0) * opentelemetry-instrumentation-rdkafka 0.4.6 (was 0.4.5) * opentelemetry-instrumentation-base 0.22.4 (was 0.22.3) * opentelemetry-instrumentation-http_client 0.22.6 (was 0.22.5) * opentelemetry-instrumentation-koala 0.20.5 (was 0.20.4) * opentelemetry-instrumentation-dalli 0.25.3 (was 0.25.2) * opentelemetry-instrumentation-ethon 0.21.7 (was 0.21.6) * opentelemetry-instrumentation-excon 0.22.3 (was 0.22.2) * opentelemetry-instrumentation-faraday 0.24.4 (was 0.24.3) * opentelemetry-instrumentation-net_http 0.22.6 (was 0.22.5) * opentelemetry-instrumentation-rack 0.24.5 (was 0.24.4) * opentelemetry-instrumentation-redis 0.25.6 (was 0.25.5) * opentelemetry-instrumentation-restclient 0.22.6 (was 0.22.5) * opentelemetry-instrumentation-sidekiq 0.25.5 (was 0.25.4) * opentelemetry-instrumentation-sinatra 0.23.5 (was 0.23.4) * opentelemetry-processor-baggage 0.2.0 (was 0.1.0) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * chore: fix base changelog (#1029) * chore: bump versions in release-please * feat: ActiveSupport user specified span kind (#1016) This will allow users to subscribe to notifications for Server Ingress, Messaging, or clients See /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/957 Co-authored-by: Xuan <112967240+xuan-cao-swi@users.noreply.github.com> * chore: switch back to actions token * fix: compatibility with Faraday v1 (#1032) Fixes /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/1031 Faraday >= 1 changes where the #adapter method gets called, affecting the RackBuilder patch in this gem. Without this change, the tracer middleware gets added to the beginning of the stack instead of the end, and explicitly adding the middleware would cause it to get added twice. This change patches `Connection#initialize` for Faraday >= 1 to preserve the behavior we had with Faraday < 1. This is similar to the dd-trace-rb change made in /~https://github.com/DataDog/dd-trace-rb/pull/906. There is some followup work here to explore moving the middleware to the beginning of the stack instead of the end, but we can work on that separately. * release: Release opentelemetry-instrumentation-faraday 0.24.5 (was 0.24.4) (#1034) * release: Release opentelemetry-instrumentation-active_support 0.5.2 (was 0.5.1) (#1035) * release: Release opentelemetry-instrumentation-active_support 0.5.2 (was 0.5.1) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: Include span kind in ActiveSupport Instrumentation helper (#1036) * fix: Include span kind in AS helper This change fixes a bug where the kind parameter was not passed along to the subscriber object * squash: add test coverage * squash: updated example * perf: Reduce Context Allocations in ActiveJob (#1018) * perf: Reduce Context Allocations in ActiveJob After seeing changes to Rack::Events that reduced the number of active contexts required for ingress spans, I decided to apply the same changes to ActiveJob * doc: Update default.rb Co-authored-by: Steven Harman --------- Co-authored-by: Steven Harman * release: Release opentelemetry-instrumentation-active_support 0.5.3 (was 0.5.2) (#1037) * release: Release opentelemetry-instrumentation-active_support 0.5.3 (was 0.5.2) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * feat: Make Rack install optional for sinatra (#1019) * feat: Make Rack install optional for sinatra This allows for scenarios when there are multiple Rack Applications mounted in the same builder and you want to avoid installing the Rack Events middleware multiple times in the stack. A common example is a Rails application that mounts a Sinatra App in the routes file: ```ruby Rails.application.routes.draw do mount Sinatra::Application, at: '/sinatra' end ``` This results in the Rack middleware being installed multiple times. Once by the Rails ActionPack instrumentation and then by the Sinatra application leading to multiple Rack spans being created in the same trace. This change allows you to avoid this by setting the install_rack option to false when using Sinatra instrumentation and allowing users to manually configure the middleware in the stack as they see fit. * very strange * squash: will this work * squash: wtf * squash: fix tests * squash: Add docs PR feedback * squash: add example * squash: add more test * Update instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/instrumentation.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * Update instrumentation/sinatra/test/opentelemetry/instrumentation/sinatra_test.rb Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> --------- Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> * docs: Fix CHANGELOGs to reflect a past breaking change (#1038) This was actually a breaking change, and despite `:fix!` being used in the commit, the release tooling failed to mark the change as breaking. Refs: #648, #655 * feat!: Custom ActiveSupport Span Names (#1014) * feat!: Custom ActiveSupport Span Names The current implementation of ActiveSupport instrumentation sets the span name to the reverse tokenized name, e.g. `render_template.action_view` is converted to `action_view render_template` This default behavior can sometimes seem counter intuitive for users who use ActiveSupport Notifications to instrument their own code or users who are familiar with Rails instrumentation names. This change does a few things to address the issues listed above: 1. Uses the notification name by default as oppossed to the legacy span name 2. Allows users to provide a custom span name formatter lambda 3. Provides a proc with backward compatible span name formatter `OpenTelemetry::Instrumentation::ActiveSupport::LEGACY_NAME_FORMATTER` See /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/957 * squash: Bolt on a few things * squash: would be great if the tests passed * squash: Linter * feat: make the install of rack instrumentation by grape instrumentation optional (#1043) * feat: optional rack instrumentation install by grape instrumentation In some circumstances we may want to defer the installation of the rack instrumentation that is orchestrated during the grape instrumentation installation. For example, we may want to customise the installation of the rack instrumentation or we can rely on the installation being orchestrated by another framework's instrumentation (such as when Grape is mounted with Rails). This change allows for Grape to skip the installation of the Rack instrumentation so that it can manually installed at a later time. * move the comments above the class name for RubyDocs * release: Release 9 gems (#1049) * release: Release 9 gems * opentelemetry-instrumentation-racecar 0.3.3 (was 0.3.2) * opentelemetry-instrumentation-rdkafka 0.4.7 (was 0.4.6) * opentelemetry-instrumentation-active_job 0.7.2 (was 0.7.1) * opentelemetry-instrumentation-bunny 0.21.4 (was 0.21.3) * opentelemetry-instrumentation-aws_sdk 0.5.3 (was 0.5.2) * opentelemetry-instrumentation-delayed_job 0.22.3 (was 0.22.2) * opentelemetry-instrumentation-que 0.8.2 (was 0.8.1) * opentelemetry-instrumentation-ruby_kafka 0.21.2 (was 0.21.1) * opentelemetry-instrumentation-sidekiq 0.25.6 (was 0.25.5) * ci: Force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * release: Release 5 gems (#1050) * release: Release 5 gems * opentelemetry-instrumentation-grape 0.2.0 (was 0.1.8) * opentelemetry-instrumentation-sinatra 0.24.0 (was 0.23.5) * opentelemetry-instrumentation-active_support 0.6.0 (was 0.5.3) * opentelemetry-instrumentation-rails 0.31.0 (was 0.30.2) * opentelemetry-instrumentation-all 0.62.0 (was 0.61.0) * feat: Bump Versions --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Ariel Valentin * fix: suppress header access with symbol key deprecation warning in Racecar Instrumentation (#1040) fix: suppress deprecation warning in Racecar Instrumentation Use the text_map_getter because accessing kafka message headers with a symbol key is now deprecated in rdkafka. Co-authored-by: Ariel Valentin * release: Release opentelemetry-instrumentation-racecar 0.3.4 (was 0.3.3) (#1055) * release: Release opentelemetry-instrumentation-racecar 0.3.4 (was 0.3.3) * ci: force --------- Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Kayla Reopelle * remove CODEOWNERS --------- Signed-off-by: dependabot[bot] Co-authored-by: Ariel Valentin Co-authored-by: Xuan <112967240+xuan-cao-swi@users.noreply.github.com> Co-authored-by: Robert Mosolgo Co-authored-by: Yohei Kitamura <3087402+yoheyk@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ariel Valentin Co-authored-by: Ariel Valentin Co-authored-by: Josef Šimánek Co-authored-by: Jean byroot Boussier Co-authored-by: Jean Boussier Co-authored-by: Muriel Co-authored-by: Francis Bogsanyi Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sander Jans Co-authored-by: Katherine Oelsner <49968061+octokatherine@users.noreply.github.com> Co-authored-by: Tiago Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com> Co-authored-by: Zachery Hostens Co-authored-by: Robb Kidd Co-authored-by: Rémi Piotaix Co-authored-by: Bruno Facca Co-authored-by: Miguel D. Salcedo Co-authored-by: Robb Kidd Co-authored-by: Dave Lahn Co-authored-by: Ben Sheldon [he/him] Co-authored-by: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Co-authored-by: Matthew Wear Co-authored-by: kohbis <18735471+kohbis@users.noreply.github.com> Co-authored-by: Sam <370182+plantfansam@users.noreply.github.com> Co-authored-by: Robert Co-authored-by: Daniel Azuma Co-authored-by: Elena Tanasoiu Co-authored-by: Aleksandr Starovojtov <37301326+AS-AlStar@users.noreply.github.com> Co-authored-by: Earlopain <14981592+Earlopain@users.noreply.github.com> Co-authored-by: Christian Gregg Co-authored-by: Damien Mathieu <42@dmathieu.com> Co-authored-by: Mark Provan Co-authored-by: Andrew Novoselac <39735028+andrewn617@users.noreply.github.com> Co-authored-by: James White Co-authored-by: Scott Walkinshaw Co-authored-by: Severin Neumann Co-authored-by: Mike Goldsmith Co-authored-by: Robert Laurin Co-authored-by: Lin Lin Co-authored-by: Keiko Oda Co-authored-by: Troels Thomsen <19824+tt@users.noreply.github.com> Co-authored-by: Daniel Colson Co-authored-by: Steven Harman Co-authored-by: Christopher Holmes Co-authored-by: Ewan Whorton Co-authored-by: Kayla Reopelle --- .commit-me.json | 4 + .github/actions/test_gem/action.yml | 6 +- .github/dependabot.yml | 16 +- .github/workflows/ci-contrib-canary.yml | 113 --- .github/workflows/ci-contrib.yml | 104 ++- .../workflows/ci-instrumentation-canary.yml | 132 ---- ...i-instrumentation-with-services-canary.yml | 155 ---- .../ci-instrumentation-with-services.yml | 38 +- .github/workflows/ci-instrumentation.yml | 17 +- .github/workflows/conventional-commits.yaml | 32 + .github/workflows/installation-tests.yml | 5 +- .github/workflows/release-hook-on-closed.yml | 4 +- .github/workflows/release-hook-on-push.yml | 4 +- .github/workflows/release-perform.yml | 4 +- .github/workflows/release-please.yaml | 64 ++ .github/workflows/release-request-weekly.yml | 28 + .github/workflows/release-request.yml | 4 +- .github/workflows/release-retry.yml | 4 +- .github/workflows/stale.yaml | 6 +- .../templates/gemspec.tt | 1 + .release-please-manifest.json | 54 ++ .rubocop.yml | 2 + .toys/.data/releases.yml | 39 +- .toys/.toys.rb | 4 +- CODEOWNERS | 31 - CONTRIBUTING.md | 107 +-- Dockerfile | 1 + Gemfile | 3 +- LICENSE | 2 +- README.md | 28 +- docker-compose.yml | 6 + helpers/mysql/.rubocop.yml | 4 + helpers/mysql/.yardopts | 9 + helpers/mysql/CHANGELOG.md | 9 + .../mysql/Gemfile | 4 +- {resource_detectors => helpers/mysql}/LICENSE | 0 helpers/mysql/README.md | 56 ++ .../mysql}/Rakefile | 0 .../mysql/lib/opentelemetry-helpers-mysql.rb | 7 + helpers/mysql/lib/opentelemetry/helpers.rb | 15 + .../mysql/lib/opentelemetry/helpers/mysql.rb | 91 +++ .../opentelemetry/helpers/mysql}/version.rb | 6 +- .../mysql/opentelemetry-helpers-mysql.gemspec | 27 +- helpers/mysql/test/helpers/mysql_test.rb | 162 +++++ .../mysql}/test/test_helper.rb | 7 +- helpers/sql-obfuscation/.rubocop.yml | 4 + helpers/sql-obfuscation/.yardopts | 9 + helpers/sql-obfuscation/CHANGELOG.md | 9 + .../sql-obfuscation}/Gemfile | 5 - helpers/sql-obfuscation/LICENSE | 201 +++++ helpers/sql-obfuscation/README.md | 62 ++ helpers/sql-obfuscation/Rakefile | 28 + .../opentelemetry-helpers-sql-obfuscation.rb | 7 + .../lib/opentelemetry/helpers.rb | 14 + .../opentelemetry/helpers/sql_obfuscation.rb | 139 ++++ .../helpers/sql_obfuscation/version.rb | 13 + ...ntelemetry-helpers-sql-obfuscation.gemspec | 45 ++ .../test/fixtures/sql_obfuscation.json | 685 ++++++++++++++++++ .../test/helpers/sql_obfuscation_test.rb | 108 +++ helpers/sql-obfuscation/test/test_helper.rb | 11 + instrumentation/CONTRIBUTING.md | 486 +++++++++++++ instrumentation/README.md | 18 +- instrumentation/action_mailer/.rubocop.yml | 1 + instrumentation/action_mailer/.yardopts | 9 + instrumentation/action_mailer/Appraisals | 17 + instrumentation/action_mailer/CHANGELOG.md | 5 + instrumentation/action_mailer/Gemfile | 14 + instrumentation/action_mailer/LICENSE | 201 +++++ instrumentation/action_mailer/README.md | 102 +++ instrumentation/action_mailer/Rakefile | 28 + .../views/test_mailer/welcome_email.html.erb | 10 + .../example/tmp/local_secret.txt | 1 + .../example/trace_request_demonstration.ru | 63 ++ ...telemetry-instrumentation-action_mailer.rb | 7 + .../lib/opentelemetry/instrumentation.rb | 19 + .../instrumentation/action_mailer.rb | 19 + .../action_mailer/instrumentation.rb | 88 +++ .../instrumentation/action_mailer/railtie.rb | 32 + .../instrumentation/action_mailer/version.rb | 13 + ...etry-instrumentation-action_mailer.gemspec | 50 ++ .../action_mailer/instrumentation_test.rb | 116 +++ .../action_mailer/test/test_helper.rb | 24 + instrumentation/action_pack/Appraisals | 8 +- instrumentation/action_pack/CHANGELOG.md | 14 + instrumentation/action_pack/README.md | 19 + .../example/trace_demonstration.ru | 4 +- .../instrumentation/action_pack/handlers.rb | 38 + .../action_pack/handlers/action_controller.rb | 59 ++ .../action_pack/instrumentation.rb | 6 +- .../patches/action_controller/metal.rb | 40 - .../instrumentation/action_pack/version.rb | 2 +- ...emetry-instrumentation-action_pack.gemspec | 5 +- .../action_controller_test.rb} | 72 +- .../action_pack/handlers_test.rb | 34 + .../test/test_helpers/app_config.rb | 13 +- .../middlewares/redirect_middleware.rb | 2 +- instrumentation/action_view/Appraisals | 8 +- instrumentation/action_view/CHANGELOG.md | 10 + .../example/trace_request_demonstration.ru | 2 +- .../action_view/instrumentation.rb | 2 +- .../instrumentation/action_view/version.rb | 2 +- ...emetry-instrumentation-action_view.gemspec | 5 +- instrumentation/active_job/Appraisals | 14 +- instrumentation/active_job/CHANGELOG.md | 24 +- instrumentation/active_job/README.md | 58 ++ instrumentation/active_job/example/Gemfile | 8 - .../active_job/example/active_job.rb | 99 ++- .../instrumentation/active_job.rb | 33 + .../instrumentation/active_job/handlers.rb | 73 ++ .../active_job/handlers/default.rb | 113 +++ .../active_job/handlers/enqueue.rb | 39 + .../active_job/handlers/perform.rb | 64 ++ .../active_job/instrumentation.rb | 11 +- .../active_job/mappers/attribute.rb | 43 ++ .../patches/active_job_callbacks.rb | 96 --- .../active_job/patches/base.rb | 23 +- .../instrumentation/active_job/version.rb | 2 +- ...lemetry-instrumentation-active_job.gemspec | 6 +- .../active_job/handlers/discard_test.rb | 55 ++ .../active_job/handlers/perform_test.rb} | 232 ++---- .../active_job/handlers/retry_stopped_test.rb | 57 ++ .../active_job/handlers_test.rb | 53 ++ .../active_job/instrumentation_test.rb | 2 +- .../active_job/mappers/attribute_test.rb | 77 ++ .../active_job/patches/base_test.rb | 17 +- .../active_job/test/test_helper.rb | 26 +- ...mentation-active_model_serializers.gemspec | 3 +- instrumentation/active_record/Appraisals | 8 +- instrumentation/active_record/CHANGELOG.md | 18 + instrumentation/active_record/Gemfile | 2 +- .../active_record/instrumentation.rb | 12 +- .../persistence_insert_class_methods.rb | 12 +- .../active_record/patches/querying.rb | 2 +- .../instrumentation/active_record/version.rb | 2 +- ...etry-instrumentation-active_record.gemspec | 6 +- .../active_record/instrumentation_test.rb | 12 - instrumentation/active_support/Appraisals | 8 +- instrumentation/active_support/CHANGELOG.md | 32 + instrumentation/active_support/README.md | 10 +- .../active_support/instrumentation.rb | 10 + .../active_support/span_subscriber.rb | 69 +- .../instrumentation/active_support/version.rb | 2 +- ...try-instrumentation-active_support.gemspec | 6 +- .../active_support/span_subscriber_test.rb | 126 +++- .../active_support/test/test_helper.rb | 1 + instrumentation/all/CHANGELOG.md | 66 +- instrumentation/all/Gemfile | 8 +- .../lib/opentelemetry/instrumentation/all.rb | 2 + .../instrumentation/all/version.rb | 2 +- .../opentelemetry-instrumentation-all.gemspec | 32 +- instrumentation/aws_lambda/.rubocop.yml | 1 + instrumentation/aws_lambda/.yardopts | 9 + instrumentation/aws_lambda/CHANGELOG.md | 5 + instrumentation/aws_lambda/Gemfile | 14 + instrumentation/aws_lambda/LICENSE | 201 +++++ instrumentation/aws_lambda/README.md | 58 ++ instrumentation/aws_lambda/Rakefile | 29 + instrumentation/aws_lambda/example/Gemfile | 6 + instrumentation/aws_lambda/example/sample.rb | 9 + .../aws_lambda/example/trace_demonstration.rb | 102 +++ ...pentelemetry-instrumentation-aws_lambda.rb | 7 + .../lib/opentelemetry/instrumentation.rb | 22 + .../instrumentation/aws_lambda.rb | 19 + .../instrumentation/aws_lambda/handler.rb | 198 +++++ .../aws_lambda/instrumentation.rb | 29 + .../instrumentation/aws_lambda/version.rb | 13 + ...lemetry-instrumentation-aws_lambda.gemspec | 51 ++ .../opentelemetry/instrumentation_test.rb | 217 ++++++ .../aws_lambda/test/test_helper.rb | 162 +++++ instrumentation/aws_sdk/CHANGELOG.md | 14 +- .../aws_sdk/message_attributes.rb | 3 +- .../instrumentation/aws_sdk/version.rb | 2 +- ...ntelemetry-instrumentation-aws_sdk.gemspec | 4 +- .../opentelemetry/instrumentation_test.rb | 20 + instrumentation/base/CHANGELOG.md | 13 +- .../lib/opentelemetry/instrumentation/base.rb | 14 +- .../opentelemetry/instrumentation/version.rb | 2 +- ...opentelemetry-instrumentation-base.gemspec | 4 +- instrumentation/bunny/CHANGELOG.md | 14 +- .../instrumentation/bunny/patches/channel.rb | 2 +- .../instrumentation/bunny/version.rb | 2 +- ...pentelemetry-instrumentation-bunny.gemspec | 4 +- instrumentation/concurrent_ruby/CHANGELOG.md | 8 + .../patches/thread_pool_executor.rb | 2 +- .../concurrent_ruby/version.rb | 2 +- ...ry-instrumentation-concurrent_ruby.gemspec | 4 +- .../concurrent-ruby/instrumentation_test.rb | 2 +- instrumentation/dalli/CHANGELOG.md | 18 + instrumentation/dalli/README.md | 16 + .../instrumentation/dalli/instrumentation.rb | 2 +- .../instrumentation/dalli/version.rb | 2 +- ...pentelemetry-instrumentation-dalli.gemspec | 5 +- .../dalli/instrumentation_test.rb | 2 +- instrumentation/datadog-porting-guide.md | 8 +- instrumentation/delayed_job/Appraisals | 16 +- instrumentation/delayed_job/CHANGELOG.md | 20 +- instrumentation/delayed_job/Gemfile | 1 + .../delayed_job/plugins/tracer_plugin.rb | 7 +- .../instrumentation/delayed_job/version.rb | 2 +- ...emetry-instrumentation-delayed_job.gemspec | 5 +- .../delayed_job/plugins/tracer_plugin_test.rb | 29 +- .../instrumentation/delayed_job_test.rb | 9 +- .../delayed_job/test/test_helper.rb | 39 +- instrumentation/ethon/Appraisals | 8 +- instrumentation/ethon/CHANGELOG.md | 20 + .../instrumentation/ethon/patches/easy.rb | 23 +- .../instrumentation/ethon/version.rb | 2 +- ...pentelemetry-instrumentation-ethon.gemspec | 5 +- .../ethon/instrumentation_test.rb | 5 +- instrumentation/excon/Appraisals | 10 +- instrumentation/excon/CHANGELOG.md | 23 + .../concerns/untraced_hosts.rb | 41 ++ .../instrumentation/excon/instrumentation.rb | 13 +- .../excon/middlewares/tracer_middleware.rb | 89 ++- .../instrumentation/excon/patches/socket.rb | 58 ++ .../instrumentation/excon/version.rb | 2 +- ...pentelemetry-instrumentation-excon.gemspec | 5 +- .../excon/instrumentation_test.rb | 193 ++++- instrumentation/faraday/CHANGELOG.md | 32 + .../faraday/instrumentation.rb | 8 +- .../faraday/middlewares/tracer_middleware.rb | 30 +- .../faraday/patches/connection.rb | 27 + .../instrumentation/faraday/version.rb | 2 +- ...ntelemetry-instrumentation-faraday.gemspec | 5 +- .../middlewares/tracer_middleware_test.rb | 217 ++++-- instrumentation/grape/Appraisals | 25 +- instrumentation/grape/CHANGELOG.md | 20 + instrumentation/grape/Gemfile | 1 + .../grape/example/trace_demonstration.rb | 19 +- .../instrumentation/grape/event_handler.rb | 10 +- .../instrumentation/grape/instrumentation.rb | 13 + .../instrumentation/grape/version.rb | 2 +- ...pentelemetry-instrumentation-grape.gemspec | 4 +- .../instrumentation/grape_test.rb | 55 +- instrumentation/grape/test/test_helper.rb | 2 +- instrumentation/graphql/Appraisals | 35 +- instrumentation/graphql/CHANGELOG.md | 22 + .../graphql/tracers/graphql_trace.rb | 83 ++- .../graphql/tracers/graphql_tracer.rb | 68 +- .../instrumentation/graphql/version.rb | 2 +- ...ntelemetry-instrumentation-graphql.gemspec | 4 +- .../graphql/instrumentation_test.rb | 4 +- .../graphql/tracers/graphql_trace_test.rb | 11 +- .../graphql/tracers/graphql_tracer_test.rb | 12 +- instrumentation/graphql/test/test_helper.rb | 14 +- instrumentation/gruf/Appraisals | 12 +- instrumentation/gruf/CHANGELOG.md | 12 + .../gruf/interceptors/client.rb | 2 +- .../gruf/interceptors/server.rb | 2 +- .../instrumentation/gruf/version.rb | 2 +- ...opentelemetry-instrumentation-gruf.gemspec | 4 +- instrumentation/http/CHANGELOG.md | 8 + .../instrumentation/http/patches/client.rb | 2 +- .../instrumentation/http/version.rb | 2 +- ...opentelemetry-instrumentation-http.gemspec | 4 +- .../http/patches/client_test.rb | 4 +- instrumentation/http_client/CHANGELOG.md | 16 + .../http_client/patches/client.rb | 2 +- .../instrumentation/http_client/version.rb | 2 +- ...emetry-instrumentation-http_client.gemspec | 5 +- instrumentation/httpx/.yardopts | 9 + instrumentation/httpx/Appraisals | 13 + instrumentation/httpx/CHANGELOG.md | 13 + instrumentation/httpx/Gemfile | 14 + instrumentation/httpx/LICENSE | 201 +++++ instrumentation/httpx/README.md | 49 ++ instrumentation/httpx/Rakefile | 28 + .../httpx/example/trace_demonstration.rb | 27 + .../opentelemetry-instrumentation-httpx.rb | 7 + .../lib/opentelemetry/instrumentation.rb | 19 + .../opentelemetry/instrumentation/httpx.rb | 19 + .../instrumentation/httpx/instrumentation.rb | 40 + .../instrumentation/httpx/plugin.rb | 92 +++ .../instrumentation/httpx/version.rb | 13 + ...pentelemetry-instrumentation-httpx.gemspec | 50 ++ .../httpx/instrumentation_test.rb | 28 + .../httpx/test/instrumentation/plugin_test.rb | 139 ++++ instrumentation/httpx/test/test_helper.rb | 22 + instrumentation/koala/CHANGELOG.md | 12 + .../instrumentation/koala/version.rb | 2 +- ...pentelemetry-instrumentation-koala.gemspec | 5 +- instrumentation/lmdb/CHANGELOG.md | 4 + .../instrumentation/lmdb/version.rb | 2 +- ...opentelemetry-instrumentation-lmdb.gemspec | 4 +- instrumentation/mongo/CHANGELOG.md | 8 + .../mongo/command_serializer.rb | 2 +- .../instrumentation/mongo/version.rb | 2 +- ...pentelemetry-instrumentation-mongo.gemspec | 4 +- instrumentation/mysql2/CHANGELOG.md | 23 + instrumentation/mysql2/Gemfile | 2 + instrumentation/mysql2/README.md | 6 +- instrumentation/mysql2/example/mysql2.rb | 4 +- .../opentelemetry/instrumentation/mysql2.rb | 10 +- .../instrumentation/mysql2/instrumentation.rb | 2 +- .../instrumentation/mysql2/patches/client.rb | 152 ++-- .../instrumentation/mysql2/version.rb | 2 +- ...entelemetry-instrumentation-mysql2.gemspec | 6 +- .../mysql2/instrumentation_test.rb | 51 +- instrumentation/net_http/CHANGELOG.md | 16 + .../net/http/patches/instrumentation.rb | 16 +- .../instrumentation/net/http/version.rb | 2 +- ...telemetry-instrumentation-net_http.gemspec | 5 +- .../net/http/instrumentation_test.rb | 28 + instrumentation/net_http/test/test_helper.rb | 1 + instrumentation/pg/CHANGELOG.md | 27 + instrumentation/pg/Gemfile | 1 + instrumentation/pg/README.md | 4 +- .../instrumentation/pg/constants.rb | 23 - .../instrumentation/pg/instrumentation.rb | 2 +- .../instrumentation/pg/patches/connection.rb | 43 +- .../instrumentation/pg/version.rb | 2 +- .../opentelemetry-instrumentation-pg.gemspec | 5 +- .../pg/instrumentation_test.rb | 16 +- instrumentation/que/Appraisals | 13 +- instrumentation/que/CHANGELOG.md | 19 +- instrumentation/que/Gemfile | 6 +- .../que/middlewares/server_middleware.rb | 4 +- .../instrumentation/que/patches/poller.rb | 4 +- .../instrumentation/que/version.rb | 2 +- .../opentelemetry-instrumentation-que.gemspec | 7 +- instrumentation/que/test/test_helper.rb | 7 +- instrumentation/racecar/CHANGELOG.md | 18 +- instrumentation/racecar/README.md | 2 +- .../racecar/process_message_subscriber.rb | 9 +- .../instrumentation/racecar/version.rb | 2 +- ...ntelemetry-instrumentation-racecar.gemspec | 5 +- instrumentation/rack/Appraisals | 4 + instrumentation/rack/CHANGELOG.md | 31 + instrumentation/rack/Gemfile | 1 + instrumentation/rack/README.md | 30 + .../rack/example/trace_demonstration.rb | 2 +- .../rack/example/trace_demonstration2.rb | 2 +- .../rack/example/trace_demonstration3.rb | 2 +- .../instrumentation/rack/instrumentation.rb | 6 +- .../rack/middlewares/event_handler.rb | 40 +- .../rack/middlewares/tracer_middleware.rb | 4 +- .../instrumentation/rack/version.rb | 2 +- ...opentelemetry-instrumentation-rack.gemspec | 6 +- .../rack/instrumentation_test.rb | 4 +- ...st.rb => event_handler_resiliency_test.rb} | 0 .../rack/middlewares/event_handler_test.rb | 68 +- .../middlewares/tracer_middleware_test.rb | 14 +- instrumentation/rails/Appraisals | 8 +- instrumentation/rails/CHANGELOG.md | 33 +- instrumentation/rails/README.md | 4 +- .../example/trace_request_demonstration.ru | 2 +- .../opentelemetry/instrumentation/rails.rb | 1 + .../instrumentation/rails/instrumentation.rb | 4 +- .../instrumentation/rails/version.rb | 2 +- ...pentelemetry-instrumentation-rails.gemspec | 15 +- .../rails/instrumentation_test.rb | 13 - .../test_helpers/app_config.rb | 9 - .../initializers/content_security_policy.rb | 1 + .../dummy/config/initializers/inflections.rb | 1 + .../config/initializers/permissions_policy.rb | 1 + .../instrumentation/rails/railtie_test.rb | 8 +- instrumentation/rake/CHANGELOG.md | 4 + .../instrumentation/rake/version.rb | 2 +- ...opentelemetry-instrumentation-rake.gemspec | 4 +- instrumentation/rdkafka/CHANGELOG.md | 30 +- .../rdkafka/instrumentation.rb | 2 +- .../rdkafka/patches/consumer.rb | 11 +- .../instrumentation/rdkafka/version.rb | 2 +- ...ntelemetry-instrumentation-rdkafka.gemspec | 5 +- instrumentation/redis/Appraisals | 29 +- instrumentation/redis/CHANGELOG.md | 12 + .../redis/patches/redis_v4_client.rb | 2 +- .../instrumentation/redis/version.rb | 2 +- ...pentelemetry-instrumentation-redis.gemspec | 6 +- instrumentation/resque/CHANGELOG.md | 8 + .../opentelemetry/instrumentation/resque.rb | 2 +- .../instrumentation/resque/instrumentation.rb | 80 +- .../instrumentation/resque/version.rb | 2 +- ...entelemetry-instrumentation-resque.gemspec | 4 +- instrumentation/restclient/CHANGELOG.md | 16 + .../restclient/patches/request.rb | 6 +- .../instrumentation/restclient/version.rb | 2 +- ...lemetry-instrumentation-restclient.gemspec | 5 +- instrumentation/rspec/CHANGELOG.md | 8 + instrumentation/rspec/Gemfile | 1 + .../instrumentation/rspec/formatter.rb | 2 +- .../instrumentation/rspec/version.rb | 2 +- ...pentelemetry-instrumentation-rspec.gemspec | 4 +- .../instrumentation/rspec/formatter_test.rb | 4 +- instrumentation/ruby_kafka/CHANGELOG.md | 10 +- .../ruby_kafka/instrumentation.rb | 2 +- .../instrumentation/ruby_kafka/version.rb | 2 +- ...lemetry-instrumentation-ruby_kafka.gemspec | 4 +- instrumentation/sidekiq/.yardopts | 1 + instrumentation/sidekiq/CHANGELOG.md | 28 +- .../opentelemetry/instrumentation/sidekiq.rb | 2 +- .../sidekiq/instrumentation.rb | 77 +- .../instrumentation/sidekiq/version.rb | 2 +- ...ntelemetry-instrumentation-sidekiq.gemspec | 5 +- instrumentation/sinatra/Appraisals | 12 +- instrumentation/sinatra/CHANGELOG.md | 16 + instrumentation/sinatra/README.md | 14 + instrumentation/sinatra/example/Gemfile | 3 +- instrumentation/sinatra/example/config.ru | 51 ++ .../opentelemetry/instrumentation/sinatra.rb | 2 +- .../sinatra/extensions/tracer_extension.rb | 30 +- .../sinatra/instrumentation.rb | 41 +- .../instrumentation/sinatra/version.rb | 2 +- ...ntelemetry-instrumentation-sinatra.gemspec | 7 +- .../instrumentation/sinatra_test.rb | 54 +- instrumentation/trilogy/CHANGELOG.md | 29 + instrumentation/trilogy/Gemfile | 3 + instrumentation/trilogy/README.md | 13 + .../trilogy/instrumentation.rb | 23 +- .../instrumentation/trilogy/patches/client.rb | 171 ++--- .../instrumentation/trilogy/version.rb | 2 +- ...ntelemetry-instrumentation-trilogy.gemspec | 7 +- .../trilogy/instrumentation_test.rb | 119 ++- processor/baggage/.rubocop.yml | 1 + processor/baggage/.yardopts | 9 + processor/baggage/CHANGELOG.md | 11 + processor/baggage/Gemfile | 6 + processor/baggage/LICENSE | 201 +++++ processor/baggage/README.md | 99 +++ processor/baggage/Rakefile | 30 + .../lib/opentelemetry-processor-baggage.rb | 7 + .../baggage/baggage_span_processor.rb | 126 ++++ .../processor/baggage/version.rb | 15 +- .../opentelemetry-processor-baggage.gemspec | 44 ++ .../baggage/baggage_span_processor_test.rb | 148 ++++ processor/baggage/test/test_helper.rb | 11 + propagator/ottrace/CHANGELOG.md | 4 + .../propagator/ottrace/text_map_propagator.rb | 4 +- .../propagator/ottrace/version.rb | 2 +- .../opentelemetry-propagator-ottrace.gemspec | 3 +- propagator/vitess/.rubocop.yml | 1 + propagator/vitess/.yardopts | 9 + propagator/vitess/CHANGELOG.md | 5 + propagator/vitess/Gemfile | 6 + propagator/vitess/LICENSE | 201 +++++ propagator/vitess/README.md | 64 ++ propagator/vitess/Rakefile | 30 + .../lib/opentelemetry-propagator-vitess.rb | 8 + .../lib/opentelemetry/propagator/vitess.rb | 38 + .../propagator/vitess/sql_query_propagator.rb | 78 ++ .../propagator/vitess/version.rb | 21 + .../opentelemetry-propagator-vitess.gemspec | 43 ++ .../vitess/sql_query_propagator_test.rb | 125 ++++ .../opentelemetry/propagator/vitess_test.rb | 18 + propagator/vitess/test/test_helper.rb | 19 + propagator/xray/CHANGELOG.md | 4 + .../propagator/xray/text_map_propagator.rb | 2 +- .../opentelemetry/propagator/xray/version.rb | 2 +- .../opentelemetry-propagator-xray.gemspec | 3 +- release-please-config.json | 226 ++++++ releases/Gemfile | 1 - resource_detectors/.rubocop.yml | 1 - resource_detectors/.yardopts | 9 - resource_detectors/CHANGELOG.md | 106 --- resource_detectors/README.md | 51 -- .../resource/detectors/auto_detector.rb | 25 - .../opentelemetry/resource/detectors/azure.rb | 77 -- .../detectors/google_cloud_platform.rb | 80 -- .../detectors/auto_detector_test.rb | 52 -- .../opentelemetry/detectors/azure_test.rb | 120 --- .../detectors/google_cloud_platform_test.rb | 110 --- ...ntelemetry-resource-detector-azure.gemspec | 5 +- ...emetry-resource-detector-container.gemspec | 3 +- ...rce-detector-google_cloud_platform.gemspec | 5 +- 464 files changed, 11111 insertions(+), 2691 deletions(-) create mode 100644 .commit-me.json delete mode 100644 .github/workflows/ci-contrib-canary.yml delete mode 100644 .github/workflows/ci-instrumentation-canary.yml delete mode 100644 .github/workflows/ci-instrumentation-with-services-canary.yml create mode 100644 .github/workflows/conventional-commits.yaml create mode 100644 .github/workflows/release-please.yaml create mode 100644 .github/workflows/release-request-weekly.yml create mode 100644 .release-please-manifest.json delete mode 100644 CODEOWNERS create mode 100644 helpers/mysql/.rubocop.yml create mode 100644 helpers/mysql/.yardopts create mode 100644 helpers/mysql/CHANGELOG.md rename resource_detectors/lib/opentelemetry-resource_detectors.rb => helpers/mysql/Gemfile (67%) rename {resource_detectors => helpers/mysql}/LICENSE (100%) create mode 100644 helpers/mysql/README.md rename {resource_detectors => helpers/mysql}/Rakefile (100%) create mode 100644 helpers/mysql/lib/opentelemetry-helpers-mysql.rb create mode 100644 helpers/mysql/lib/opentelemetry/helpers.rb create mode 100644 helpers/mysql/lib/opentelemetry/helpers/mysql.rb rename {resource_detectors/lib/opentelemetry/resource/detectors => helpers/mysql/lib/opentelemetry/helpers/mysql}/version.rb (69%) rename resource_detectors/opentelemetry-resource_detectors.gemspec => helpers/mysql/opentelemetry-helpers-mysql.gemspec (60%) create mode 100644 helpers/mysql/test/helpers/mysql_test.rb rename {resource_detectors => helpers/mysql}/test/test_helper.rb (72%) create mode 100644 helpers/sql-obfuscation/.rubocop.yml create mode 100644 helpers/sql-obfuscation/.yardopts create mode 100644 helpers/sql-obfuscation/CHANGELOG.md rename {resource_detectors => helpers/sql-obfuscation}/Gemfile (61%) create mode 100644 helpers/sql-obfuscation/LICENSE create mode 100644 helpers/sql-obfuscation/README.md create mode 100644 helpers/sql-obfuscation/Rakefile create mode 100644 helpers/sql-obfuscation/lib/opentelemetry-helpers-sql-obfuscation.rb create mode 100644 helpers/sql-obfuscation/lib/opentelemetry/helpers.rb create mode 100644 helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation.rb create mode 100644 helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation/version.rb create mode 100644 helpers/sql-obfuscation/opentelemetry-helpers-sql-obfuscation.gemspec create mode 100644 helpers/sql-obfuscation/test/fixtures/sql_obfuscation.json create mode 100644 helpers/sql-obfuscation/test/helpers/sql_obfuscation_test.rb create mode 100644 helpers/sql-obfuscation/test/test_helper.rb create mode 100644 instrumentation/CONTRIBUTING.md create mode 100644 instrumentation/action_mailer/.rubocop.yml create mode 100644 instrumentation/action_mailer/.yardopts create mode 100644 instrumentation/action_mailer/Appraisals create mode 100644 instrumentation/action_mailer/CHANGELOG.md create mode 100644 instrumentation/action_mailer/Gemfile create mode 100644 instrumentation/action_mailer/LICENSE create mode 100644 instrumentation/action_mailer/README.md create mode 100644 instrumentation/action_mailer/Rakefile create mode 100644 instrumentation/action_mailer/example/app/views/test_mailer/welcome_email.html.erb create mode 100644 instrumentation/action_mailer/example/tmp/local_secret.txt create mode 100644 instrumentation/action_mailer/example/trace_request_demonstration.ru create mode 100644 instrumentation/action_mailer/lib/opentelemetry-instrumentation-action_mailer.rb create mode 100644 instrumentation/action_mailer/lib/opentelemetry/instrumentation.rb create mode 100644 instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer.rb create mode 100644 instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/instrumentation.rb create mode 100644 instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/railtie.rb create mode 100644 instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/version.rb create mode 100644 instrumentation/action_mailer/opentelemetry-instrumentation-action_mailer.gemspec create mode 100644 instrumentation/action_mailer/test/opentelemetry/instrumentation/action_mailer/instrumentation_test.rb create mode 100644 instrumentation/action_mailer/test/test_helper.rb create mode 100644 instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers.rb create mode 100644 instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb delete mode 100644 instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/patches/action_controller/metal.rb rename instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/{patches/action_controller/metal_test.rb => handlers/action_controller_test.rb} (53%) create mode 100644 instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/handlers_test.rb delete mode 100644 instrumentation/active_job/example/Gemfile create mode 100644 instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers.rb create mode 100644 instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/default.rb create mode 100644 instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/enqueue.rb create mode 100644 instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/perform.rb create mode 100644 instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/mappers/attribute.rb delete mode 100644 instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/active_job_callbacks.rb create mode 100644 instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/discard_test.rb rename instrumentation/active_job/test/{instrumentation/active_job/patches/active_job_callbacks_test.rb => opentelemetry/instrumentation/active_job/handlers/perform_test.rb} (50%) create mode 100644 instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/retry_stopped_test.rb create mode 100644 instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers_test.rb rename instrumentation/active_job/test/{ => opentelemetry}/instrumentation/active_job/instrumentation_test.rb (93%) create mode 100644 instrumentation/active_job/test/opentelemetry/instrumentation/active_job/mappers/attribute_test.rb rename instrumentation/active_job/test/{ => opentelemetry}/instrumentation/active_job/patches/base_test.rb (63%) create mode 100644 instrumentation/aws_lambda/.rubocop.yml create mode 100644 instrumentation/aws_lambda/.yardopts create mode 100644 instrumentation/aws_lambda/CHANGELOG.md create mode 100644 instrumentation/aws_lambda/Gemfile create mode 100644 instrumentation/aws_lambda/LICENSE create mode 100644 instrumentation/aws_lambda/README.md create mode 100644 instrumentation/aws_lambda/Rakefile create mode 100644 instrumentation/aws_lambda/example/Gemfile create mode 100644 instrumentation/aws_lambda/example/sample.rb create mode 100755 instrumentation/aws_lambda/example/trace_demonstration.rb create mode 100644 instrumentation/aws_lambda/lib/opentelemetry-instrumentation-aws_lambda.rb create mode 100644 instrumentation/aws_lambda/lib/opentelemetry/instrumentation.rb create mode 100644 instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda.rb create mode 100644 instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb create mode 100644 instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/instrumentation.rb create mode 100644 instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/version.rb create mode 100644 instrumentation/aws_lambda/opentelemetry-instrumentation-aws_lambda.gemspec create mode 100644 instrumentation/aws_lambda/test/opentelemetry/instrumentation_test.rb create mode 100644 instrumentation/aws_lambda/test/test_helper.rb create mode 100644 instrumentation/excon/lib/opentelemetry/instrumentation/concerns/untraced_hosts.rb create mode 100644 instrumentation/excon/lib/opentelemetry/instrumentation/excon/patches/socket.rb create mode 100644 instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/patches/connection.rb create mode 100644 instrumentation/httpx/.yardopts create mode 100644 instrumentation/httpx/Appraisals create mode 100644 instrumentation/httpx/CHANGELOG.md create mode 100644 instrumentation/httpx/Gemfile create mode 100644 instrumentation/httpx/LICENSE create mode 100644 instrumentation/httpx/README.md create mode 100644 instrumentation/httpx/Rakefile create mode 100644 instrumentation/httpx/example/trace_demonstration.rb create mode 100644 instrumentation/httpx/lib/opentelemetry-instrumentation-httpx.rb create mode 100644 instrumentation/httpx/lib/opentelemetry/instrumentation.rb create mode 100644 instrumentation/httpx/lib/opentelemetry/instrumentation/httpx.rb create mode 100644 instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/instrumentation.rb create mode 100644 instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/plugin.rb create mode 100644 instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/version.rb create mode 100644 instrumentation/httpx/opentelemetry-instrumentation-httpx.gemspec create mode 100644 instrumentation/httpx/test/instrumentation/httpx/instrumentation_test.rb create mode 100644 instrumentation/httpx/test/instrumentation/plugin_test.rb create mode 100644 instrumentation/httpx/test/test_helper.rb rename instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/{event_handler_reseliency_test.rb => event_handler_resiliency_test.rb} (100%) delete mode 100644 instrumentation/rails/test/instrumentation/opentelemetry/instrumentation/rails/instrumentation_test.rb create mode 100755 instrumentation/sinatra/example/config.ru create mode 100644 processor/baggage/.rubocop.yml create mode 100644 processor/baggage/.yardopts create mode 100644 processor/baggage/CHANGELOG.md create mode 100644 processor/baggage/Gemfile create mode 100644 processor/baggage/LICENSE create mode 100644 processor/baggage/README.md create mode 100644 processor/baggage/Rakefile create mode 100644 processor/baggage/lib/opentelemetry-processor-baggage.rb create mode 100644 processor/baggage/lib/opentelemetry/processor/baggage/baggage_span_processor.rb rename resource_detectors/lib/opentelemetry/resource/detectors.rb => processor/baggage/lib/opentelemetry/processor/baggage/version.rb (53%) create mode 100644 processor/baggage/opentelemetry-processor-baggage.gemspec create mode 100644 processor/baggage/test/opentelemetry/processor/baggage/baggage_span_processor_test.rb create mode 100644 processor/baggage/test/test_helper.rb create mode 100644 propagator/vitess/.rubocop.yml create mode 100644 propagator/vitess/.yardopts create mode 100644 propagator/vitess/CHANGELOG.md create mode 100644 propagator/vitess/Gemfile create mode 100644 propagator/vitess/LICENSE create mode 100644 propagator/vitess/README.md create mode 100644 propagator/vitess/Rakefile create mode 100644 propagator/vitess/lib/opentelemetry-propagator-vitess.rb create mode 100644 propagator/vitess/lib/opentelemetry/propagator/vitess.rb create mode 100644 propagator/vitess/lib/opentelemetry/propagator/vitess/sql_query_propagator.rb create mode 100644 propagator/vitess/lib/opentelemetry/propagator/vitess/version.rb create mode 100644 propagator/vitess/opentelemetry-propagator-vitess.gemspec create mode 100644 propagator/vitess/test/opentelemetry/propagator/vitess/sql_query_propagator_test.rb create mode 100644 propagator/vitess/test/opentelemetry/propagator/vitess_test.rb create mode 100644 propagator/vitess/test/test_helper.rb create mode 100644 release-please-config.json delete mode 100644 resource_detectors/.rubocop.yml delete mode 100644 resource_detectors/.yardopts delete mode 100644 resource_detectors/CHANGELOG.md delete mode 100644 resource_detectors/README.md delete mode 100644 resource_detectors/lib/opentelemetry/resource/detectors/auto_detector.rb delete mode 100644 resource_detectors/lib/opentelemetry/resource/detectors/azure.rb delete mode 100644 resource_detectors/lib/opentelemetry/resource/detectors/google_cloud_platform.rb delete mode 100644 resource_detectors/test/opentelemetry/detectors/auto_detector_test.rb delete mode 100644 resource_detectors/test/opentelemetry/detectors/azure_test.rb delete mode 100644 resource_detectors/test/opentelemetry/detectors/google_cloud_platform_test.rb diff --git a/.commit-me.json b/.commit-me.json new file mode 100644 index 000000000..80224890c --- /dev/null +++ b/.commit-me.json @@ -0,0 +1,4 @@ +{ + "include-pull-requests": true, + "types": [ "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "release", "revert", "squash", "style", "test" ] +} diff --git a/.github/actions/test_gem/action.yml b/.github/actions/test_gem/action.yml index 07e934fb2..06999271c 100644 --- a/.github/actions/test_gem/action.yml +++ b/.github/actions/test_gem/action.yml @@ -66,18 +66,18 @@ runs: # ...but not for appraisals, sadly. - name: Install Ruby ${{ inputs.ruby }} with dependencies if: "${{ steps.setup.outputs.appraisals == 'false' }}" - uses: ruby/setup-ruby@v1.144.1 + uses: ruby/setup-ruby@v1.180.1 with: ruby-version: "${{ inputs.ruby }}" working-directory: "${{ steps.setup.outputs.gem_dir }}" bundler: "latest" bundler-cache: true - cache-version: "v1-${{ steps.setup.outputs.cache_key }}" + cache-version: "${{ inputs.ruby }}-${{ steps.setup.outputs.cache_key }}" # If we're using appraisals, do it all manually. - name: Install Ruby ${{ inputs.ruby }} without dependencies if: "${{ steps.setup.outputs.appraisals == 'true' }}" - uses: ruby/setup-ruby@v1.144.1 + uses: ruby/setup-ruby@v1.180.1 with: ruby-version: "${{ inputs.ruby }}" bundler: "latest" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bc622e2e7..5cf3f91db 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,14 @@ updates: directory: "/" schedule: interval: weekly +- package-ecosystem: bundler + directory: "/helpers/mysql" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/helpers/sql-obfuscation" + schedule: + interval: weekly - package-ecosystem: bundler directory: "/propagator/ottrace" schedule: @@ -81,6 +89,10 @@ updates: directory: "/instrumentation/aws_sdk" schedule: interval: weekly +- package-ecosystem: bundler + directory: "/instrumentation/aws_lambda" + schedule: + interval: weekly - package-ecosystem: bundler directory: "/instrumentation/active_record" schedule: @@ -173,10 +185,6 @@ updates: directory: "/instrumentation/rails" schedule: interval: weekly -- package-ecosystem: bundler - directory: "/resource_detectors" - schedule: - interval: weekly - package-ecosystem: bundler directory: "/resources/azure" schedule: diff --git a/.github/workflows/ci-contrib-canary.yml b/.github/workflows/ci-contrib-canary.yml deleted file mode 100644 index ebf4f534a..000000000 --- a/.github/workflows/ci-contrib-canary.yml +++ /dev/null @@ -1,113 +0,0 @@ -name: CI Contrib - Canary - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * *" - -jobs: - propagators: - strategy: - fail-fast: false - matrix: - gem: - - ottrace - - xray - os: - - ubuntu-latest - - macos-latest - - windows-latest - name: "propagator-${{ matrix.gem }} / ${{ matrix.os }}" - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: "Test Ruby 3.2" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-propagator-${{ matrix.gem }}" - ruby: "3.2" - latest: "true" - - name: "Test Ruby 3.1" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-propagator-${{ matrix.gem }}" - ruby: "3.1" - latest: "true" - - name: "Test Ruby 3.0" - if: "${{ matrix.os == 'ubuntu-latest' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-propagator-${{ matrix.gem }}" - ruby: "3.0" - latest: "true" - yard: true - rubocop: true - build: true - - name: "Test JRuby" - if: "${{ matrix.os == 'ubuntu-latest' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-propagator-${{ matrix.gem }}" - ruby: "jruby-9.4.2.0" - latest: "true" - - name: "Test truffleruby" - if: "${{ matrix.os == 'ubuntu-latest' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-propagator-${{ matrix.gem }}" - ruby: "truffleruby" - latest: "true" - - resource-detectors: - strategy: - fail-fast: false - matrix: - gem: - - resource_detectors - - resource-detector-azure - - resource-detector-container - - resource-detector-google_cloud_platform - os: - - ubuntu-latest - - macos-latest - - windows-latest - name: "opentelemetry-${{ matrix.gem }} / ${{ matrix.os }}" - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: "Test Ruby 3.2" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-${{ matrix.gem }}" - ruby: "3.2" - latest: "true" - - name: "Test Ruby 3.1" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-${{ matrix.gem }}" - ruby: "3.1" - latest: "true" - - name: "Test Ruby 3.0" - if: "${{ matrix.os == 'ubuntu-latest' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-${{ matrix.gem }}" - ruby: "3.0" - latest: "true" - yard: true - rubocop: true - build: true - - name: "Test JRuby" - if: "${{ matrix.os == 'ubuntu-latest' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-${{ matrix.gem }}" - ruby: "jruby-9.4.2.0" - latest: "true" - - name: "Test truffleruby" - if: "${{ matrix.os == 'ubuntu-latest' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-${{ matrix.gem }}" - ruby: "truffleruby" - latest: "true" diff --git a/.github/workflows/ci-contrib.yml b/.github/workflows/ci-contrib.yml index c2a86e1b0..5f7490cfa 100644 --- a/.github/workflows/ci-contrib.yml +++ b/.github/workflows/ci-contrib.yml @@ -8,14 +8,60 @@ on: pull_request: branches: - main + schedule: + - cron: "0 0 * * *" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} # Ensure that only one instance of this workflow is running per Pull Request + cancel-in-progress: true # Cancel any previous runs of this workflow jobs: + helpers: + strategy: + fail-fast: false + matrix: + gem: + - mysql + - sql-obfuscation + os: + - ubuntu-latest + name: "helpers-${{ matrix.gem }} / ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: "Test Ruby 3.2" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-helpers-${{ matrix.gem }}" + ruby: "3.2" + - name: "Test Ruby 3.1" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-helpers-${{ matrix.gem }}" + ruby: "3.1" + - name: "Test Ruby 3.0" + if: "${{ matrix.os == 'ubuntu-latest' }}" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-helpers-${{ matrix.gem }}" + ruby: "3.0" + yard: true + rubocop: true + build: true + - name: "Test JRuby" + if: "${{ matrix.os == 'ubuntu-latest' }}" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-helpers-${{ matrix.gem }}" + ruby: "jruby-9.4.6.0" + propagators: strategy: fail-fast: false matrix: gem: - ottrace + - vitess - xray os: - ubuntu-latest @@ -23,6 +69,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-propagator-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -47,14 +98,13 @@ jobs: uses: ./.github/actions/test_gem with: gem: "opentelemetry-propagator-${{ matrix.gem }}" - ruby: "jruby-9.4.2.0" + ruby: "jruby-9.4.6.0" resource-detectors: strategy: fail-fast: false matrix: gem: - - resource_detectors - resource-detector-azure - resource-detector-container - resource-detector-google_cloud_platform @@ -64,6 +114,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -88,4 +143,47 @@ jobs: uses: ./.github/actions/test_gem with: gem: "opentelemetry-${{ matrix.gem }}" - ruby: "jruby-9.4.2.0" + ruby: "jruby-9.4.6.0" + + processors: + strategy: + fail-fast: false + matrix: + gem: + - baggage + os: + - ubuntu-latest + name: "processors-${{ matrix.gem }} / ${{ matrix.os }}" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-processor-${{ matrix.gem }}" + ruby: "3.3" + - name: "Test Ruby 3.2" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-processor-${{ matrix.gem }}" + ruby: "3.2" + - name: "Test Ruby 3.1" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-processor-${{ matrix.gem }}" + ruby: "3.1" + - name: "Test Ruby 3.0" + if: "${{ matrix.os == 'ubuntu-latest' }}" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-processor-${{ matrix.gem }}" + ruby: "3.0" + yard: true + rubocop: true + build: true + - name: "Test JRuby" + if: "${{ matrix.os == 'ubuntu-latest' && steps.jruby_skip.outputs.skip == 'false' }}" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-processor-${{ matrix.gem }}" + ruby: "jruby-9.4.6.0" diff --git a/.github/workflows/ci-instrumentation-canary.yml b/.github/workflows/ci-instrumentation-canary.yml deleted file mode 100644 index 4dd45a722..000000000 --- a/.github/workflows/ci-instrumentation-canary.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: CI Instrumentation - Canary - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * *" - -jobs: - instrumentation: - strategy: - fail-fast: false - matrix: - gem: - - action_pack - - action_view - - active_job - - active_model_serializers - - active_record - - active_support - - all - - aws_sdk - - base - - concurrent_ruby - - delayed_job - - ethon - - excon - - faraday - - grape - - graphql - - gruf - - http - - http_client - - koala - - lmdb - - net_http - - rack - - rails - - restclient - - rspec - - sinatra - os: - - ubuntu-latest - - macos-latest - - windows-latest - exclude: - - os: windows-latest - gem: ethon - - os: windows-latest - gem: action_pack - - os: windows-latest - gem: restclient - - os: windows-latest - gem: rails - - os: macos-latest - gem: lmdb - - name: ${{ matrix.gem }} / ${{ matrix.os }} - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: "Test Ruby 3.2" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "3.2" - latest: "true" - - name: "Test Ruby 3.1" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "3.1" - latest: "true" - - name: "Test Ruby 3.0" - if: "${{ matrix.os == 'ubuntu-latest' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "3.0" - latest: "true" - yard: true - rubocop: true - build: true - - name: "JRuby Filter" - id: jruby_skip - shell: bash - run: | - echo "skip=false" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "action_pack" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "action_view" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "active_model_serializers" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "active_record" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "active_support" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "aws_sdk" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "delayed_job" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "graphql" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "http" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "http_client" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "koala" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "lmdb" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "rack" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "rails" ]] && echo "skip=true" >> $GITHUB_OUTPUT - # This is essentially a bash script getting evaluated, so we need to return true or the whole job fails. - true - - name: "Test JRuby" - if: "${{ matrix.os == 'ubuntu-latest' && steps.jruby_skip.outputs.skip == 'false' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - latest: "true" - ruby: "jruby-9.4.2.0" - - name: "Truffleruby Filter" - id: truffleruby_skip - shell: bash - run: | - echo "skip=false" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "action_pack" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "action_view" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "active_job" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "active_record" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "active_support" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "delayed_job" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "lmdb" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "rails" ]] && echo "skip=true" >> $GITHUB_OUTPUT - # This is essentially a bash script getting evaluated, so we need to return true or the whole job fails. - true - - name: "Test Truffleruby" - if: "${{ matrix.os == 'ubuntu-latest' && steps.truffleruby_skip.outputs.skip == 'false' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "truffleruby" - latest: "true" diff --git a/.github/workflows/ci-instrumentation-with-services-canary.yml b/.github/workflows/ci-instrumentation-with-services-canary.yml deleted file mode 100644 index b375afb7c..000000000 --- a/.github/workflows/ci-instrumentation-with-services-canary.yml +++ /dev/null @@ -1,155 +0,0 @@ -name: CI Instrumentation With Services - Canary - -on: - workflow_dispatch: - schedule: - - cron: "0 0 * * *" - -jobs: - # These builds require sidecar services (postgres, redis, etc) in order to run their test suites. - instrumentation_with_services: - strategy: - fail-fast: false - # We don't run on macos and windows since service containers aren't supported there. - matrix: - gem: - - bunny - - dalli - - mongo - - mysql2 - - pg - - que - - rdkafka - - redis - - resque - - ruby_kafka - - sidekiq - - trilogy - name: ${{ matrix.gem }} / ubuntu-latest / services - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: "Test Ruby 3.2" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "3.2" - latest: "true" - - name: "Test Ruby 3.1" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "3.1" - latest: "true" - - name: "Test Ruby 3.0" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "3.0" - latest: "true" - yard: true - rubocop: true - build: true - - name: "JRuby Filter" - id: jruby_skip - shell: bash - run: | - echo "skip=false" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "bunny" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "mysql2" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "pg" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "que" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "rdkafka" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "redis" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "resque" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "ruby_kafka" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "sidekiq" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "trilogy" ]] && echo "skip=true" >> $GITHUB_OUTPUT - # This is essentially a bash script getting evaluated, so we need to return true or the whole job fails. - true - - name: "Test JRuby" - if: "${{ steps.jruby_skip.outputs.skip == 'false' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "jruby-9.4.2.0" - latest: "true" - - name: "Truffleruby Filter" - id: truffleruby_skip - shell: bash - run: | - echo "skip=false" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "que" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "rdkafka" ]] && echo "skip=true" >> $GITHUB_OUTPUT - [[ "${{ matrix.gem }}" == "ruby_kafka" ]] && echo "skip=true" >> $GITHUB_OUTPUT - # This is essentially a bash script getting evaluated, so we need to return true or the whole job fails. - true - - name: "Test Truffleruby" - if: "${{ steps.truffleruby_skip.outputs.skip == 'false' }}" - uses: ./.github/actions/test_gem - with: - gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "truffleruby" - latest: "true" - services: - zookeeper: - image: confluentinc/cp-zookeeper:latest - ports: - - 2181:2181 - env: - ZOOKEEPER_CLIENT_PORT: 2181 - ZOOKEEPER_TICK_TIME: 2000 - kafka: - image: confluentinc/cp-kafka:latest - ports: - - 9092:9092 - - 29092:29092 - env: - KAFKA_BROKER_ID: 1 - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:29092,PLAINTEXT_HOST://localhost:9092 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT - KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - memcached: - image: memcached:alpine - ports: - - 11211:11211 - mongodb: - image: mongo:4.4 - ports: - - 27017:27017 - redis: - image: bitnami/redis:6.2 - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - env: - REDIS_PASSWORD: "passw0rd" - mysql: - image: mysql:8.0.31 - env: - MYSQL_DATABASE: mysql - MYSQL_ROOT_PASSWORD: root - MYSQL_PASSWORD: mysql - MYSQL_USER: mysql - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - rabbitmq: - image: rabbitmq:3.8-alpine - ports: - - "5672:5672" - postgres: - image: postgres:13 - env: - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 - options: --health-cmd="pg_isready" --health-interval=5s --health-timeout=2s --health-retries=3 - volumes: - - "/var/run/postgresql:/var/run/postgresql" diff --git a/.github/workflows/ci-instrumentation-with-services.yml b/.github/workflows/ci-instrumentation-with-services.yml index 3b1f9a342..0aeb807d2 100644 --- a/.github/workflows/ci-instrumentation-with-services.yml +++ b/.github/workflows/ci-instrumentation-with-services.yml @@ -8,6 +8,12 @@ on: pull_request: branches: - main + schedule: + - cron: "0 0 * * *" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} # Ensure that only one instance of this workflow is running per Pull Request + cancel-in-progress: true # Cancel any previous runs of this workflow jobs: instrumentation_with_services: @@ -23,6 +29,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -45,7 +56,7 @@ jobs: uses: ./.github/actions/test_gem with: gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "jruby-9.4.2.0" + ruby: "jruby-9.4.6.0" services: memcached: image: memcached:alpine @@ -69,6 +80,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -106,6 +122,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -159,6 +180,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -203,6 +229,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -244,6 +275,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: diff --git a/.github/workflows/ci-instrumentation.yml b/.github/workflows/ci-instrumentation.yml index b5bacdeb2..8b70f7ee6 100644 --- a/.github/workflows/ci-instrumentation.yml +++ b/.github/workflows/ci-instrumentation.yml @@ -8,6 +8,12 @@ on: pull_request: branches: - main + schedule: + - cron: "0 0 * * *" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} # Ensure that only one instance of this workflow is running per Pull Request + cancel-in-progress: true # Cancel any previous runs of this workflow jobs: instrumentation: @@ -15,6 +21,7 @@ jobs: fail-fast: false matrix: gem: + - action_mailer - action_pack - action_view - active_job @@ -23,6 +30,7 @@ jobs: - active_support - all - aws_sdk + - aws_lambda - base - concurrent_ruby - delayed_job @@ -34,6 +42,7 @@ jobs: - gruf - http - http_client + - httpx - koala - lmdb - net_http @@ -49,6 +58,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" - name: "Test Ruby 3.2" uses: ./.github/actions/test_gem with: @@ -79,6 +93,7 @@ jobs: [[ "${{ matrix.gem }}" == "active_record" ]] && echo "skip=true" >> $GITHUB_OUTPUT [[ "${{ matrix.gem }}" == "active_support" ]] && echo "skip=true" >> $GITHUB_OUTPUT [[ "${{ matrix.gem }}" == "aws_sdk" ]] && echo "skip=true" >> $GITHUB_OUTPUT + [[ "${{ matrix.gem }}" == "aws_lambda" ]] && echo "skip=true" >> $GITHUB_OUTPUT [[ "${{ matrix.gem }}" == "delayed_job" ]] && echo "skip=true" >> $GITHUB_OUTPUT [[ "${{ matrix.gem }}" == "graphql" ]] && echo "skip=true" >> $GITHUB_OUTPUT [[ "${{ matrix.gem }}" == "http" ]] && echo "skip=true" >> $GITHUB_OUTPUT @@ -95,4 +110,4 @@ jobs: uses: ./.github/actions/test_gem with: gem: "opentelemetry-instrumentation-${{ matrix.gem }}" - ruby: "jruby-9.4.2.0" + ruby: "jruby-9.4.6.0" diff --git a/.github/workflows/conventional-commits.yaml b/.github/workflows/conventional-commits.yaml new file mode 100644 index 000000000..26cd79d03 --- /dev/null +++ b/.github/workflows/conventional-commits.yaml @@ -0,0 +1,32 @@ +name: Conventional Commits Validation + +on: + workflow_dispatch: + pull_request: + types: + - opened + - synchronize + - reopened + - edited + +permissions: + contents: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} # Ensure that only one instance of this workflow is running per Pull Request + cancel-in-progress: true # Cancel any previous runs of this workflow + +jobs: + validate-commits: + name: Conventional Commits Validation + runs-on: ubuntu-latest + steps: + - uses: dev-build-deploy/commit-me@v1.5.0 + env: + FORCE_COLOR: 3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + include-commits: false + update-labels: false + config: ".commit-me.json" diff --git a/.github/workflows/installation-tests.yml b/.github/workflows/installation-tests.yml index c5f792896..3fcd407da 100644 --- a/.github/workflows/installation-tests.yml +++ b/.github/workflows/installation-tests.yml @@ -15,6 +15,7 @@ jobs: fail-fast: false matrix: ruby-version: + - 3.3 - 3.2 - 3.1 - 3.0 @@ -22,7 +23,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 + # ATTENTION: Dependabot does not know how to update shared actions file. + # If you see it update setup-ruby here also update it as part of actions/test_gem/action.yml + - uses: ruby/setup-ruby@v1.180.1 with: ruby-version: ${{ matrix.ruby-version }} - name: "Install Latest Gem Versions on ${{ matrix.ruby-version }}" diff --git a/.github/workflows/release-hook-on-closed.yml b/.github/workflows/release-hook-on-closed.yml index bbbcfd735..bf1273ceb 100644 --- a/.github/workflows/release-hook-on-closed.yml +++ b/.github/workflows/release-hook-on-closed.yml @@ -12,13 +12,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Ruby ${{ env.ruby_version }} - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@v1.180.1 with: ruby-version: ${{ env.ruby_version }} - name: Checkout repo uses: actions/checkout@v4 - name: Install Toys - run: "gem install --no-document toys" + run: "gem install --no-document toys -v 0.15.5" - name: Process release request env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-hook-on-push.yml b/.github/workflows/release-hook-on-push.yml index 1dfc480fd..76c2870c0 100644 --- a/.github/workflows/release-hook-on-push.yml +++ b/.github/workflows/release-hook-on-push.yml @@ -13,13 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Ruby ${{ env.ruby_version }} - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@v1.180.1 with: ruby-version: ${{ env.ruby_version }} - name: Checkout repo uses: actions/checkout@v4 - name: Install Toys - run: "gem install --no-document toys" + run: "gem install --no-document toys -v 0.15.5" - name: Update open releases env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-perform.yml b/.github/workflows/release-perform.yml index 4e8b37bc6..ebfef57a4 100644 --- a/.github/workflows/release-perform.yml +++ b/.github/workflows/release-perform.yml @@ -22,13 +22,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Ruby ${{ env.ruby_version }} - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@v1.180.1 with: ruby-version: ${{ env.ruby_version }} - name: Checkout repo uses: actions/checkout@v4 - name: Install Toys - run: "gem install --no-document toys" + run: "gem install --no-document toys -v 0.15.5" - name: Perform release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml new file mode 100644 index 000000000..fd2f79052 --- /dev/null +++ b/.github/workflows/release-please.yaml @@ -0,0 +1,64 @@ +--- +name: Release Please + +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + if: ${{ github.repository == 'open-telemetry/opentelemetry-ruby-contrib' }} + name: Process Release + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4.1.3 + id: prepare + # with: + # token: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }} + + outputs: + paths_released: ${{ steps.prepare.outputs.paths_released }} + + publish: + needs: prepare + name: Publish Gems + runs-on: ubuntu-latest + if: ${{ needs.prepare.outputs.paths_released != '[]'}} + + strategy: + fail-fast: false + max-parallel: 1 + matrix: + path: ${{ fromJson(needs.prepare.outputs.paths_released) }} + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Configure RubyGems + env: + GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_API_KEY}}" + + run: | + mkdir -p $HOME/.gem + touch $HOME/.gem/credentials + chmod 0600 $HOME/.gem/credentials + printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials + + - uses: ruby/setup-ruby@v1.180.1 + with: + ruby-version: "3.0" + bundler: latest + bundler-cache: false + working-directory: ${{ matrix.path }} + + - name: Publish to RubyGems + working-directory: ${{ matrix.path }} + run: | + bundle exec rake --trace build + bundle exec rake --trace release:rubygem_push diff --git a/.github/workflows/release-request-weekly.yml b/.github/workflows/release-request-weekly.yml new file mode 100644 index 000000000..44831942d --- /dev/null +++ b/.github/workflows/release-request-weekly.yml @@ -0,0 +1,28 @@ +name: Open release request - Weekly + +on: + schedule: + - cron: "0 15 * * 2" + +jobs: + release-request: + if: ${{ github.repository == 'open-telemetry/opentelemetry-ruby-contrib' }} + env: + ruby_version: "3.0" + runs-on: ubuntu-latest + steps: + - name: Install Ruby ${{ env.ruby_version }} + uses: ruby/setup-ruby@v1.180.1 + with: + ruby-version: ${{ env.ruby_version }} + - name: Checkout repo + uses: actions/checkout@v4 + - name: Install Toys + run: "gem install --no-document toys -v 0.15.5" + - name: Open release pull request + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + toys release request --yes --verbose \ + "--release-ref=${{ github.ref }}" \ + < /dev/null diff --git a/.github/workflows/release-request.yml b/.github/workflows/release-request.yml index 964847450..d3f776130 100644 --- a/.github/workflows/release-request.yml +++ b/.github/workflows/release-request.yml @@ -16,13 +16,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Ruby ${{ env.ruby_version }} - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@v1.180.1 with: ruby-version: ${{ env.ruby_version }} - name: Checkout repo uses: actions/checkout@v4 - name: Install Toys - run: "gem install --no-document toys" + run: "gem install --no-document toys -v 0.15.5" - name: Open release pull request env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-retry.yml b/.github/workflows/release-retry.yml index dd5c3a81c..a0a13e890 100644 --- a/.github/workflows/release-retry.yml +++ b/.github/workflows/release-retry.yml @@ -19,13 +19,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Ruby ${{ env.ruby_version }} - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@v1.180.1 with: ruby-version: ${{ env.ruby_version }} - name: Checkout repo uses: actions/checkout@v4 - name: Install Toys - run: "gem install --no-document toys" + run: "gem install --no-document toys -v 0.15.5" - name: Retry release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 27551316f..c85932c9c 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v8 + - uses: actions/stale@v9 name: Clean up stale issues and PRs with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -18,9 +18,9 @@ jobs: stale-issue-label: "stale" exempt-issue-labels: "keep" days-before-issue-stale: 30 - days-before-issue-close: -1 + days-before-issue-close: 30 stale-pr-message: "👋 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" stale-pr-label: "stale" exempt-pr-labels: "keep" days-before-pr-stale: 30 - days-before-pr-close: 15 + days-before-pr-close: 30 diff --git a/.instrumentation_generator/templates/gemspec.tt b/.instrumentation_generator/templates/gemspec.tt index 6305d176e..cadf4adc7 100644 --- a/.instrumentation_generator/templates/gemspec.tt +++ b/.instrumentation_generator/templates/gemspec.tt @@ -35,6 +35,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rubocop', '~> 1.48.1' + spec.add_development_dependency 'rubocop-performance', '~> 1.19.1' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.7.6' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..b72270bee --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,54 @@ +{ + "helpers/mysql": "0.1.1", + "helpers/sql_obfuscation": "0.1.1", + "instrumentation/base": "0.22.4", + "instrumentation/graphql": "0.28.2", + "instrumentation/gruf": "0.2.1", + "instrumentation/racecar": "0.3.2", + "instrumentation/rake": "0.2.2", + "instrumentation/rdkafka": "0.4.6", + "instrumentation/trilogy": "0.59.3", + "instrumentation/active_support": "0.5.1", + "instrumentation/action_mailer": "0.1.0", + "instrumentation/action_view": "0.7.0", + "instrumentation/action_pack": "0.9.0", + "instrumentation/active_job": "0.7.1", + "instrumentation/resque": "0.5.2", + "instrumentation/bunny": "0.21.3", + "instrumentation/active_record": "0.7.2", + "instrumentation/aws_sdk": "0.5.2", + "instrumentation/aws_lambda": "0.1.0", + "instrumentation/lmdb": "0.22.2", + "instrumentation/http": "0.23.3", + "instrumentation/http_client": "0.22.6", + "instrumentation/httpx": "0.1.2", + "instrumentation/koala": "0.20.5", + "instrumentation/active_model_serializers": "0.20.1", + "instrumentation/concurrent_ruby": "0.21.3", + "instrumentation/dalli": "0.25.3", + "instrumentation/delayed_job": "0.22.2", + "instrumentation/ethon": "0.21.7", + "instrumentation/excon": "0.22.3", + "instrumentation/faraday": "0.24.4", + "instrumentation/mongo": "0.22.3", + "instrumentation/mysql2": "0.27.1", + "instrumentation/net_http": "0.22.6", + "instrumentation/pg": "0.27.3", + "instrumentation/que": "0.8.1", + "instrumentation/rack": "0.24.5", + "instrumentation/rails": "0.30.2", + "instrumentation/grape": "0.1.8", + "instrumentation/redis": "0.25.6", + "instrumentation/restclient": "0.22.6", + "instrumentation/rspec": "0.3.3", + "instrumentation/ruby_kafka": "0.21.1", + "instrumentation/sidekiq": "0.25.5", + "instrumentation/sinatra": "0.23.5", + "instrumentation/all": "0.56.0", + "processor/baggage": "0.2.0", + "propagator/ottrace": "0.21.2", + "propagator/xray": "0.22.1", + "resources/azure": "0.1.0", + "resources/container": "0.1.1", + "resources/google_cloud_platform": "0.1.0" +} diff --git a/.rubocop.yml b/.rubocop.yml index efc914429..028015df7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,5 @@ +require: rubocop-performance + AllCops: TargetRubyVersion: '3.0' NewCops: enable diff --git a/.toys/.data/releases.yml b/.toys/.data/releases.yml index 8b78685f3..a90d626e3 100644 --- a/.toys/.data/releases.yml +++ b/.toys/.data/releases.yml @@ -6,9 +6,10 @@ repo: open-telemetry/opentelemetry-ruby-contrib main_branch: main # Time in seconds for release scripts to wait for CI to complete. required_checks_timeout: 1200 +required_checks: "^(ci|CI).*" # Git user attached to commits for release pull requests. -git_user_name: Ariel Valentin -git_user_email: ariel@arielvalentin.com +git_user_name: OpenTelemetry Bot +git_user_email: 107717825+opentelemetrybot@users.noreply.github.com # Control the conventional commit linter. commit_lint: @@ -33,6 +34,15 @@ gems: directory: instrumentation/gruf version_constant: [OpenTelemetry, Instrumentation, Gruf, VERSION] + - name: opentelemetry-helpers-mysql + directory: helpers/mysql + version_constant: [OpenTelemetry, Helpers, MySQL, VERSION] + + - name: opentelemetry-helpers-sql-obfuscation + directory: helpers/sql-obfuscation + version_rb_path: lib/opentelemetry/helpers/sql_obfuscation/version.rb + version_constant: [OpenTelemetry, Helpers, SqlObfuscation, VERSION] + - name: opentelemetry-instrumentation-grape directory: instrumentation/grape version_constant: [OpenTelemetry, Instrumentation, Grape, VERSION] @@ -57,6 +67,10 @@ gems: directory: instrumentation/active_support version_constant: [OpenTelemetry, Instrumentation, ActiveSupport, VERSION] + - name: opentelemetry-instrumentation-action_mailer + directory: instrumentation/action_mailer + version_constant: [OpenTelemetry, Instrumentation, ActionMailer, VERSION] + - name: opentelemetry-instrumentation-action_view directory: instrumentation/action_view version_constant: [OpenTelemetry, Instrumentation, ActionView, VERSION] @@ -90,6 +104,10 @@ gems: directory: instrumentation/aws_sdk version_constant: [OpenTelemetry, Instrumentation, AwsSdk, VERSION] + - name: opentelemetry-instrumentation-aws_lambda + directory: instrumentation/aws_lambda + version_constant: [OpenTelemetry, Instrumentation, AwsLambda, VERSION] + - name: opentelemetry-instrumentation-lmdb directory: instrumentation/lmdb version_constant: [OpenTelemetry, Instrumentation, LMDB, VERSION] @@ -106,6 +124,10 @@ gems: directory: instrumentation/http_client version_constant: [OpenTelemetry, Instrumentation, HttpClient, VERSION] + - name: opentelemetry-instrumentation-httpx + directory: instrumentation/httpx + version_constant: [OpenTelemetry, Instrumentation, HTTPX, VERSION] + - name: opentelemetry-instrumentation-koala directory: instrumentation/koala version_constant: [OpenTelemetry, Instrumentation, Koala, VERSION] @@ -195,19 +217,22 @@ gems: directory: instrumentation/all version_constant: [OpenTelemetry, Instrumentation, All, VERSION] + - name: opentelemetry-processor-baggage + directory: processor/baggage + version_constant: [OpenTelemetry, Processor, Baggage, VERSION] + - name: opentelemetry-propagator-ottrace directory: propagator/ottrace version_constant: [OpenTelemetry, Propagator, OTTrace, VERSION] + - name: opentelemetry-propagator-vitess + directory: propagator/vitess + version_constant: [OpenTelemetry, Propagator, Vitess, VERSION] + - name: opentelemetry-propagator-xray directory: propagator/xray version_constant: [OpenTelemetry, Propagator, XRay, VERSION] - - name: opentelemetry-resource_detectors - directory: resource_detectors - version_rb_path: lib/opentelemetry/resource/detectors/version.rb - version_constant: [OpenTelemetry, Resource, Detectors, VERSION] - - name: opentelemetry-resource-detector-azure directory: resources/azure version_rb_path: lib/opentelemetry/resource/detector/azure/version.rb diff --git a/.toys/.toys.rb b/.toys/.toys.rb index 76d3765c5..f1c1fb1d9 100644 --- a/.toys/.toys.rb +++ b/.toys/.toys.rb @@ -1,7 +1,7 @@ -toys_version! "0.14.7" +toys_version! ">= 0.15.5" load_git remote: "/~https://github.com/dazuma/toys.git", path: ".toys/release", as: "release", - commit: "toys/v0.14.7", + commit: "common-tools/v0.15.5.1", update: 3600 diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index ee9e46837..000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,31 +0,0 @@ -##################################################### -# -# List of approvers for this repository -# -##################################################### -# -# Learn about membership in OpenTelemetry community: -# /~https://github.com/open-telemetry/community/blob/master/community-membership.md -# -# -# Learn about CODEOWNERS file format: -# https://help.github.com/en/articles/about-code-owners -# - -* @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -resources/container/ @scbjans @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -instrumentation/aws_sdk/ @NathanielRN @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -instrumentation/grape/ @muripic @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -instrumentation/graphql/ @swalkinshaw @rmosolgo @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -instrumentation/mongo/ @johnnyshields @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -instrumentation/racecar/ @chrisholmes @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -instrumentation/rspec/ @chrisholmes @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd - -instrumentation/que/ @indrekj @fbogsany @mwear @robertlaurin @dazuma @ericmustin @arielvalentin @ahayworth @plantfansam @robbkidd diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 391929061..b885db0ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ conforms to the specification, but the interface and structure are flexible. It is preferable to have contributions follow the idioms of the language rather than conform to specific API names or argument patterns in the spec. -For a deeper discussion, see: /~https://github.com/open-telemetry/opentelemetry-specification/issues/165 +For a deeper discussion, see: ## Getting started @@ -40,6 +40,7 @@ git clone git@github.com:YOUR_GITHUB_NAME/opentelemetry-ruby-contrib.git ``` or + ```sh git clone /~https://github.com/YOUR_GITHUB_NAME/opentelemetry-ruby-contrib.git ``` @@ -66,16 +67,16 @@ _Setting up a running Ruby environment is outside the scope of this document._ This repository contains multiple Ruby gems: - * Various instrumentation gems located in subdirectories of `instrumentation` - * `opentelemetry-propagator-xray` located in the `propagator/xray` directory - * `opentelemetry-propagator-ottrace` located in the `propagator/ottrace` directory - * `opentelemetry-resource_detectors` located in the `resource_detectors` directory +* Various instrumentation gems located in subdirectories of `instrumentation` +* Various resource detector gems located in subdirectories of `resources` +* `opentelemetry-propagator-xray` located in the `propagator/xray` directory +* `opentelemetry-propagator-ottrace` located in the `propagator/ottrace` directory Each of these gems has its configuration and tests. -For example, to test `opentelemetry-resource_detectors` you would: +For example, to test `opentelemetry-instrumentation-action_pack` you would: - 1. Change directory to `resource_detectors` + 1. Change directory to `instrumentation/action_pack` 2. Install the bundle with `bundle install` 3. Run the tests with `bundle exec rake` @@ -88,9 +89,9 @@ configuration details. The services provided include: - * `app` - main container environment scoped to the `/app` directory. Used +* `app` - main container environment scoped to the `/app` directory. Used primarily to build and tag the `opentelemetry/opentelemetry-ruby-contrib:latest` image. - * `x-instrumentation-` - container environment scoped to a specific instrumentation library. See `docker-compose.yml` for available services. +* `x-instrumentation-` - container environment scoped to a specific instrumentation library. See `docker-compose.yml` for available services. To test using Docker: @@ -100,9 +101,9 @@ To test using Docker: * `docker-compose build` * This makes the image available locally 4. Install dependencies for the service you want to interact with - * `docker-compose run bundle install` + * `docker-compose run bundle install` 5. Run the tests - * `docker-compose run bundle exec rake test` + * `docker-compose run bundle exec rake test` ## Processing and visualizing traces locally @@ -132,7 +133,7 @@ merging. All commits to the main repository should follow the [conventional commit](https://conventionalcommits.org) standard. In a nutshell, this means commit messages should begin with a semantic tag such as `feat:`, -`fix:`, `docs:`, `tests:`, or `refactor:`. Our release tooling uses these tags +`fix:`, `docs:`, `test:`, `refactor:`, etc... Our release tooling uses these tags to determine the semantics of your commit, such as how it affects semantic versioning of the libraries, and to generate changelogs from commit descriptions. If you are not familiar with conventional commits, please review @@ -155,8 +156,8 @@ to ensure that your code complies before opening a pull request. We also use Yard to generate class documentation automatically. Among other things, this means: - * Methods and arguments should include the appropriate type annotations - * You can use markdown formatting in your documentation comments +* Methods and arguments should include the appropriate type annotations +* You can use markdown formatting in your documentation comments You can generate the docs locally to see the results, by running: @@ -165,7 +166,11 @@ bundle install bundle exec rake yard ``` -## Create a Pull Request +## Instrumentation author's guide + +Please make sure that you review the [Instrumentation author's guide](.instrumentation/CONTRIBUTING.md) before submitting a new instrumentation. + +## Create a pull request You'll need to create a Pull Request once you've finished your work. The [Kubernetes GitHub Workflow][kube-github-workflow-pr] document has @@ -223,8 +228,8 @@ Releases are normally performed using GitHub Actions. 2. In the GitHub UI, go to the `Actions` tab, select the `Open release request` workflow, and run the workflow manually using the dropdown in the upper right. - * Releases must be run from the main branch. - * If you leave the `Gems to release` field, blank, and the script will + * Releases must be run from the main branch. + * If you leave the `Gems to release` field, blank, and the script will find all the gems that have had conventional-commit-tagged changes since their last release. Alternately, you can specify which gems to release by including their names, space-delimited, in this this field. You can @@ -247,9 +252,9 @@ Releases are normally performed using GitHub Actions. was opened; make sure the label is still there when you merge it. 6. The automated release script will run automatically, and will release the gem(s) once CI has completed. This includes: - * For each gem, it will create a release tag and a GitHub release. - * It will build and push the gems to rubygems. - * If the releases succeed, the script will update the release pull + * For each gem, it will create a release tag and a GitHub release. + * It will build and push the gems to rubygems. + * If the releases succeed, the script will update the release pull request with the results and change its label to `release: complete`. If something went wrong, the script will, if possible, report the error on the release pull request and change its label to `release: error`. @@ -268,20 +273,20 @@ review the release logs for the GitHub Actions workflows. There are four GitHub actions workflows related to releases. - * `Open release request` is the main release entrypoint, and is used to open - a release pull request. If something goes wrong with this process, the logs - will appear in the workflow run. - * `Force release` is generally used only to restart a failed release. - * `[release hook] Update open releases` is run on pushes to the main branch, - and pushes warnings to open release pull requests if you make modifications - before triggering the release (i.e. because you might need to update the - changelogs.) - * `[release hook] Process release` is the main release automation script and - is run when a pull request is closed. If it determines that a release pull - request was merged, it kicks off the release process for the affected gems. - It also updates the label on a closed release pull request. Finally, it - deletes release branches when they are no longer being used. If something - goes wrong with any of these processes, the logs will appear here. +* `Open release request` is the main release entrypoint, and is used to open + a release pull request. If something goes wrong with this process, the logs + will appear in the workflow run. +* `Force release` is generally used only to restart a failed release. +* `[release hook] Update open releases` is run on pushes to the main branch, + and pushes warnings to open release pull requests if you make modifications + before triggering the release (i.e. because you might need to update the + changelogs.) +* `[release hook] Process release` is the main release automation script and + is run when a pull request is closed. If it determines that a release pull + request was merged, it kicks off the release process for the affected gems. + It also updates the label on a closed release pull request. Finally, it + deletes release branches when they are no longer being used. If something + goes wrong with any of these processes, the logs will appear here. #### Restarting a release @@ -292,14 +297,14 @@ release, you can use the `Force release` workflow. GitHub UI. 2. In the GitHub UI, go to the `Actions` tab, select the `Force release` workflow, and run it manually. - * You must provide the gem name and version explicitly in the fields. - * The `Extra flags` field is useful for advanced cases. For example, if - the GitHub release tag is already created and the gem already pushed to - Rubygems, but the docs still need to be built, you can pass - `--only=docs` to perform only that one step. You can also force a - release even if the build is not green or the version/changelog checks - are failing, by passing `--skip-checks`. For more details, install the - `toys` gem and run `toys release perform --help` locally. + * You must provide the gem name and version explicitly in the fields. + * The `Extra flags` field is useful for advanced cases. For example, if + the GitHub release tag is already created and the gem already pushed to + Rubygems, but the docs still need to be built, you can pass + `--only=docs` to perform only that one step. You can also force a + release even if the build is not green or the version/changelog checks + are failing, by passing `--skip-checks`. For more details, install the + `toys` gem and run `toys release perform --help` locally. #### Running releases locally @@ -319,7 +324,7 @@ changed gems. To force-release, assuming the version and changelog are already modified: -``` +```console toys release perform --rubygems-api-key=$API_KEY $GEM_NAME $GEM_VERSION ``` @@ -337,17 +342,19 @@ not correspond exactly to the gem name. For releases to succeed, new gems MUST include the following: - * The above configuration entry. - * The `*.gemspec` file, with the name matching the gem name. - * A `version.rb` file in the standard location, or in a location listed in - the configuration. - * A `CHANGELOG.md` file. - * A `yard` rake task. +* The above configuration entry. +* The `*.gemspec` file, with the name matching the gem name. +* A `version.rb` file in the standard location, or in a location listed in + the configuration. +* A `CHANGELOG.md` file. +* A `yard` rake task. -## Dependabot Updates +## Dependabot updates This repository uses [Dependabot](https://dependabot.com/) to keep dependencies up to date, however there shared development dependencies are often scattered across multiple gems. Dependabot does not currently support the ability to group dependencies for gems in multiple subdirectories, so we use a custom script to bulk update dependencies across all gems. +**Note:** This script uses a version of sed that isn't available on MacOS bash. You'll need to use an ubuntu-linux machine to execute it. One way to accomplish this is to run `docker-compose run app` and execute the script within the container. + E.g. if you want to update Rubocop to version 1.56.1, you would run: ```console diff --git a/Dockerfile b/Dockerfile index 0e534bc0a..a52d46c22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ ARG PACKAGES="\ libxml2-dev \ libxslt-dev \ mariadb-dev \ + sqlite-dev \ openssl \ postgresql-dev \ tzdata \ diff --git a/Gemfile b/Gemfile index d39b109d8..bbd1188ad 100644 --- a/Gemfile +++ b/Gemfile @@ -7,4 +7,5 @@ source 'https://rubygems.org' gem 'rake', '~> 13.0' -gem 'rubocop', '~> 1.56.2' +gem 'rubocop', '~> 1.64.0' +gem 'rubocop-performance', '~> 1.21.0' diff --git a/LICENSE b/LICENSE index 1ef7dad2c..261eeb9e9 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright The OpenTelemetry Authors + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index ea2c37820..926def2f1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ The Ruby special interest group (SIG) meets regularly. See the OpenTelemetry Approvers ([@open-telemetry/ruby-contrib-approvers](/~https://github.com/orgs/open-telemetry/teams/ruby-contrib-approvers)): -- (Could _your_ name appear here?) +- [Josef Šimánek](/~https://github.com/simi) +- [Xuan Cao](/~https://github.com/xuan-cao-swi), Solarwinds *Find more about the approver role in [community repository](/~https://github.com/open-telemetry/community/blob/master/community-membership.md#approver).* @@ -37,6 +38,7 @@ Maintainers ([@open-telemetry/ruby-contrib-maintainers](/~https://github.com/orgs/ - [Daniel Azuma](/~https://github.com/dazuma), Google - [Eric Mustin](/~https://github.com/ericmustin) - [Francis Bogsanyi](/~https://github.com/fbogsany), Shopify +- [Kayla Reopelle](/~https://github.com/kaylareopelle), New Relic - [Matthew Wear](/~https://github.com/mwear), Lightstep - [Robb Kidd](/~https://github.com/robbkidd), Honeycomb - [Robert Laurin](/~https://github.com/robertlaurin), Shopify @@ -51,12 +53,19 @@ gems, including Rails, Rack, Sinatra, and others, so you can start using OpenTelemetry with minimal changes to your application. See the [instrumentation README](instrumentation/) for more details. +## Helpers + +This repository also contains libraries that hold code shared among +multiple instrumentation libraries. + +- [MySQL](helpers/mysql/) +- [SQL Obfuscation](helpers/sql-obfuscation/) ## Additional Libraries This repository also contains libraries to aid with interoperablity with vendor specific tracing solutions: - [Context Propagation](propagator/): OTTrace and Amazon X-Ray -- [Resource Detectors](resource_detectors/): +- [Resource Detectors](resources/): - Azure - Container - Google Cloud Platform @@ -65,6 +74,19 @@ This repository also contains libraries to aid with interoperablity with vendor OpenTelemetry Ruby follows the [versioning and stability document][otel-versioning] in the OpenTelemetry specification. Notably, we adhere to the outlined version numbering exception, which states that experimental signals may have a `0.x` version number. +### Library Compatability + +This project is managed on a volunteer basis and therefore we have limited capacity to support compatability with unmaintained or EOL libraries. + +We will regularly review the instrumentations to drop compatability for any versions of Ruby or gems that reach EOL or no longer receive regular maintenance. + +Should you need instrumentation for _older_ versions of a library then you must pin to a specific version of the instrumentation that supports it, +however, you will no longer receive any updates for the instrumentation from this repository. + +> When a release series is no longer supported, it's your own responsibility to deal with bugs and security issues. We may provide backports of the fixes and publish them to git, however there will be no new versions released. If you are not comfortable maintaining your own versions, you should upgrade to a supported version. + +Consult instrumentation gem's README file and gemspec for details about library compatability. + ### Releases This repository was extracted from the [OpenTelemetry Ruby repository][otel-ruby]. Versions of libraries contained in this repo released prior to 2022-06-13 are available on the [OpenTelemetry Ruby Releases][otel-ruby-releases] page. Newer versions are available [here][otel-ruby-contrib-releases] @@ -83,7 +105,7 @@ Apache 2.0 - See [LICENSE][license-url] for more information. [otel-ruby-contrib-releases]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/releases [ci-image]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/workflows/CI/badge.svg?event=push [examples-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/examples -[getting-started]: https://opentelemetry.io/docs/ruby/ +[getting-started]: https://opentelemetry.io/docs/languages/ruby/getting-started/ [issues-good-first-issue]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 [issues-help-wanted]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22 [license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat diff --git a/docker-compose.yml b/docker-compose.yml index 0a42dd986..af815eabe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -162,6 +162,12 @@ services: command: ./start_server.sh working_dir: /app/instrumentation/sinatra/example + processor-baggage-test: + <<: *base + working_dir: /app/processor/baggage + command: | + bash -c "bundle install && rake" + mongo: image: mongo:4.4 expose: diff --git a/helpers/mysql/.rubocop.yml b/helpers/mysql/.rubocop.yml new file mode 100644 index 000000000..4b31975de --- /dev/null +++ b/helpers/mysql/.rubocop.yml @@ -0,0 +1,4 @@ +inherit_from: ../../.rubocop.yml + +Gemspec/DevelopmentDependencies: + Enabled: false diff --git a/helpers/mysql/.yardopts b/helpers/mysql/.yardopts new file mode 100644 index 000000000..5e947205e --- /dev/null +++ b/helpers/mysql/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry MySQL Instrumentation Helpers +--markup=markdown +--main=README.md +./lib/opentelemetry/helpers/**/*.rb +./lib/opentelemetry/helpers.rb +- +README.md +CHANGELOG.md diff --git a/helpers/mysql/CHANGELOG.md b/helpers/mysql/CHANGELOG.md new file mode 100644 index 000000000..1dc3c9f46 --- /dev/null +++ b/helpers/mysql/CHANGELOG.md @@ -0,0 +1,9 @@ +# Release History: opentelemetry-helpers-mysql + +### v0.1.1 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.1.0 / 2024-02-08 + +Initial release. diff --git a/resource_detectors/lib/opentelemetry-resource_detectors.rb b/helpers/mysql/Gemfile similarity index 67% rename from resource_detectors/lib/opentelemetry-resource_detectors.rb rename to helpers/mysql/Gemfile index c84a217e4..f649e2f64 100644 --- a/resource_detectors/lib/opentelemetry-resource_detectors.rb +++ b/helpers/mysql/Gemfile @@ -4,4 +4,6 @@ # # SPDX-License-Identifier: Apache-2.0 -require_relative 'opentelemetry/resource/detectors' +source 'https://rubygems.org' + +gemspec diff --git a/resource_detectors/LICENSE b/helpers/mysql/LICENSE similarity index 100% rename from resource_detectors/LICENSE rename to helpers/mysql/LICENSE diff --git a/helpers/mysql/README.md b/helpers/mysql/README.md new file mode 100644 index 000000000..dcfaab979 --- /dev/null +++ b/helpers/mysql/README.md @@ -0,0 +1,56 @@ +# OpenTelemetry Instrumentation Helpers: MySQL + +This Ruby gem contains logic shared among MySQL adapter libraries, such as mysql2 and trilogy. It's intended for use by by gem authors instrumenting MySQL adapter libraries. + +## Usage + +Add the gem to your instrumentation's gemspec file: + +```ruby +# opentelemetry-instrumentation-your-gem.gemspec + spec.add_dependency 'opentelemetry-helpers-mysql' +``` + +Add the gem to your instrumentation's Gemfile: + +```ruby +# Gemfile + +group :test, :development do + gem 'opentelemetry-helpers-mysql', path: '../../helpers/mysql' +end +``` + +Make sure the `Instrumentation` class for your gem contains configuration options for: +- `:span_name`: The type of span name desired for the trace. + Example: `option :span_name, default: :statement_type, validate: %I[statement_type db_name db_operation_and_name]` + +## Examples + +To set the span name in your library: + +```ruby +tracer.in_span( + OpenTelemetry::Helpers::MySQL.database_span_name(sql, operation, database_name, config), + attributes: attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes), + kind: :client +) do + super(sql, options) +end +``` + +## How can I get involved? + +The `opentelemetry-helpers-mysql` gem source is [on github][repo-github], along with related gems including `opentelemetry-instrumentation-mysql2` and `opentelemetry-instrumentation-trilogy`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us on our [gitter channel][ruby-gitter] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +The `opentelemetry-helpers-mysql` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + +[repo-github]: /~https://github.com/open-telemetry/opentelemetry-ruby +[license-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/LICENSE +[ruby-sig]: /~https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: /~https://github.com/open-telemetry/community#community-meetings +[ruby-gitter]: https://gitter.im/open-telemetry/opentelemetry-ruby diff --git a/resource_detectors/Rakefile b/helpers/mysql/Rakefile similarity index 100% rename from resource_detectors/Rakefile rename to helpers/mysql/Rakefile diff --git a/helpers/mysql/lib/opentelemetry-helpers-mysql.rb b/helpers/mysql/lib/opentelemetry-helpers-mysql.rb new file mode 100644 index 000000000..309ce4492 --- /dev/null +++ b/helpers/mysql/lib/opentelemetry-helpers-mysql.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'opentelemetry/helpers/mysql' diff --git a/helpers/mysql/lib/opentelemetry/helpers.rb b/helpers/mysql/lib/opentelemetry/helpers.rb new file mode 100644 index 000000000..0c336fa32 --- /dev/null +++ b/helpers/mysql/lib/opentelemetry/helpers.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry' +require 'opentelemetry/helpers/mysql' + +module OpenTelemetry + # The helpers module contains functionality shared across multiple + # instrumentation libraries + module Helpers + end +end diff --git a/helpers/mysql/lib/opentelemetry/helpers/mysql.rb b/helpers/mysql/lib/opentelemetry/helpers/mysql.rb new file mode 100644 index 000000000..27eeb3984 --- /dev/null +++ b/helpers/mysql/lib/opentelemetry/helpers/mysql.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0module OpenTelemetry +require 'opentelemetry-common' + +module OpenTelemetry + module Helpers + # This module contains helpers for MySQL instrumentation libraries, like mysql2 and + # trilogy. It is intended for use by instrumentation developers and not to + # be called directly by an application. + # + # To use this in your instrumentation, the `Instrumentation` class for + # your gem must contain configuration options for: + # * `:span_name` + # Example: + # `option :span_name, default: :statement_type, validate: %I[statement_type db_name db_operation_and_name]` + # + # @api public + module MySQL + module_function + + QUERY_NAMES = [ + 'set names', + 'select', + 'insert', + 'update', + 'delete', + 'begin', + 'commit', + 'rollback', + 'savepoint', + 'release savepoint', + 'explain', + 'drop database', + 'drop table', + 'create database', + 'create table' + ].freeze + + # Ignore query names that might appear in comments prepended to the + # statement. + PREPENDED_COMMENTS_REGEX = %r{(?:\/\*.*?\*\/)}m + QUERY_NAME_REGEX = Regexp.new("^\s*(?:#{PREPENDED_COMMENTS_REGEX})?\s*\\b(#{QUERY_NAMES.join('|')})\\b.*", Regexp::IGNORECASE) + + # This is a span naming utility intended for use in MySQL database + # adapter instrumentation. + # + # @param sql [String] The SQL statement for the span. + # @param operation [String] The database operation. + # @param database_name [String] The name of the database. + # @param config [Hash] The user's configuration for the database adapter. + # Desired keys: + # * `:span_name` => A symbol describing the type of name desired. Expected options are `:statement_type`, `:db_name`, and `:db_operation_and_name`. A nil or unknown `:span_name` will return 'mysql' as the span name + # @return [String] The span name. + # @api public + def database_span_name(sql, operation, database_name, config) + case config[:span_name] + when :statement_type + extract_statement_type(sql) + when :db_name + database_name + when :db_operation_and_name + db_operation_and_name(operation, database_name) + end || 'mysql' + end + + # @api private + def extract_statement_type(sql) + sql = OpenTelemetry::Common::Utilities.utf8_encode(sql, binary: true) + + QUERY_NAME_REGEX.match(sql) { |match| match[1].downcase } unless sql.nil? + rescue StandardError => e + OpenTelemetry.handle_error(message: 'Error extracting SQL statement type', exception: e) + nil + end + + # @api private + def db_operation_and_name(operation, database_name) + if operation && database_name + "#{operation} #{database_name}" + elsif operation + operation + elsif database_name + database_name + end + end + end + end +end diff --git a/resource_detectors/lib/opentelemetry/resource/detectors/version.rb b/helpers/mysql/lib/opentelemetry/helpers/mysql/version.rb similarity index 69% rename from resource_detectors/lib/opentelemetry/resource/detectors/version.rb rename to helpers/mysql/lib/opentelemetry/helpers/mysql/version.rb index d500bc012..2d047426c 100644 --- a/resource_detectors/lib/opentelemetry/resource/detectors/version.rb +++ b/helpers/mysql/lib/opentelemetry/helpers/mysql/version.rb @@ -5,9 +5,9 @@ # SPDX-License-Identifier: Apache-2.0 module OpenTelemetry - module Resource - module Detectors - VERSION = '0.24.2' + module Helpers + module MySQL + VERSION = '0.1.1' end end end diff --git a/resource_detectors/opentelemetry-resource_detectors.gemspec b/helpers/mysql/opentelemetry-helpers-mysql.gemspec similarity index 60% rename from resource_detectors/opentelemetry-resource_detectors.gemspec rename to helpers/mysql/opentelemetry-helpers-mysql.gemspec index 0f6eb3693..1c18e0fe8 100644 --- a/resource_detectors/opentelemetry-resource_detectors.gemspec +++ b/helpers/mysql/opentelemetry-helpers-mysql.gemspec @@ -6,16 +6,16 @@ lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'opentelemetry/resource/detectors/version' +require 'opentelemetry/helpers/mysql/version' Gem::Specification.new do |spec| - spec.name = 'opentelemetry-resource_detectors' - spec.version = OpenTelemetry::Resource::Detectors::VERSION + spec.name = 'opentelemetry-helpers-mysql' + spec.version = OpenTelemetry::Helpers::MySQL::VERSION spec.authors = ['OpenTelemetry Authors'] spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] - spec.summary = 'Resource detection helpers for OpenTelemetry' - spec.description = 'Resource detection helpers for OpenTelemetry' + spec.summary = 'MySQL Instrumentation Helpers for the OpenTelemetry framework' + spec.description = 'MySQL Instrumentation Helpers for the OpenTelemetry framework' spec.homepage = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib' spec.license = 'Apache-2.0' @@ -25,23 +25,22 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.required_ruby_version = '>= 3.0' - spec.add_dependency 'google-cloud-env' - spec.add_dependency 'opentelemetry-sdk', '~> 1.0' + spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-common', '~> 0.21' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' - spec.add_development_dependency 'simplecov', '~> 0.17' - spec.add_development_dependency 'webmock', '~> 3.19' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.21.0' + spec.add_development_dependency 'simplecov', '~> 0.22.0' spec.add_development_dependency 'yard', '~> 0.9' - - spec.post_install_message = 'This gem has been deprecated. Please use opentelemetry-resource-detector-azure ' \ - 'or opentelemetry-resource-detector-google_cloud_platform onwards.' + spec.add_development_dependency 'yard-doctest', '~> 0.1.6' if spec.respond_to?(:metadata) spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md" - spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/resource_detectors' + spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/helpers/mysql' spec.metadata['bug_tracker_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues' spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}" end diff --git a/helpers/mysql/test/helpers/mysql_test.rb b/helpers/mysql/test/helpers/mysql_test.rb new file mode 100644 index 000000000..e72d1ca86 --- /dev/null +++ b/helpers/mysql/test/helpers/mysql_test.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Helpers::MySQL do + describe '.database_span_name' do + let(:sql) { 'SELECT * FROM users' } + let(:operation) { 'operation' } + let(:database_name) { 'database_name' } + let(:config) { { span_name: span_name } } + let(:database_span_name) { OpenTelemetry::Helpers::MySQL.database_span_name(sql, operation, database_name, config) } + + describe 'when config[:span_name] is :statement_type' do + let(:span_name) { :statement_type } + + it 'returns the statement type' do + assert_equal(database_span_name, 'select') + end + end + + describe 'when config[:span_name] is :db_name' do + let(:span_name) { :db_name } + + it 'returns database name' do + assert_equal(database_span_name, database_name) + end + end + + describe 'when config[:span_name] is :db_operation_and_name' do + let(:span_name) { :db_operation_and_name } + + it 'returns db operation and name' do + assert_equal(database_span_name, 'operation database_name') + end + end + + describe 'when config[:span_name] does not match a case' do + let(:span_name) { 'something_unexpected' } + + it 'returns mysql' do + assert_equal(database_span_name, 'mysql') + end + end + + describe 'when config[:span_name] is nil' do + let(:span_name) { nil } + + it 'returns mysql' do + assert_equal(database_span_name, 'mysql') + end + end + end + + describe '.db_operation_and_name' do + let(:operation) { 'operation' } + let(:database_name) { 'database_name' } + let(:db_operation_and_name) { OpenTelemetry::Helpers::MySQL.db_operation_and_name(operation, database_name) } + + describe 'when operation and database_name are present' do + it 'returns a combination of the operation and database_name' do + assert_equal(db_operation_and_name, 'operation database_name') + end + end + + describe 'when operation is nil' do + let(:operation) { nil } + + it 'returns database_name' do + assert_equal(db_operation_and_name, database_name) + end + end + + describe 'when database_name is nil' do + let(:database_name) { nil } + + it 'returns the operation name' do + assert_equal(db_operation_and_name, operation) + end + end + + describe 'when both database_name and operation are nil' do + let(:database_name) { nil } + let(:operation) { nil } + + it 'returns nil' do + assert_nil(db_operation_and_name) + end + end + end + + describe '.extract_statement_type' do + let(:sql) { 'SELECT * FROM users' } + let(:extract_statement_type) { OpenTelemetry::Helpers::MySQL.extract_statement_type(sql) } + + describe 'when it finds a match' do + it 'returns the query name' do + assert_equal('select', extract_statement_type) + end + end + + describe 'when sql contains invalid byte sequences' do + let(:sql) { "SELECT * from users where users.id = 1 and users.email = 'test@test.com\255'" } + + it 'extracts the statement' do + assert_equal('select', extract_statement_type) + end + end + + describe 'when sql contains unknown query statement' do + let(:sql) { 'DESELECT 1' } + + # nil sets the span name to 'mysql' + it 'returns nil' do + assert_nil(extract_statement_type) + end + end + + describe 'when sql contains multiple query statements' do + let(:sql) { 'EXPLAIN SELECT 1' } + + it 'extracts the statement type that begins the query' do + assert_equal('explain', extract_statement_type) + end + end + + describe 'when sql with marginalia-style prepended comments includes a query statement' do + let(:sql) do + "/*action='update',application='TrilogyTest',controller='users'*/ SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1" + end + + it 'does not capture the query statement within the comment' do + assert_equal('select', extract_statement_type) + end + end + + describe 'when sql is nil' do + let(:sql) { nil } + + it 'returns nil' do + assert_nil(extract_statement_type) + end + end + + describe 'when an error is raised' do + it 'logs a message' do + result = nil + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + OpenTelemetry::Common::Utilities.stub(:utf8_encode, ->(_) { raise 'boom!' }) do + extract_statement_type + + assert_nil(result) + assert_match(/Error extracting/, log_stream.string) + end + end + end + end + end +end diff --git a/resource_detectors/test/test_helper.rb b/helpers/mysql/test/test_helper.rb similarity index 72% rename from resource_detectors/test/test_helper.rb rename to helpers/mysql/test/test_helper.rb index aee3060a7..0f824f6b8 100644 --- a/resource_detectors/test/test_helper.rb +++ b/helpers/mysql/test/test_helper.rb @@ -7,11 +7,8 @@ require 'bundler/setup' Bundler.require(:default, :development, :test) -SimpleCov.minimum_coverage 85 -SimpleCov.start - -require 'opentelemetry-resource_detectors' +require 'opentelemetry' +require 'opentelemetry-helpers-mysql' require 'minitest/autorun' -require 'webmock/minitest' OpenTelemetry.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym) diff --git a/helpers/sql-obfuscation/.rubocop.yml b/helpers/sql-obfuscation/.rubocop.yml new file mode 100644 index 000000000..4b31975de --- /dev/null +++ b/helpers/sql-obfuscation/.rubocop.yml @@ -0,0 +1,4 @@ +inherit_from: ../../.rubocop.yml + +Gemspec/DevelopmentDependencies: + Enabled: false diff --git a/helpers/sql-obfuscation/.yardopts b/helpers/sql-obfuscation/.yardopts new file mode 100644 index 000000000..d90322845 --- /dev/null +++ b/helpers/sql-obfuscation/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry SQL Obfuscation Instrumentation Helpers +--markup=markdown +--main=README.md +./lib/opentelemetry/helpers/**/*.rb +./lib/opentelemetry/helpers.rb +- +README.md +CHANGELOG.md diff --git a/helpers/sql-obfuscation/CHANGELOG.md b/helpers/sql-obfuscation/CHANGELOG.md new file mode 100644 index 000000000..7a62283ca --- /dev/null +++ b/helpers/sql-obfuscation/CHANGELOG.md @@ -0,0 +1,9 @@ +# Release History: opentelemetry-helpers-sql-obfuscation + +### v0.1.1 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.1.0 / 2024-02-08 + +Initial release. diff --git a/resource_detectors/Gemfile b/helpers/sql-obfuscation/Gemfile similarity index 61% rename from resource_detectors/Gemfile rename to helpers/sql-obfuscation/Gemfile index 6ae9e48bc..f649e2f64 100644 --- a/resource_detectors/Gemfile +++ b/helpers/sql-obfuscation/Gemfile @@ -7,8 +7,3 @@ source 'https://rubygems.org' gemspec - -group :development, :test do - gem 'byebug' unless RUBY_PLATFORM == 'java' - gem 'pry' -end diff --git a/helpers/sql-obfuscation/LICENSE b/helpers/sql-obfuscation/LICENSE new file mode 100644 index 000000000..1ef7dad2c --- /dev/null +++ b/helpers/sql-obfuscation/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/helpers/sql-obfuscation/README.md b/helpers/sql-obfuscation/README.md new file mode 100644 index 000000000..7165c1aa6 --- /dev/null +++ b/helpers/sql-obfuscation/README.md @@ -0,0 +1,62 @@ +# OpenTelemetry Instrumentation Helpers: SQL Obfuscation + +This Ruby gem contains logic to obfuscate SQL. It's intended for use by by gem authors instrumenting SQL adapter libraries, such as mysql2, pg, and trilogy. + +The logic is largely drawn from the [New Relic Ruby agent's SQL Obfuscation Helpers module][new-relic-obfuscation-helpers]. + +## Usage + +Add the gem to your instrumentation's gemspec file: + +```ruby +# opentelemetry-instrumentation-your-gem.gemspec + spec.add_dependency 'opentelemetry-helpers-sql-obfuscation' +``` + +Add the gem to your instrumentation's Gemfile: + +```ruby +# Gemfile + +group :test, :development do + gem 'opentelemetry-helpers-sql-obfuscation', path: '../../helpers/sql-obfuscation' +end +``` + +Make sure the `Instrumentation` class for your gem contains configuration options for: +- `:obfuscation_limit`: the length at which the obfuscated SQL string will be truncated. + Example: `option :obfuscation_limit, default: 2000, validate: :integer` + +If you want to add support for a new adapter, update the following constants to include keys for your adapter: + +- `DIALECT_COMPONENTS` +- `CLEANUP_REGEX` + +You must also add a new constant that calls the `generate_regex` method with your adapter's DIALECT_COMPONENTS that is named like `_COMPONENTS_REGEX`, such as: `MYSQL_COMPONENTS_REGEX`. + +Check [New Relic's SQL Obfuscation Helpers module][new-relic-obfuscation-helpers] to see if regular expressions for your adapter already exist. + +## Examples + +To obfuscate sql in your library: + +```ruby +OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql, obfuscation_limit: config[:obfuscation_limit], adapter: :postgres) +``` + +## How can I get involved? + +The `opentelemetry-helpers-sql-obfuscation` gem source is [on github][repo-github], along with related gems including `opentelemetry-instrumentation-pg` and `opentelemetry-instrumentation-trilogy`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us on our [gitter channel][ruby-gitter] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +The `opentelemetry-helpers-sql-obfuscation` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + +[new-relic-obfuscation-helpers]: /~https://github.com/newrelic/newrelic-ruby-agent/blob/96e7aca22c1c873c0f5fe704a2b3bb19652db68e/lib/new_relic/agent/database/obfuscation_helpers.rb +[repo-github]: /~https://github.com/open-telemetry/opentelemetry-ruby +[license-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/LICENSE +[ruby-sig]: /~https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: /~https://github.com/open-telemetry/community#community-meetings +[ruby-gitter]: https://gitter.im/open-telemetry/opentelemetry-ruby diff --git a/helpers/sql-obfuscation/Rakefile b/helpers/sql-obfuscation/Rakefile new file mode 100644 index 000000000..1a64ba842 --- /dev/null +++ b/helpers/sql-obfuscation/Rakefile @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] +end + +YARD::Rake::YardocTask.new do |t| + t.stats_options = ['--list-undoc'] +end + +if RUBY_ENGINE == 'truffleruby' + task default: %i[test] +else + task default: %i[test rubocop yard] +end diff --git a/helpers/sql-obfuscation/lib/opentelemetry-helpers-sql-obfuscation.rb b/helpers/sql-obfuscation/lib/opentelemetry-helpers-sql-obfuscation.rb new file mode 100644 index 000000000..a839b7e58 --- /dev/null +++ b/helpers/sql-obfuscation/lib/opentelemetry-helpers-sql-obfuscation.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'opentelemetry/helpers' diff --git a/helpers/sql-obfuscation/lib/opentelemetry/helpers.rb b/helpers/sql-obfuscation/lib/opentelemetry/helpers.rb new file mode 100644 index 000000000..671d2b6e1 --- /dev/null +++ b/helpers/sql-obfuscation/lib/opentelemetry/helpers.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry/helpers/sql_obfuscation' + +module OpenTelemetry + # The helpers module contains functionality shared across multiple + # instrumentation libraries + module Helpers + end +end diff --git a/helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation.rb b/helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation.rb new file mode 100644 index 000000000..da0394b42 --- /dev/null +++ b/helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0module OpenTelemetry + +require 'opentelemetry-common' + +module OpenTelemetry + module Helpers + # + # This module contains SQL obfuscation behavior to share with + # instrumentation for specific database adapters. + # The class uses code from: /~https://github.com/newrelic/newrelic-ruby-agent/blob/1fca78cc7a087421ad58088d8bea72c0362bc62f/lib/new_relic/agent/database/obfuscation_helpers.rb + # + # To use this in your instrumentation, the `Instrumentation` class for + # your gem must contain configuration options for: + # * `:db_statement` + # Example: + # `option :db_statement, default: :include, validate: %I[omit include obfuscate]` + # * `:obfuscation_limit` + # Example: + # `option :obfuscation_limit, default: 2000, validate: :integer` + # + # If you want to add support for a new adapter, update the following + # constants to include keys for your adapter: + # * DIALECT_COMPONENTS + # * CLEANUP_REGEX + # You must also add a new constant that uses `generate_regex` with your + # adapter's dialect components that is named like + # `_COMPONENTS_REGEX`, such as: `MYSQL_COMPONENTS_REGEX`. + # + # @api public + module SqlObfuscation + module_function + + # From: /~https://github.com/newrelic/newrelic-ruby-agent/blob/1fca78cc7a087421ad58088d8bea72c0362bc62f/lib/new_relic/agent/database/obfuscation_helpers.rb + COMPONENTS_REGEX_MAP = { + single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/, + double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/, + dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/, + uuids: /\{?(?:[0-9a-fA-F]\-*){32}\}?/, + numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/, + boolean_literals: /\b(?:true|false|null)\b/i, + hexadecimal_literals: /0x[0-9a-fA-F]+/, + comments: /(?:#|--).*?(?=\r|\n|$)/i, + multi_line_comments: %r{(?:\/\*.*?\*\/)}m, + oracle_quoted_strings: /q'\[.*?(?:\]'|$)|q'\{.*?(?:\}'|$)|q'\<.*?(?:\>'|$)|q'\(.*?(?:\)'|$)/ + }.freeze + + DIALECT_COMPONENTS = { + default: COMPONENTS_REGEX_MAP.keys, + mysql: %i[single_quotes double_quotes numeric_literals boolean_literals + hexadecimal_literals comments multi_line_comments], + postgres: %i[single_quotes dollar_quotes uuids numeric_literals + boolean_literals comments multi_line_comments], + sqlite: %i[single_quotes numeric_literals boolean_literals hexadecimal_literals + comments multi_line_comments], + oracle: %i[single_quotes oracle_quoted_strings numeric_literals comments + multi_line_comments], + cassandra: %i[single_quotes uuids numeric_literals boolean_literals + hexadecimal_literals comments multi_line_comments] + }.freeze + + PLACEHOLDER = '?' + + # We use these to check whether the query contains any quote characters + # after obfuscation. If so, that's a good indication that the original + # query was malformed, and so our obfuscation can't reliably find + # literals. In such a case, we'll replace the entire query with a + # placeholder. + CLEANUP_REGEX = { + default: %r{'|"|\/\*|\*\/}, + mysql: %r{'|"|\/\*|\*\//}, + postgres: %r{'|\/\*|\*\/|\$(?!\?)/}, + sqlite: %r{'|\/\*|\*\//}, + cassandra: %r{'|\/\*|\*\//}, + oracle: %r{'|\/\*|\*\//} + }.freeze + + # @api private + def generate_regex(dialect) + components = DIALECT_COMPONENTS[dialect] + Regexp.union(components.map { |component| COMPONENTS_REGEX_MAP[component] }) + end + + DEFAULT_COMPONENTS_REGEX = generate_regex(:default) + MYSQL_COMPONENTS_REGEX = generate_regex(:mysql) + POSTGRES_COMPONENTS_REGEX = generate_regex(:postgres) + SQLITE_COMPONENTS_REGEX = generate_regex(:sqlite) + CASSANDRA_COMPONENTS_REGEX = generate_regex(:cassandra) + ORACLE_COMPONENTS_REGEX = generate_regex(:oracle) + + # This is a SQL obfuscation utility intended for use in database adapter instrumentation. + # + # @param sql [String] The SQL to obfuscate. + # @param obfuscation_limit [optional Integer] The maximum length of an obfuscated sql statement. + # @param adapter [optional Symbol] the type of database adapter calling the method. `:default`, `:mysql` and `:postgres` are supported. + # @return [String] The SQL query string where the values are replaced with "?". When the sql statement exceeds the obufscation limit + # the first matched pair from the SQL statement will be returned, with an appended truncation message. If trunaction is unsuccessful, + # a string describing the error will be returned. + # + # @api public + def obfuscate_sql(sql, obfuscation_limit: 2000, adapter: :default) + regex = case adapter + when :mysql + MYSQL_COMPONENTS_REGEX + when :postgres + POSTGRES_COMPONENTS_REGEX + else + DEFAULT_COMPONENTS_REGEX + end + + # Original MySQL UTF-8 Encoding Fixes: + # /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/160 + # /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/345 + sql = OpenTelemetry::Common::Utilities.utf8_encode(sql, binary: true) + return truncate_statement(sql, regex, obfuscation_limit) if sql.size > obfuscation_limit + + sql = sql.gsub(regex, PLACEHOLDER) + return 'Failed to obfuscate SQL query - quote characters remained after obfuscation' if CLEANUP_REGEX[adapter].match(sql) + + sql + rescue StandardError => e + OpenTelemetry.handle_error(message: 'Failed to obfuscate SQL', exception: e) + end + + # @api private + def truncate_statement(sql, regex, limit) + first_match_index = sql.index(regex) + truncation_message = "SQL truncated (> #{limit} characters)" + return truncation_message unless first_match_index + + truncated_sql = sql[..first_match_index - 1] + "#{truncated_sql}...\n#{truncation_message}" + end + end + end +end diff --git a/helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation/version.rb b/helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation/version.rb new file mode 100644 index 000000000..4d0787456 --- /dev/null +++ b/helpers/sql-obfuscation/lib/opentelemetry/helpers/sql_obfuscation/version.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Helpers + module SqlObfuscation + VERSION = '0.1.1' + end + end +end diff --git a/helpers/sql-obfuscation/opentelemetry-helpers-sql-obfuscation.gemspec b/helpers/sql-obfuscation/opentelemetry-helpers-sql-obfuscation.gemspec new file mode 100644 index 000000000..5143fc7d6 --- /dev/null +++ b/helpers/sql-obfuscation/opentelemetry-helpers-sql-obfuscation.gemspec @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'opentelemetry/helpers/sql_obfuscation/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-helpers-sql-obfuscation' + spec.version = OpenTelemetry::Helpers::SqlObfuscation::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'SQL Obfuscation Instrumentation Helpers for the OpenTelemetry framework' + spec.description = 'SQL Obfuscation Instrumentation Helpers for the OpenTelemetry framework' + spec.homepage = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib' + spec.license = 'Apache-2.0' + + spec.files = Dir.glob('lib/**/*.rb') + + Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.0' + + spec.add_dependency 'opentelemetry-common', '~> 0.21' + + spec.add_development_dependency 'bundler', '~> 2.4' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' + spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.21.0' + spec.add_development_dependency 'yard', '~> 0.9' + spec.add_development_dependency 'yard-doctest', '~> 0.1.6' + + if spec.respond_to?(:metadata) + spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md" + spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/helpers/sql-obfuscation' + spec.metadata['bug_tracker_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues' + spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}" + end +end diff --git a/helpers/sql-obfuscation/test/fixtures/sql_obfuscation.json b/helpers/sql-obfuscation/test/fixtures/sql_obfuscation.json new file mode 100644 index 000000000..9c75b8f16 --- /dev/null +++ b/helpers/sql-obfuscation/test/fixtures/sql_obfuscation.json @@ -0,0 +1,685 @@ +[ + { + "name": "back_quoted_identifiers.mysql", + "obfuscated": [ + "SELECT `t001`.`c2` FROM `t001` WHERE `t001`.`c2` = ? AND c3=? LIMIT ?" + ], + "dialects": [ + "mysql" + ], + "sql": "SELECT `t001`.`c2` FROM `t001` WHERE `t001`.`c2` = 'value' AND c3=\"othervalue\" LIMIT ?" + }, + { + "name": "comment_delimiters_in_double_quoted_strings", + "obfuscated": [ + "SELECT * FROM t WHERE foo=? AND baz=?" + ], + "dialects": [ + "mysql" + ], + "sql": "SELECT * FROM t WHERE foo=\"bar/*\" AND baz=\"whatever */qux\"" + }, + { + "name": "comment_delimiters_in_single_quoted_strings", + "obfuscated": [ + "SELECT * FROM t WHERE foo=? AND baz=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM t WHERE foo='bar/*' AND baz='whatever */qux'" + }, + { + "name": "double_quoted_identifiers.postgres", + "obfuscated": [ + "SELECT \"t001\".\"c2\" FROM \"t001\" WHERE \"t001\".\"c2\" = ? AND c3=? LIMIT ?" + ], + "dialects": [ + "postgres" + ], + "sql": "SELECT \"t001\".\"c2\" FROM \"t001\" WHERE \"t001\".\"c2\" = 'value' AND c3=1234 LIMIT 1" + }, + { + "name": "end_of_line_comment_in_double_quoted_string", + "obfuscated": [ + "SELECT * FROM t WHERE foo=? AND\n baz=?" + ], + "dialects": [ + "mysql" + ], + "sql": "SELECT * FROM t WHERE foo=\"bar--\" AND\n baz=\"qux--\"" + }, + { + "name": "end_of_line_comment_in_single_quoted_string", + "obfuscated": [ + "SELECT * FROM t WHERE foo=? AND\n baz=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM t WHERE foo='bar--' AND\n baz='qux--'" + }, + { + "name": "end_of_query_comment_cstyle", + "obfuscated": [ + "SELECT * FROM foo WHERE bar=? ?", + "SELECT * FROM foo WHERE bar=? " + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM foo WHERE bar='baz' /* Hide Me */" + }, + { + "name": "end_of_query_comment_doubledash", + "obfuscated": [ + "SELECT * FROM foobar WHERE password=?\n?", + "SELECT * FROM foobar WHERE password=?\n" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM foobar WHERE password='secret2'\n-- No peeking!" + }, + { + "name": "end_of_query_comment_hash", + "obfuscated": [ + "SELECT foo, bar FROM baz WHERE password=? ?", + "SELECT foo, bar FROM baz WHERE password=? " + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT foo, bar FROM baz WHERE password='secret2' # Secret" + }, + { + "name": "escape_string_constants.postgres", + "sql": "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E'foo\\'bar\\\\baz' AND country=e'foo\\'bar\\\\baz'", + "obfuscated": [ + "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E?", + "SELECT \"col1\", \"col2\" from \"table\" WHERE \"col3\"=E? AND country=e?" + ], + "dialects": [ + "postgres" + ], + "comments": [ + "PostgreSQL supports an alternate string quoting mode where backslash escape", + "sequences are interpreted.", + "See: http://www.postgresql.org/docs/9.3/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE" + ] + }, + { + "name": "multiple_literal_types.mysql", + "obfuscated": [ + "INSERT INTO `X` values(?,?, ? , ?, ?)" + ], + "dialects": [ + "mysql" + ], + "sql": "INSERT INTO `X` values(\"test\",0, 1 , 2, 'test')" + }, + { + "name": "numbers_in_identifiers", + "obfuscated": [ + "SELECT c11.col1, c22.col2 FROM table c11, table c22 WHERE value=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT c11.col1, c22.col2 FROM table c11, table c22 WHERE value='nothing'" + }, + { + "name": "numeric_literals", + "sql": "INSERT INTO X VALUES(1, 23456, 123.456, 99+100)", + "obfuscated": [ + "INSERT INTO X VALUES(?, ?, ?, ?+?)", + "INSERT INTO X VALUES(?, ?, ?.?, ?+?)" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "string_double_quoted.mysql", + "obfuscated": [ + "SELECT * FROM table WHERE name=? AND value=?" + ], + "dialects": [ + "mysql" + ], + "sql": "SELECT * FROM table WHERE name=\"foo\" AND value=\"don't\"" + }, + { + "name": "string_single_quoted", + "obfuscated": [ + "SELECT * FROM table WHERE name=? AND value = ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM table WHERE name='foo' AND value = 'bar'" + }, + { + "name": "string_with_backslash_and_twin_single_quotes", + "obfuscated": [ + "SELECT * FROM table WHERE col=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM table WHERE col='foo\\''bar'", + "comments": [ + "If backslashes are being ignored in single-quoted strings", + "(standard_conforming_strings=on in PostgreSQL, or NO_BACKSLASH_ESCAPES is on", + "in MySQL), then this is valid SQL." + ] + }, + { + "name": "string_with_embedded_double_quote", + "obfuscated": [ + "SELECT * FROM table WHERE col1=? AND col2=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM table WHERE col1='foo\"bar' AND col2='what\"ever'" + }, + { + "name": "string_with_embedded_newline", + "obfuscated": [ + "select * from accounts where accounts.name != ? order by accounts.name" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "select * from accounts where accounts.name != 'dude \n newline' order by accounts.name" + }, + { + "name": "string_with_embedded_single_quote.mysql", + "obfuscated": [ + "SELECT * FROM table WHERE col1=? AND col2=?" + ], + "dialects": [ + "mysql" + ], + "sql": "SELECT * FROM table WHERE col1=\"don't\" AND col2=\"won't\"" + }, + { + "name": "string_with_escaped_quotes.mysql", + "sql": "INSERT INTO X values('', 'jim''s ssn',0, 1 , 'jim''s son''s son', \"\"\"jim''s\"\" hat\", \"\\\"jim''s secret\\\"\")", + "obfuscated": [ + "INSERT INTO X values(?, ?,?, ? , ?, ?, ?", + "INSERT INTO X values(?, ?,?, ? , ?, ?, ?)" + ], + "dialects": [ + "mysql" + ] + }, + { + "name": "string_with_trailing_backslash", + "sql": "SELECT * FROM table WHERE name='foo\\' AND color='blue'", + "obfuscated": [ + "SELECT * FROM table WHERE name=?", + "SELECT * FROM table WHERE name=? AND color=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "comments": [ + "If backslashes are being ignored in single-quoted strings", + "(standard_conforming_strings=on in PostgreSQL, or NO_BACKSLASH_ESCAPES is on", + "in MySQL), then this is valid SQL." + ] + }, + { + "name": "string_with_trailing_escaped_backslash.mysql", + "obfuscated": [ + "SELECT * FROM table WHERE foo=?" + ], + "dialects": [ + "mysql" + ], + "sql": "SELECT * FROM table WHERE foo=\"this string ends with a backslash\\\\\"" + }, + { + "name": "string_with_trailing_escaped_backslash_single_quoted", + "obfuscated": [ + "SELECT * FROM table WHERE foo=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "SELECT * FROM table WHERE foo='this string ends with a backslash\\\\'" + }, + { + "name": "string_with_trailing_escaped_quote", + "sql": "SELECT * FROM table WHERE name='foo\\'' AND color='blue'", + "obfuscated": [ + "SELECT * FROM table WHERE name=?", + "SELECT * FROM table WHERE name=? AND color=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "string_with_twin_single_quotes", + "obfuscated": [ + "INSERT INTO X values(?, ?,?, ? , ?)" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "sql": "INSERT INTO X values('', 'a''b c',0, 1 , 'd''e f''s h')" + }, + { + "name": "end_of_line_comments_with_quotes", + "sql": "SELECT * FROM t WHERE -- '\n bar='baz' -- '", + "obfuscated": [ + "SELECT * FROM t WHERE ?\n bar=? ?", + "SELECT * FROM t WHERE ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "mixed_comments_and_quotes", + "sql": "SELECT * FROM t WHERE /* ' */ \n bar='baz' -- '", + "obfuscated": [ + "SELECT * FROM t WHERE ? \n bar=? ?", + "SELECT * FROM t WHERE ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "mixed_quotes_comments_and_newlines", + "sql": "SELECT * FROM t WHERE -- '\n /* ' */ c2='xxx' /* ' */\n c='x\n xx' -- '", + "obfuscated": [ + "SELECT * FROM t WHERE ?\n ? c2=? ?\n c=? ?", + "SELECT * FROM t WHERE ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "mixed_quotes_end_of_line_comments", + "sql": "SELECT * FROM t WHERE -- '\n c='x\n xx' -- '", + "obfuscated": [ + "SELECT * FROM t WHERE ?\n c=? ?", + "SELECT * FROM t WHERE ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "quote_delimiters_in_comments", + "sql": "SELECT * FROM foo WHERE col='value1' AND /* don't */ col2='value1' /* won't */", + "obfuscated": [ + "SELECT * FROM foo WHERE col=? AND ? col2=? ?", + "SELECT * FROM foo WHERE col=? AND ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "malformed/unterminated_double_quoted_string.mysql", + "sql": "SELECT * FROM table WHERE foo='bar' AND baz=\"nothing to see here'", + "dialects": [ + "mysql" + ], + "obfuscated": [ + "?" + ], + "malformed": true + }, + { + "name": "malformed/unterminated_single_quoted_string", + "sql": "SELECT * FROM table WHERE foo='bar' AND baz='nothing to see here", + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ], + "obfuscated": [ + "?" + ], + "malformed": true + }, + { + "name": "dollar_quotes", + "sql": "SELECT * FROM \"foo\" WHERE \"foo\" = $a$dollar quotes can be $b$nested$b$$a$ and bar = 'baz'", + "obfuscated": [ + "SELECT * FROM \"foo\" WHERE \"foo\" = ? and bar = ?" + ], + "dialects": [ + "postgres" + ] + }, + { + "name": "variable_substitution_not_mistaken_for_dollar_quotes", + "sql": "INSERT INTO \"foo\" (\"bar\", \"baz\", \"qux\") VALUES ($1, $2, $3) RETURNING \"id\"", + "obfuscated": [ + "INSERT INTO \"foo\" (\"bar\", \"baz\", \"qux\") VALUES ($?, $?, $?) RETURNING \"id\"" + ], + "dialects": [ + "postgres" + ] + }, + { + "name": "non_quote_escape", + "sql": "select * from foo where bar = 'some\\tthing' and baz = 10", + "obfuscated": [ + "select * from foo where bar = ? and baz = ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "end_of_string_backslash_and_line_comment_with_quite", + "sql": "select * from users where user = 'user1\\' password = 'secret 2' -- ->don't count this quote", + "obfuscated": [ + "select * from users where user = ?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "oracle_bracket_quote", + "sql": "select * from foo where bar=q'[baz's]' and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "oracle" + ] + }, + { + "name": "oracle_brace_quote", + "sql": "select * from foo where bar=q'{baz's}' and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "oracle" + ] + }, + { + "name": "oracle_angle_quote", + "sql": "select * from foo where bar=q'' and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "oracle" + ] + }, + { + "name": "oracle_paren_quote", + "sql": "select * from foo where bar=q'(baz's)' and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "oracle" + ] + }, + { + "name": "cassandra_blobs", + "sql": "select * from foo where bar=0xabcdef123 and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "cassandra", + "sqlite" + ] + }, + { + "name": "hex_literals", + "sql": "select * from foo where bar=0x2F and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "mysql", + "cassandra", + "sqlite" + ] + }, + { + "name": "exponential_literals", + "sql": "select * from foo where bar=1.234e-5 and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "negative_integer_literals", + "sql": "select * from foo where bar=-1.234e-5 and x=-5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra", + "sqlite" + ] + }, + { + "name": "uuid", + "sql": "select * from foo where bar=01234567-89ab-cdef-0123-456789abcdef and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "postgres", + "cassandra" + ] + }, + { + "name": "uuid_with_braces", + "sql": "select * from foo where bar={01234567-89ab-cdef-0123-456789abcdef} and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "postgres" + ] + }, + { + "name": "uuid_no_dashes", + "sql": "select * from foo where bar=0123456789abcdef0123456789abcdef and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "postgres" + ] + }, + { + "name": "uuid_random_dashes", + "sql": "select * from foo where bar={012-345678-9abc-def012345678-9abcdef} and x=5", + "obfuscated": [ + "select * from foo where bar=? and x=?" + ], + "dialects": [ + "postgres" + ] + }, + { + "name": "booleans", + "sql": "select * from truestory where bar=true and x=FALSE", + "obfuscated": [ + "select * from truestory where bar=? and x=?" + ], + "dialects": [ + "mysql", + "postgres", + "cassandra", + "sqlite" + ] + }, + { + "name": "in_clause_digits", + "sql": "select * from foo where bar IN (123, 456, 789)", + "obfuscated": [ + "select * from foo where bar IN (?, ?, ?)" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra" + ] + }, + { + "name": "in_clause_strings", + "sql": "select * from foo where bar IN ('asdf', 'fdsa')", + "obfuscated": [ + "select * from foo where bar IN (?, ?)" + ], + "dialects": [ + "mysql", + "postgres", + "oracle", + "cassandra" + ] + }, + { + "name": "prepended_comments_with_quotes.postgres", + "sql": "/*application:Demo,controller:posts,action:update*/ UPDATE \"posts\" SET \"updated_at\" = '2023-11-01 19:02:34.795909' WHERE \"posts\".\"id\" = 3", + "obfuscated": [ + "? UPDATE \"posts\" SET \"updated_at\" = ? WHERE \"posts\".\"id\" = ?" + ], + "dialects": [ + "postgres" + ] + }, + { + "name": "prepended_comments_with_quotes.mysql", + "sql": "/*action='show',application='TrilogyTest',controller='users'*/ SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1", + "obfuscated": [ + "? SELECT `users`.* FROM `users` WHERE `users`.`id` = ? LIMIT ?" + ], + "dialects": [ + "mysql" + ] + }, + { + "name": "prepended_multiline_comments_with_quotes.mysql", + "sql": "/*action='show',\napplication='TrilogyTest',controller='users'*/\nSELECT `users`.*\nFROM `users`\nWHERE `users`.`id` = 1 LIMIT 1", + "obfuscated": [ + "?\nSELECT `users`.*\nFROM `users`\nWHERE `users`.`id` = ? LIMIT ?" + ], + "dialects": [ + "mysql" + ] + } +] diff --git a/helpers/sql-obfuscation/test/helpers/sql_obfuscation_test.rb b/helpers/sql-obfuscation/test/helpers/sql_obfuscation_test.rb new file mode 100644 index 000000000..e340cbdb5 --- /dev/null +++ b/helpers/sql-obfuscation/test/helpers/sql_obfuscation_test.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# This file is distributed under New Relic's license terms. +# See /~https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. + +require_relative '../test_helper' + +class SqlObfuscationTest < Minitest::Test + def test_named_arg_defaults_obfuscates + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expected = 'SELECT * from users where users.id = ? and users.email = ?' + result = OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql) + + assert_equal(expected, result) + end + + def test_obfuscation_limit_truncates_query_after_first_match + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expected = "SELECT * from users where users.id = ...\nSQL truncated (> 42 characters)" + result = OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql, obfuscation_limit: 42) + + assert_equal(expected, result) + end + + def test_obfuscation_limit_truncates_when_query_not_encoded_with_utf8 + sql = "SELECT * from 😄 where users.id = 1 and users.😄 = 'test@test.com'" + expected = "SELECT * from where users.id = ...\nSQL truncated (> 42 characters)" + result = OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql, obfuscation_limit: 42) + + assert_equal(expected, result) + end + + def test_non_utf_8_encoded_string_obfuscates_with_mysql + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com\255'" + expected = 'SELECT * from users where users.id = ? and users.email = ?' + result = OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql, adapter: :mysql) + + assert_equal(expected, result) + end + + def test_non_utf_8_encoded_string_obfuscates_with_postgres + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com\255'" + expected = 'SELECT * from users where users.id = ? and users.email = ?' + result = OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql, adapter: :postgres) + + assert_equal(expected, result) + end + + def test_statement_with_emoji_encodes_utf_8_and_obfuscates + sql = "SELECT * from users where users.id = 1 and users.email = 'test@😄.com'" + expected = 'SELECT * from users where users.id = ? and users.email = ?' + result = OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql) + + assert_equal(expected, result) + end + + # The following tests and their corresponding fixture are based on code from + # the New Relic Ruby agent. + # source: /~https://github.com/newrelic/newrelic-ruby-agent/blob/cb72bb5fab3fb318613421c86863a5ccdd2ff250/test/new_relic/agent/database/sql_obfuscation_test.rb + + FAILED_TO_OBFUSCATE_MESSAGE = 'Failed to obfuscate SQL query - quote characters remained after obfuscation' + + def build_failure_message(statement, dialect, acceptable_outputs, actual_output) + msg = +"Failed to obfuscate #{dialect} query correctly.\n" + msg << "Input: #{statement}\n" + if acceptable_outputs.size == 1 + msg << "Expected: #{acceptable_outputs.first}\n" + else + msg << "Acceptable outputs:\n" + acceptable_outputs.each do |output| + msg << " #{output}\n" + end + end + msg << "Actual: #{actual_output}\n" + msg + end + + def self.load_fixture + data = File.read("#{Dir.pwd}/test/fixtures/sql_obfuscation.json") + JSON.parse(data) + end + + load_fixture.each do |test_case| + name = test_case['name'] + query = test_case['sql'] + acceptable_outputs = test_case['obfuscated'] + dialects = test_case['dialects'] + + # If the entire query is obfuscated because it's malformed, we use a + # placeholder message instead of just '?', so add that to the acceptable + # outputs. + acceptable_outputs << FAILED_TO_OBFUSCATE_MESSAGE if test_case['malformed'] + + dialects.each do |dialect| + define_method(:"test_sql_obfuscation_#{name}_#{dialect}") do + actual_obfuscated = OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(query, adapter: dialect.to_sym) + message = build_failure_message(query, dialect, acceptable_outputs, actual_obfuscated) + + assert_includes(acceptable_outputs, actual_obfuscated, message) + end + end + end + ## End New Relic tests +end diff --git a/helpers/sql-obfuscation/test/test_helper.rb b/helpers/sql-obfuscation/test/test_helper.rb new file mode 100644 index 000000000..6e84b456c --- /dev/null +++ b/helpers/sql-obfuscation/test/test_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/setup' +Bundler.require(:default, :development, :test) + +require 'minitest/autorun' +require 'opentelemetry-helpers-sql-obfuscation' diff --git a/instrumentation/CONTRIBUTING.md b/instrumentation/CONTRIBUTING.md new file mode 100644 index 000000000..ff45dcbbb --- /dev/null +++ b/instrumentation/CONTRIBUTING.md @@ -0,0 +1,486 @@ +# Instrumentation author's guide + +This guide is for authors of OpenTelemetry Ruby instrumentation libraries. It provides guidance on how to contribute an instrumentation library. + +Please make sure to read and understand the [CONTRIBUTING](../CONTRIBUTING.md) guide before submitting any changes to instrumentation. + +## What we expect from you + +We are a community of volunteers with a shared goal of improving observability in Ruby applications. + +We welcome contributions from everyone. We want to make sure that you have a great experience contributing to this project, as well as a positive impact on the Ruby community. + +We have limited capacity to maintain instrumentation libraries, so we ask that you commit to maintaining the instrumentation library you contribute. + +In addition to the requirements to maintain at least [community member status](/~https://github.com/open-telemetry/community/blob/main/community-membership.md), contributing an instrumentation to this project requires the following: + +1. Responding to issues and pull requests +2. Performing timely code reviews and responding to issues +3. Addressing security vulnerabilities +4. Keeping the instrumentation library up to date with the latest: + * OpenTelemetry Ruby API and SDK changes + * Ruby language changes + * Instrumented library changes + +If you do not have the capacity to maintain the instrumentation library, please consider contributing to the OpenTelemetry Ruby project in other ways or consider creating a separate project for the instrumentation library. + +> :warning: Libraries that do not meet these requirements may be removed from the project at any time at the discretion of OpenTelemetry Ruby Contrib Maintainers. + +## Contributing a new instrumentation library + +Our long-term goal is to provide instrumentation for all popular Ruby libraries. Ideally, we would like to have first-party instrumentation for all libraries maintained by the gem's authors to ensure compatibility with upstream gems. However, in many cases this is not possible. + +For this reason, we welcome contributions of new instrumentation libraries that cannot be maintained by the original gem authors as first-party instrumentation. + +The following steps are required to contribute a new instrumentation library: + +1. Generate an instrumentation gem skeleton +2. Implement the instrumentation library, including comprehensive automated tests +3. Add the instrumentation library to the appropriate CI workflows +4. Include documentation for your instrumentation + * Document all instrumentation-specific configuration options in the `README.md` and `yardoc` class comments + * Document all semantic conventions used by the instrumentation in the `README.md` + * Provide executable examples in an `examples` directory +5. Submit a pull request + +## Generate the gem + +This repository contains a script to generate a new instrumentation library. + +The snippet below demonstrates how to generate a an instrumentation for the `werewolf` gem, starting from the repository root. + +```console + +$bash opentelemetry-ruby-contrib> bin/instrumentation_generator werewolf + +``` + +The output of the generator shows that it creates a new directory in the `instrumentation` directory using the name of the gem: + +``` console + +🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 + + WARNING: Your gem will *NOT* be tested until you add it to the CI workflows in `.github/workflows/ci.yml`!! + +🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 + + create instrumentation/werewolf/.rubocop.yml + create instrumentation/werewolf/.yardopts + create instrumentation/werewolf/Appraisals + create instrumentation/werewolf/CHANGELOG.md + create instrumentation/werewolf/Gemfile + create instrumentation/werewolf/LICENSE + create instrumentation/werewolf/opentelemetry-instrumentation-werewolf.gemspec + create instrumentation/werewolf/Rakefile + create instrumentation/werewolf/README.md + create instrumentation/werewolf/lib/opentelemetry-instrumentation-werewolf.rb + create instrumentation/werewolf/lib/opentelemetry/instrumentation.rb + create instrumentation/werewolf/lib/opentelemetry/instrumentation/werewolf.rb + create instrumentation/werewolf/lib/opentelemetry/instrumentation/werewolf/instrumentation.rb + create instrumentation/werewolf/lib/opentelemetry/instrumentation/werewolf/version.rb + create instrumentation/werewolf/test/test_helper.rb + create instrumentation/werewolf/test/opentelemetry/instrumentation/werewolf/instrumentation_test.rb + insert .toys/.data/releases.yml + insert instrumentation/all/Gemfile + insert instrumentation/all/opentelemetry-instrumentation-all.gemspec + insert instrumentation/all/lib/opentelemetry/instrumentation/all.rb + +``` + +## Implementation guidelines + +The original design and implementation of this project was heavily influenced by Datadog's `dd-trace-rb` project. You may refer to the [Datadog Porting Guide](datadog-porting-guide.md) as a reference for implementing instrumentations, however, the following guidelines are specific to OpenTelemetry Ruby: + +* Use `OpenTelemetry::Instrumentation::Base` +* Use the OpenTelemetry API +* Use first-party extension points +* Use Semantic Conventions +* Write comprehensive automated tests +* Understand performance characteristics + +### Use `OpenTelemetry::Instrumentation::Base` + +The entry point of your instrumentation should be implemented as a subclass of `OpenTelemetry::Instrumentation::Base`: + +* Implement an `install` block, where all of the integration work happens +* Implement a `present` block, which checks whether the library you are instrumenting was loaded +* Implement a `compatible` block and check for at least the minimum required library version + * OpenTelemetry Ruby Contrib generally supports only versions of gems that are within the maintenance window +* Add any custom configuration `options` you want to support + +The example below demonstrates how to implement the `Werewolf` instrumentation: + +```ruby + +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module Werewolf + class Instrumentation < OpenTelemetry::Instrumentation::Base + MINIMUM_VERSION = Gem::Version.new('0.1.0') + + install do |_config| + require_relative 'handlers' + Handlers.subscribe + end + + option :transformations, default: :omit, validate: %i[omit include] + + present do + defined?(::Werewolf) && defined?(::ActiveSupport) + end + + compatible do + Gem::Version.new(::Wereworlf::VERSION) >= MINIMUM_VERSION + end + end + end + end +end + +``` + +* The `install` block lazily requires the instrumentation handlers, which subscribe to events published by the `Werewolf` event hooks. +* The `present` block checks if the `Werewolf` and `ActiveSupport` libraries are loaded, which it will use to subscribe to events and generate spans. It will skip the installation if those dependencies were not loaded before the instrumentation was initialized. +* The `compatible` block checks if the `Werewolf` library version is at least `0.1.0` and will skip installation if it is not. +* The `options` section allows you to define custom configuration options that can be passed to the instrumentation. In this example, the `transformations` option is defined with a default value of `:omit` and a validation rule that only allows `:omit` or `:include` values. + +### Use the OpenTelemetry API + +Instrumentations are intended to be portable and usable with vendor distributions of the SDK. For this reason, you must use the [OpenTelemetry API](/~https://github.com/open-telemetry/opentelemetry-ruby/tree/main/api) to create spans and add attributes, events, and links to spans and avoid using the [OpenTelemetry SDK](/~https://github.com/open-telemetry/opentelemetry-ruby/tree/main/sdk) directly. + +Each instrumentation _must_ use a named tracer. Instrumentations that inherit from `OpenTelemetry::Instrumentation::Base` will get a single helper method that will automatically provide your instrumentation with a named tracer under `OpenTelemetry::Instrumentation::${Gem Name}::Instrumentation.instance.tracer`. + +For example, the `Werewolf` module generated in the example above is available via `OpenTelemetry::Instrumentation::Werewolf::Instrumentation.instance.tracer`. You should reference this tracer in your code when creating spans like this: + +```ruby + + OpenTelemetry::Instrumentation::Werewolf::Instrumentation.instance.tracer.start_span('transform') do + # code to be traced + end + +``` + +> :warning: This tracer is not _upgradable_ before the SDK is initialized, therefore it is important that your instrumentation _always_ use stack local references of the tracer. + +### Use first-party extension points + +Whenever possible, use first-party extension points (hooks) to instrument libraries. This ensures that the instrumentation is compatible with the latest versions of the library and that the instrumentation is maintained by the library authors. [`ActiveSupport::Notifications`](https://guides.rubyonrails.org/active_support_instrumentation.html) and `Middleware` are good examples of first-party extension points used by our instrumentation libraries. + +Monkey patching is discouraged in OpenTelemetry Ruby because it is the most common source of bugs and incompatability with the libraries we instrument. If you must monkey patch, please ensure that the monkey patch is as isolated as possible and that it is clearly documented. + +### Use Semantic Conventions + +Use the [OpenTelemetry Semantic Conventions](https://opentelemetry.io/docs/concepts/semantic-conventions/) to ensure the instrumentation is compatible with other OpenTelemetry libraries and that the data is useful in a distributed context. + +> :information_source: Privacy and security are important considerations when adding attributes to spans. Please ensure that you are not adding sensitive information to spans. If you are unsure, please ask for a review. + +When semantic conventions do not exist, use the [Elastic Common Schema](https://www.elastic.co/guide/en/ecs/current/index.html) and submit an Issue/PR with your attributes to the [Semantic Conventions repo](/~https://github.com/open-telemetry/semantic-conventions) to propose a new set of standard attributes. + +If the attribute is specific to your instrumentation, then consider namespacing it using the `instrumentation` prefix e.g. `werewolf.bite.damage` and calling it out in the instrumentation README. + +### Write comprehensive automated tests + +Code that is not tested will not be accepted by maintainers. We understand that providing 100% test coverage is not always possible but we still ask that you provide your best effort when writing automated tests. + +Most of the libraries instrument introduce changes outside of our control. For this reason, integration or state-based tests are preferred over interaction (mock) tests. + +When you do in fact run into cases where test doubles or API stubs are absolutely necessary, we recommend using the [`rspec-mocks`](/~https://github.com/rspec/rspec-mocks) and [`webmocks`](/~https://github.com/bblimke/webmock) gems. + +### Understand performance characteristics + +The OTel Specification describes expectations around the performance of SDKs, which you must review and apply to instrumentation: + +Instrumentation libraries should be as lightweight as possible and must: + +* Rely on `rubocop-performance` linters to catch performance issues +* Consider using [microbenchmarks](/~https://github.com/evanphx/benchmark-ips) and [profiling](https://ruby-prof.github.io/) to address any possible performance issues +* Provide minimal solutions and code paths + +#### Provide minimal solutions and code paths + +Instrumentation should have the minimal amount of code necessary to provide useful insights to our users. It may sound contrary to good engineering practices, but you must avoid adding lots of small methods, classes, and objects when instrumenting a library. + +Though often easier to maintain and reason about; small and well-factored code adds overhead to the library you're instrumenting, resulting in performance degradation due to unnecessary object allocations, method dispatching, and other performance overhead. + +It also contributes to building large backtraces, making it more difficult for our end users to understand the essential parts of exception reports. That will likely result in additional filtering logic in their application to avoid reporting unnecessary stack frames. + +In cases when code uses monkey patching, it runs the risk of _adding_ methods that conflict with the internal implementation of the library and may result in unexpected behavior and bugs. + +Avoid instrumenting _every_ method in a library and instead focus on the methods that provide the _most_ insights into what typically causes performance problems for applications, e.g. I/O and network calls. The use case for this type of low-level granularity falls under the purview of profiling. + +In the near future, [OTel Profiling](https://opentelemetry.io/blog/2024/profiling/) will provide users an even deeper understanding of what is happening in their applications at a more granular level. + +Here are some examples of performance fixes that reduced object allocations and method dispatching: + +* +* +* +* +* +* + +#### Avoid adding custom extensions + +Though your instrumentation may accept configurations options to customize the output, you should consider that the more options you add, the more complexity you will have to manage. + +You should _avoid_ adding options that allow custom code blocks (`type: :callable`) to be executed as part of the instrumentation. It is often difficult to predict error modes and the performance impact custom code will have on your instrumentation, which in turn will impact the service being instrumented. + +You should steer users towards post-processing as part of the [OTel Collector](https://opentelemetry.io/docs/collector/), which has a richer and more powerful toolset, and executes out of the application's critical code path. + +## Enable CI + +This project contains multiple CI workflows that execute tests and ensure the gems are installable. + +### Standalone instrumentation + +For standalone instrumentation that does not have any external service dependencies, add the gem to the `/.github/workflows/ci-instrumentation.yml` file under `jobs/instrumentation/strategy/matrix/gem`: + +``` yaml + +jobs: + instrumentation: + strategy: + fail-fast: false + matrix: + gem: + - action_pack + - action_view + - active_job + # ... + - werewolf + os: + - ubuntu-latest + +``` + +#### JRuby Compatibility + +If your gem is incompatible with `JRuby`, you can exclude it from the matrix by adding an entry to the `/.github/workflows/ci-instrumentation.yml` file under `jobs/instrumentation/steps/[name="JRuby Filter"]`: + +``` yaml + - name: "JRuby Filter" + id: jruby_skip + shell: bash + run: | + echo "skip=false" >> $GITHUB_OUTPUT + [[ "${{ matrix.gem }}" == "action_pack" ]] && echo "skip=true" >> $GITHUB_OUTPUT + # ... + [[ "${{ matrix.gem }}" == "werewolf" ]] && echo "skip=true" >> $GITHUB_OUTPUT + # This is essentially a bash script getting evaluated, so we need to return true or the whole job fails. + true +``` + +### External service instrumentations + +Adding jobs for instrumentation with external service dependencies may be a bit more difficult if the job does not already have a similar service configured. + +#### Using Existing Services + +CI is currently configured to support the following services: + +* kafka +* memcached +* mongodb +* mysql +* postgresql +* rabbitmq +* redis + +If your gem depends on one of those services, then great! The next step is to add the gem to matrix in the `/.github/workflows/ci-service-instrumentation.yml` file under `jobs/instrumentation_*/strategy/matrix/gem`: + +```yaml + + instrumentation_kafka: + strategy: + fail-fast: false + matrix: + gem: + - racecar + - rdkafka + - ruby_kafka + - werewolf + os: + - ubuntu-latest +``` + +#### Adding a New Service + +Assuming your external service is not supported, you may consider adding it as a new job in the `/.github/workflows/ci-service-instrumentation.yml` file, however we will accept new services on a case-by-case basis. + +Add the service container to `jobs/instrumentation_with_services/services` and add the gem to the matrix in `jobs/instrumentation_with_services/strategy/matrix/gem`: + +```yaml + + instrumentation_with_services: + strategy: + fail-fast: false + matrix: + gem: + - dalli + - mongo + - werewolf + os: + - ubuntu-latest + services: + # ... + my_service: + image: my_service:latest + # ... +``` + +> :information_source: Please refer to the official [GitHub Actions Documentation](https://docs.github.com/en/actions/using-containerized-services/about-service-containers) for more information on how to add a service container. + +If we determine the service container slows down the test suite significantly, it may make sense to copy the marix and steps stanzas from an existing instrumentation and update it to use the new service container as a dependency: + +```yaml + + instrumentation_silver: + strategy: + fail-fast: false + matrix: + gem: + - werewolf + os: + - ubuntu-latest + name: other / ${{ matrix.gem }} / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: "Test Ruby 3.3" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.3" + - name: "Test Ruby 3.2" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.2" + - name: "Test Ruby 3.1" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.1" + - name: "Test Ruby 3.0" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "3.0" + yard: true + rubocop: true + build: true + - name: "Test JRuby" + uses: ./.github/actions/test_gem + with: + gem: "opentelemetry-instrumentation-${{ matrix.gem }}" + ruby: "jruby-9.4.2.0" + services: + # ... + my_service: + image: my_service:latest + # ... +``` + +## Documentation + +### README and Yardoc + +The `instrumentation_generator` creates a `README.md` file for your instrumentation. Please ensure that the `README` is up-to-date and contains the following: + +1. The span names, events, and semantic attributes emitted by the instrumentation +2. The configuration options available +3. Any known limitations or caveats +4. The minimum supported gem version + +> :information_source: See the `ActiveJob` instrumentation [`README`](./active_job/README.md) for a comprehensive example. + +In addition to that, there should also be redundant `yardoc` comments in the entrypoint of your gem, i.e. the subclass `OpenTelemetry::Instrumentation::Base`. + +> :information_source: See the `Sidekiq::Instrumentation` [class description](./sidekiq/lib/opentelemetry/instrumentation/sidekiq/instrumentation.rb) for a comprehensive example. + +### Examples + +Executuable examples should be included in the `examples` directory that demonstrate how to use the instrumentation in a real-world scenario. + +We recommend using [Bundler's inline gemfile](https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html) to run the examples. Here is an example from the `grape` instrumentation: + +```ruby + +# frozen_string_literal: true + +require 'bundler/inline' + +gemfile(true) do + source 'https://rubygems.org' + gem 'grape', '~> 1.2' + gem 'opentelemetry-sdk' + gem 'opentelemetry-instrumentation-rack' + gem 'opentelemetry-instrumentation-grape' +end + +# Export traces to console +ENV['OTEL_TRACES_EXPORTER'] ||= 'console' + +OpenTelemetry::SDK.configure do |c| + c.service_name = 'trace_demonstration' + c.use_all # this will only require instrumentation gems it finds that are installed by bundler. +end + +at_exit do + OpenTelemetry.tracer_provider.shutdown +end + +# A basic Grape API example +class ExampleAPI < Grape::API + format :json + + desc 'Return a greeting message' + get :hello do + { message: 'Hello, world!' } + end + + desc 'Return information about a user' + params do + requires :id, type: Integer, desc: 'User ID' + end + get 'users/:id' do + { id: params[:id], name: 'John Doe', email: 'johndoe@example.com' } + end +end + +# Set up fake Rack application +builder = Rack::Builder.app do + # Integration is automatic in web frameworks but plain Rack applications require this line. + # Enable it in your config.ru. + use *OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args + run ExampleAPI +end +app = Rack::MockRequest.new(builder) + +app.get('/hello') +app.get('/users/1') + +``` + +## Submit a pull request + +You are encouraged to submit a `draft` pull request early in the development process to get feedback from the maintainers, run your tests via CI, and ensure that your changes are in line with the project's goals. + +The `CODEOWNERS` is used to notify instrumentation authors when a pull request is opened. Please add yourself to the `instrumentation` section of the `CODEOWNERS` file, e.g. + +```plaintext + +instrumentation/werewolf/ @lycanthrope @open-telemetry/ruby-contrib-maintainers @open-telemetry/ruby-contrib-approvers + +``` + +> :information_source: In order for you to receive a request to review PRs, you must be a member of the [`open-telemetry/community`](/~https://github.com/open-telemetry/community/blob/5db097b38ce930fb1ff3eb79a1625bae46136894/community-membership.md#community-membership). Please consider applying for membership if you are not already a member. + +The [CONTRIBUTING.md](../CONTRIBUTING.md) guide has the remaining steps to get your contribution reviewed, merged, and released. diff --git a/instrumentation/README.md b/instrumentation/README.md index 1ebae6a59..770e1a70d 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -10,7 +10,6 @@ Released instrumentations can be found at the [OpenTelemetry registry](https://o In-progress instrumentations can be found [here](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues?q=is%3Aopen+label%3Ainstrumentation+-label%3A%22help+wanted%22+). - ## How do I get started? ### Individual instrumentation libraries @@ -19,16 +18,20 @@ To get started with a single instrumentation library, for example `opentelemetry ### 1. Install the gem -``` +```console + gem install opentelemetry-instrumentation-rack + ``` ### 2. Configure OpenTelemetry to use the instrumentation -``` +```console + OpenTelemetry::SDK.configure do |c| c.use 'OpenTelemetry::Instrumentation::Rack' end + ``` Instrumentation-specific documentation can be found in each subdirectory's `README.md`. @@ -37,6 +40,15 @@ Instrumentation-specific documentation can be found in each subdirectory's `READ You also have the option of installing all of the instrumentation libraries by installing `opentelemetry-instrumentation-all`. See that gem's [README](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/all) for more. +### Maintenance and Version Compatability + +We are a community of volunteers who do our best to provide our users with up to date support for instrumentations, +however we have limited capacity and are unable to support compatability with EOL or unmaintained libraries. + +Should you need to instrument an _older_ version of a library you will have to ensure to pin to an instrumentation version that is compatible with that library. + +Please review the individual instrumentation READMEs for more information about version compatability. + ## How can I get involved? The source for all OpenTelemetry Ruby instrumentation gems is [on github](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation). diff --git a/instrumentation/action_mailer/.rubocop.yml b/instrumentation/action_mailer/.rubocop.yml new file mode 100644 index 000000000..1248a2f82 --- /dev/null +++ b/instrumentation/action_mailer/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: ../../.rubocop.yml diff --git a/instrumentation/action_mailer/.yardopts b/instrumentation/action_mailer/.yardopts new file mode 100644 index 000000000..cd50652b7 --- /dev/null +++ b/instrumentation/action_mailer/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry Action Mailer Instrumentation +--markup=markdown +--main=README.md +./lib/opentelemetry/instrumentation/**/*.rb +./lib/opentelemetry/instrumentation.rb +- +README.md +CHANGELOG.md diff --git a/instrumentation/action_mailer/Appraisals b/instrumentation/action_mailer/Appraisals new file mode 100644 index 000000000..018ce75b2 --- /dev/null +++ b/instrumentation/action_mailer/Appraisals @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +appraise 'rails-6.1' do + gem 'rails', '~> 6.1.0' +end + +appraise 'rails-7.0' do + gem 'rails', '~> 7.0.0' +end + +appraise 'rails-7.1' do + gem 'rails', '~> 7.1.0' +end diff --git a/instrumentation/action_mailer/CHANGELOG.md b/instrumentation/action_mailer/CHANGELOG.md new file mode 100644 index 000000000..8f0fc70b8 --- /dev/null +++ b/instrumentation/action_mailer/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History: opentelemetry-instrumentation-action_mailer + +### v0.1.0 / 2024-05-13 + +Initial release. diff --git a/instrumentation/action_mailer/Gemfile b/instrumentation/action_mailer/Gemfile new file mode 100644 index 000000000..2ededff74 --- /dev/null +++ b/instrumentation/action_mailer/Gemfile @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +source 'https://rubygems.org' + +gemspec + +group :test do + gem 'opentelemetry-instrumentation-base', path: '../base' + gem 'opentelemetry-instrumentation-active_support', path: '../active_support' +end diff --git a/instrumentation/action_mailer/LICENSE b/instrumentation/action_mailer/LICENSE new file mode 100644 index 000000000..1ef7dad2c --- /dev/null +++ b/instrumentation/action_mailer/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/instrumentation/action_mailer/README.md b/instrumentation/action_mailer/README.md new file mode 100644 index 000000000..7744fc202 --- /dev/null +++ b/instrumentation/action_mailer/README.md @@ -0,0 +1,102 @@ +# OpenTelemetry ActionMailer Instrumentation + +The ActionMailer instrumentation is a community-maintained instrumentation for the ActionMailer portion of the [Ruby on Rails][rails-home] web-application framework. + +## How do I get started? + +Install the gem using: + +```bash +# Install just the ActionMailer instrumentation +gem install opentelemetry-instrumentation-action_mailer +# Install the ActionMailer instrumentation along with the rest of the Rails-related instrumentation +gem install opentelemetry-instrumentation-rails +``` + +Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-action_mailer` in your `Gemfile`. + +## Usage + +To use the instrumentation, call `use` with the name of the instrumentation: + +```ruby +OpenTelemetry::SDK.configure do |c| + # Use only the ActionMailer instrumentation + c.use 'OpenTelemetry::Instrumentation::ActionMailer' + # Use the ActionMailer instrumentation along with the rest of the Rails-related instrumentation + c.use 'OpenTelemetry::Instrumentation::Rails' +end +``` + +Alternatively, you can also call `use_all` to install all the available instrumentation. + +```ruby +OpenTelemetry::SDK.configure do |c| + c.use_all +end +``` + +## Active Support Instrumentation + +This instrumentation relies entirely on `ActiveSupport::Notifications` and registers a custom Subscriber that listens to relevant events to report as spans. + +See the table below for details of what [Rails Framework Hook Events](https://guides.rubyonrails.org/active_support_instrumentation.html#action-mailer) are recorded by this instrumentation: + +| Event Name | Creates Span? | Notes | +| - | - | - | +| `deliver.action_mailer` | :white_check_mark: | Creates an span with kind `internal` and email content and status| +| `process.action_mailer` | :x: | Lack of useful info so ignored | + +### Options + +ActionMailer instrumentation doesn't expose email addresses by default, but if email addresses are needed, simply use `:email_address` option: +```ruby +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::ActionMailer', { email_address: :include } +end +``` + +If only want to hide certain attributes from the notifications payload for email address: +```ruby +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::ActionMailer', { email_address: :include, disallowed_notification_payload_keys: ['email.to.address'] } +end +``` + +## Semantic Conventions + +Internal spans are named using the name of the `ActiveSupport` event that was provided (e.g. `action_mailer deliver`). + +The following attributes from the notification payload for the `deliver.action_mailer` event are attached to `action_mailer deliver` spans: + +| Attribute Name | Type | Notes | +| - | - | - | +| `email.x_mailer` | String | Mailer class that is used to send mail | +| `email.message_id` | String | Set from Mail gem | +| `email.subject` | String | Mail subject | +| `email.to.address` | Array | Receiver for mail (omit by default, include when `email_address` set to `:include`) | +| `email.from.address` | Array | Sender for mail (omit by default, include when `email_address` set to `:include`) | +| `email.cc.address` | Array | mail CC (omit by default, include when `email_address` set to `:include`) | +| `email.bcc.address` | Array | mail BCC (omit by default, include when `email_address` set to `:include`) | +## Examples + +Example usage can be seen in the `./example/trace_request_demonstration.ru` file [here](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/instrumentation/action_mailer/example/trace_request_demonstration.ru) + + +## How can I get involved? + +The `opentelemetry-instrumentation-action_mailer` gem source is [on GitHub][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us in [GitHub Discussions][discussions-url] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +The `opentelemetry-instrumentation-action_mailer` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + +[rails-home]: /~https://github.com/rails/rails +[bundler-home]: https://bundler.io +[repo-github]: /~https://github.com/open-telemetry/opentelemetry-ruby +[license-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/LICENSE +[ruby-sig]: /~https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: /~https://github.com/open-telemetry/community#community-meetings +[discussions-url]: /~https://github.com/open-telemetry/opentelemetry-ruby/discussions diff --git a/instrumentation/action_mailer/Rakefile b/instrumentation/action_mailer/Rakefile new file mode 100644 index 000000000..1a64ba842 --- /dev/null +++ b/instrumentation/action_mailer/Rakefile @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] +end + +YARD::Rake::YardocTask.new do |t| + t.stats_options = ['--list-undoc'] +end + +if RUBY_ENGINE == 'truffleruby' + task default: %i[test] +else + task default: %i[test rubocop yard] +end diff --git a/instrumentation/action_mailer/example/app/views/test_mailer/welcome_email.html.erb b/instrumentation/action_mailer/example/app/views/test_mailer/welcome_email.html.erb new file mode 100644 index 000000000..1f25f75d9 --- /dev/null +++ b/instrumentation/action_mailer/example/app/views/test_mailer/welcome_email.html.erb @@ -0,0 +1,10 @@ + + + + Welcome to OpenTelemetry! + + +

Welcome!

+

Thank you for joining OpenTelemetry.

+ + diff --git a/instrumentation/action_mailer/example/tmp/local_secret.txt b/instrumentation/action_mailer/example/tmp/local_secret.txt new file mode 100644 index 000000000..277a74ad5 --- /dev/null +++ b/instrumentation/action_mailer/example/tmp/local_secret.txt @@ -0,0 +1 @@ +11007269270afc9d0da1bf5aebb7b00264473f1f36b82208c8f0d4375f516e03f41d91e2f81c1f6482b95540b07a7c4444d8cf329e8337dd397b14a59dd14a0e \ No newline at end of file diff --git a/instrumentation/action_mailer/example/trace_request_demonstration.ru b/instrumentation/action_mailer/example/trace_request_demonstration.ru new file mode 100644 index 000000000..2f6f6672c --- /dev/null +++ b/instrumentation/action_mailer/example/trace_request_demonstration.ru @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/inline' + +gemfile(true) do + source 'https://rubygems.org' + + gem 'rails' + gem 'opentelemetry-sdk' + gem 'stringio', '~> 3' + gem 'opentelemetry-instrumentation-active_support', path: '../../active_support' + gem 'opentelemetry-instrumentation-action_mailer', path: '../' +end + +require 'action_mailer/railtie' + +# TraceRequestApp is a minimal Rails application inspired by the Rails +# bug report template for action controller. +# The configuration is compatible with Rails 6.0 +class TraceRequestApp < Rails::Application + config.root = __dir__ + config.hosts << 'example.org' + credentials.secret_key_base = 'secret_key_base' + + config.eager_load = false + + config.logger = Logger.new($stdout) + Rails.logger = config.logger + + config.action_mailer.delivery_method = :test +end + +# A minimal test ApplicationMailer +class TestMailer < ActionMailer::Base + default from: 'no-reply@example.com' + + def welcome_email + mail(to: 'test_mailer@otel.org', subject: 'Welcome to OpenTelemetry!', cc: 'cc@example.com', bcc: 'bcc@example.com') + end +end + +# Simple setup for demonstration purposes, simple span processor should not be +# used in a production environment +span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new( + OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new +) + +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::ActionMailer' + c.add_span_processor(span_processor) +end + +Rails.application.initialize! + +TestMailer.welcome_email.deliver_now + +# To run this example run the `ruby` command with this file +# Example: ruby trace_request_demonstration.ru +# Spans for the requests will appear in the console diff --git a/instrumentation/action_mailer/lib/opentelemetry-instrumentation-action_mailer.rb b/instrumentation/action_mailer/lib/opentelemetry-instrumentation-action_mailer.rb new file mode 100644 index 000000000..c034f140f --- /dev/null +++ b/instrumentation/action_mailer/lib/opentelemetry-instrumentation-action_mailer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'opentelemetry/instrumentation' diff --git a/instrumentation/action_mailer/lib/opentelemetry/instrumentation.rb b/instrumentation/action_mailer/lib/opentelemetry/instrumentation.rb new file mode 100644 index 000000000..2111a4c9e --- /dev/null +++ b/instrumentation/action_mailer/lib/opentelemetry/instrumentation.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# OpenTelemetry is an open source observability framework, providing a +# general-purpose API, SDK, and related tools required for the instrumentation +# of cloud-native software, frameworks, and libraries. +# +# The OpenTelemetry module provides global accessors for telemetry objects. +# See the documentation for the `opentelemetry-api` gem for details. +module OpenTelemetry + # Instrumentation should be able to handle the case when the library is not installed on a user's system. + module Instrumentation + end +end + +require_relative 'instrumentation/action_mailer' diff --git a/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer.rb b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer.rb new file mode 100644 index 000000000..198b9197b --- /dev/null +++ b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry' +require 'opentelemetry-instrumentation-base' + +module OpenTelemetry + module Instrumentation + # Contains the OpenTelemetry instrumentation for the ActionMailer gem + module ActionMailer + end + end +end + +require_relative 'action_mailer/instrumentation' +require_relative 'action_mailer/version' diff --git a/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/instrumentation.rb b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/instrumentation.rb new file mode 100644 index 000000000..2b33478aa --- /dev/null +++ b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/instrumentation.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActionMailer + # The Instrumentation class contains logic to detect and install the ActionMailer instrumentation + class Instrumentation < OpenTelemetry::Instrumentation::Base + MINIMUM_VERSION = Gem::Version.new('6.1.0') + EMAIL_ATTRIBUTE = %w[email.to.address email.from.address email.cc.address email.bcc.address].freeze + + install do |_config| + resolve_email_address + ecs_mail_convention + require_dependencies + end + + present do + defined?(::ActionMailer) + end + + compatible do + gem_version >= MINIMUM_VERSION + end + + option :disallowed_notification_payload_keys, default: [], validate: :array + option :notification_payload_transform, default: nil, validate: :callable + option :email_address, default: :omit, validate: %I[omit include] + + private + + def gem_version + ::ActionMailer.version + end + + def resolve_email_address + return unless _config[:email_address] == :omit + + _config[:disallowed_notification_payload_keys] += EMAIL_ATTRIBUTE + end + + def ecs_mail_convention + if _config[:notification_payload_transform].nil? + transform_attributes = ->(payload) { transform_payload(payload) } + else + original_callable = _config[:notification_payload_transform] + transform_attributes = lambda do |payload| + new_payload = transform_payload(payload) + user_payload = original_callable.call(new_payload) + if user_payload.instance_of?(Hash) + user_payload + else + OpenTelemetry.logger.error("ActionMailer: transformed payload is #{user_payload.class} (require Hash)") + new_payload + end + end + end + _config[:notification_payload_transform] = transform_attributes + end + + def _config + ActionMailer::Instrumentation.instance.config + end + + # email attribute key convention is obtained from: https://www.elastic.co/guide/en/ecs/8.11/ecs-email.html + def transform_payload(payload) + new_payload = {} + new_payload['email.message_id'] = payload[:message_id] if payload[:message_id] + new_payload['email.subject'] = payload[:subject] if payload[:subject] + new_payload['email.x_mailer'] = payload[:mailer] if payload[:mailer] + new_payload['email.to.address'] = payload[:to] if payload[:to] + new_payload['email.from.address'] = payload[:from] if payload[:from] + new_payload['email.cc.address'] = payload[:cc] if payload[:cc] + new_payload['email.bcc.address'] = payload[:bcc] if payload[:bcc] + new_payload['email.origination_timestamp'] = payload[:date] if payload[:date] + new_payload + end + + def require_dependencies + require_relative 'railtie' + end + end + end + end +end diff --git a/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/railtie.rb b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/railtie.rb new file mode 100644 index 000000000..0b036d0e9 --- /dev/null +++ b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/railtie.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActionMailer + SUBSCRIPTIONS = %w[ + deliver.action_mailer + ].freeze + + # This Railtie sets up subscriptions to relevant ActionMailer notifications + class Railtie < ::Rails::Railtie + config.after_initialize do + ::OpenTelemetry::Instrumentation::ActiveSupport::Instrumentation.instance.install({}) + + SUBSCRIPTIONS.each do |subscription_name| + config = ActionMailer::Instrumentation.instance.config + ::OpenTelemetry::Instrumentation::ActiveSupport.subscribe( + ActionMailer::Instrumentation.instance.tracer, + subscription_name, + config[:notification_payload_transform], + config[:disallowed_notification_payload_keys] + ) + end + end + end + end + end +end diff --git a/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/version.rb b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/version.rb new file mode 100644 index 000000000..20756b5f2 --- /dev/null +++ b/instrumentation/action_mailer/lib/opentelemetry/instrumentation/action_mailer/version.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActionMailer + VERSION = '0.1.0' + end + end +end diff --git a/instrumentation/action_mailer/opentelemetry-instrumentation-action_mailer.gemspec b/instrumentation/action_mailer/opentelemetry-instrumentation-action_mailer.gemspec new file mode 100644 index 000000000..74308531b --- /dev/null +++ b/instrumentation/action_mailer/opentelemetry-instrumentation-action_mailer.gemspec @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'opentelemetry/instrumentation/action_mailer/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-instrumentation-action_mailer' + spec.version = OpenTelemetry::Instrumentation::ActionMailer::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'ActionMailer instrumentation for the OpenTelemetry framework' + spec.description = 'ActionMailer instrumentation for the OpenTelemetry framework' + spec.homepage = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib' + spec.license = 'Apache-2.0' + + spec.files = Dir.glob('lib/**/*.rb') + + Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.0' + + spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-instrumentation-active_support', '~> 0.1' + spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' + + spec.add_development_dependency 'appraisal', '~> 2.5' + spec.add_development_dependency 'bundler', '~> 2.4' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' + spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' + spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' + spec.add_development_dependency 'simplecov', '~> 0.17.1' + spec.add_development_dependency 'webmock', '~> 3.19' + spec.add_development_dependency 'yard', '~> 0.9' + + if spec.respond_to?(:metadata) + spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md" + spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/action_mailer' + spec.metadata['bug_tracker_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues' + spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}" + end +end diff --git a/instrumentation/action_mailer/test/opentelemetry/instrumentation/action_mailer/instrumentation_test.rb b/instrumentation/action_mailer/test/opentelemetry/instrumentation/action_mailer/instrumentation_test.rb new file mode 100644 index 000000000..24d7cc8c8 --- /dev/null +++ b/instrumentation/action_mailer/test/opentelemetry/instrumentation/action_mailer/instrumentation_test.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Instrumentation::ActionMailer do + let(:instrumentation) { OpenTelemetry::Instrumentation::ActionMailer::Instrumentation.instance } + let(:payload) do + { + mailer: 'TestMailer', + message_id: '6638fab8d3cdb_f0b2c52420@8b5092010d2f.mail', + subject: 'Welcome to OpenTelemetry!', + to: ['test_mailer@otel.org'], + from: ['no-reply@example.com'], + bcc: ['bcc@example.com'], + cc: ['cc@example.com'], + perform_deliveries: true + } + end + + it 'has #name' do + _(instrumentation.name).must_equal 'OpenTelemetry::Instrumentation::ActionMailer' + end + + it 'has #version' do + _(instrumentation.version).wont_be_nil + _(instrumentation.version).wont_be_empty + end + + describe '#install' do + it 'accepts argument' do + _(instrumentation.install({})).must_equal(true) + instrumentation.instance_variable_set(:@installed, false) + end + end + + describe '#install with default options' do + it 'with default options' do + _(instrumentation.config[:disallowed_notification_payload_keys]).wont_be_empty + _(instrumentation.config[:email_address]).must_equal :omit + end + end + + describe '#resolve_email_address' do + it 'with include' do + original_config = instrumentation.instance_variable_get(:@config) + modified_config = original_config.dup + modified_config[:email_address] = :include + modified_config[:disallowed_notification_payload_keys] = [] + instrumentation.instance_variable_set(:@config, modified_config) + + instrumentation.send(:resolve_email_address) + _(instrumentation.config[:disallowed_notification_payload_keys].size).must_equal 0 + + instrumentation.instance_variable_set(:@config, original_config) + end + end + + describe '#transform_payload' do + it 'with simple payload' do + payload = { + mailer: 'TestMailer', + message_id: '6638fab8d3cdb_f0b2c52420@8b5092010d2f.mail', + subject: 'Welcome to OpenTelemetry!', + to: ['test_mailer@otel.org'], + from: ['no-reply@example.com'], + bcc: ['bcc@example.com'], + cc: ['cc@example.com'], + perform_deliveries: true + } + tranformed_payload = instrumentation.send(:transform_payload, payload) + + _(tranformed_payload['email.message_id']).must_equal '6638fab8d3cdb_f0b2c52420@8b5092010d2f.mail' + _(tranformed_payload['email.subject']).must_equal 'Welcome to OpenTelemetry!' + _(tranformed_payload['email.x_mailer']).must_equal 'TestMailer' + _(tranformed_payload['email.to.address'][0]).must_equal 'test_mailer@otel.org' + _(tranformed_payload['email.from.address'][0]).must_equal 'no-reply@example.com' + _(tranformed_payload['email.cc.address'][0]).must_equal 'cc@example.com' + _(tranformed_payload['email.bcc.address'][0]).must_equal 'bcc@example.com' + end + end + + describe '#ecs_mail_convention' do + it 'with user-defined payload' do + original_config = instrumentation.instance_variable_get(:@config) + modified_config = original_config.dup + + modified_config[:notification_payload_transform] = ->(payload) { payload['email.message_id'] = 'fake_message_id' } + instrumentation.instance_variable_set(:@config, modified_config) + + instrumentation.send(:ecs_mail_convention) + payload = { mailer: 'TestMailer' } + + tranformed_payload = instrumentation.config[:notification_payload_transform].call(payload) + + _(tranformed_payload['email.message_id']).must_equal 'fake_message_id' + + instrumentation.instance_variable_set(:@config, original_config) + end + + it 'without user-defined payload' do + tranformed_payload = instrumentation.config[:notification_payload_transform].call(payload) + + _(tranformed_payload['email.message_id']).must_equal '6638fab8d3cdb_f0b2c52420@8b5092010d2f.mail' + _(tranformed_payload['email.subject']).must_equal 'Welcome to OpenTelemetry!' + _(tranformed_payload['email.x_mailer']).must_equal 'TestMailer' + _(tranformed_payload['email.to.address'][0]).must_equal 'test_mailer@otel.org' + _(tranformed_payload['email.from.address'][0]).must_equal 'no-reply@example.com' + _(tranformed_payload['email.cc.address'][0]).must_equal 'cc@example.com' + _(tranformed_payload['email.bcc.address'][0]).must_equal 'bcc@example.com' + end + end +end diff --git a/instrumentation/action_mailer/test/test_helper.rb b/instrumentation/action_mailer/test/test_helper.rb new file mode 100644 index 000000000..b68d22b82 --- /dev/null +++ b/instrumentation/action_mailer/test/test_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/setup' +Bundler.require(:default, :development, :test) + +require 'action_mailer' +require 'opentelemetry-instrumentation-action_mailer' +require 'minitest/autorun' +require 'webmock/minitest' + +# global opentelemetry-sdk setup: +EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new +span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(EXPORTER) + +OpenTelemetry::SDK.configure do |c| + c.error_handler = ->(exception:, message:) { raise(exception || message) } + c.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym) + c.use 'OpenTelemetry::Instrumentation::ActionMailer' + c.add_span_processor span_processor +end diff --git a/instrumentation/action_pack/Appraisals b/instrumentation/action_pack/Appraisals index 03f29edc9..018ce75b2 100644 --- a/instrumentation/action_pack/Appraisals +++ b/instrumentation/action_pack/Appraisals @@ -4,10 +4,6 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'rails-6.0' do - gem 'rails', '~> 6.0.0' -end - appraise 'rails-6.1' do gem 'rails', '~> 6.1.0' end @@ -15,3 +11,7 @@ end appraise 'rails-7.0' do gem 'rails', '~> 7.0.0' end + +appraise 'rails-7.1' do + gem 'rails', '~> 7.1.0' +end diff --git a/instrumentation/action_pack/CHANGELOG.md b/instrumentation/action_pack/CHANGELOG.md index 957d32f8d..015a170fd 100644 --- a/instrumentation/action_pack/CHANGELOG.md +++ b/instrumentation/action_pack/CHANGELOG.md @@ -1,5 +1,19 @@ # Release History: opentelemetry-instrumentation-action_pack +### v0.9.0 / 2024-01-09 + +* BREAKING CHANGE: Use ActiveSupport instead of patches #703 + +### v0.8.0 / 2023-11-22 + +* BREAKING CHANGE: Drop Rails 6.0 EOL + +* ADDED: Drop Rails 6.0 EOL + +### v0.7.1 / 2023-10-16 + +* FIXED: Add Rails 7.1 compatibility + ### v0.7.0 / 2023-06-05 * ADDED: Use Rack Middleware Helper diff --git a/instrumentation/action_pack/README.md b/instrumentation/action_pack/README.md index 877e0ec4b..8bbcd69bf 100644 --- a/instrumentation/action_pack/README.md +++ b/instrumentation/action_pack/README.md @@ -30,6 +30,25 @@ OpenTelemetry::SDK.configure do |c| end ``` +## Active Support Instrumentation + +Earlier versions of this instrumentation relied on patching custom `dispatch` hooks from Rails's [Action Controller](/~https://github.com/rails/rails/blob/main/actionpack/lib/action_controller/metal.rb#L224) to extract request information. + +This instrumentation now relies on `ActiveSupport::Notifications` and registers a custom Subscriber that listens to relevant events to modify the Rack span. + +See the table below for details of what [Rails Framework Hook Events](https://guides.rubyonrails.org/active_support_instrumentation.html#action-controller) are recorded by this instrumentation: + +| Event Name | Subscribe? | Creates Span? | Notes | +| - | - | - | - | +| `process_action.action_controller` | :white_check_mark: | :x: | It modifies the existing Rack span | + + +### Error Handling for Action Controller + +If an error is triggered by Action Controller (such as a 500 internal server error), Action Pack will typically employ the default `ActionDispatch::PublicExceptions.new(Rails.public_path)` as the `exceptions_app`, as detailed in the [documentation](https://guides.rubyonrails.org/configuring.html#config-exceptions-app). + +The error object will be retained within `payload[:exception_object]`. Additionally, its storage in `request.env['action_dispatch.exception']` is contingent upon the configuration of `action_dispatch.show_exceptions` in Rails. + ## Examples Example usage can be seen in the `./example/trace_demonstration.rb` file [here](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/instrumentation/action_pack/example/trace_demonstration.ru) diff --git a/instrumentation/action_pack/example/trace_demonstration.ru b/instrumentation/action_pack/example/trace_demonstration.ru index 6e9f08c94..0a8130efb 100644 --- a/instrumentation/action_pack/example/trace_demonstration.ru +++ b/instrumentation/action_pack/example/trace_demonstration.ru @@ -23,7 +23,7 @@ require 'action_controller/railtie' class TraceRequestApp < Rails::Application config.root = __dir__ config.hosts << 'example.org' - secrets.secret_key_base = 'secret_key_base' + credentials.secret_key_base = 'secret_key_base' config.eager_load = false config.logger = Logger.new($stdout) Rails.logger = config.logger @@ -39,6 +39,6 @@ Rails.application.initialize! run Rails.application # To run this example run the `rackup` command with this file -# Example: rackup trace_request_demonstration.ru +# Example: rackup trace_demonstration.ru # Navigate to http://localhost:9292/ # Spans for the requests will appear in the console diff --git a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers.rb b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers.rb new file mode 100644 index 000000000..1e902d4e3 --- /dev/null +++ b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'handlers/action_controller' + +module OpenTelemetry + module Instrumentation + module ActionPack + # Module that contains custom event handlers, which are used to generate spans per event + module Handlers + module_function + + def subscribe + return unless Array(@subscriptions).empty? + + config = ActionPack::Instrumentation.instance.config + handlers_by_pattern = { + 'process_action.action_controller' => Handlers::ActionController.new(config) + } + + @subscriptions = handlers_by_pattern.map do |key, handler| + ::ActiveSupport::Notifications.subscribe(key, handler) + end + end + + # Removes Event Handler Subscriptions for Action Controller notifications + # @note this method is not thread-safe and should not be used in a multi-threaded context + def unsubscribe + @subscriptions&.each { |subscriber| ::ActiveSupport::Notifications.unsubscribe(subscriber) } + @subscriptions = nil + end + end + end + end +end diff --git a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb new file mode 100644 index 000000000..d7b79b64d --- /dev/null +++ b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/handlers/action_controller.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActionPack + module Handlers + # Action Controller handler to handle the notification from Active Support + class ActionController + # @param config [Hash] of instrumentation options + def initialize(config) + @config = config + end + + # Invoked by ActiveSupport::Notifications at the start of the instrumentation block + # + # @param _name [String] of the event (unused) + # @param _id [String] of the event (unused) + # @param payload [Hash] the payload passed as a method argument + # @return [Hash] the payload passed as a method argument + def start(_name, _id, payload) + rack_span = OpenTelemetry::Instrumentation::Rack.current_span + + request = payload[:request] + + rack_span.name = "#{payload[:controller]}##{payload[:action]}" unless request.env['action_dispatch.exception'] + + attributes_to_append = { + OpenTelemetry::SemanticConventions::Trace::CODE_NAMESPACE => String(payload[:controller]), + OpenTelemetry::SemanticConventions::Trace::CODE_FUNCTION => String(payload[:action]) + } + + attributes_to_append[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] = request.filtered_path if request.filtered_path != request.fullpath + + rack_span.add_attributes(attributes_to_append) + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end + + # Invoked by ActiveSupport::Notifications at the end of the instrumentation block + # + # @param _name [String] of the event (unused) + # @param _id [String] of the event (unused) + # @param payload [Hash] the payload passed as a method argument + # @return [Hash] the payload passed as a method argument + def finish(_name, _id, payload) + rack_span = OpenTelemetry::Instrumentation::Rack.current_span + rack_span.record_exception(payload[:exception_object]) if payload[:exception_object] + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end + end + end + end + end +end diff --git a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/instrumentation.rb b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/instrumentation.rb index 52252ff24..cad8a14a4 100644 --- a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/instrumentation.rb +++ b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/instrumentation.rb @@ -9,7 +9,7 @@ module Instrumentation module ActionPack # The Instrumentation class contains logic to detect and install the ActionPack instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - MINIMUM_VERSION = Gem::Version.new('6.0.0') + MINIMUM_VERSION = Gem::Version.new('6.1.0') install do |_config| require_railtie @@ -32,11 +32,11 @@ def gem_version end def patch - ::ActionController::Metal.prepend(Patches::ActionController::Metal) + Handlers.subscribe end def require_dependencies - require_relative 'patches/action_controller/metal' + require_relative 'handlers' end def require_railtie diff --git a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/patches/action_controller/metal.rb b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/patches/action_controller/metal.rb deleted file mode 100644 index 6e3b17c9e..000000000 --- a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/patches/action_controller/metal.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - module Instrumentation - module ActionPack - module Patches - module ActionController - # Module to prepend to ActionController::Metal for instrumentation - module Metal - def dispatch(name, request, response) - rack_span = OpenTelemetry::Instrumentation::Rack.current_span - if rack_span.recording? - rack_span.name = "#{self.class.name}##{name}" unless request.env['action_dispatch.exception'] - - attributes_to_append = { - OpenTelemetry::SemanticConventions::Trace::CODE_NAMESPACE => self.class.name, - OpenTelemetry::SemanticConventions::Trace::CODE_FUNCTION => String(name) - } - attributes_to_append[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] = request.filtered_path if request.filtered_path != request.fullpath - rack_span.add_attributes(attributes_to_append) - end - - super(name, request, response) - end - - private - - def instrumentation_config - ActionPack::Instrumentation.instance.config - end - end - end - end - end - end -end diff --git a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/version.rb b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/version.rb index 0f344d159..b746ac414 100644 --- a/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/version.rb +++ b/instrumentation/action_pack/lib/opentelemetry/instrumentation/action_pack/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module ActionPack - VERSION = '0.7.0' + VERSION = '0.9.0' end end end diff --git a/instrumentation/action_pack/opentelemetry-instrumentation-action_pack.gemspec b/instrumentation/action_pack/opentelemetry-instrumentation-action_pack.gemspec index f12531d06..e47b4aaaf 100644 --- a/instrumentation/action_pack/opentelemetry-instrumentation-action_pack.gemspec +++ b/instrumentation/action_pack/opentelemetry-instrumentation-action_pack.gemspec @@ -34,9 +34,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rails', '>= 6' + spec.add_development_dependency 'rails', '>= 6.1' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/patches/action_controller/metal_test.rb b/instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/handlers/action_controller_test.rb similarity index 53% rename from instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/patches/action_controller/metal_test.rb rename to instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/handlers/action_controller_test.rb index fe488d31c..0da17ab15 100644 --- a/instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/patches/action_controller/metal_test.rb +++ b/instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/handlers/action_controller_test.rb @@ -6,19 +6,30 @@ require 'test_helper' -require_relative '../../../../../../lib/opentelemetry/instrumentation/action_pack' -require_relative '../../../../../../lib/opentelemetry/instrumentation/action_pack/patches/action_controller/metal' +require_relative '../../../../../lib/opentelemetry/instrumentation/action_pack' +require_relative '../../../../../lib/opentelemetry/instrumentation/action_pack/handlers' -describe OpenTelemetry::Instrumentation::ActionPack::Patches::ActionController::Metal do +describe OpenTelemetry::Instrumentation::ActionPack::Handlers::ActionController do include Rack::Test::Methods + let(:instrumentation) { OpenTelemetry::Instrumentation::ActionPack::Instrumentation.instance } let(:exporter) { EXPORTER } let(:spans) { exporter.finished_spans } let(:span) { exporter.finished_spans.last } let(:rails_app) { DEFAULT_RAILS_APP } + let(:config) { {} } # Clear captured spans - before { exporter.reset } + before do + OpenTelemetry::Instrumentation::ActionPack::Handlers.unsubscribe + + instrumentation.instance_variable_set(:@config, config) + instrumentation.instance_variable_set(:@installed, false) + + instrumentation.install(config) + + exporter.reset + end it 'sets the span name to the format: ControllerName#action' do get '/ok' @@ -75,18 +86,57 @@ get 'internal_server_error' _(span.name).must_equal 'ExampleController#internal_server_error' + _(span.kind).must_equal :server + _(span.status.ok?).must_equal false + + _(span.instrumentation_library.name).must_equal 'OpenTelemetry::Instrumentation::Rack' + _(span.instrumentation_library.version).must_equal OpenTelemetry::Instrumentation::Rack::VERSION + + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.host']).must_equal 'example.org' + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.target']).must_equal '/internal_server_error' + _(span.attributes['http.status_code']).must_equal 500 + _(span.attributes['http.user_agent']).must_be_nil + _(span.attributes['code.namespace']).must_equal 'ExampleController' + _(span.attributes['code.function']).must_equal 'internal_server_error' end it 'does not set the span name when an exception is raised in middleware' do get '/ok?raise_in_middleware' _(span.name).must_equal 'HTTP GET' + _(span.kind).must_equal :server + _(span.status.ok?).must_equal false + + _(span.instrumentation_library.name).must_equal 'OpenTelemetry::Instrumentation::Rack' + _(span.instrumentation_library.version).must_equal OpenTelemetry::Instrumentation::Rack::VERSION + + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.host']).must_equal 'example.org' + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.target']).must_equal '/ok?raise_in_middleware' + _(span.attributes['http.status_code']).must_equal 500 + _(span.attributes['http.user_agent']).must_be_nil + _(span.attributes['code.namespace']).must_be_nil + _(span.attributes['code.function']).must_be_nil end it 'does not set the span name when the request is redirected in middleware' do get '/ok?redirect_in_middleware' _(span.name).must_equal 'HTTP GET' + _(span.kind).must_equal :server + _(span.status.ok?).must_equal true + + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.host']).must_equal 'example.org' + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.target']).must_equal '/ok?redirect_in_middleware' + _(span.attributes['http.status_code']).must_equal 307 + _(span.attributes['http.user_agent']).must_be_nil + _(span.attributes['code.namespace']).must_be_nil + _(span.attributes['code.function']).must_be_nil end describe 'when the application has exceptions_app configured' do @@ -96,6 +146,20 @@ get 'internal_server_error' _(span.name).must_equal 'ExampleController#internal_server_error' + _(span.kind).must_equal :server + _(span.status.ok?).must_equal false + + _(span.instrumentation_library.name).must_equal 'OpenTelemetry::Instrumentation::Rack' + _(span.instrumentation_library.version).must_equal OpenTelemetry::Instrumentation::Rack::VERSION + + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.host']).must_equal 'example.org' + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.target']).must_equal '/internal_server_error' + _(span.attributes['http.status_code']).must_equal 500 + _(span.attributes['http.user_agent']).must_be_nil + _(span.attributes['code.namespace']).must_equal 'ExceptionsController' + _(span.attributes['code.function']).must_equal 'show' end end diff --git a/instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/handlers_test.rb b/instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/handlers_test.rb new file mode 100644 index 000000000..3d2419b03 --- /dev/null +++ b/instrumentation/action_pack/test/opentelemetry/instrumentation/action_pack/handlers_test.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../lib/opentelemetry/instrumentation/action_pack' + +describe 'OpenTelemetry::Instrumentation::ActionPack::Handlers' do + let(:instrumentation) { OpenTelemetry::Instrumentation::ActionPack::Instrumentation.instance } + let(:config) { {} } + + before do + OpenTelemetry::Instrumentation::ActionPack::Handlers.unsubscribe + instrumentation.instance_variable_set(:@config, config) + instrumentation.instance_variable_set(:@installed, false) + + instrumentation.install(config) + end + + it 'success subscribe the notification' do + subscriptions = OpenTelemetry::Instrumentation::ActionPack::Handlers.instance_variable_get(:@subscriptions) + _(subscriptions.count).must_equal 1 + _(subscriptions[0].pattern).must_equal 'process_action.action_controller' + end + + it 'success unsubscribe the notification' do + OpenTelemetry::Instrumentation::ActionPack::Handlers.unsubscribe + subscriptions = OpenTelemetry::Instrumentation::ActionPack::Handlers.instance_variable_get(:@subscriptions) + _(subscriptions).must_be_nil + end +end diff --git a/instrumentation/action_pack/test/test_helpers/app_config.rb b/instrumentation/action_pack/test/test_helpers/app_config.rb index b62d198de..f00733ad4 100644 --- a/instrumentation/action_pack/test/test_helpers/app_config.rb +++ b/instrumentation/action_pack/test/test_helpers/app_config.rb @@ -28,8 +28,6 @@ def initialize_app(use_exceptions_app: false, remove_rack_tracer_middleware: fal new_app.config.filter_parameters = [:param_to_be_filtered] case Rails.version - when /^6\.0/ - apply_rails_6_0_configs(new_app) when /^6\.1/ apply_rails_6_1_configs(new_app) when /^7\./ @@ -50,9 +48,7 @@ def initialize_app(use_exceptions_app: false, remove_rack_tracer_middleware: fal private def remove_rack_middleware(application) - application.middleware.delete( - OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware - ) + application.middleware.delete(Rack::Events) end def add_exceptions_app(application) @@ -73,13 +69,6 @@ def add_middlewares(application) ) end - def apply_rails_6_0_configs(application) - # Required in Rails 6 - application.config.hosts << 'example.org' - # Creates a lot of deprecation warnings on subsequent app initializations if not explicitly set. - application.config.action_view.finalize_compiled_template_methods = ActionView::Railtie::NULL_OPTION - end - def apply_rails_6_1_configs(application) # Required in Rails 6 application.config.hosts << 'example.org' diff --git a/instrumentation/action_pack/test/test_helpers/middlewares/redirect_middleware.rb b/instrumentation/action_pack/test/test_helpers/middlewares/redirect_middleware.rb index c531fe3dd..da843c704 100644 --- a/instrumentation/action_pack/test/test_helpers/middlewares/redirect_middleware.rb +++ b/instrumentation/action_pack/test/test_helpers/middlewares/redirect_middleware.rb @@ -10,7 +10,7 @@ def initialize(app, _options = {}) end def call(env) - return [307, {}, 'Temporary Redirect'] if should_redirect?(env) + return [307, { 'Location' => '/ok', 'Content-Type' => 'text/plain' }, ['Temporary Redirect']] if should_redirect?(env) @app.call(env) end diff --git a/instrumentation/action_view/Appraisals b/instrumentation/action_view/Appraisals index 03f29edc9..018ce75b2 100644 --- a/instrumentation/action_view/Appraisals +++ b/instrumentation/action_view/Appraisals @@ -4,10 +4,6 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'rails-6.0' do - gem 'rails', '~> 6.0.0' -end - appraise 'rails-6.1' do gem 'rails', '~> 6.1.0' end @@ -15,3 +11,7 @@ end appraise 'rails-7.0' do gem 'rails', '~> 7.0.0' end + +appraise 'rails-7.1' do + gem 'rails', '~> 7.1.0' +end diff --git a/instrumentation/action_view/CHANGELOG.md b/instrumentation/action_view/CHANGELOG.md index 2cdacb9d1..2758fae95 100644 --- a/instrumentation/action_view/CHANGELOG.md +++ b/instrumentation/action_view/CHANGELOG.md @@ -1,5 +1,15 @@ # Release History: opentelemetry-instrumentation-action_view +### v0.7.0 / 2023-11-22 + +* BREAKING CHANGE: Drop Rails 6.0 EOL + +* ADDED: Drop Rails 6.0 EOL + +### v0.6.1 / 2023-10-16 + +* FIXED: Add Rails 7.1 compatibility + ### v0.6.0 / 2023-06-05 * ADDED: Render layout for action view diff --git a/instrumentation/action_view/example/trace_request_demonstration.ru b/instrumentation/action_view/example/trace_request_demonstration.ru index 58c04a8db..21177c292 100644 --- a/instrumentation/action_view/example/trace_request_demonstration.ru +++ b/instrumentation/action_view/example/trace_request_demonstration.ru @@ -26,7 +26,7 @@ require 'action_view/railtie' class TraceRequestApp < Rails::Application config.root = __dir__ config.hosts << 'example.org' - secrets.secret_key_base = 'secret_key_base' + credentials.secret_key_base = 'secret_key_base' config.eager_load = false diff --git a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/instrumentation.rb b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/instrumentation.rb index fe5b74781..9087d4475 100644 --- a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/instrumentation.rb +++ b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/instrumentation.rb @@ -9,7 +9,7 @@ module Instrumentation module ActionView # The Instrumentation class contains logic to detect and install the ActionView instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - MINIMUM_VERSION = Gem::Version.new('6.0.0') + MINIMUM_VERSION = Gem::Version.new('6.1.0') install do |_config| require_dependencies end diff --git a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/version.rb b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/version.rb index 7fdbddebc..76b5b4867 100644 --- a/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/version.rb +++ b/instrumentation/action_view/lib/opentelemetry/instrumentation/action_view/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module ActionView - VERSION = '0.6.0' + VERSION = '0.7.0' end end end diff --git a/instrumentation/action_view/opentelemetry-instrumentation-action_view.gemspec b/instrumentation/action_view/opentelemetry-instrumentation-action_view.gemspec index 26502b714..737fd963c 100644 --- a/instrumentation/action_view/opentelemetry-instrumentation-action_view.gemspec +++ b/instrumentation/action_view/opentelemetry-instrumentation-action_view.gemspec @@ -34,9 +34,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rails', '>= 6' + spec.add_development_dependency 'rails', '>= 6.1' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/active_job/Appraisals b/instrumentation/active_job/Appraisals index 5ebb30636..a25edd334 100644 --- a/instrumentation/active_job/Appraisals +++ b/instrumentation/active_job/Appraisals @@ -4,14 +4,12 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'activejob-6.0' do - gem 'activejob', '~> 6.0.0' +%w[6.1.0 7.0.0 7.1.0].each do |version| + appraise "activejob-#{version}" do + gem 'activejob', "~> #{version}" + end end -appraise 'activejob-6.1' do - gem 'activejob', '~> 6.1.0' -end - -appraise 'activejob-7.0' do - gem 'activejob', '~> 7.0.0' +appraise 'activejob-latest' do + gem 'activejob' end diff --git a/instrumentation/active_job/CHANGELOG.md b/instrumentation/active_job/CHANGELOG.md index fdc4420c9..49d92a237 100644 --- a/instrumentation/active_job/CHANGELOG.md +++ b/instrumentation/active_job/CHANGELOG.md @@ -1,8 +1,30 @@ # Release History: opentelemetry-instrumentation-active_job +### v0.7.2 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.7.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.7.0 / 2023-11-22 + +* BREAKING CHANGE: Drop Rails 6.0 EOL + +* ADDED: Drop Rails 6.0 EOL + +* BREAKING CHANGE: Use ActiveSupport Instrumentation instead of Monkey Patches + +* CHANGED: Use ActiveSupport Instrumentation instead of Money Patches [#677](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/677) + +### v0.6.1 / 2023-10-16 + +* FIXED: Add Rails 7.1 compatibility + ### v0.6.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.5.2 / 2023-08-03 diff --git a/instrumentation/active_job/README.md b/instrumentation/active_job/README.md index 7e0c4ba6d..686247ab9 100644 --- a/instrumentation/active_job/README.md +++ b/instrumentation/active_job/README.md @@ -30,6 +30,64 @@ OpenTelemetry::SDK.configure do |c| end ``` +## Active Support Instrumentation + +Earlier versions of this instrumentation relied on registering custom `around_perform` hooks in order to deal with limitations +in `ActiveSupport::Notifications`, however those patches resulted in error reports and inconsistent behavior when combined with other gems. + +This instrumentation now relies entirely on `ActiveSupport::Notifications` and registers a custom Subscriber that listens to relevant events to report as spans. + +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: + +| Event Name | Creates Span? | Notes | +| - | - | - | +| `enqueue_at.active_job` | :white_check_mark: | Creates an egress span with kind `producer` | +| `enqueue.active_job` | :white_check_mark: | Creates an egress span with kind `producer` | +| `enqueue_retry.active_job` | :white_check_mark: | Creates an `internal` span | +| `perform_start.active_job` | :x: | This is invoked prior to the appropriate ingress point and is therefore ignored | +| `perform.active_job` | :white_check_mark: | Creates an ingress span with kind `consumer` | +| `retry_stopped.active_job` | :white_check_mark: | Creates and `internal` span with an `exception` event | +| `discard.active_job` | :white_check_mark: | Creates and `internal` span with an `exception` event | + +## Semantic Conventions + +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`. + +Internal spans are named using the name of the `ActiveSupport` event that was provided. + +Attributes that are specific to this instrumentation are recorded under `messaging.active_job.*`: + +| Attribute Name | Type | Notes | +| - | - | - | +| `code.namespace` | String | `ActiveJob` class name | +| `messaging.system` | String | Static value set to `active_job` | +| `messaging.destination` | String | Set from `ActiveJob#queue_name` | +| `messaging.message.id` | String | Set from `ActiveJob#job_id` | +| `messaging.active_job.adapter.name` | String | The name of the `ActiveJob` adapter implementation | +| `messaging.active_job.message.priority` | String | Present when set by the client from `ActiveJob#priority` | +| `messaging.active_job.message.provider_job_id` | String | Present if the underlying adapter has backend specific message ids | + +## Differences between ActiveJob versions + +### ActiveJob 6.1 + +`perform.active_job` events do not include timings for `ActiveJob` callbacks therefore time spent in `before` and `after` hooks will be missing + +### ActiveJob 7+ + +`perform.active_job` no longer includes exceptions handled using `rescue_from` in the payload. + +In order to preserve this behavior you will have to update the span yourself, e.g. + +```ruby + rescue_from MyCustomError do |e| + # Custom code to handle the error + span = OpenTelemetry::Instrumentation::ActiveJob.current_span + span.record_exception(e) + span.status = OpenTelemetry::Trace::Status.error('Job failed') + end +``` + ## Examples 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) diff --git a/instrumentation/active_job/example/Gemfile b/instrumentation/active_job/example/Gemfile deleted file mode 100644 index 103826612..000000000 --- a/instrumentation/active_job/example/Gemfile +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -source 'https://rubygems.org' - -gem 'activejob' -gem 'opentelemetry-api' -gem 'opentelemetry-instrumentation-active_job' -gem 'opentelemetry-sdk' diff --git a/instrumentation/active_job/example/active_job.rb b/instrumentation/active_job/example/active_job.rb index 808a17b44..71cd31646 100644 --- a/instrumentation/active_job/example/active_job.rb +++ b/instrumentation/active_job/example/active_job.rb @@ -4,30 +4,115 @@ # # SPDX-License-Identifier: Apache-2.0 +ENV['OTEL_SERVICE_NAME'] ||= 'otel-active-job-demo' require 'rubygems' -require 'bundler/setup' -require 'active_job' +require 'bundler/inline' -Bundler.require +gemfile do + source 'https://rubygems.org' + gem 'activejob', '~> 7.0.0', require: 'active_job' + gem 'opentelemetry-instrumentation-active_job', path: '../' + gem 'opentelemetry-sdk' + gem 'opentelemetry-exporter-otlp' +end +ENV['OTEL_LOG_LEVEL'] ||= 'fatal' ENV['OTEL_TRACES_EXPORTER'] ||= 'console' OpenTelemetry::SDK.configure do |c| c.use 'OpenTelemetry::Instrumentation::ActiveJob' + at_exit { OpenTelemetry.tracer_provider.shutdown } +end + +class FailingJob < ::ActiveJob::Base + queue_as :demo + def perform + raise 'this job failed' + end +end + +class FailingRetryJob < ::ActiveJob::Base + queue_as :demo + + retry_on StandardError, attempts: 2, wait: 0 + def perform + raise 'this job failed' + end end +class RetryJob < ::ActiveJob::Base + queue_as :demo + + retry_on StandardError, attempts: 3, wait: 0 + def perform + if executions < 3 + raise 'this job failed' + else + puts <<~EOS + + -------------------------------------------------- + Done Retrying! + -------------------------------------------------- + + EOS + end + end +end + +class DiscardJob < ::ActiveJob::Base + queue_as :demo + + class DiscardError < StandardError; end + + discard_on DiscardError + + def perform + raise DiscardError, 'this job failed' + end +end + +EXAMPLE_TRACER = OpenTelemetry.tracer_provider.tracer('activejob-example', '1.0') + class TestJob < ::ActiveJob::Base def perform - puts <<~EOS + EXAMPLE_TRACER.in_span("custom span") do + puts <<~EOS + + -------------------------------------------------- + The computer is doing some work, beep beep boop. + -------------------------------------------------- + + EOS + end + end +end + +class DoItNowJob < ::ActiveJob::Base + def perform + $stderr.puts <<~EOS -------------------------------------------------- - The computer is doing some work, beep beep boop. + Called with perform_now! -------------------------------------------------- EOS end end +class BatchJob < ::ActiveJob::Base + def perform + TestJob.perform_later + FailingJob.perform_later + FailingRetryJob.perform_later + RetryJob.perform_later + DiscardJob.perform_later + end +end + ::ActiveJob::Base.queue_adapter = :async -TestJob.perform_later -sleep 0.1 # To ensure we see both spans! +EXAMPLE_TRACER.in_span('run-jobs') do + DoItNowJob.perform_now + BatchJob.perform_later +end + +sleep 5 # allow the job to complete diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job.rb index 259425a9c..b158de4ca 100644 --- a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job.rb +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job.rb @@ -11,6 +11,39 @@ module OpenTelemetry module Instrumentation # Contains the OpenTelemetry instrumentation for the ActiveJob gem module ActiveJob + extend self + + CURRENT_SPAN_KEY = Context.create_key('current-span') + private_constant :CURRENT_SPAN_KEY + + # Returns the current span from the current or provided context + # + # @param [optional Context] context The context to lookup the current + # {Span} from. Defaults to Context.current + def current_span(context = nil) + context ||= Context.current + context.value(CURRENT_SPAN_KEY) || OpenTelemetry::Trace::Span::INVALID + end + + # Returns a context containing the span, derived from the optional parent + # context, or the current context if one was not provided. + # + # @param [optional Context] context The context to use as the parent for + # the returned context + def context_with_span(span, parent_context: Context.current) + parent_context.set_value(CURRENT_SPAN_KEY, span) + end + + # Activates/deactivates the Span within the current Context, which makes the "current span" + # available implicitly. + # + # On exit, the Span that was active before calling this method will be reactivated. + # + # @param [Span] span the span to activate + # @yield [span, context] yields span and a context containing the span to the block. + def with_span(span) + Context.with_value(CURRENT_SPAN_KEY, span) { |c, s| yield s, c } + end end end end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers.rb new file mode 100644 index 000000000..7eafb9425 --- /dev/null +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'mappers/attribute' +require_relative 'handlers/default' +require_relative 'handlers/enqueue' +require_relative 'handlers/perform' + +module OpenTelemetry + module Instrumentation + module ActiveJob + # Module that contains custom event handlers, which are used to generate spans per event + module Handlers + module_function + + # Subscribes Event Handlers to relevant ActiveJob notifications + # + # The following events are recorded as spans: + # - enqueue + # - enqueue_at + # - enqueue_retry + # - perform + # - retry_stopped + # - discard + # + # Ingress and Egress spans (perform, enqueue, enqueue_at) use Messaging semantic conventions for naming the span, + # while internal spans keep their ActiveSupport event name. + # + # @note this method is not thread safe and should not be used in a multi-threaded context + # @note Why no perform_start? + # This event causes much heartache as it is the first in a series of events that is triggered. + # It should not be the ingress span because it does not measure anything. + # /~https://github.com/rails/rails/blob/v6.1.7.6/activejob/lib/active_job/instrumentation.rb#L14 + # /~https://github.com/rails/rails/blob/v7.0.8/activejob/lib/active_job/instrumentation.rb#L19 + def subscribe + return unless Array(@subscriptions).empty? + + mapper = Mappers::Attribute.new + config = ActiveJob::Instrumentation.instance.config + parent_span_provider = OpenTelemetry::Instrumentation::ActiveJob + + # TODO, use delegation instead of inheritance + default_handler = Handlers::Default.new(parent_span_provider, mapper, config) + enqueue_handler = Handlers::Enqueue.new(parent_span_provider, mapper, config) + perform_handler = Handlers::Perform.new(parent_span_provider, mapper, config) + + handlers_by_pattern = { + 'enqueue' => enqueue_handler, + 'enqueue_at' => enqueue_handler, + 'enqueue_retry' => default_handler, + 'perform' => perform_handler, + 'retry_stopped' => default_handler, + 'discard' => default_handler + } + + @subscriptions = handlers_by_pattern.map do |key, handler| + ::ActiveSupport::Notifications.subscribe("#{key}.active_job", handler) + end + end + + # Removes Event Handler Subscriptions for ActiveJob notifications + # @note this method is not thread-safe and should not be used in a multi-threaded context + def unsubscribe + @subscriptions&.each { |subscriber| ActiveSupport::Notifications.unsubscribe(subscriber) } + @subscriptions = nil + end + end + end + end +end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/default.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/default.rb new file mode 100644 index 000000000..692346aeb --- /dev/null +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/default.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActiveJob + module Handlers + # Default handler to create internal spans for events + # This class provides default template methods that derived classes may override to generate spans and register contexts. + class Default + # @param parent_span_provider [Object] provides access to the top most parent span (usually the ingress span) + # @param mapper [Callable] converts ActiveSupport::Notifications payloads to span attributes + # @param config [Hash] of instrumentation options + def initialize(parent_span_provider, mapper, config) + @mapper = mapper + @config = config + @parent_span_provider = parent_span_provider + end + + # Invoked by ActiveSupport::Notifications at the start of the instrumentation block + # It amends the otel context of a Span and Context tokens to the payload + # + # @param name [String] of the Event + # @param id [String] of the event + # @param payload [Hash] containing job run information + # @return [Hash] the payload passed as a method argument + def start(name, id, payload) + payload.merge!(__otel: start_span(name, id, payload)) + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end + + # Creates a span and registers it with the current context + # + # @param name [String] of the Event + # @param id [String] of the event + # @param payload [Hash] containing job run information + # @return [Hash] with the span and generated context tokens + def start_span(name, _id, payload) + span = tracer.start_span(name, attributes: @mapper.call(payload)) + token = OpenTelemetry::Context.attach(OpenTelemetry::Trace.context_with_span(span)) + + { span: span, ctx_token: token } + end + + # Creates a span and registers it with the current context + # + # @param _name [String] of the Event (unused) + # @param _id [String] of the event (unused) + # @param payload [Hash] containing job run information + # @return [Hash] with the span and generated context tokens + def finish(_name, _id, payload) + otel = payload.delete(:__otel) + span = otel&.fetch(:span) + token = otel&.fetch(:ctx_token) + + on_exception((payload[:error] || payload[:exception_object]), span) + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + ensure + finish_span(span, token) + end + + # Finishes the provided spans and also detaches the associated contexts + # + # @param span [OpenTelemetry::Trace::Span] + # @param token [Numeric] to unregister + def finish_span(span, token) + # closes the span after all attributes have been finalized + begin + if span&.recording? + span.status = OpenTelemetry::Trace::Status.ok if span.status.code == OpenTelemetry::Trace::Status::UNSET + span.finish + end + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end + + begin + OpenTelemetry::Context.detach(token) + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end + end + + # Records exceptions on spans and sets Span statuses to `Error` + # + # Handled exceptions are recorded on internal spans related to the event. E.g. `discard` events are recorded on the `discard.active_job` span + # 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 + # Unhandled exceptions bubble up to the ingress span and are recorded there. + # + # @param [Exception] exception to report as a Span Event + # @param [OpenTelemetry::Trace::Span] the currently active span used to record the exception and set the status + def on_exception(exception, span) + return unless exception && span + + span.record_exception(exception) + span.status = + @parent_span_provider.current_span.status = + OpenTelemetry::Trace::Status.error("Unexpected ActiveJob Error #{exception.class.name}") + end + + def tracer + OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance.tracer + end + end + end + end + end +end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/enqueue.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/enqueue.rb new file mode 100644 index 000000000..4b4c55937 --- /dev/null +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/enqueue.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActiveJob + module Handlers + # Handles `enqueue.active_job` and `enqueue_at.active_job` to generate egress spans + class Enqueue < Default + def initialize(...) + super + @span_name_formatter = if @config[:span_naming] == :job_class + ->(job) { "#{job.class.name} publish" } + else + ->(job) { "#{job.queue_name} publish" } + end + end + + # Overrides the `Default#start_span` method to create an egress span + # and registers it with the current context + # + # @param name [String] of the Event + # @param id [String] of the event + # @param payload [Hash] containing job run information + # @return [Hash] with the span and generated context tokens + def start_span(name, _id, payload) + job = payload.fetch(:job) + span = tracer.start_span(@span_name_formatter.call(job), kind: :producer, attributes: @mapper.call(payload)) + OpenTelemetry.propagation.inject(job.__otel_headers) # This must be transmitted over the wire + { span: span, ctx_token: OpenTelemetry::Context.attach(OpenTelemetry::Trace.context_with_span(span)) } + end + end + end + end + end +end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/perform.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/perform.rb new file mode 100644 index 000000000..bbfd2ed27 --- /dev/null +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/handlers/perform.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActiveJob + module Handlers + # Handles perform.active_job to generate ingress spans + class Perform < Default + def initialize(...) + super + @span_name_formatter = if @config[:span_naming] == :job_class + ->(job) { "#{job.class.name} process" } + else + ->(job) { "#{job.queue_name} process" } + end + end + + # Overrides the `Default#start_span` method to create an ingress span + # and registers it with the current context + # + # @param name [String] of the Event + # @param id [String] of the event + # @param payload [Hash] containing job run information + # @return [Hash] with the span and generated context tokens + def start_span(name, _id, payload) + job = payload.fetch(:job) + parent_context = OpenTelemetry.propagation.extract(job.__otel_headers) + + span_name = @span_name_formatter.call(job) + + # TODO: Refactor into a propagation strategy + propagation_style = @config[:propagation_style] + if propagation_style == :child + span = tracer.start_span(span_name, kind: :consumer, attributes: @mapper.call(payload)) + else + span_context = OpenTelemetry::Trace.current_span(parent_context).context + links = [OpenTelemetry::Trace::Link.new(span_context)] if span_context.valid? && propagation_style == :link + span = tracer.start_root_span(span_name, kind: :consumer, attributes: @mapper.call(payload), links: links) + end + + { span: span, ctx_token: attach_consumer_context(span) } + end + + # This method attaches a span to multiple contexts: + # 1. Registers the ingress span as the top level ActiveJob span. + # 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 + # 2. Registers the ingress span as the "active" span, which is the default behavior of the SDK. + # @param span [OpenTelemetry::Trace::Span] the currently active span used to record the exception and set the status + # @return [Numeric] Context token that must be detached when finished + def attach_consumer_context(span) + consumer_context = OpenTelemetry::Trace.context_with_span(span) + internal_context = OpenTelemetry::Instrumentation::ActiveJob.context_with_span(span, parent_context: consumer_context) + + OpenTelemetry::Context.attach(internal_context) + end + end + end + end + end +end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/instrumentation.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/instrumentation.rb index 9178e51ce..d9c6063c5 100644 --- a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/instrumentation.rb +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/instrumentation.rb @@ -9,7 +9,7 @@ module Instrumentation module ActiveJob # The Instrumentation class contains logic to detect and install the ActiveJob instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - MINIMUM_VERSION = Gem::Version.new('6.0.0') + MINIMUM_VERSION = Gem::Version.new('6.1.0') install do |_config| require_dependencies @@ -17,7 +17,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end present do - defined?(::ActiveJob) + defined?(::ActiveJob) && defined?(::ActiveSupport) end compatible do @@ -64,12 +64,13 @@ def gem_version def require_dependencies require_relative 'patches/base' - require_relative 'patches/active_job_callbacks' + require_relative 'handlers' end def patch_activejob - ::ActiveJob::Base.prepend(Patches::Base) - ::ActiveJob::Base.prepend(Patches::ActiveJobCallbacks) + ::ActiveJob::Base.prepend(Patches::Base) unless ::ActiveJob::Base <= Patches::Base + + Handlers.subscribe end end end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/mappers/attribute.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/mappers/attribute.rb new file mode 100644 index 000000000..d62405b46 --- /dev/null +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/mappers/attribute.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module ActiveJob + module Mappers + # Maps ActiveJob Attributes to Semantic Conventions + class Attribute + # Generates a set of attributes to add to a span using + # general and messaging semantic conventions as well as + # using `rails.active_job.*` namespace for custom attributes + # + # @param payload [Hash] of an ActiveSupport::Notifications payload + # @return [Hash] of semantic attributes + def call(payload) + job = payload.fetch(:job) + + otel_attributes = { + 'code.namespace' => job.class.name, + 'messaging.system' => 'active_job', + 'messaging.destination' => job.queue_name, + 'messaging.message.id' => job.job_id, + 'messaging.active_job.adapter.name' => job.class.queue_adapter_name + } + + # Not all adapters generate or provide back end specific ids for messages + otel_attributes['messaging.active_job.message.provider_job_id'] = job.provider_job_id.to_s if job.provider_job_id + # This can be problematic if programs use invalid attribute types like Symbols for priority instead of using Integers. + otel_attributes['messaging.active_job.message.priority'] = job.priority.to_s if job.priority + + otel_attributes.compact! + + otel_attributes + end + end + end + end + end +end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/active_job_callbacks.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/active_job_callbacks.rb deleted file mode 100644 index c3f612599..000000000 --- a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/active_job_callbacks.rb +++ /dev/null @@ -1,96 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - module Instrumentation - module ActiveJob - module Patches - # Module to prepend to ActiveJob::Base for instrumentation. - module ActiveJobCallbacks - def self.prepended(base) - base.class_eval do - around_enqueue do |job, block| - span_kind = job.class.queue_adapter_name == 'inline' ? :client : :producer - span_name = "#{otel_config[:span_naming] == :job_class ? job.class : job.queue_name} publish" - span_attributes = job_attributes(job) - otel_tracer.in_span(span_name, attributes: span_attributes, kind: span_kind) do - OpenTelemetry.propagation.inject(job.metadata) - block.call - end - end - end - end - - def perform_now - span_kind = self.class.queue_adapter_name == 'inline' ? :server : :consumer - span_name = "#{otel_config[:span_naming] == :job_class ? self.class : queue_name} process" - span_attributes = job_attributes(self).merge('messaging.operation' => 'process', 'code.function' => 'perform_now') - executions_count = (executions || 0) + 1 # because we run before the count is incremented in ActiveJob::Execution - - extracted_context = OpenTelemetry.propagation.extract(metadata) - OpenTelemetry::Context.with_current(extracted_context) do - if otel_config[:propagation_style] == :child - otel_tracer.in_span(span_name, attributes: span_attributes, kind: span_kind) do |span| - span.set_attribute('messaging.active_job.executions', executions_count) - super - end - else - span_links = [] - if otel_config[:propagation_style] == :link - span_context = OpenTelemetry::Trace.current_span(extracted_context).context - span_links << OpenTelemetry::Trace::Link.new(span_context) if span_context.valid? - end - - root_span = otel_tracer.start_root_span(span_name, attributes: span_attributes, links: span_links, kind: span_kind) - OpenTelemetry::Trace.with_span(root_span) do |span| - span.set_attribute('messaging.active_job.executions', executions_count) - super - rescue Exception => e # rubocop:disable Lint/RescueException - span.record_exception(e) - span.status = OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{e.class}") - raise e - ensure - root_span.finish - end - end - end - ensure - # We may be in a job system (eg: resque) that forks and kills worker processes often. - # We don't want to lose spans by not flushing any span processors, so we optionally force it here. - OpenTelemetry.tracer_provider.force_flush if otel_config[:force_flush] - end - - private - - def job_attributes(job) - otel_attributes = { - 'code.namespace' => job.class.name, - 'messaging.destination_kind' => 'queue', - 'messaging.system' => job.class.queue_adapter_name, - 'messaging.destination' => job.queue_name, - 'messaging.message_id' => job.job_id, - 'messaging.active_job.provider_job_id' => job.provider_job_id, - 'messaging.active_job.scheduled_at' => job.scheduled_at, - 'messaging.active_job.priority' => job.priority - } - - otel_attributes['net.transport'] = 'inproc' if %w[async inline].include?(job.class.queue_adapter_name) - - otel_attributes.compact - end - - def otel_tracer - ActiveJob::Instrumentation.instance.tracer - end - - def otel_config - ActiveJob::Instrumentation.instance.config - end - end - end - end - end -end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/base.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/base.rb index 42a3626d6..fc61f34aa 100644 --- a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/base.rb +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/patches/base.rb @@ -12,22 +12,33 @@ module Patches module Base def self.prepended(base) base.class_eval do - attr_accessor :metadata + attr_accessor :__otel_headers end end - def initialize(*args) - @metadata = {} + def initialize(...) + @__otel_headers = {} super end - ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true) def serialize - super.merge('metadata' => serialize_arguments(metadata)) + message = super + + begin + message.merge!('__otel_headers' => serialize_arguments(@__otel_headers)) + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end + + message end def deserialize(job_data) - self.metadata = deserialize_arguments(job_data['metadata'] || []).to_h + begin + @__otel_headers = deserialize_arguments(job_data.delete('__otel_headers') || []).to_h + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end super end end diff --git a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/version.rb b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/version.rb index 5d7f6add4..ece2c148c 100644 --- a/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/version.rb +++ b/instrumentation/active_job/lib/opentelemetry/instrumentation/active_job/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module ActiveJob - VERSION = '0.6.0' + VERSION = '0.7.2' end end end diff --git a/instrumentation/active_job/opentelemetry-instrumentation-active_job.gemspec b/instrumentation/active_job/opentelemetry-instrumentation-active_job.gemspec index 2e0ab8892..44ab428fc 100644 --- a/instrumentation/active_job/opentelemetry-instrumentation-active_job.gemspec +++ b/instrumentation/active_job/opentelemetry-instrumentation-active_job.gemspec @@ -28,15 +28,15 @@ Gem::Specification.new do |spec| spec.add_dependency 'opentelemetry-api', '~> 1.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' - spec.add_development_dependency 'activejob', '>= 6.0.0' + spec.add_development_dependency 'activejob', '>= 6.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'pry' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/discard_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/discard_test.rb new file mode 100644 index 000000000..89a32b6c6 --- /dev/null +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/discard_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../../lib/opentelemetry/instrumentation/active_job' + +describe 'OpenTelemetry::Instrumentation::ActiveJob::Handlers::Discard' do + let(:instrumentation) { OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance } + let(:config) { { propagation_style: :link, span_naming: :queue } } + let(:exporter) { EXPORTER } + let(:spans) { exporter.finished_spans } + let(:publish_span) { spans.find { |s| s.name == 'default publish' } } + let(:process_span) { spans.find { |s| s.name == 'default process' } } + let(:discard_span) { spans.find { |s| s.name == 'discard.active_job' } } + + before do + OpenTelemetry::Instrumentation::ActiveJob::Handlers.unsubscribe + instrumentation.instance_variable_set(:@config, config) + instrumentation.instance_variable_set(:@installed, false) + + instrumentation.install(config) + ActiveJob::Base.queue_adapter = :async + ActiveJob::Base.queue_adapter.immediate = true + + exporter.reset + end + + after do + begin + ActiveJob::Base.queue_adapter.shutdown + rescue StandardError + nil + end + ActiveJob::Base.queue_adapter = :inline + end + + describe 'exception handling' do + it 'sets discard span status to error' do + DiscardJob.perform_later + + _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + _(process_span.status.description).must_equal 'Unexpected ActiveJob Error DiscardJob::DiscardError' + + _(discard_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + _(discard_span.status.description).must_equal 'Unexpected ActiveJob Error DiscardJob::DiscardError' + _(discard_span.events.first.name).must_equal 'exception' + _(discard_span.events.first.attributes['exception.type']).must_equal 'DiscardJob::DiscardError' + _(discard_span.events.first.attributes['exception.message']).must_equal 'discard me' + end + end +end diff --git a/instrumentation/active_job/test/instrumentation/active_job/patches/active_job_callbacks_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/perform_test.rb similarity index 50% rename from instrumentation/active_job/test/instrumentation/active_job/patches/active_job_callbacks_test.rb rename to instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/perform_test.rb index f2c6f996c..cf4c9b976 100644 --- a/instrumentation/active_job/test/instrumentation/active_job/patches/active_job_callbacks_test.rb +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/perform_test.rb @@ -6,25 +6,26 @@ require 'test_helper' -require_relative '../../../../lib/opentelemetry/instrumentation/active_job' +require_relative '../../../../../lib/opentelemetry/instrumentation/active_job' -describe OpenTelemetry::Instrumentation::ActiveJob::Patches::ActiveJobCallbacks do +describe OpenTelemetry::Instrumentation::ActiveJob::Handlers::Perform do let(:instrumentation) { OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance } - # Technically these are the defaults. But ActiveJob seems to act oddly if you re-install - # the instrumentation over and over again - so we manipulate instance variables to - # reset between tests, and that means we should set the defaults here. - let(:config) { { propagation_style: :link, force_flush: false, span_naming: :queue } } + let(:config) { { propagation_style: :link, span_naming: :queue } } let(:exporter) { EXPORTER } let(:spans) { exporter.finished_spans } let(:publish_span) { spans.find { |s| s.name == 'default publish' } } let(:process_span) { spans.find { |s| s.name == 'default process' } } before do + OpenTelemetry::Instrumentation::ActiveJob::Handlers.unsubscribe instrumentation.instance_variable_set(:@config, config) - exporter.reset + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(config) ActiveJob::Base.queue_adapter = :async ActiveJob::Base.queue_adapter.immediate = true + + exporter.reset end after do @@ -34,7 +35,6 @@ nil end ActiveJob::Base.queue_adapter = :inline - instrumentation.instance_variable_set(:@config, config) end describe 'perform_later' do @@ -53,37 +53,41 @@ _(publish_span).must_be_nil _(process_span).wont_be_nil _(process_span.attributes['code.namespace']).must_equal('TestJob') - _(process_span.attributes['code.function']).must_equal('perform_now') - end - end - - describe 'compatibility' do - it 'works with positional args' do - _(PositionalOnlyArgsJob.perform_now('arg1')).must_be_nil # Make sure this runs without raising an error - end - - it 'works with keyword args' do - _(KeywordOnlyArgsJob.perform_now(keyword2: :keyword2)).must_be_nil # Make sure this runs without raising an error - end - - it 'works with mixed args' do - _(MixedArgsJob.perform_now('arg1', 'arg2', keyword2: :keyword2)).must_be_nil # Make sure this runs without raising an error end end describe 'exception handling' do it 'sets span status to error' do - _ { ExceptionJob.perform_now }.must_raise StandardError, 'This job raises an exception' + _ { ExceptionJob.perform_later }.must_raise StandardError, 'This job raises an exception' + _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR - _(process_span.status.description).must_equal 'Unhandled exception of type: StandardError' - end + _(process_span.status.description).must_equal 'Unexpected ActiveJob Error StandardError' - it 'records the exception' do - _ { ExceptionJob.perform_now }.must_raise StandardError, 'This job raises an exception' _(process_span.events.first.name).must_equal 'exception' _(process_span.events.first.attributes['exception.type']).must_equal 'StandardError' _(process_span.events.first.attributes['exception.message']).must_equal 'This job raises an exception' end + + it 'captures errors that were handled by rescue_from in versions earlier than Rails 7' do + skip 'rescue_from jobs behave differently in Rails 7 and newer' if ActiveJob.version >= Gem::Version.new('7') + RescueFromJob.perform_later + + _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + _(process_span.status.description).must_equal 'Unexpected ActiveJob Error RescueFromJob::RescueFromError' + + _(process_span.events.first.name).must_equal 'exception' + _(process_span.events.first.attributes['exception.type']).must_equal 'RescueFromJob::RescueFromError' + _(process_span.events.first.attributes['exception.message']).must_equal 'I was handled by rescue_from' + end + + it 'ignores errors that were handled by rescue_from in versions of Rails 7 or newer' do + skip 'rescue_from jobs behave differently in Rails 7 and newer' if ActiveJob.version < Gem::Version.new('7') + RescueFromJob.perform_later + + _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::OK + + _(process_span.events).must_be_nil + end end describe 'span kind' do @@ -97,8 +101,8 @@ TestJob.perform_later - _(publish_span.kind).must_equal(:client) - _(process_span.kind).must_equal(:server) + _(publish_span.kind).must_equal(:producer) + _(process_span.kind).must_equal(:consumer) end it 'sets correct span kinds for all other jobs' do @@ -110,37 +114,12 @@ end describe 'attributes' do - it 'sets the messaging.operation attribute only when processing the job' do - TestJob.perform_later - - _(publish_span.attributes['messaging.operation']).must_be_nil - _(process_span.attributes['messaging.operation']).must_equal('process') - end - - describe 'net.transport' do - it 'is sets correctly for inline jobs' do - TestJob.perform_later - - [publish_span, process_span].each do |span| - _(span.attributes['net.transport']).must_equal('inproc') - end - end - - it 'is set correctly for async jobs' do - TestJob.perform_later - - [publish_span, process_span].each do |span| - _(span.attributes['net.transport']).must_equal('inproc') - end - end - end - - describe 'messaging.active_job.priority' do + describe 'active_job.priority' do it 'is unset for unprioritized jobs' do TestJob.perform_later [publish_span, process_span].each do |span| - _(span.attributes['messaging.active_job.priority']).must_be_nil + _(span.attributes['messaging.active_job.message.priority']).must_be_nil end end @@ -148,97 +127,10 @@ TestJob.set(priority: 1).perform_later [publish_span, process_span].each do |span| - _(span.attributes['messaging.active_job.priority']).must_equal(1) + _(span.attributes['messaging.active_job.message.priority']).must_equal('1') end end end - - describe 'messaging.active_job.scheduled_at' do - it 'is unset for jobs that do not specify a wait time' do - TestJob.perform_later - - [publish_span, process_span].each do |span| - _(span.attributes['messaging.active_job.scheduled_at']).must_be_nil - end - end - - it 'is set correctly for jobs that do wait' do - job = TestJob.set(wait: 0.second).perform_later - - # Only the sending span is a 'scheduled' thing - _(publish_span.attributes['messaging.active_job.scheduled_at']).must_equal(job.scheduled_at) - assert(publish_span.attributes['messaging.active_job.scheduled_at']) - - # The processing span isn't a 'scheduled' thing - _(process_span.attributes['messaging.active_job.scheduled_at']).must_be_nil - end - end - - describe 'messaging.system' do - it 'is set correctly for the inline adapter' do - begin - ActiveJob::Base.queue_adapter.shutdown - rescue StandardError - nil - end - - ActiveJob::Base.queue_adapter = :inline - TestJob.perform_later - - [publish_span, process_span].each do |span| - _(span.attributes['messaging.system']).must_equal('inline') - end - end - - it 'is set correctly for the async adapter' do - TestJob.perform_later - - [publish_span, process_span].each do |span| - _(span.attributes['messaging.system']).must_equal('async') - end - end - end - - describe 'messaging.active_job.executions' do - it 'is 1 for a normal job that does not retry' do - TestJob.perform_now - _(process_span.attributes['messaging.active_job.executions']).must_equal(1) - end - - it 'tracks correctly for jobs that do retry' do - begin - RetryJob.perform_later - rescue StandardError - nil - end - - executions = spans.filter { |s| s.kind == :consumer }.sum { |s| s.attributes['messaging.active_job.executions'] } - _(executions).must_equal(3) # total of 3 runs. The initial and 2 retries. - end - end - - describe 'messaging.active_job.provider_job_id' do - it 'is empty for a job that do not sets provider_job_id' do - TestJob.perform_now - _(process_span.attributes['messaging.active_job.provider_job_id']).must_be_nil - end - - it 'sets the correct value if provider_job_id is provided' do - job = TestJob.perform_later - _(process_span.attributes['messaging.active_job.provider_job_id']).must_equal(job.provider_job_id) - end - end - - it 'generally sets other attributes as expected' do - job = TestJob.perform_later - - [publish_span, process_span].each do |span| - _(span.attributes['code.namespace']).must_equal('TestJob') - _(span.attributes['messaging.destination_kind']).must_equal('queue') - _(span.attributes['messaging.system']).must_equal('async') - _(span.attributes['messaging.message_id']).must_equal(job.job_id) - end - end end describe 'span_naming option' do @@ -258,6 +150,7 @@ it 'names span according to the job class' do TestJob.set(queue: :foo).perform_later + publish_span = exporter.finished_spans.find { |s| s.name == 'TestJob publish' } _(publish_span).wont_be_nil @@ -267,39 +160,6 @@ end end - describe 'force_flush option' do - let(:mock_tracer_provider) do - mock_tracer_provider = Minitest::Mock.new - mock_tracer_provider.expect(:force_flush, true) - - mock_tracer_provider - end - - describe 'false - default' do - it 'does not forcibly flush the tracer' do - OpenTelemetry.stub(:tracer_provider, mock_tracer_provider) do - TestJob.perform_later - end - - # We *do not* actually force flush in this case, so we expect the mock - # to fail validation - we will not actually call the mocked force_flush method. - expect { mock_tracer_provider.verify }.must_raise MockExpectationError - end - end - - describe 'true' do - let(:config) { { propagation_style: :link, force_flush: true, span_naming: :job_class } } - it 'does forcibly flush the tracer' do - OpenTelemetry.stub(:tracer_provider, mock_tracer_provider) do - TestJob.perform_later - end - - # Nothing should raise, the mock should be successful, we should have flushed. - mock_tracer_provider.verify - end - end - end - describe 'propagation_style option' do describe 'link - default' do # The inline job adapter executes the job immediately upon enqueuing it @@ -327,6 +187,7 @@ _(process_span.total_recorded_links).must_equal(1) _(process_span.links[0].span_context.trace_id).must_equal(publish_span.trace_id) _(process_span.links[0].span_context.span_id).must_equal(publish_span.span_id) + _(process_span.attributes['success']).must_equal(true) end end @@ -385,6 +246,7 @@ describe 'active_job callbacks' do it 'makes the tracing context available in before_perform callbacks' do + skip "ActiveJob #{ActiveJob.version} subscribers do not include timing information for callbacks" if ActiveJob.version < Gem::Version.new('7') CallbacksJob.perform_now _(CallbacksJob.context_before).wont_be_nil @@ -392,23 +254,11 @@ end it 'makes the tracing context available in after_perform callbacks' do + skip "ActiveJob #{ActiveJob.version} subscribers do not include timing information for callbacks" if ActiveJob.version < Gem::Version.new('7') CallbacksJob.perform_now _(CallbacksJob.context_after).wont_be_nil _(CallbacksJob.context_after).must_be :valid? end end - - describe 'perform.active_job notifications' do - it 'makes the tracing context available in notifications' do - context = nil - callback = proc { context = OpenTelemetry::Trace.current_span.context } - ActiveSupport::Notifications.subscribed(callback, 'perform.active_job') do - TestJob.perform_now - end - - _(context).wont_be_nil - _(context).must_be :valid? - end - end end diff --git a/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/retry_stopped_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/retry_stopped_test.rb new file mode 100644 index 000000000..5b4eb774c --- /dev/null +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers/retry_stopped_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../../lib/opentelemetry/instrumentation/active_job' + +describe 'OpenTelemetry::Instrumentation::ActiveJob::Handlers::RetryStopped' do + let(:instrumentation) { OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance } + let(:config) { { propagation_style: :link, span_naming: :queue } } + let(:exporter) { EXPORTER } + let(:spans) { exporter.finished_spans } + let(:publish_span) { spans.find { |s| s.name == 'default publish' } } + let(:process_span) { spans.find { |s| s.name == 'default process' } } + let(:retry_span) { spans.find { |s| s.name == 'retry_stopped.active_job' } } + + before do + OpenTelemetry::Instrumentation::ActiveJob::Handlers.unsubscribe + instrumentation.instance_variable_set(:@config, config) + instrumentation.instance_variable_set(:@installed, false) + + instrumentation.install(config) + ActiveJob::Base.queue_adapter = :async + ActiveJob::Base.queue_adapter.immediate = true + + exporter.reset + end + + after do + begin + ActiveJob::Base.queue_adapter.shutdown + rescue StandardError + nil + end + ActiveJob::Base.queue_adapter = :inline + end + + describe 'attributes' do + describe 'active_job.executions' do + it 'records retry errors' do + _ { RetryJob.perform_later }.must_raise StandardError + + _(process_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + _(process_span.status.description).must_equal 'Unexpected ActiveJob Error StandardError' + + _(retry_span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + _(retry_span.status.description).must_equal 'Unexpected ActiveJob Error StandardError' + _(retry_span.events.first.name).must_equal 'exception' + _(retry_span.events.first.attributes['exception.type']).must_equal 'StandardError' + _(retry_span.events.first.attributes['exception.message']).must_equal 'from retry job' + end + end + end +end diff --git a/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers_test.rb new file mode 100644 index 000000000..790ace52c --- /dev/null +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/handlers_test.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../lib/opentelemetry/instrumentation/active_job' + +describe OpenTelemetry::Instrumentation::ActiveJob::Handlers do + let(:instrumentation) { OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance } + let(:config) { { propagation_style: :link, span_naming: :queue } } + let(:exporter) { EXPORTER } + let(:spans) { exporter.finished_spans } + let(:publish_span) { spans.find { |s| s.name == 'default publish' } } + let(:process_span) { spans.find { |s| s.name == 'default process' } } + + before do + OpenTelemetry::Instrumentation::ActiveJob::Handlers.unsubscribe + instrumentation.instance_variable_set(:@config, config) + instrumentation.instance_variable_set(:@installed, false) + + instrumentation.install(config) + ActiveJob::Base.queue_adapter = :async + ActiveJob::Base.queue_adapter.immediate = true + + exporter.reset + end + + after do + begin + ActiveJob::Base.queue_adapter.shutdown + rescue StandardError + nil + end + ActiveJob::Base.queue_adapter = :inline + end + + describe 'compatibility' do + it 'works with positional args' do + _(PositionalOnlyArgsJob.perform_now('arg1')).must_be_nil # Make sure this runs without raising an error + end + + it 'works with keyword args' do + _(KeywordOnlyArgsJob.perform_now(keyword2: :keyword2)).must_be_nil # Make sure this runs without raising an error + end + + it 'works with mixed args' do + _(MixedArgsJob.perform_now('arg1', 'arg2', keyword2: :keyword2)).must_be_nil # Make sure this runs without raising an error + end + end +end diff --git a/instrumentation/active_job/test/instrumentation/active_job/instrumentation_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/instrumentation_test.rb similarity index 93% rename from instrumentation/active_job/test/instrumentation/active_job/instrumentation_test.rb rename to instrumentation/active_job/test/opentelemetry/instrumentation/active_job/instrumentation_test.rb index c1c2381ea..e41b7eb5c 100644 --- a/instrumentation/active_job/test/instrumentation/active_job/instrumentation_test.rb +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/instrumentation_test.rb @@ -6,7 +6,7 @@ require 'test_helper' -require_relative '../../../lib/opentelemetry/instrumentation/active_job' +require_relative '../../../../lib/opentelemetry/instrumentation/active_job' describe OpenTelemetry::Instrumentation::ActiveJob do let(:instrumentation) { OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance } diff --git a/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/mappers/attribute_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/mappers/attribute_test.rb new file mode 100644 index 000000000..060e93f67 --- /dev/null +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/mappers/attribute_test.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../../../lib/opentelemetry/instrumentation/active_job' + +describe OpenTelemetry::Instrumentation::ActiveJob::Mappers::Attribute do + let(:instrumentation) { OpenTelemetry::Instrumentation::ActiveJob::Instrumentation.instance } + let(:config) { { propagation_style: :link, span_naming: :queue } } + let(:exporter) { EXPORTER } + let(:spans) { exporter.finished_spans } + let(:publish_span) { spans.find { |s| s.name == 'default publish' } } + let(:process_span) { spans.find { |s| s.name == 'default process' } } + + before do + OpenTelemetry::Instrumentation::ActiveJob::Handlers.unsubscribe + instrumentation.instance_variable_set(:@config, config) + instrumentation.instance_variable_set(:@installed, false) + + instrumentation.install(config) + ActiveJob::Base.queue_adapter = :async + ActiveJob::Base.queue_adapter.immediate = true + + exporter.reset + end + + after do + begin + ActiveJob::Base.queue_adapter.shutdown + rescue StandardError + nil + end + ActiveJob::Base.queue_adapter = :inline + end + + it 'uses trace semantic conventions and Rails specific attributes' do + job = TestJob.perform_later + + [publish_span, process_span].each do |span| + _(span.attributes['code.namespace']).must_equal('TestJob') + _(span.attributes['messaging.system']).must_equal('active_job') + _(span.attributes['messaging.active_job.adapter.name']).must_equal('async') + _(span.attributes['messaging.destination']).must_equal('default') + _(span.attributes['messaging.message.id']).must_equal(job.job_id) + _(span.attributes['messaging.active_job.message.priority']).must_be_nil + end + + _(process_span.attributes['messaging.active_job.message.provider_job_id']).must_equal(job.provider_job_id) + end + + it 'tracks the job priority' do + TestJob.set(priority: 5).perform_later + + [publish_span, process_span].each do |span| + _(span.attributes['messaging.active_job.message.priority']).must_equal('5') + end + end + + it 'is set correctly for the inline adapter' do + begin + ActiveJob::Base.queue_adapter.shutdown + rescue StandardError + nil + end + + ActiveJob::Base.queue_adapter = :inline + TestJob.perform_later + + [publish_span, process_span].each do |span| + _(span.attributes['messaging.active_job.adapter.name']).must_equal('inline') + end + end +end diff --git a/instrumentation/active_job/test/instrumentation/active_job/patches/base_test.rb b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/patches/base_test.rb similarity index 63% rename from instrumentation/active_job/test/instrumentation/active_job/patches/base_test.rb rename to instrumentation/active_job/test/opentelemetry/instrumentation/active_job/patches/base_test.rb index c22dc0b8a..d8c5cc22e 100644 --- a/instrumentation/active_job/test/instrumentation/active_job/patches/base_test.rb +++ b/instrumentation/active_job/test/opentelemetry/instrumentation/active_job/patches/base_test.rb @@ -6,29 +6,20 @@ require 'test_helper' -require_relative '../../../../lib/opentelemetry/instrumentation/active_job' +require_relative '../../../../../lib/opentelemetry/instrumentation/active_job' describe OpenTelemetry::Instrumentation::ActiveJob::Patches::Base do - describe 'attr_accessor' do - it 'adds a "metadata" accessor' do - job = TestJob.new - - _(job).must_respond_to :metadata - _(job).must_respond_to :metadata= - end - end - describe 'serialization / deserialization' do it 'must handle metadata' do job = TestJob.new - job.metadata = { 'foo' => 'bar' } + job.__otel_headers = { 'foo' => 'bar' } serialized_job = job.serialize - _(serialized_job.keys).must_include 'metadata' + _(serialized_job.keys).must_include '__otel_headers' job = TestJob.new job.deserialize(serialized_job) - _(job.metadata).must_equal('foo' => 'bar') + _(job.__otel_headers).must_equal('foo' => 'bar') end it 'handles jobs queued without instrumentation' do # e.g. during a rolling deployment diff --git a/instrumentation/active_job/test/test_helper.rb b/instrumentation/active_job/test/test_helper.rb index 1af04821d..dd134b0ff 100644 --- a/instrumentation/active_job/test/test_helper.rb +++ b/instrumentation/active_job/test/test_helper.rb @@ -11,7 +11,6 @@ require 'active_job' require 'opentelemetry-instrumentation-active_job' require 'minitest/autorun' -require 'webmock/minitest' class TestJob < ActiveJob::Base def perform; end @@ -21,7 +20,16 @@ class RetryJob < ActiveJob::Base retry_on StandardError, wait: 0, attempts: 2 def perform - raise StandardError + raise StandardError, 'from retry job' + end +end + +class DiscardJob < ActiveJob::Base + class DiscardError < StandardError; end + discard_on DiscardError + + def perform + raise DiscardError, 'discard me' end end @@ -33,7 +41,7 @@ def perform class BaggageJob < ActiveJob::Base def perform - OpenTelemetry::Trace.current_span['success'] = true if OpenTelemetry::Baggage.value('testing_baggage') == 'it_worked' + OpenTelemetry::Trace.current_span['success'] = OpenTelemetry::Baggage.value('testing_baggage') == 'it_worked' end end @@ -68,6 +76,18 @@ def initialize(*) end end +class RescueFromJob < ActiveJob::Base + class RescueFromError < StandardError; end + + rescue_from RescueFromError do + # do nothing + end + + def perform + raise RescueFromError, 'I was handled by rescue_from' + end +end + ActiveJob::Base.queue_adapter = :inline ActiveJob::Base.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym) diff --git a/instrumentation/active_model_serializers/opentelemetry-instrumentation-active_model_serializers.gemspec b/instrumentation/active_model_serializers/opentelemetry-instrumentation-active_model_serializers.gemspec index fa465c7ac..e5c99a191 100644 --- a/instrumentation/active_model_serializers/opentelemetry-instrumentation-active_model_serializers.gemspec +++ b/instrumentation/active_model_serializers/opentelemetry-instrumentation-active_model_serializers.gemspec @@ -35,7 +35,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/active_record/Appraisals b/instrumentation/active_record/Appraisals index 7a6f38c98..36f3effee 100644 --- a/instrumentation/active_record/Appraisals +++ b/instrumentation/active_record/Appraisals @@ -4,10 +4,6 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'activerecord-6.0' do - gem 'activerecord', '~> 6.0.0' -end - appraise 'activerecord-6.1' do gem 'activerecord', '~> 6.1.0' end @@ -15,3 +11,7 @@ end appraise 'activerecord-7.0' do gem 'activerecord', '~> 7.0.0' end + +appraise 'activerecord-7.1' do + gem 'activerecord', '~> 7.1.0' +end diff --git a/instrumentation/active_record/CHANGELOG.md b/instrumentation/active_record/CHANGELOG.md index 1d0d48f84..54f1c74ca 100644 --- a/instrumentation/active_record/CHANGELOG.md +++ b/instrumentation/active_record/CHANGELOG.md @@ -1,5 +1,23 @@ # Release History: opentelemetry-instrumentation-active_record +### v0.7.2 / 2024-04-30 + +* FIXED: Resolve active_record testing issue + +### v0.7.1 / 2024-04-05 + +* FIXED: Instrumentation/active_record: add `:allow_retry` option to `find_by_sql` patch + +### v0.7.0 / 2023-11-22 + +* BREAKING CHANGE: Drop Rails 6.0 EOL + +* ADDED: Drop Rails 6.0 EOL + +### v0.6.3 / 2023-10-16 + +* FIXED: Add Rails 7.1 compatibility + ### v0.6.2 / 2023-08-14 * FIXED: Ensure that transaction name property is used, rather than self diff --git a/instrumentation/active_record/Gemfile b/instrumentation/active_record/Gemfile index 200584109..4475d27bf 100644 --- a/instrumentation/active_record/Gemfile +++ b/instrumentation/active_record/Gemfile @@ -12,5 +12,5 @@ group :test do gem 'byebug' gem 'opentelemetry-instrumentation-base', path: '../base' gem 'pry-byebug' - gem 'sqlite3-ruby' + gem 'sqlite3', '~> 1.4' end diff --git a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/instrumentation.rb b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/instrumentation.rb index 423d6b3f5..e0d319704 100644 --- a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/instrumentation.rb +++ b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/instrumentation.rb @@ -9,8 +9,7 @@ module Instrumentation module ActiveRecord # The Instrumentation class contains logic to detect and install the ActiveRecord instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - MINIMUM_VERSION = Gem::Version.new('6.0.0') - MAX_MAJOR_VERSION = 7 + MINIMUM_VERSION = Gem::Version.new('6.1.0') install do |_config| require_dependencies @@ -22,11 +21,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end compatible do - # We know that releases after MAX_MAJOR_VERSION are unstable so we - # check the major version number of the gem installed to make sure we - # do not install on a pre-release or full release of the latest - # if it exceeds the MAX_MAJOR_VERSION version. - gem_version >= MINIMUM_VERSION && gem_version.segments[0] <= MAX_MAJOR_VERSION + gem_version >= MINIMUM_VERSION end private @@ -56,9 +51,6 @@ def patch end def require_dependencies - # Our patches depend on Ruby 2 Keyword Syntax compatability since it is decorating the existing AR API - # Once we migrate to ActiveSupport Notifications based instrumentation we can remove this require statement. - require 'ruby2_keywords' # rubocop:disable Lint/RedundantRequireStatement require_relative 'patches/querying' require_relative 'patches/persistence' require_relative 'patches/persistence_class_methods' diff --git a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/persistence_insert_class_methods.rb b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/persistence_insert_class_methods.rb index 6354ee0be..8472feafe 100644 --- a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/persistence_insert_class_methods.rb +++ b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/persistence_insert_class_methods.rb @@ -20,37 +20,37 @@ class << base module ClassMethods ruby2_keywords def insert(*args) tracer.in_span("#{self}.insert") do - super(*args) + super end end ruby2_keywords def insert_all(*args) tracer.in_span("#{self}.insert_all") do - super(*args) + super end end ruby2_keywords def insert!(*args) tracer.in_span("#{self}.insert!") do - super(*args) + super end end ruby2_keywords def insert_all!(*args) tracer.in_span("#{self}.insert_all!") do - super(*args) + super end end ruby2_keywords def upsert(*args) tracer.in_span("#{self}.upsert") do - super(*args) + super end end ruby2_keywords def upsert_all(*args) tracer.in_span("#{self}.upsert_all") do - super(*args) + super end end diff --git a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/querying.rb b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/querying.rb index d359c31ff..41280b1f1 100644 --- a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/querying.rb +++ b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/patches/querying.rb @@ -18,7 +18,7 @@ class << base # Contains ActiveRecord::Querying to be patched module ClassMethods - def find_by_sql(sql, binds = [], preparable: nil, &block) + def find_by_sql(...) tracer.in_span("#{self}.find_by_sql") do super end diff --git a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/version.rb b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/version.rb index add97fb06..7d198a293 100644 --- a/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/version.rb +++ b/instrumentation/active_record/lib/opentelemetry/instrumentation/active_record/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module ActiveRecord - VERSION = '0.6.2' + VERSION = '0.7.2' end end end diff --git a/instrumentation/active_record/opentelemetry-instrumentation-active_record.gemspec b/instrumentation/active_record/opentelemetry-instrumentation-active_record.gemspec index b9351875f..1e38841c2 100644 --- a/instrumentation/active_record/opentelemetry-instrumentation-active_record.gemspec +++ b/instrumentation/active_record/opentelemetry-instrumentation-active_record.gemspec @@ -27,16 +27,16 @@ Gem::Specification.new do |spec| spec.add_dependency 'opentelemetry-api', '~> 1.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' - spec.add_dependency 'ruby2_keywords' - spec.add_development_dependency 'activerecord' + spec.add_development_dependency 'activerecord', '>= 6.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/active_record/test/instrumentation/active_record/instrumentation_test.rb b/instrumentation/active_record/test/instrumentation/active_record/instrumentation_test.rb index 546ac39a9..95848825e 100644 --- a/instrumentation/active_record/test/instrumentation/active_record/instrumentation_test.rb +++ b/instrumentation/active_record/test/instrumentation/active_record/instrumentation_test.rb @@ -28,18 +28,6 @@ end end - it 'when a version above the maximum supported gem version is installed' do - ActiveRecord.stub(:version, Gem::Version.new('8.0.0')) do - _(instrumentation.compatible?).must_equal false - end - end - - it 'it treats pre releases as being equivalent to a full release' do - ActiveRecord.stub(:version, Gem::Version.new('8.0.0.alpha')) do - _(instrumentation.compatible?).must_equal false - end - end - it 'when supported gem version installed' do _(instrumentation.compatible?).must_equal true end diff --git a/instrumentation/active_support/Appraisals b/instrumentation/active_support/Appraisals index 9b71bf262..086c10ee9 100644 --- a/instrumentation/active_support/Appraisals +++ b/instrumentation/active_support/Appraisals @@ -4,10 +4,6 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'activesupport-6.0' do - gem 'activesupport', '~> 6.0.0' -end - appraise 'activesupport-6.1' do gem 'activesupport', '~> 6.1.0' end @@ -15,3 +11,7 @@ end appraise 'activesupport-7.0' do gem 'activesupport', '~> 7.0.0' end + +appraise 'activesupport-7.1' do + gem 'activesupport', '~> 7.1.0' +end diff --git a/instrumentation/active_support/CHANGELOG.md b/instrumentation/active_support/CHANGELOG.md index b2ad56d80..7c20d34c6 100644 --- a/instrumentation/active_support/CHANGELOG.md +++ b/instrumentation/active_support/CHANGELOG.md @@ -1,5 +1,37 @@ # Release History: opentelemetry-instrumentation-active_support +### v0.6.0 / 2024-07-02 + +* BREAKING CHANGE: Custom ActiveSupport Span Names + +* ADDED: Custom ActiveSupport Span Names + +### v0.5.3 / 2024-06-20 + +* FIXED: Include span kind in ActiveSupport Instrumentation helper + +### v0.5.2 / 2024-06-20 + +* ADDED: ActiveSupport user specified span kind + +### v0.5.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.5.0 / 2023-11-22 + +* BREAKING CHANGE: Drop Rails 6.0 EOL + +* ADDED: Drop Rails 6.0 EOL + +### v0.4.4 / 2023-10-31 + +* FIXED: Remove call to ActiveSupport::Notifications.notifier#synchronize deprecated in Rails 7.2 + +### v0.4.3 / 2023-10-16 + +* FIXED: Add Rails 7.1 compatibility + ### v0.4.2 / 2023-09-07 FIXED: Reduce Object allocation diff --git a/instrumentation/active_support/README.md b/instrumentation/active_support/README.md index 04235ba73..9cefa2f4f 100644 --- a/instrumentation/active_support/README.md +++ b/instrumentation/active_support/README.md @@ -1,12 +1,15 @@ # OpenTelemetry ActiveSupport Instrumentation + The Active Support instrumentation is a community-maintained instrumentation for the Active Support portion of the [Ruby on Rails][rails-home] web-application framework. ## How do I get started? Install the gem using: -``` +```console + gem install opentelemetry-instrumentation-active_support + ``` Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-active_support` in your `Gemfile`. @@ -17,21 +20,24 @@ To use the instrumentation, call `use` with the name of the instrumentation and to desired ActiveSupport notification: ```ruby + OpenTelemetry::SDK.configure do |c| c.use 'OpenTelemetry::Instrumentation::ActiveSupport' end - tracer = OpenTelemetry.tracer_provider.tracer('my_app_or_gem', '0.1.0') ::OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo') + ``` Alternatively, you can also call `use_all` to install all the available instrumentation. ```ruby + OpenTelemetry::SDK.configure do |c| c.use_all end + ``` ## Examples diff --git a/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/instrumentation.rb b/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/instrumentation.rb index f5d91e2d3..d10c3cbac 100644 --- a/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/instrumentation.rb +++ b/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/instrumentation.rb @@ -9,6 +9,8 @@ module Instrumentation module ActiveSupport # The Instrumentation class contains logic to detect and install the ActiveSupport instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base + MINIMUM_VERSION = Gem::Version.new('6.1.0') + install do |_config| require_dependencies end @@ -17,8 +19,16 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base defined?(::ActiveSupport) end + compatible do + gem_version >= MINIMUM_VERSION + end + private + def gem_version + ::ActiveSupport.version + end + def require_dependencies require_relative 'span_subscriber' end diff --git a/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/span_subscriber.rb b/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/span_subscriber.rb index 72e8f04a9..66f1f96a2 100644 --- a/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/span_subscriber.rb +++ b/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/span_subscriber.rb @@ -8,6 +8,9 @@ module OpenTelemetry module Instrumentation # rubocop:disable Style/Documentation module ActiveSupport + LEGACY_NAME_FORMATTER = ->(name) { name.split('.')[0..1].reverse.join(' ') } + + # rubocop:disable Metrics/ParameterLists # The SpanSubscriber is a special ActiveSupport::Notification subscription # handler which turns notifications into generic spans, taking care to handle # context appropriately. @@ -19,54 +22,64 @@ def self.subscribe( tracer, pattern, notification_payload_transform = nil, - disallowed_notification_payload_keys = [] + disallowed_notification_payload_keys = nil, + kind: nil, + span_name_formatter: nil ) subscriber = OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new( name: pattern, tracer: tracer, notification_payload_transform: notification_payload_transform, - disallowed_notification_payload_keys: disallowed_notification_payload_keys + disallowed_notification_payload_keys: disallowed_notification_payload_keys, + kind: kind, + span_name_formatter: span_name_formatter ) subscriber_object = ::ActiveSupport::Notifications.subscribe(pattern, subscriber) - ::ActiveSupport::Notifications.notifier.synchronize do - subscribers = ::ActiveSupport::Notifications.notifier.instance_variable_get(:@string_subscribers)[pattern] - - if subscribers.nil? - OpenTelemetry.handle_error( - message: 'Unable to move OTEL ActiveSupport Notifications subscriber to the front of the notifications list which may cause incomplete traces.' \ - 'Please report an issue here: ' \ - '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/new?labels=bug&template=bug_report.md&title=ActiveSupport%20Notifications%20subscribers%20list%20is%20nil' - ) - else - subscribers.unshift( - subscribers.delete(subscriber_object) - ) + # this can be removed once we drop support for Rails < 7.2 + # see /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/707 for more context + if ::ActiveSupport::Notifications.notifier.respond_to?(:synchronize) + ::ActiveSupport::Notifications.notifier.synchronize do + subscribers = ::ActiveSupport::Notifications.notifier.instance_variable_get(:@string_subscribers)[pattern] + + if subscribers.nil? + OpenTelemetry.handle_error( + message: 'Unable to move OTEL ActiveSupport Notifications subscriber to the front of the notifications list which may cause incomplete traces.' \ + 'Please report an issue here: ' \ + '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/new?labels=bug&template=bug_report.md&title=ActiveSupport%20Notifications%20subscribers%20list%20is%20nil' + ) + else + subscribers.unshift( + subscribers.delete(subscriber_object) + ) + end end end subscriber_object end + # rubocop:enable Metrics/ParameterLists class SpanSubscriber ALWAYS_VALID_PAYLOAD_TYPES = [TrueClass, FalseClass, String, Numeric, Symbol].freeze - def initialize(name:, tracer:, notification_payload_transform: nil, disallowed_notification_payload_keys: []) - @span_name = name.split('.')[0..1].reverse.join(' ').freeze + # rubocop:disable Metrics/ParameterLists + def initialize(name:, tracer:, notification_payload_transform: nil, disallowed_notification_payload_keys: nil, kind: nil, span_name_formatter: nil) + @span_name = safe_span_name_for(span_name_formatter, name).dup.freeze @tracer = tracer @notification_payload_transform = notification_payload_transform - @disallowed_notification_payload_keys = disallowed_notification_payload_keys + @disallowed_notification_payload_keys = Array(disallowed_notification_payload_keys) + @kind = kind || :internal end + # rubocop:enable Metrics/ParameterLists def start(name, id, payload) - span = @tracer.start_span(@span_name, kind: :internal) + span = @tracer.start_span(@span_name, kind: @kind) token = OpenTelemetry::Context.attach( OpenTelemetry::Trace.context_with_span(span) ) - payload.merge!( - __opentelemetry_span: span, - __opentelemetry_ctx_token: token - ) + payload[:__opentelemetry_span] = span + payload[:__opentelemetry_ctx_token] = token [span, token] end @@ -123,6 +136,16 @@ def sanitized_value(value) value end end + + # Helper method to try an shield the span name formatter from errors + # + # It wraps the user supplied formatter in a rescue block and returns the original name if a StandardError is raised by the formatter + def safe_span_name_for(span_name_formatter, name) + span_name_formatter&.call(name) || name + rescue StandardError => e + OpenTelemetry.handle_error(exception: e, message: 'Error calling span_name_formatter. Using default span name.') + name + end end end end diff --git a/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/version.rb b/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/version.rb index bb7b408cc..4026a632f 100644 --- a/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/version.rb +++ b/instrumentation/active_support/lib/opentelemetry/instrumentation/active_support/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module ActiveSupport - VERSION = '0.4.2' + VERSION = '0.6.0' end end end diff --git a/instrumentation/active_support/opentelemetry-instrumentation-active_support.gemspec b/instrumentation/active_support/opentelemetry-instrumentation-active_support.gemspec index 6b6aaa7d3..30201b7d6 100644 --- a/instrumentation/active_support/opentelemetry-instrumentation-active_support.gemspec +++ b/instrumentation/active_support/opentelemetry-instrumentation-active_support.gemspec @@ -36,9 +36,11 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-byebug' - spec.add_development_dependency 'rails', '>= 6' + spec.add_development_dependency 'rails', '>= 6.1' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rspec-mocks' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/active_support/test/opentelemetry/instrumentation/active_support/span_subscriber_test.rb b/instrumentation/active_support/test/opentelemetry/instrumentation/active_support/span_subscriber_test.rb index b88b27a1a..f42141afb 100644 --- a/instrumentation/active_support/test/opentelemetry/instrumentation/active_support/span_subscriber_test.rb +++ b/instrumentation/active_support/test/opentelemetry/instrumentation/active_support/span_subscriber_test.rb @@ -11,10 +11,13 @@ let(:tracer) { instrumentation.tracer } let(:exporter) { EXPORTER } let(:last_span) { exporter.finished_spans.last } + let(:span_kind) { nil } + let(:notification_name) { 'bar.foo' } let(:subscriber) do OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new( - name: 'bar.foo', - tracer: tracer + name: notification_name, + tracer: tracer, + kind: span_kind ) end @@ -34,7 +37,7 @@ def finish(name, id, payload) it 'memoizes the span name' do span, = subscriber.start('oh.hai', 'abc', {}) - _(span.name).must_equal('foo bar') + _(span.name).must_equal(notification_name) end it 'uses the provided tracer' do @@ -76,6 +79,7 @@ def finish(name, id, payload) ) _(last_span).wont_be_nil + _(last_span.kind).must_equal(:internal) _(last_span.attributes['string']).must_equal('keys_are_present') _(last_span.attributes['numeric_is_fine']).must_equal(1) _(last_span.attributes['boolean_okay?']).must_equal(true) @@ -112,7 +116,7 @@ def finish(name, id, payload) describe 'instrumentation option - disallowed_notification_payload_keys' do let(:subscriber) do OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new( - name: 'bar.foo', + name: notification_name, tracer: tracer, notification_payload_transform: nil, disallowed_notification_payload_keys: [:foo] @@ -150,7 +154,7 @@ def finish(name, id, payload) let(:transformer_proc) { ->(v) { v.transform_values { 'optimus prime' } } } let(:subscriber) do OpenTelemetry::Instrumentation::ActiveSupport::SpanSubscriber.new( - name: 'bar.foo', + name: notification_name, tracer: tracer, notification_payload_transform: transformer_proc, disallowed_notification_payload_keys: [:foo] @@ -182,62 +186,142 @@ def finish(name, id, payload) end end + describe 'given a span kind' do + let(:span_kind) { :client } + + it 'sets the kind on the span' do + span, token = subscriber.start('hai', 'abc', {}) + # We only use the finished attributes - could change in the future, perhaps. + subscriber.finish( + 'hai', + 'abc', + __opentelemetry_span: span, + __opentelemetry_ctx_token: token + ) + + _(last_span).wont_be_nil + _(last_span.kind).must_equal(:client) + end + end + describe 'instrument' do before do - ActiveSupport::Notifications.unsubscribe('bar.foo') + ActiveSupport::Notifications.unsubscribe(notification_name) end it 'does not trace an event by default' do - ActiveSupport::Notifications.subscribe('bar.foo') do + ActiveSupport::Notifications.subscribe(notification_name) do # pass end - ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') _(last_span).must_be_nil end it 'traces an event when a span subscriber is used' do - OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo') - ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name) + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') _(last_span).wont_be_nil - _(last_span.name).must_equal('foo bar') + _(last_span.name).must_equal(notification_name) _(last_span.attributes['extra']).must_equal('context') + _(last_span.kind).must_equal(:internal) + end + + describe 'when using a custom span name formatter' do + describe 'when using the LEGACY_NAME_FORMATTER' do + let(:span_name_formatter) { OpenTelemetry::Instrumentation::ActiveSupport::LEGACY_NAME_FORMATTER } + it 'uses the user supplied formatter' do + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: span_name_formatter) + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') + + _(last_span).wont_be_nil + _(last_span.name).must_equal('foo bar') + _(last_span.attributes['extra']).must_equal('context') + end + end + + describe 'when using a custom formatter' do + let(:span_name_formatter) { ->(name) { "custom.#{name}" } } + + it 'uses the user supplied formatter' do + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: span_name_formatter) + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') + + _(last_span).wont_be_nil + _(last_span.name).must_equal('custom.bar.foo') + _(last_span.attributes['extra']).must_equal('context') + end + end + + describe 'when using a invalid formatter' do + it 'defaults to the notification name' do + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: ->(_) {}) + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') + + _(last_span).wont_be_nil + _(last_span.name).must_equal(notification_name) + _(last_span.attributes['extra']).must_equal('context') + end + end + + describe 'when using a unstable formatter' do + it 'defaults to the notification name' do + allow(OpenTelemetry).to receive(:handle_error).with(exception: RuntimeError, message: String) + + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name, nil, nil, span_name_formatter: ->(_) { raise 'boom' }) + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') + + _(last_span).wont_be_nil + _(last_span.name).must_equal(notification_name) + _(last_span.attributes['extra']).must_equal('context') + end + end end it 'finishes spans even when block subscribers blow up' do - ActiveSupport::Notifications.subscribe('bar.foo') { raise 'boom' } - OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo') + ActiveSupport::Notifications.subscribe(notification_name) { raise 'boom' } + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name) expect do - ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') end.must_raise RuntimeError _(last_span).wont_be_nil - _(last_span.name).must_equal('foo bar') + _(last_span.name).must_equal(notification_name) _(last_span.attributes['extra']).must_equal('context') end it 'finishes spans even when complex subscribers blow up' do - ActiveSupport::Notifications.subscribe('bar.foo', CrashingEndSubscriber.new) - OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo') + ActiveSupport::Notifications.subscribe(notification_name, CrashingEndSubscriber.new) + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name) expect do - ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') end.must_raise RuntimeError _(last_span).wont_be_nil - _(last_span.name).must_equal('foo bar') + _(last_span.name).must_equal(notification_name) _(last_span.attributes['extra']).must_equal('context') end it 'supports unsubscribe' do - obj = OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo') + obj = OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, notification_name) ActiveSupport::Notifications.unsubscribe(obj) - ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + ActiveSupport::Notifications.instrument(notification_name, extra: 'context') _(obj.class).must_equal(ActiveSupport::Notifications::Fanout::Subscribers::Evented) _(last_span).must_be_nil end + + it 'supports setting the span kind' do + OpenTelemetry::Instrumentation::ActiveSupport.subscribe(tracer, 'bar.foo', nil, [], kind: :client) + ActiveSupport::Notifications.instrument('bar.foo', extra: 'context') + + _(last_span).wont_be_nil + _(last_span.name).must_equal('bar.foo') + _(last_span.attributes['extra']).must_equal('context') + _(last_span.kind).must_equal(:client) + end end end diff --git a/instrumentation/active_support/test/test_helper.rb b/instrumentation/active_support/test/test_helper.rb index 011abee7b..2c5c5cce2 100644 --- a/instrumentation/active_support/test/test_helper.rb +++ b/instrumentation/active_support/test/test_helper.rb @@ -12,6 +12,7 @@ require 'opentelemetry-instrumentation-active_support' require 'minitest/autorun' +require 'rspec/mocks/minitest_integration' require 'webmock/minitest' # global opentelemetry-sdk setup: diff --git a/instrumentation/all/CHANGELOG.md b/instrumentation/all/CHANGELOG.md index c81851ebf..540939338 100644 --- a/instrumentation/all/CHANGELOG.md +++ b/instrumentation/all/CHANGELOG.md @@ -1,12 +1,74 @@ # Release History: opentelemetry-instrumentation-all +### v0.62.0 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change +* CHANGED: Update Rails Instrumentation +* CHANGED: Update Grape Instrumentation +* CHANGED: Update Sinatra Instrumentation + +### v0.61.0 / 2024-06-04 + +* ADDED: Add aws lambda to instrumentation-all +* FIXED: Add action_mailer to rails and all + +### v0.60.0 / 2024-02-20 + +* ADDED: Add support gruf 2.19 +* ADDED: Faraday add support for internal spans + +### v0.59.0 / 2024-02-16 + +* BREAKING CHANGE: GraphQL Legacy Tracer perf improvements [#867](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/867). + +### v0.58.0 / 2024-02-15 + +* CHANGED: upgrade mysql2 instrumentation + +### v0.57.0 / 2024-02-08 + +* BREAKING CHANGE: Move shared sql behavior to helper gems + + +### v0.56.0 / 2024-01-09 + +* BREAKING CHANGE: Use ActiveSupport instead of patches #703 + +### v0.55.0 / 2024-01-06 + +* CHANGED: Upgrade Trilogy and Rack [#796](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/796) + +### v0.54.0 / 2023-11-28 + +* ADDED: Updated excon to include connect spans + +### v0.53.0 / 2023-11-28 + +* CHANGED: Performance optimization cache attribute hashes [#723](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/723) + +### v0.52.0 / 2023-11-21 + +* BREAKING CHANGE: Drop Support for EoL Rails 6.0 [#680](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/680) +* BREAKING CHANGE: Use ActiveSupport Instrumentation instead of Money Patches [#677](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/677) + +* CHANGED: Drop Support for EoL Rails 6.0 [#680](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/680) +* CHANGED: Use ActiveSupport Instrumentation instead of Money Patches [#677](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/677) + +### v0.51.1 / 2023-10-27 + +* ADDED: Instrument connect and ping (Trilogy) + +### v0.51.0 / 2023-10-16 + +* CHANGED: See [#695](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/695) for details + ### v0.50.1 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names (Resque) +* BREAKING CHANGE: Align messaging instrumentation operation names (Resque) ### v0.50.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.40.0 / 2023-08-07 diff --git a/instrumentation/all/Gemfile b/instrumentation/all/Gemfile index 4ef786f4f..554672bec 100644 --- a/instrumentation/all/Gemfile +++ b/instrumentation/all/Gemfile @@ -9,9 +9,15 @@ source 'https://rubygems.org' gemspec group :test do + Dir.entries('../../helpers') + .select { |entry| File.directory?(File.join('../../helpers', entry)) } + .reject { |entry| %w[. ..].include?(entry) } # rubocop:disable Performance/CollectionLiteralInLoop + .sort + .each { |dir| gem "opentelemetry-helpers-#{dir}", path: "../../helpers/#{dir}" } + Dir.entries('../') .select { |entry| File.directory?(File.join('../', entry)) } - .reject { |entry| %w[. .. all].include?(entry) } + .reject { |entry| %w[. .. all].include?(entry) } # rubocop:disable Performance/CollectionLiteralInLoop .sort .each { |dir| gem "opentelemetry-instrumentation-#{dir}", path: "../#{dir}" } end diff --git a/instrumentation/all/lib/opentelemetry/instrumentation/all.rb b/instrumentation/all/lib/opentelemetry/instrumentation/all.rb index 93fde7e84..b53195678 100644 --- a/instrumentation/all/lib/opentelemetry/instrumentation/all.rb +++ b/instrumentation/all/lib/opentelemetry/instrumentation/all.rb @@ -11,7 +11,9 @@ require 'opentelemetry-instrumentation-active_job' require 'opentelemetry-instrumentation-active_record' require 'opentelemetry-instrumentation-action_view' +require 'opentelemetry-instrumentation-action_mailer' require 'opentelemetry-instrumentation-aws_sdk' +require 'opentelemetry-instrumentation-aws_lambda' require 'opentelemetry-instrumentation-bunny' require 'opentelemetry-instrumentation-lmdb' require 'opentelemetry-instrumentation-http' diff --git a/instrumentation/all/lib/opentelemetry/instrumentation/all/version.rb b/instrumentation/all/lib/opentelemetry/instrumentation/all/version.rb index 038f4e9a2..546bdf7e7 100644 --- a/instrumentation/all/lib/opentelemetry/instrumentation/all/version.rb +++ b/instrumentation/all/lib/opentelemetry/instrumentation/all/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module All - VERSION = '0.50.1' + VERSION = '0.62.0' end end end diff --git a/instrumentation/all/opentelemetry-instrumentation-all.gemspec b/instrumentation/all/opentelemetry-instrumentation-all.gemspec index 0bb1151ac..db7effd29 100644 --- a/instrumentation/all/opentelemetry-instrumentation-all.gemspec +++ b/instrumentation/all/opentelemetry-instrumentation-all.gemspec @@ -27,29 +27,30 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1' + spec.add_dependency 'opentelemetry-instrumentation-aws_lambda', '~> 0.1.0' spec.add_dependency 'opentelemetry-instrumentation-aws_sdk', '~> 0.5.0' spec.add_dependency 'opentelemetry-instrumentation-bunny', '~> 0.21.0' spec.add_dependency 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.1' - spec.add_dependency 'opentelemetry-instrumentation-dalli', '~> 0.24.1' - spec.add_dependency 'opentelemetry-instrumentation-delayed_job', '~> 0.21.0' + spec.add_dependency 'opentelemetry-instrumentation-dalli', '~> 0.25.0' + spec.add_dependency 'opentelemetry-instrumentation-delayed_job', '~> 0.22.0' spec.add_dependency 'opentelemetry-instrumentation-ethon', '~> 0.21.1' - spec.add_dependency 'opentelemetry-instrumentation-excon', '~> 0.21.1' - spec.add_dependency 'opentelemetry-instrumentation-faraday', '~> 0.23.1' - spec.add_dependency 'opentelemetry-instrumentation-grape', '~> 0.1.3' - spec.add_dependency 'opentelemetry-instrumentation-graphql', '~> 0.26.2' - spec.add_dependency 'opentelemetry-instrumentation-gruf', '~> 0.1.0' + spec.add_dependency 'opentelemetry-instrumentation-excon', '~> 0.22.0' + spec.add_dependency 'opentelemetry-instrumentation-faraday', '~> 0.24.0' + spec.add_dependency 'opentelemetry-instrumentation-grape', '~> 0.2.0' + spec.add_dependency 'opentelemetry-instrumentation-graphql', '~> 0.28.0' + spec.add_dependency 'opentelemetry-instrumentation-gruf', '~> 0.2.0' spec.add_dependency 'opentelemetry-instrumentation-http', '~> 0.23.1' spec.add_dependency 'opentelemetry-instrumentation-http_client', '~> 0.22.1' spec.add_dependency 'opentelemetry-instrumentation-koala', '~> 0.20.1' spec.add_dependency 'opentelemetry-instrumentation-lmdb', '~> 0.22.1' spec.add_dependency 'opentelemetry-instrumentation-mongo', '~> 0.22.1' - spec.add_dependency 'opentelemetry-instrumentation-mysql2', '~> 0.24.2' + spec.add_dependency 'opentelemetry-instrumentation-mysql2', '~> 0.27.0' spec.add_dependency 'opentelemetry-instrumentation-net_http', '~> 0.22.1' - spec.add_dependency 'opentelemetry-instrumentation-pg', '~> 0.25.2' - spec.add_dependency 'opentelemetry-instrumentation-que', '~> 0.7.0' + spec.add_dependency 'opentelemetry-instrumentation-pg', '~> 0.27.0' + spec.add_dependency 'opentelemetry-instrumentation-que', '~> 0.8.0' spec.add_dependency 'opentelemetry-instrumentation-racecar', '~> 0.3.0' - spec.add_dependency 'opentelemetry-instrumentation-rack', '~> 0.23.1' - spec.add_dependency 'opentelemetry-instrumentation-rails', '~> 0.28.0' + spec.add_dependency 'opentelemetry-instrumentation-rack', '~> 0.24.0' + spec.add_dependency 'opentelemetry-instrumentation-rails', '~> 0.31.0' spec.add_dependency 'opentelemetry-instrumentation-rake', '~> 0.2.1' spec.add_dependency 'opentelemetry-instrumentation-rdkafka', '~> 0.4.0' spec.add_dependency 'opentelemetry-instrumentation-redis', '~> 0.25.1' @@ -57,15 +58,16 @@ Gem::Specification.new do |spec| spec.add_dependency 'opentelemetry-instrumentation-restclient', '~> 0.22.1' spec.add_dependency 'opentelemetry-instrumentation-ruby_kafka', '~> 0.21.0' spec.add_dependency 'opentelemetry-instrumentation-sidekiq', '~> 0.25.0' - spec.add_dependency 'opentelemetry-instrumentation-sinatra', '~> 0.23.1' - spec.add_dependency 'opentelemetry-instrumentation-trilogy', '~> 0.56.1' + spec.add_dependency 'opentelemetry-instrumentation-sinatra', '~> 0.24.0' + spec.add_dependency 'opentelemetry-instrumentation-trilogy', '~> 0.59.0' spec.add_development_dependency 'active_model_serializers' spec.add_development_dependency 'activesupport' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/aws_lambda/.rubocop.yml b/instrumentation/aws_lambda/.rubocop.yml new file mode 100644 index 000000000..1248a2f82 --- /dev/null +++ b/instrumentation/aws_lambda/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: ../../.rubocop.yml diff --git a/instrumentation/aws_lambda/.yardopts b/instrumentation/aws_lambda/.yardopts new file mode 100644 index 000000000..7bd7686dc --- /dev/null +++ b/instrumentation/aws_lambda/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry AWS Lambda Instrumentation +--markup=markdown +--main=README.md +./lib/opentelemetry/instrumentation/**/*.rb +./lib/opentelemetry/instrumentation.rb +- +README.md +CHANGELOG.md diff --git a/instrumentation/aws_lambda/CHANGELOG.md b/instrumentation/aws_lambda/CHANGELOG.md new file mode 100644 index 000000000..9e8d73ce0 --- /dev/null +++ b/instrumentation/aws_lambda/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History: opentelemetry-instrumentation-aws_lambda + +### v0.1.0 / 2024-05-11 + +Initial release. diff --git a/instrumentation/aws_lambda/Gemfile b/instrumentation/aws_lambda/Gemfile new file mode 100644 index 000000000..a03fdb17e --- /dev/null +++ b/instrumentation/aws_lambda/Gemfile @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +source 'https://rubygems.org' + +gemspec + +group :test do + gem 'opentelemetry-instrumentation-base', path: '../base' + gem 'webrick', '~> 1.7' +end diff --git a/instrumentation/aws_lambda/LICENSE b/instrumentation/aws_lambda/LICENSE new file mode 100644 index 000000000..1ef7dad2c --- /dev/null +++ b/instrumentation/aws_lambda/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/instrumentation/aws_lambda/README.md b/instrumentation/aws_lambda/README.md new file mode 100644 index 000000000..90dc918c9 --- /dev/null +++ b/instrumentation/aws_lambda/README.md @@ -0,0 +1,58 @@ +# OpenTelemetry AWS-Lambda Instrumentation + +The OpenTelemetry `aws-lambda` gem is a community-maintained instrumentation for [AWS Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/ruby-handler.html). + +## How do I get started? + +Installation of the `opentelemetry-instrumentation-aws_lambda` gem is handled by the [OpenTelemetry Lambda Layer for Ruby](/~https://github.com/open-telemetry/opentelemetry-lambda/tree/main/ruby). + +We do not advise installing the `opentelemetry-instrumentation-aws_lambda` gem directly into your Ruby lambda. Instead, clone the [OpenTelemetry Lambda Layer for Ruby](/~https://github.com/open-telemetry/opentelemetry-lambda/tree/main/ruby) and build the layer locally. Then, save it in your AWS account. + +## Usage + +From the Lambda Layer side, create the wrapper. More information can be found at /~https://github.com/open-telemetry/opentelemetry-lambda/tree/main/ruby + +Below is an example of `ruby/src/layer/wrapper.rb`, where you can configure the layer to suit your needs before building it: +```ruby +require 'opentelemetry/sdk' +require 'opentelemetry/instrumentation/aws_lambda' +OpenTelemetry::SDK.configure do |c| + c.service_name = '' + c.use 'OpenTelemetry::Instrumentation::AwsLambda' +end + +def otel_wrapper(event:, context:) + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new() + otel_wrapper.call_wrapped(event: event, context: context) +end +``` + +## Example + +To run the example: + +1. `cd` to the examples directory and install gems + * `cd example` + * `bundle install` +2. Run the sample client script + * `ruby trace_demonstration.rb` + +This will run SNS publish command, printing OpenTelemetry traces to the console as it goes. + +## How can I get involved? + +The `opentelemetry-instrumentation-aws_lambda` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us in [GitHub Discussions][discussions-url] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +Apache 2.0 license. See [LICENSE][license-github] for more information. + +[aws-sdk-home]: /~https://github.com/aws/aws-sdk-ruby +[bundler-home]: https://bundler.io +[repo-github]: /~https://github.com/open-telemetry/opentelemetry-ruby +[license-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/LICENSE +[ruby-sig]: /~https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: /~https://github.com/open-telemetry/community#community-meetings +[discussions-url]: /~https://github.com/open-telemetry/opentelemetry-ruby/discussions diff --git a/instrumentation/aws_lambda/Rakefile b/instrumentation/aws_lambda/Rakefile new file mode 100644 index 000000000..4b0e9b5a8 --- /dev/null +++ b/instrumentation/aws_lambda/Rakefile @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] + t.warning = false +end + +YARD::Rake::YardocTask.new do |t| + t.stats_options = ['--list-undoc'] +end + +if RUBY_ENGINE == 'truffleruby' + task default: %i[test] +else + task default: %i[test rubocop yard] +end diff --git a/instrumentation/aws_lambda/example/Gemfile b/instrumentation/aws_lambda/example/Gemfile new file mode 100644 index 000000000..934ac342f --- /dev/null +++ b/instrumentation/aws_lambda/example/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'opentelemetry-instrumentation-aws_lambda', path: '../' +gem 'opentelemetry-sdk' diff --git a/instrumentation/aws_lambda/example/sample.rb b/instrumentation/aws_lambda/example/sample.rb new file mode 100644 index 000000000..7efc9561a --- /dev/null +++ b/instrumentation/aws_lambda/example/sample.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +def handler(event:, context:) + puts "Success" +end diff --git a/instrumentation/aws_lambda/example/trace_demonstration.rb b/instrumentation/aws_lambda/example/trace_demonstration.rb new file mode 100755 index 000000000..b135bad5b --- /dev/null +++ b/instrumentation/aws_lambda/example/trace_demonstration.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'rubygems' +require 'bundler/setup' +require_relative './sample' + +Bundler.require + +# Export traces to console by default +ENV['OTEL_TRACES_EXPORTER'] ||= 'console' +ENV['ORIG_HANDLER'] ||= 'sample.handler' + +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::AwsLambda' +end + +class MockLambdaContext + attr_reader :aws_request_id, :invoked_function_arn, :function_name + + def initialize(aws_request_id:, invoked_function_arn:, function_name:) + @aws_request_id = aws_request_id + @invoked_function_arn = invoked_function_arn + @function_name = function_name + end +end + +# To accommendate the test case, handler class doesn't need to require the sample file if it's required here +# In lambda environment, the env will find the handler file. +module OpenTelemetry + module Instrumentation + module AwsLambda + class Handler + def resolve_original_handler + original_handler = ENV['ORIG_HANDLER'] || ENV['_HANDLER'] || '' + original_handler_parts = original_handler.split('.') + if original_handler_parts.size == 2 + handler_file, @handler_method = original_handler_parts + elsif original_handler_parts.size == 3 + handler_file, @handler_class, @handler_method = original_handler_parts + else + OpenTelemetry.logger.warn("aws-lambda instrumentation: Invalid handler #{original_handler}, must be of form FILENAME.METHOD or FILENAME.CLASS.METHOD.") + end + + # require handler_file #-> don't require file for this sample test + + original_handler + end + end + end + end +end + +def otel_wrapper(event:, context:) + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new() + otel_wrapper.call_wrapped(event: event, context: context) +end + +# sample event obtained from sample test +event = { + "body" => nil, + "headers" => { + "Accept" => "*/*", + "Host" => "127.0.0.1:3000", + "User-Agent" => "curl/8.1.2", + "X-Forwarded-Port" => 3000, + "X-Forwarded-Proto" => "http" + }, + "httpMethod" => "GET", + "isBase64Encoded" => false, + "multiValueHeaders" => {}, + "multiValueQueryStringParameters" => nil, + "path" => "/", + "pathParameters" => nil, + "queryStringParameters" => nil, + "requestContext" => { + "accountId" => 123456789012, + "apiId" => 1234567890, + "domainName" => "127.0.0.1:3000", + "extendedRequestId" => nil, + "httpMethod" => "GET", + "identity" => {}, + "path" => "/", + "protocol" => "HTTP/1.1", + "requestId" => "db7f8e7a-4cc5-4f6d-987b-713d0d9052c3", + "requestTime" => "08/Nov/2023:19:09:59 +0000", + "requestTimeEpoch" => 1699470599, + "resourceId" => "123456", + "resourcePath" => "/", + "stage" => "api" + }, + "resource" => "/", + "stageVariables" => nil, + "version" => "1.0" +} + +context = MockLambdaContext.new(aws_request_id: "aws_request_id",invoked_function_arn: "invoked_function_arn",function_name: "function") + +otel_wrapper(event: event, context: context) # you should see Success before the trace diff --git a/instrumentation/aws_lambda/lib/opentelemetry-instrumentation-aws_lambda.rb b/instrumentation/aws_lambda/lib/opentelemetry-instrumentation-aws_lambda.rb new file mode 100644 index 000000000..c034f140f --- /dev/null +++ b/instrumentation/aws_lambda/lib/opentelemetry-instrumentation-aws_lambda.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'opentelemetry/instrumentation' diff --git a/instrumentation/aws_lambda/lib/opentelemetry/instrumentation.rb b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation.rb new file mode 100644 index 000000000..8dd0f5127 --- /dev/null +++ b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# OpenTelemetry is an open source observability framework, providing a +# general-purpose API, SDK, and related tools required for the instrumentation +# of cloud-native software, frameworks, and libraries. +# +# The OpenTelemetry module provides global accessors for telemetry objects. +# See the documentation for the `opentelemetry-api` gem for details. +module OpenTelemetry + # "Instrumentation" are specified by + # /~https://github.com/open-telemetry/opentelemetry-specification/blob/784635d01d8690c8f5fcd1f55bdbc8a13cf2f4f2/specification/glossary.md#instrumentation-library + # + # Instrumentation should be able to handle the case when the library is not installed on a user's system. + module Instrumentation + end +end + +require_relative 'instrumentation/aws_lambda' diff --git a/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda.rb b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda.rb new file mode 100644 index 000000000..fb8d41885 --- /dev/null +++ b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry' +require 'opentelemetry-instrumentation-base' + +module OpenTelemetry + module Instrumentation + # Contains the OpenTelemetry instrumentation for the aws_lambda gem + module AwsLambda + end + end +end + +require_relative 'aws_lambda/instrumentation' +require_relative 'aws_lambda/version' diff --git a/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb new file mode 100644 index 000000000..7c9a32c18 --- /dev/null +++ b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/handler.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module AwsLambda + AWS_TRIGGERS = ['aws:sqs', 'aws:s3', 'aws:sns', 'aws:dynamodb'].freeze + + # Handler class that creates a span around the _HANDLER + class Handler + attr_reader :handler_method, :handler_class + + # anytime when the code in a Lambda function is updated or the functional configuration is changed, + # the next invocation results in a cold start; therefore these instance variables will be up-to-date + def initialize + @flush_timeout = ENV.fetch('OTEL_INSTRUMENTATION_AWS_LAMBDA_FLUSH_TIMEOUT', '30000').to_i + @original_handler = ENV['ORIG_HANDLER'] || ENV['_HANDLER'] || '' + @handler_class = nil + @handler_method = nil + @handler_file = nil + + resolve_original_handler + end + + # Try to record and re-raise any exception from the wrapped function handler + # Instrumentation should never raise its own exception + def call_wrapped(event:, context:) + parent_context = extract_parent_context(event) + + span_kind = nil + span_kind = if event['Records'] && AWS_TRIGGERS.include?(event['Records'].dig(0, 'eventSource')) + :consumer + else + :server + end + + original_handler_error = nil + original_response = nil + OpenTelemetry::Context.with_current(parent_context) do + span_attributes = otel_attributes(event, context) + span = tracer.start_span( + @original_handler, + attributes: span_attributes, + kind: span_kind + ) + + begin + response = call_original_handler(event: event, context: context) + status_code = response['statusCode'] || response[:statusCode] if response.is_a?(Hash) + span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, status_code) if status_code + rescue StandardError => e + original_handler_error = e + ensure + original_response = response + end + rescue StandardError => e + OpenTelemetry.logger.error("aws-lambda instrumentation #{e.class}: #{e.message}") + ensure + if original_handler_error + span&.record_exception(original_handler_error) + span&.status = OpenTelemetry::Trace::Status.error(original_handler_error.message) + end + span&.finish + OpenTelemetry.tracer_provider.force_flush(timeout: @flush_timeout) + end + + raise original_handler_error if original_handler_error + + original_response + end + + def instrumentation_config + AwsLambda::Instrumentation.instance.config + end + + def tracer + AwsLambda::Instrumentation.instance.tracer + end + + private + + # we don't expose error if our code cause issue that block user's code + def resolve_original_handler + original_handler_parts = @original_handler.split('.') + if original_handler_parts.size == 2 + @handler_file, @handler_method = original_handler_parts + elsif original_handler_parts.size == 3 + @handler_file, @handler_class, @handler_method = original_handler_parts + else + OpenTelemetry.logger.error("aws-lambda instrumentation: Invalid handler #{original_handler}, must be of form FILENAME.METHOD or FILENAME.CLASS.METHOD.") + end + + require @handler_file if @handler_file + end + + def call_original_handler(event:, context:) + if @handler_class + Kernel.const_get(@handler_class).send(@handler_method, event: event, context: context) + else + __send__(@handler_method, event: event, context: context) + end + end + + # Extract parent context from request headers + # Downcase Traceparent and Tracestate because TraceContext::TextMapPropagator's TRACEPARENT_KEY and TRACESTATE_KEY are all lowercase + # If any error occur, rescue and give empty context + def extract_parent_context(event) + headers = event['headers'] || {} + headers.transform_keys! do |key| + %w[Traceparent Tracestate].include?(key) ? key.downcase : key + end + + OpenTelemetry.propagation.extract( + headers, + getter: OpenTelemetry::Context::Propagation.text_map_getter + ) + rescue StandardError => e + OpenTelemetry.logger.error("aws-lambda instrumentation exception occurred while extracting the parent context: #{e.message}") + OpenTelemetry::Context.empty + end + + # lambda event version 1.0 and version 2.0 + # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + def v1_proxy_attributes(event) + attributes = { + OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => event['httpMethod'], + OpenTelemetry::SemanticConventions::Trace::HTTP_ROUTE => event['resource'], + OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => event['resource'] + } + attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] += "?#{event['queryStringParameters']}" if event['queryStringParameters'] + + headers = event['headers'] + if headers + attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_USER_AGENT] = headers['User-Agent'] + attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME] = headers['X-Forwarded-Proto'] + attributes[OpenTelemetry::SemanticConventions::Trace::NET_HOST_NAME] = headers['Host'] + end + attributes + end + + def v2_proxy_attributes(event) + request_context = event['requestContext'] + attributes = { + OpenTelemetry::SemanticConventions::Trace::NET_HOST_NAME => request_context['domainName'], + OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => request_context['http']['method'], + OpenTelemetry::SemanticConventions::Trace::HTTP_USER_AGENT => request_context['http']['userAgent'], + OpenTelemetry::SemanticConventions::Trace::HTTP_ROUTE => request_context['http']['path'], + OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => request_context['http']['path'] + } + attributes[OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET] += "?#{event['rawQueryString']}" if event['rawQueryString'] + attributes + end + + # fass.trigger set to http: /~https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#api-gateway + # TODO: need to update Semantic Conventions for invocation_id, trigger and resource_id + def otel_attributes(event, context) + span_attributes = {} + span_attributes['faas.invocation_id'] = context.aws_request_id + span_attributes['cloud.resource_id'] = context.invoked_function_arn + span_attributes[OpenTelemetry::SemanticConventions::Trace::AWS_LAMBDA_INVOKED_ARN] = context.invoked_function_arn + span_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID] = context.invoked_function_arn.split(':')[4] + + if event['requestContext'] + request_attributes = event['version'] == '2.0' ? v2_proxy_attributes(event) : v1_proxy_attributes(event) + request_attributes[OpenTelemetry::SemanticConventions::Trace::FAAS_TRIGGER] = 'http' + span_attributes.merge!(request_attributes) + end + + if event['Records'] + trigger_attributes = trigger_type_attributes(event) + span_attributes.merge!(trigger_attributes) + end + + span_attributes + rescue StandardError => e + OpenTelemetry.logger.error("aws-lambda instrumentation exception occurred while preparing span attributes: #{e.message}") + {} + end + + # sqs spec for lambda: /~https://github.com/open-telemetry/semantic-conventions/blob/main/docs/faas/aws-lambda.md#sqs + # current there is no spec for 'aws:sns', 'aws:s3' and 'aws:dynamodb' + def trigger_type_attributes(event) + attributes = {} + case event['Records'].dig(0, 'eventSource') + when 'aws:sqs' + attributes[OpenTelemetry::SemanticConventions::Trace::FAAS_TRIGGER] = 'pubsub' + attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_OPERATION] = 'process' + attributes[OpenTelemetry::SemanticConventions::Trace::MESSAGING_SYSTEM] = 'AmazonSQS' + end + attributes + end + end + end + end +end diff --git a/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/instrumentation.rb b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/instrumentation.rb new file mode 100644 index 000000000..08566cac8 --- /dev/null +++ b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/instrumentation.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module AwsLambda + # Instrumentation class that detects and installs the AwsLambda instrumentation + class Instrumentation < OpenTelemetry::Instrumentation::Base + install do |_config| + require_dependencies + end + + # determine if current environment is lambda by checking _HANLDER or ORIG_HANDLER + present do + ENV.key?('_HANDLER') || ENV.key?('ORIG_HANDLER') + end + + private + + def require_dependencies + require_relative 'handler' + end + end + end + end +end diff --git a/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/version.rb b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/version.rb new file mode 100644 index 000000000..6723022fd --- /dev/null +++ b/instrumentation/aws_lambda/lib/opentelemetry/instrumentation/aws_lambda/version.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module AwsLambda + VERSION = '0.1.0' + end + end +end diff --git a/instrumentation/aws_lambda/opentelemetry-instrumentation-aws_lambda.gemspec b/instrumentation/aws_lambda/opentelemetry-instrumentation-aws_lambda.gemspec new file mode 100644 index 000000000..dedd36693 --- /dev/null +++ b/instrumentation/aws_lambda/opentelemetry-instrumentation-aws_lambda.gemspec @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'opentelemetry/instrumentation/aws_lambda/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-instrumentation-aws_lambda' + spec.version = OpenTelemetry::Instrumentation::AwsLambda::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'AWS Lambda instrumentation for the OpenTelemetry framework' + spec.description = 'AWS Lambda instrumentation for the OpenTelemetry framework' + spec.homepage = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib' + spec.license = 'Apache-2.0' + + spec.files = Dir.glob('lib/**/*.rb') + + Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.0' + + spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' + + spec.add_development_dependency 'appraisal', '~> 2.5' + spec.add_development_dependency 'bundler', '~> 2.4' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' + spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' + spec.add_development_dependency 'pry' + spec.add_development_dependency 'pry-byebug' unless RUBY_ENGINE == 'jruby' + spec.add_development_dependency 'rspec-mocks' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.19.1' + spec.add_development_dependency 'simplecov', '~> 0.17.1' + spec.add_development_dependency 'webmock', '~> 3.19' + spec.add_development_dependency 'yard', '~> 0.9' + + if spec.respond_to?(:metadata) + spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md" + spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/aws_lambda' + spec.metadata['bug_tracker_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues' + spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}" + end +end diff --git a/instrumentation/aws_lambda/test/opentelemetry/instrumentation_test.rb b/instrumentation/aws_lambda/test/opentelemetry/instrumentation_test.rb new file mode 100644 index 000000000..e737544f6 --- /dev/null +++ b/instrumentation/aws_lambda/test/opentelemetry/instrumentation_test.rb @@ -0,0 +1,217 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Instrumentation::AwsLambda do + let(:instrumentation) { OpenTelemetry::Instrumentation::AwsLambda::Instrumentation.instance } + let(:exporter) { EXPORTER } + let(:event_v1) { EVENT_V1 } + let(:event_v2) { EVENT_V2 } + let(:event_record) { EVENT_RECORD } + let(:sqs_record) { SQS_RECORD } + let(:context) { CONTEXT } + let(:last_span) { exporter.finished_spans.last } + + it 'has #name' do + _(instrumentation.name).must_equal 'OpenTelemetry::Instrumentation::AwsLambda' + end + + it 'has #version' do + _(instrumentation.version).wont_be_nil + _(instrumentation.version).wont_be_empty + end + + describe '#compatible' do + it 'returns true for supported gem versions' do + _(instrumentation.compatible?).must_equal true + end + end + + describe '#install' do + it 'accepts argument' do + _(instrumentation.install({})).must_equal(true) + instrumentation.instance_variable_set(:@installed, false) + end + end + + describe 'validate_wrapper' do + it 'result should be span' do + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_original_handler, {}) do + otel_wrapper.call_wrapped(event: event_v1, context: context) + _(last_span).must_be_kind_of(OpenTelemetry::SDK::Trace::SpanData) + end + end + + it 'validate_spans' do + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_original_handler, {}) do + otel_wrapper.call_wrapped(event: event_v1, context: context) + + _(last_span.name).must_equal 'sample.test' + _(last_span.kind).must_equal :server + _(last_span.status.code).must_equal 1 + _(last_span.hex_parent_span_id).must_equal '0000000000000000' + + _(last_span.attributes['aws.lambda.invoked_arn']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['faas.invocation_id']).must_equal '41784178-4178-4178-4178-4178417855e' + _(last_span.attributes['faas.trigger']).must_equal 'http' + _(last_span.attributes['cloud.resource_id']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['cloud.account.id']).must_equal 'id' + _(last_span.attributes['http.method']).must_equal 'GET' + _(last_span.attributes['http.route']).must_equal '/' + _(last_span.attributes['http.target']).must_equal '/' + _(last_span.attributes['http.user_agent']).must_equal 'curl/8.1.2' + _(last_span.attributes['http.scheme']).must_equal 'http' + _(last_span.attributes['net.host.name']).must_equal '127.0.0.1:3000' + + _(last_span.instrumentation_scope).must_be_kind_of OpenTelemetry::SDK::InstrumentationScope + _(last_span.instrumentation_scope.name).must_equal 'OpenTelemetry::Instrumentation::AwsLambda' + _(last_span.instrumentation_scope.version).must_equal '0.1.0' + + _(last_span.hex_span_id.size).must_equal 16 + _(last_span.hex_trace_id.size).must_equal 32 + _(last_span.trace_flags.sampled?).must_equal true + _(last_span.tracestate.to_h.to_s).must_equal '{}' + end + end + + it 'validate_spans_with_parent_context' do + event_v1['headers']['Traceparent'] = '00-48b05d64abe4690867685635f72bdbac-ff40ea9699e62af2-01' + event_v1['headers']['Tracestate'] = 'otel=ff40ea9699e62af2-01' + + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_original_handler, {}) do + otel_wrapper.call_wrapped(event: event_v1, context: context) + + _(last_span.name).must_equal 'sample.test' + _(last_span.kind).must_equal :server + + _(last_span.hex_parent_span_id).must_equal 'ff40ea9699e62af2' + _(last_span.hex_span_id.size).must_equal 16 + _(last_span.hex_trace_id.size).must_equal 32 + _(last_span.trace_flags.sampled?).must_equal true + _(last_span.tracestate.to_h.to_s).must_equal '{"otel"=>"ff40ea9699e62af2-01"}' + end + event_v1['headers'].delete('traceparent') + event_v1['headers'].delete('tracestate') + end + + it 'validate_spans_with_v2_events' do + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_original_handler, {}) do + otel_wrapper.call_wrapped(event: event_v2, context: context) + + _(last_span.name).must_equal 'sample.test' + _(last_span.kind).must_equal :server + _(last_span.status.code).must_equal 1 + _(last_span.hex_parent_span_id).must_equal '0000000000000000' + + _(last_span.attributes['aws.lambda.invoked_arn']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['faas.invocation_id']).must_equal '41784178-4178-4178-4178-4178417855e' + _(last_span.attributes['faas.trigger']).must_equal 'http' + _(last_span.attributes['cloud.account.id']).must_equal 'id' + _(last_span.attributes['cloud.resource_id']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['net.host.name']).must_equal 'id.execute-api.us-east-1.amazonaws.com' + _(last_span.attributes['http.method']).must_equal 'POST' + _(last_span.attributes['http.user_agent']).must_equal 'agent' + _(last_span.attributes['http.route']).must_equal '/path/to/resource' + _(last_span.attributes['http.target']).must_equal '/path/to/resource?parameter1=value1¶meter1=value2¶meter2=value' + end + end + + it 'validate_spans_with_records_from_non_gateway_request' do + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_original_handler, {}) do + otel_wrapper.call_wrapped(event: event_record, context: context) + + _(last_span.name).must_equal 'sample.test' + _(last_span.kind).must_equal :consumer + _(last_span.status.code).must_equal 1 + _(last_span.hex_parent_span_id).must_equal '0000000000000000' + + _(last_span.attributes['aws.lambda.invoked_arn']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['faas.invocation_id']).must_equal '41784178-4178-4178-4178-4178417855e' + _(last_span.attributes['cloud.resource_id']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['cloud.account.id']).must_equal 'id' + + assert_nil(last_span.attributes['faas.trigger']) + assert_nil(last_span.attributes['http.method']) + assert_nil(last_span.attributes['http.user_agent']) + assert_nil(last_span.attributes['http.route']) + assert_nil(last_span.attributes['http.target']) + assert_nil(last_span.attributes['net.host.name']) + end + end + + it 'validate_spans_with_records_from_sqs' do + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_original_handler, {}) do + otel_wrapper.call_wrapped(event: sqs_record, context: context) + + _(last_span.name).must_equal 'sample.test' + _(last_span.kind).must_equal :consumer + _(last_span.status.code).must_equal 1 + _(last_span.hex_parent_span_id).must_equal '0000000000000000' + + _(last_span.attributes['aws.lambda.invoked_arn']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['faas.invocation_id']).must_equal '41784178-4178-4178-4178-4178417855e' + _(last_span.attributes['cloud.resource_id']).must_equal 'arn:aws:lambda:location:id:function_name:function_name' + _(last_span.attributes['cloud.account.id']).must_equal 'id' + _(last_span.attributes['faas.trigger']).must_equal 'pubsub' + _(last_span.attributes['messaging.operation']).must_equal 'process' + _(last_span.attributes['messaging.system']).must_equal 'AmazonSQS' + + assert_nil(last_span.attributes['http.method']) + assert_nil(last_span.attributes['http.user_agent']) + assert_nil(last_span.attributes['http.route']) + assert_nil(last_span.attributes['http.target']) + assert_nil(last_span.attributes['net.host.name']) + end + end + end + + describe 'validate_error_handling' do + it 'handle error if original handler cause issue' do + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_original_handler, ->(**_args) { raise StandardError, 'Simulated Error' }) do + otel_wrapper.call_wrapped(event: event_v1, context: context) + rescue StandardError + _(last_span.name).must_equal 'sample.test' + _(last_span.kind).must_equal :server + + _(last_span.status.code).must_equal 2 + _(last_span.status.description).must_equal 'Simulated Error' + _(last_span.hex_parent_span_id).must_equal '0000000000000000' + + _(last_span.events[0].name).must_equal 'exception' + _(last_span.events[0].attributes['exception.type']).must_equal 'StandardError' + _(last_span.events[0].attributes['exception.message']).must_equal 'Simulated Error' + + _(last_span.hex_span_id.size).must_equal 16 + _(last_span.hex_trace_id.size).must_equal 32 + _(last_span.trace_flags.sampled?).must_equal true + _(last_span.tracestate.to_h.to_s).must_equal '{}' + end + end + + it 'if wrapped handler cause otel-related issue, wont break the entire lambda call' do + otel_wrapper = OpenTelemetry::Instrumentation::AwsLambda::Handler.new + otel_wrapper.stub(:call_wrapped, { 'test' => 'ok' }) do + otel_wrapper.stub(:call_original_handler, {}) do + OpenTelemetry::Context.stub(:with_current, lambda { |_context| + tracer.start_span('test_span', attributes: {}, kind: :server) + raise StandardError, 'OTEL Error' + }) do + response = otel_wrapper.call_wrapped(event: event_v1, context: context) + _(response['test']).must_equal 'ok' + end + end + end + end + end +end diff --git a/instrumentation/aws_lambda/test/test_helper.rb b/instrumentation/aws_lambda/test/test_helper.rb new file mode 100644 index 000000000..cf3cde320 --- /dev/null +++ b/instrumentation/aws_lambda/test/test_helper.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/setup' +Bundler.require(:default, :development, :test) + +require 'opentelemetry-instrumentation-aws_lambda' + +require 'minitest/autorun' +require 'rspec/mocks/minitest_integration' + +class MockLambdaContext + attr_reader :aws_request_id, :invoked_function_arn, :function_name + + def initialize(aws_request_id:, invoked_function_arn:, function_name:) + @aws_request_id = aws_request_id + @invoked_function_arn = invoked_function_arn + @function_name = function_name + end +end + +EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new +span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(EXPORTER) + +EVENT_V1 = { + 'body' => nil, + 'headers' => { + 'Accept' => '*/*', + 'Host' => '127.0.0.1:3000', + 'User-Agent' => 'curl/8.1.2', + 'X-Forwarded-Port' => 3000, + 'X-Forwarded-Proto' => 'http' + }, + 'httpMethod' => 'GET', + 'isBase64Encoded' => false, + 'multiValueHeaders' => {}, + 'multiValueQueryStringParameters' => nil, + 'path' => '/', + 'pathParameters' => nil, + 'queryStringParameters' => nil, + 'requestContext' => { + 'accountId' => 123_456_789_012, + 'apiId' => 1_234_567_890, + 'domainName' => '127.0.0.1:3000', + 'extendedRequestId' => nil, + 'httpMethod' => 'GET', + 'identity' => {}, + 'path' => '/', + 'protocol' => 'HTTP/1.1', + 'requestId' => 'db7f8e7a-4cc5-4f6d-987b-713d0d9052c3', + 'requestTime' => '08/Nov/2023:19:09:59 +0000', + 'requestTimeEpoch' => 1_699_470_599, + 'resourceId' => '123456', + 'resourcePath' => '/', + 'stage' => 'api' + }, + 'resource' => '/', + 'stageVariables' => nil, + 'version' => '1.0' +}.freeze + +EVENT_V2 = { + 'version' => '2.0', + 'routeKey' => '$default', + 'rawPath' => '/path/to/resource', + 'rawQueryString' => 'parameter1=value1¶meter1=value2¶meter2=value', + 'cookies' => %w[cookie1 cookie2], + 'headers' => { 'header1' => 'value1', 'Header2' => 'value1,value2' }, + 'queryStringParameters' => {}, + 'requestContext' => { + 'accountId' => '123456789012', + 'apiId' => 'api-id', + 'authentication' => { 'clientCert' => {} }, + 'authorizer' => {}, + 'domainName' => 'id.execute-api.us-east-1.amazonaws.com', + 'domainPrefix' => 'id', + 'http' => { + 'method' => 'POST', + 'path' => '/path/to/resource', + 'protocol' => 'HTTP/1.1', + 'sourceIp' => '192.168.0.1/32', + 'userAgent' => 'agent' + }, + 'requestId' => 'id', + 'routeKey' => '$default', + 'stage' => '$default', + 'time' => '12/Mar/2020:19:03:58 +0000', + 'timeEpoch' => 1_583_348_638_390 + }, + 'body' => 'eyJ0ZXN0IjoiYm9keSJ9', + 'pathParameters' => { 'parameter1' => 'value1' }, + 'isBase64Encoded' => true, + 'stageVariables' => { 'stageVariable1' => 'value1', 'stageVariable2' => 'value2' } +}.freeze + +EVENT_RECORD = { + 'Records' => + [ + { 'eventVersion' => '2.0', + 'eventSource' => 'aws:s3', + 'awsRegion' => 'us-east-1', + 'eventTime' => '1970-01-01T00:00:00.000Z', + 'eventName' => 'ObjectCreated:Put', + 'userIdentity' => { 'principalId' => 'EXAMPLE' }, + 'requestParameters' => { 'sourceIPAddress' => '127.0.0.1' }, + 'responseElements' => { + 'x-amz-request-id' => 'EXAMPLE123456789', + 'x-amz-id-2' => 'EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH' + }, + 's3' => { + 's3SchemaVersion' => '1.0', + 'configurationId' => 'testConfigRule', + 'bucket' => { + 'name' => 'mybucket', + 'ownerIdentity' => { + 'principalId' => 'EXAMPLE' + }, + 'arn' => 'arn:aws:s3:::mybucket' + }, + 'object' => { + 'key' => 'test/key', + 'size' => 1024, + 'eTag' => '0123456789abcdef0123456789abcdef', + 'sequencer' => '0A1B2C3D4E5F678901' + } + } } + ] +}.freeze + +SQS_RECORD = { + 'Records' => + [{ 'messageId' => '19dd0b57-b21e-4ac1-bd88-01bbb068cb78', + 'receiptHandle' => 'MessageReceiptHandle', + 'body' => 'Hello from SQS!', + 'attributes' => + { 'ApproximateReceiveCount' => '1', + 'SentTimestamp' => '1523232000000', + 'SenderId' => '123456789012', + 'ApproximateFirstReceiveTimestamp' => '1523232000001' }, + 'messageAttributes' => {}, + 'md5OfBody' => '7b270e59b47ff90a553787216d55d91d', + 'eventSource' => 'aws:sqs', + 'eventSourceARN' => 'arn:aws:sqs:us-east-1:123456789012:MyQueue', + 'awsRegion' => 'us-east-1' }] +}.freeze + +CONTEXT = MockLambdaContext.new(aws_request_id: '41784178-4178-4178-4178-4178417855e', + invoked_function_arn: 'arn:aws:lambda:location:id:function_name:function_name', + function_name: 'funcion') + +$LOAD_PATH.unshift("#{Dir.pwd}/example/") +ENV['ORIG_HANDLER'] = 'sample.test' +ENV['_HANDLER'] = 'sample.test' +OpenTelemetry::SDK.configure do |c| + c.error_handler = ->(exception:, message:) { raise(exception || message) } + c.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym) + c.use 'OpenTelemetry::Instrumentation::AwsLambda' + c.add_span_processor span_processor +end diff --git a/instrumentation/aws_sdk/CHANGELOG.md b/instrumentation/aws_sdk/CHANGELOG.md index 686a7708a..f2674ad57 100644 --- a/instrumentation/aws_sdk/CHANGELOG.md +++ b/instrumentation/aws_sdk/CHANGELOG.md @@ -1,8 +1,20 @@ # Release History: opentelemetry-instrumentation-aws_sdk +### v0.5.3 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.5.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.5.1 / 2024-02-08 + +* FIXED: Return nil for non-existant key in AwsSdk::MessageAttributeGetter + ### v0.5.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.4.2 / 2023-08-03 diff --git a/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/message_attributes.rb b/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/message_attributes.rb index 4a4519f02..40598b977 100644 --- a/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/message_attributes.rb +++ b/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/message_attributes.rb @@ -29,7 +29,8 @@ def self.set(carrier, key, value) # OpenTelemetry.propagation.extract(message, getter: MessageAttributeGetter) class MessageAttributeGetter def self.get(carrier, key) - carrier[key][:string_value] if carrier[key][:data_type] == 'String' + message_attribute = carrier[key] + message_attribute[:string_value] if message_attribute && message_attribute[:data_type] == 'String' end end end diff --git a/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/version.rb b/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/version.rb index d40079038..fa477aae1 100644 --- a/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/version.rb +++ b/instrumentation/aws_sdk/lib/opentelemetry/instrumentation/aws_sdk/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module AwsSdk - VERSION = '0.5.0' + VERSION = '0.5.3' end end end diff --git a/instrumentation/aws_sdk/opentelemetry-instrumentation-aws_sdk.gemspec b/instrumentation/aws_sdk/opentelemetry-instrumentation-aws_sdk.gemspec index 1c91da124..609cfbe56 100644 --- a/instrumentation/aws_sdk/opentelemetry-instrumentation-aws_sdk.gemspec +++ b/instrumentation/aws_sdk/opentelemetry-instrumentation-aws_sdk.gemspec @@ -29,7 +29,6 @@ Gem::Specification.new do |spec| spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' - spec.add_development_dependency 'aws-sdk', '>= 2.0' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' @@ -37,7 +36,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-byebug' unless RUBY_ENGINE == 'jruby' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/aws_sdk/test/opentelemetry/instrumentation_test.rb b/instrumentation/aws_sdk/test/opentelemetry/instrumentation_test.rb index 1917da5e0..f44e81bc5 100644 --- a/instrumentation/aws_sdk/test/opentelemetry/instrumentation_test.rb +++ b/instrumentation/aws_sdk/test/opentelemetry/instrumentation_test.rb @@ -246,5 +246,25 @@ OpenTelemetry::Instrumentation::AwsSdk::MessageAttributeSetter.set(metadata_attributes, 'new10', 'value') _(metadata_attributes.keys).must_equal(%w[existingKey0 existingKey1 existingKey2 existingKey3 existingKey4 existingKey5 existingKey6 existingKey7 existingKey8 existingKey9]) end + + describe 'MessageAttributeGetter' do + let(:getter) { OpenTelemetry::Instrumentation::AwsSdk::MessageAttributeGetter } + let(:carrier) do + { + 'traceparent' => { data_type: 'String', string_value: 'tp' }, + 'tracestate' => { data_type: 'String', string_value: 'ts' }, + 'x-source-id' => { data_type: 'String', string_value: '123' } + } + end + + it 'reads key from carrier' do + _(getter.get(carrier, 'traceparent')).must_equal('tp') + _(getter.get(carrier, 'x-source-id')).must_equal('123') + end + + it 'returns nil for non-existant key' do + _(getter.get(carrier, 'not-here')).must_be_nil + end + end end end diff --git a/instrumentation/base/CHANGELOG.md b/instrumentation/base/CHANGELOG.md index a5f77d31f..b51a63149 100644 --- a/instrumentation/base/CHANGELOG.md +++ b/instrumentation/base/CHANGELOG.md @@ -1,8 +1,17 @@ # Release History: opentelemetry-instrumentation-base -### v0.22.2 / 2023-08-03 +### v0.22.4 / 2024-06-18 + +* FIXED: Relax otel common gem constraints +* DOCS: Add function doc for config_overrides_from_env -* FIXED: Remove inline linter rules +### v0.22.3 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.22.2 / 2023-08-03 + +* FIXED: Remove inline linter rules ### v0.22.1 / 2023-06-02 diff --git a/instrumentation/base/lib/opentelemetry/instrumentation/base.rb b/instrumentation/base/lib/opentelemetry/instrumentation/base.rb index d944d1a6c..8c9ac0e3f 100644 --- a/instrumentation/base/lib/opentelemetry/instrumentation/base.rb +++ b/instrumentation/base/lib/opentelemetry/instrumentation/base.rb @@ -326,6 +326,18 @@ def enabled_by_env_var? ENV[var_name] != 'false' end + # Checks to see if the user has passed any environment variables that set options + # for instrumentation. By convention, the environment variable will be the name + # of the instrumentation, uppercased, with '::' replaced by underscores, + # OPENTELEMETRY shortened to OTEL_{LANG}, and _CONFIG_OPTS appended. + # For example, the environment variable name for OpenTelemetry::Instrumentation::Faraday + # will be OTEL_RUBY_INSTRUMENTATION_FARADAY_CONFIG_OPTS. A value of 'peer_service=new_service;' + # will override the options set from ::OpenTelemetry::SDK.configure do |c| ... end for Faraday. + # + # For an array option, simply separate the values with commas (e.g., option=a,b,c,d). + # For a boolean option, set the value to true or false (e.g., option=true). + # For integer, string, enum, set the value as a string (e.g., option=string). + # Callable options are not allowed to be set through environment variables. def config_overrides_from_env var_name = name.dup.tap do |n| n.upcase! @@ -353,7 +365,7 @@ def coerce_env_var(env_var, validation_type) when :array env_var.split(',').map(&:strip) when :boolean - env_var.to_s.strip.downcase == 'true' + env_var.to_s.strip.casecmp('true').zero? when :integer env_var.to_i when :string diff --git a/instrumentation/base/lib/opentelemetry/instrumentation/version.rb b/instrumentation/base/lib/opentelemetry/instrumentation/version.rb index 58c9df556..297acbb24 100644 --- a/instrumentation/base/lib/opentelemetry/instrumentation/version.rb +++ b/instrumentation/base/lib/opentelemetry/instrumentation/version.rb @@ -6,6 +6,6 @@ module OpenTelemetry module Instrumentation - VERSION = '0.22.2' + VERSION = '0.22.4' end end diff --git a/instrumentation/base/opentelemetry-instrumentation-base.gemspec b/instrumentation/base/opentelemetry-instrumentation-base.gemspec index f40f75af0..25e2a83c8 100644 --- a/instrumentation/base/opentelemetry-instrumentation-base.gemspec +++ b/instrumentation/base/opentelemetry-instrumentation-base.gemspec @@ -26,13 +26,15 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-common', '~> 0.21' spec.add_dependency 'opentelemetry-registry', '~> 0.1' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.22.0' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/bunny/CHANGELOG.md b/instrumentation/bunny/CHANGELOG.md index 303b724da..3a66efd52 100644 --- a/instrumentation/bunny/CHANGELOG.md +++ b/instrumentation/bunny/CHANGELOG.md @@ -1,12 +1,24 @@ # Release History: opentelemetry-instrumentation-bunny +### v0.21.4 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.21.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.21.2 / 2024-02-08 + +* FIXED: Remove disable directives leftover from older version of Rubocop + ### v0.21.1 / 2023-09-27 * FIXED: Headers property needs to exist ### v0.21.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.20.1 / 2023-06-05 diff --git a/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/patches/channel.rb b/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/patches/channel.rb index 4f75ace1a..1435ad4ae 100644 --- a/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/patches/channel.rb +++ b/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/patches/channel.rb @@ -28,7 +28,7 @@ def basic_publish(payload, exchange, routing_key, opts = {}) OpenTelemetry::Instrumentation::Bunny::PatchHelpers.with_send_span(self, tracer, exchange, routing_key) do OpenTelemetry::Instrumentation::Bunny::PatchHelpers.inject_context_into_property(opts, :headers) - super(payload, exchange, routing_key, opts) + super end end diff --git a/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/version.rb b/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/version.rb index c567843e5..453369d1c 100644 --- a/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/version.rb +++ b/instrumentation/bunny/lib/opentelemetry/instrumentation/bunny/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Bunny - VERSION = '0.21.1' + VERSION = '0.21.4' end end end diff --git a/instrumentation/bunny/opentelemetry-instrumentation-bunny.gemspec b/instrumentation/bunny/opentelemetry-instrumentation-bunny.gemspec index 40aa3a9f5..1bcb9c826 100644 --- a/instrumentation/bunny/opentelemetry-instrumentation-bunny.gemspec +++ b/instrumentation/bunny/opentelemetry-instrumentation-bunny.gemspec @@ -30,11 +30,11 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'bunny' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/concurrent_ruby/CHANGELOG.md b/instrumentation/concurrent_ruby/CHANGELOG.md index 43f2d4254..8182bda89 100644 --- a/instrumentation/concurrent_ruby/CHANGELOG.md +++ b/instrumentation/concurrent_ruby/CHANGELOG.md @@ -1,5 +1,13 @@ # Release History: opentelemetry-instrumentation-concurrent_ruby +### v0.21.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.21.2 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.21.1 / 2023-06-05 * FIXED: Base config options diff --git a/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/patches/thread_pool_executor.rb b/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/patches/thread_pool_executor.rb index 5b7f1e5e7..db8873ea2 100644 --- a/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/patches/thread_pool_executor.rb +++ b/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/patches/thread_pool_executor.rb @@ -17,7 +17,7 @@ def post(*args, **kwargs, &task) super(*args, **kwargs) do OpenTelemetry::Context.with_current(context) do - task.call(*args, **kwargs) + yield(*args, **kwargs) end end end diff --git a/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/version.rb b/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/version.rb index 3ce95b41b..d92667088 100644 --- a/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/version.rb +++ b/instrumentation/concurrent_ruby/lib/opentelemetry/instrumentation/concurrent_ruby/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module ConcurrentRuby - VERSION = '0.21.1' + VERSION = '0.21.3' end end end diff --git a/instrumentation/concurrent_ruby/opentelemetry-instrumentation-concurrent_ruby.gemspec b/instrumentation/concurrent_ruby/opentelemetry-instrumentation-concurrent_ruby.gemspec index d2cfb4b33..d4aec4ec1 100644 --- a/instrumentation/concurrent_ruby/opentelemetry-instrumentation-concurrent_ruby.gemspec +++ b/instrumentation/concurrent_ruby/opentelemetry-instrumentation-concurrent_ruby.gemspec @@ -30,11 +30,11 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'concurrent-ruby', '~> 1.1.6' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/concurrent_ruby/test/opentelemetry/instrumentation/concurrent-ruby/instrumentation_test.rb b/instrumentation/concurrent_ruby/test/opentelemetry/instrumentation/concurrent-ruby/instrumentation_test.rb index c3f538443..2aa4e5ceb 100644 --- a/instrumentation/concurrent_ruby/test/opentelemetry/instrumentation/concurrent-ruby/instrumentation_test.rb +++ b/instrumentation/concurrent_ruby/test/opentelemetry/instrumentation/concurrent-ruby/instrumentation_test.rb @@ -23,7 +23,7 @@ after do # Force re-install of instrumentation Concurrent.send(:remove_const, :ThreadPoolExecutor) - Concurrent.const_set('ThreadPoolExecutor', unmodified_future) + Concurrent.const_set(:ThreadPoolExecutor, unmodified_future) instrumentation.instance_variable_set(:@installed, false) end diff --git a/instrumentation/dalli/CHANGELOG.md b/instrumentation/dalli/CHANGELOG.md index 3b2e198bd..92c503a09 100644 --- a/instrumentation/dalli/CHANGELOG.md +++ b/instrumentation/dalli/CHANGELOG.md @@ -1,5 +1,23 @@ # Release History: opentelemetry-instrumentation-dalli +### v0.25.3 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.25.2 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.25.1 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.25.0 / 2023-10-16 + +* BREAKING CHANGE: Obfuscation for mysql2, dalli and postgresql as default option for db_statement + +* ADDED: Obfuscation for mysql2, dalli and postgresql as default option for db_statement + ### v0.24.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/dalli/README.md b/instrumentation/dalli/README.md index 3f0efd4c6..bf4f988fa 100644 --- a/instrumentation/dalli/README.md +++ b/instrumentation/dalli/README.md @@ -30,6 +30,22 @@ OpenTelemetry::SDK.configure do |c| end ``` +### Configuration options + +```ruby +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::Dalli', { + # You may optionally set a value for 'peer.service', which + # will be included on all spans from this instrumentation: + peer_service: '', + + # The obfuscation of query in the db.statement attribute is enabled by default. + # To disable, set db_statement to :include; to omit the query completely, set db_statement to :omit + db_statement: :include, + } +end +``` + ## How can I get involved? The `opentelemetry-instrumentation-dalli` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. diff --git a/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/instrumentation.rb b/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/instrumentation.rb index 014f38ccd..610bf5a5f 100644 --- a/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/instrumentation.rb +++ b/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/instrumentation.rb @@ -20,7 +20,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end option :peer_service, default: nil, validate: :string - option :db_statement, default: :include, validate: %I[omit obfuscate include] + option :db_statement, default: :obfuscate, validate: %I[omit obfuscate include] private diff --git a/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/version.rb b/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/version.rb index 424f56834..19a3ba981 100644 --- a/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/version.rb +++ b/instrumentation/dalli/lib/opentelemetry/instrumentation/dalli/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Dalli - VERSION = '0.24.2' + VERSION = '0.25.3' end end end diff --git a/instrumentation/dalli/opentelemetry-instrumentation-dalli.gemspec b/instrumentation/dalli/opentelemetry-instrumentation-dalli.gemspec index 8a12e2360..19317f687 100644 --- a/instrumentation/dalli/opentelemetry-instrumentation-dalli.gemspec +++ b/instrumentation/dalli/opentelemetry-instrumentation-dalli.gemspec @@ -26,16 +26,15 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'dalli', '>= 2.7' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/dalli/test/opentelemetry/instrumentation/dalli/instrumentation_test.rb b/instrumentation/dalli/test/opentelemetry/instrumentation/dalli/instrumentation_test.rb index d426a4bf3..512ba0359 100644 --- a/instrumentation/dalli/test/opentelemetry/instrumentation/dalli/instrumentation_test.rb +++ b/instrumentation/dalli/test/opentelemetry/instrumentation/dalli/instrumentation_test.rb @@ -28,7 +28,7 @@ describe 'tracing' do before do - instrumentation.install + instrumentation.install(db_statement: :include) end it 'accepts peer service name from config' do diff --git a/instrumentation/datadog-porting-guide.md b/instrumentation/datadog-porting-guide.md index dc9c63c75..1ccadefd5 100644 --- a/instrumentation/datadog-porting-guide.md +++ b/instrumentation/datadog-porting-guide.md @@ -16,11 +16,15 @@ Aid developers who wish to port existing datadog (dd-trace-rb) instrumentation t * Add Gemfile, opentelemetry-instrumentation-#{name}.gemspec * Add runnable (docker) example (using its own Gemfile) -``` + +```console + $ docker-compose run ex-instrumentation-myinstrumentation bundle install $ docker-compose run ex-instrumentation-myinstrumentation bash-5.0$ ruby trace_demonstration.rb + ``` + * Rakefile * `tests/test_helper.rb` * Integrate rubocop (see /~https://github.com/open-telemetry/opentelemetry-ruby/pull/172#pullrequestreview-349183775) @@ -66,7 +70,7 @@ bash-5.0$ ruby trace_demonstration.rb ### Runtime performance considerations Watch for "low hanging fruit" performance improvements including: -* reduce object allocations - move to a constant, or cache values that would be generated repeatedly +* reduce object allocations - move to a constant, or cache values that would be generated repeatedly * look for "easy wins" vs. "complete redesigns" ## Testing diff --git a/instrumentation/delayed_job/Appraisals b/instrumentation/delayed_job/Appraisals index c3e9b24a2..e4d86e84f 100644 --- a/instrumentation/delayed_job/Appraisals +++ b/instrumentation/delayed_job/Appraisals @@ -4,6 +4,18 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'delayed_job-4.1' do - gem 'delayed_job', '~> 4.1.0' +appraise 'delayed_job_4.1-rails-latest' do + gem 'activejob' +end + +appraise 'delayed_job_4.1-rails-7.1' do + gem 'activejob', '~> 7.1.0' +end + +appraise 'delayed_job_4.1-rails-7.0' do + gem 'activejob', '~> 7.0.0' +end + +appraise 'delayed_job-4.1-rails-6.1' do + gem 'activejob', '~> 6.1.0' end diff --git a/instrumentation/delayed_job/CHANGELOG.md b/instrumentation/delayed_job/CHANGELOG.md index 67b9f9441..74d258391 100644 --- a/instrumentation/delayed_job/CHANGELOG.md +++ b/instrumentation/delayed_job/CHANGELOG.md @@ -1,8 +1,26 @@ # Release History: opentelemetry-instrumentation-delayed_job +### v0.22.3 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.22.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.22.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.22.0 / 2023-10-16 + +* BREAKING CHANGE: Drop DelayedJob ActiveRecord in Tests + +* FIXED: Drop DelayedJob ActiveRecord in Tests + ### v0.21.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.20.1 / 2023-06-05 diff --git a/instrumentation/delayed_job/Gemfile b/instrumentation/delayed_job/Gemfile index 2baf57ac4..3c182227f 100644 --- a/instrumentation/delayed_job/Gemfile +++ b/instrumentation/delayed_job/Gemfile @@ -9,5 +9,6 @@ source 'https://rubygems.org' gemspec group :test do + gem 'delayed_job', '~> 4.1' gem 'opentelemetry-instrumentation-base', path: '../base' end diff --git a/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin.rb b/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin.rb index fccba0641..676f720ed 100644 --- a/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin.rb +++ b/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin.rb @@ -14,7 +14,7 @@ module Plugins class TracerPlugin < Delayed::Plugin class << self def instrument_enqueue(job, &block) - return block.call(job) unless enabled? + return yield(job) unless enabled? attributes = build_attributes(job) attributes['messaging.operation'] = 'publish' @@ -28,7 +28,7 @@ def instrument_enqueue(job, &block) end def instrument_invoke(job, &block) - return block.call(job) unless enabled? + return yield(job) unless enabled? attributes = build_attributes(job) attributes['messaging.delayed_job.attempts'] = job.attempts if job.attempts @@ -56,7 +56,6 @@ def build_attributes(job) end def add_events(span, job) - span.add_event('created_at', timestamp: job.created_at) span.add_event('run_at', timestamp: job.run_at) if job.run_at span.add_event('locked_at', timestamp: job.locked_at) if job.locked_at end @@ -84,8 +83,10 @@ def job_queue(job) end callbacks do |lifecycle| + # rubocop:disable Performance/MethodObjectAsBlock lifecycle.around(:enqueue, &method(:instrument_enqueue)) lifecycle.around(:invoke_job, &method(:instrument_invoke)) + # rubocop:enable Performance/MethodObjectAsBlock end end end diff --git a/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/version.rb b/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/version.rb index 989bf86fb..ed2e12e04 100644 --- a/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/version.rb +++ b/instrumentation/delayed_job/lib/opentelemetry/instrumentation/delayed_job/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module DelayedJob - VERSION = '0.21.0' + VERSION = '0.22.3' end end end diff --git a/instrumentation/delayed_job/opentelemetry-instrumentation-delayed_job.gemspec b/instrumentation/delayed_job/opentelemetry-instrumentation-delayed_job.gemspec index 86648b55b..111cea40d 100644 --- a/instrumentation/delayed_job/opentelemetry-instrumentation-delayed_job.gemspec +++ b/instrumentation/delayed_job/opentelemetry-instrumentation-delayed_job.gemspec @@ -30,13 +30,12 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'delayed_job', '~> 4.1.0' - spec.add_development_dependency 'delayed_job_active_record' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'sqlite3' spec.add_development_dependency 'webmock', '~> 3.19' diff --git a/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb b/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb index 2f358cefe..85f8f7464 100644 --- a/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb +++ b/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job/plugins/tracer_plugin_test.rb @@ -16,8 +16,7 @@ let(:span) { exporter.finished_spans.last } before do - TestHelper.setup_active_record - + Delayed::Worker.backend.delete_all stub_const('BasicPayload', Class.new do def perform; end end) @@ -50,8 +49,6 @@ def job_data after do OpenTelemetry.propagation = @orig_propagation - - TestHelper.teardown_active_record end describe 'enqueue callback' do @@ -74,11 +71,9 @@ def job_data _(span.attributes['messaging.operation']).must_equal 'publish' _(span.attributes['messaging.message_id']).must_be_kind_of String - _(span.events.size).must_equal 2 - _(span.events[0].name).must_equal 'created_at' + _(span.events.size).must_equal 1 + _(span.events[0].name).must_equal 'run_at' _(span.events[0].timestamp).must_be_kind_of Integer - _(span.events[1].name).must_equal 'run_at' - _(span.events[1].timestamp).must_be_kind_of Integer end describe 'when queue name is set' do @@ -124,7 +119,6 @@ def job_data _(exporter.finished_spans.size).must_equal 1 _(exporter.finished_spans.first.name).must_equal 'default publish' job_run - _(exporter.finished_spans.size).must_equal 2 _(span).must_be_kind_of OpenTelemetry::SDK::Trace::SpanData _(span.name).must_equal 'default process' @@ -138,17 +132,15 @@ def job_data _(span.attributes['messaging.operation']).must_equal 'process' _(span.attributes['messaging.message_id']).must_be_kind_of String - _(span.events.size).must_equal 3 - _(span.events[0].name).must_equal 'created_at' + _(span.events[0].name).must_equal 'run_at' _(span.events[0].timestamp).must_be_kind_of Integer - _(span.events[1].name).must_equal 'run_at' + _(span.events[1].name).must_equal 'locked_at' _(span.events[1].timestamp).must_be_kind_of Integer - _(span.events[2].name).must_equal 'locked_at' - _(span.events[2].timestamp).must_be_kind_of Integer end describe 'when queue name is set' do let(:job_params) { { queue: 'foobar_queue' } } + let(:job_enqueue) { Delayed::Job.enqueue(@basic_payload.new, job_params) } it 'span tags include queue name' do job_run @@ -181,11 +173,10 @@ def job_data it 'has resource name equal to underlying ActiveJob class name' do job_run _(span.attributes['messaging.delayed_job.name']).must_equal 'ErrorPayload' - _(span.events.size).must_equal 4 - _(span.events[3].name).must_equal 'exception' - _(span.events[3].timestamp).must_be_kind_of Integer - _(span.events[3].attributes['exception.type']).must_equal 'ArgumentError' - _(span.events[3].attributes['exception.message']).must_equal 'This job failed' + _(span.events[2].name).must_equal 'exception' + _(span.events[2].timestamp).must_be_kind_of Integer + _(span.events[2].attributes['exception.type']).must_equal 'ArgumentError' + _(span.events[2].attributes['exception.message']).must_equal 'This job failed' end end end diff --git a/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job_test.rb b/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job_test.rb index 33ad547bd..907122b34 100644 --- a/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job_test.rb +++ b/instrumentation/delayed_job/test/opentelemetry/instrumentation/delayed_job_test.rb @@ -11,6 +11,7 @@ let(:exporter) { EXPORTER } before do + Delayed::Worker.backend.delete_all instrumentation.install exporter.reset end @@ -48,14 +49,6 @@ end describe 'tracing' do - before do - TestHelper.setup_active_record - end - - after do - TestHelper.teardown_active_record - end - it 'before job' do _(exporter.finished_spans.size).must_equal 0 end diff --git a/instrumentation/delayed_job/test/test_helper.rb b/instrumentation/delayed_job/test/test_helper.rb index 76ff7bba1..3b5b70195 100644 --- a/instrumentation/delayed_job/test/test_helper.rb +++ b/instrumentation/delayed_job/test/test_helper.rb @@ -7,8 +7,13 @@ require 'bundler/setup' Bundler.require(:default, :development, :test) +# These are dependencies that delayed job assumes are already loaded +# We are compensating for that here in this test... that is a smell +# NoMethodError: undefined method `extract_options!' for [#, {}]:Array +# delayed_job-4.1.11/lib/delayed/backend/job_preparer.rb:7:in `initialize'0 +require 'active_support/core_ext/array/extract_options' + require 'opentelemetry-instrumentation-delayed_job' -require 'active_support/core_ext/kernel/reporting' require 'minitest/autorun' require 'rspec/mocks/minitest_integration' @@ -24,31 +29,7 @@ c.add_span_processor span_processor end -ActiveRecord::Migration.verbose = false - -module TestHelper - extend self - - def setup_active_record - ::ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') - ::ActiveRecord::Schema.define do - create_table 'delayed_jobs', force: :cascade do |t| - t.integer 'priority', default: 0, null: false - t.integer 'attempts', default: 0, null: false - t.text 'handler', null: false - t.text 'last_error' - t.datetime 'run_at' - t.datetime 'locked_at' - t.datetime 'failed_at' - t.string 'locked_by' - t.string 'queue' - t.datetime 'created_at' - t.datetime 'updated_at' - end - end - end - - def teardown_active_record - ::ActiveRecord::Base.connection.close - end -end +gem_dir = Gem::Specification.find_by_name('delayed_job').gem_dir +require "#{gem_dir}/spec/delayed/backend/test" + +Delayed::Worker.backend = Delayed::Backend::Test::Job diff --git a/instrumentation/ethon/Appraisals b/instrumentation/ethon/Appraisals index 137aed805..61520f97d 100644 --- a/instrumentation/ethon/Appraisals +++ b/instrumentation/ethon/Appraisals @@ -1,9 +1,9 @@ # frozen_string_literal: true -appraise 'ethon-0.12' do - gem 'ethon', '~> 0.12.0' +appraise 'ethon-0.16' do + gem 'ethon', '~> 0.16.0' end -appraise 'ethon-0.11' do - gem 'ethon', '~> 0.11.0' +appraise 'ethon-latest' do + gem 'ethon' end diff --git a/instrumentation/ethon/CHANGELOG.md b/instrumentation/ethon/CHANGELOG.md index d375ab33c..5c6418836 100644 --- a/instrumentation/ethon/CHANGELOG.md +++ b/instrumentation/ethon/CHANGELOG.md @@ -1,5 +1,25 @@ # Release History: opentelemetry-instrumentation-ethon +### v0.21.7 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.21.6 / 2024-06-12 + +* FIXED: Add net.peer.name to ethon + +### v0.21.5 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.21.4 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.21.3 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.21.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb b/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb index f8727126d..74791818e 100644 --- a/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb +++ b/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/patches/easy.rb @@ -42,7 +42,7 @@ def complete @otel_span.status = OpenTelemetry::Trace::Status.error("Request has failed: #{message}") else @otel_span.set_attribute('http.status_code', response_code) - @otel_span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(response_code.to_i) + @otel_span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(response_code.to_i) end ensure @otel_span&.finish @@ -87,8 +87,11 @@ def span_creation_attributes(method) 'http.method' => method } - http_url = OpenTelemetry::Common::Utilities.cleanse_url(url) - instrumentation_attrs['http.url'] = http_url if http_url + uri = _otel_cleanse_uri(url) + if uri + instrumentation_attrs['http.url'] = uri.to_s + instrumentation_attrs['net.peer.name'] = uri.host if uri.host + end config = Ethon::Instrumentation.instance.config instrumentation_attrs['peer.service'] = config[:peer_service] if config[:peer_service] @@ -97,6 +100,20 @@ def span_creation_attributes(method) ) end + # Returns a URL string with userinfo removed. + # + # @param [String] url The URL string to cleanse. + # + # @return [String] the cleansed URL. + def _otel_cleanse_uri(url) + cleansed_url = URI.parse(url) + cleansed_url.password = nil + cleansed_url.user = nil + cleansed_url + rescue URI::Error + nil + end + def tracer Ethon::Instrumentation.instance.tracer end diff --git a/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/version.rb b/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/version.rb index e2d1f063f..32be6eb25 100644 --- a/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/version.rb +++ b/instrumentation/ethon/lib/opentelemetry/instrumentation/ethon/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Ethon - VERSION = '0.21.2' + VERSION = '0.21.7' end end end diff --git a/instrumentation/ethon/opentelemetry-instrumentation-ethon.gemspec b/instrumentation/ethon/opentelemetry-instrumentation-ethon.gemspec index e24672d52..002bcef71 100644 --- a/instrumentation/ethon/opentelemetry-instrumentation-ethon.gemspec +++ b/instrumentation/ethon/opentelemetry-instrumentation-ethon.gemspec @@ -26,16 +26,15 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'ethon', '~> 0.12.0' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb b/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb index 1c3a24035..e39779bf0 100644 --- a/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb +++ b/instrumentation/ethon/test/opentelemetry/instrumentation/ethon/instrumentation_test.rb @@ -71,6 +71,7 @@ _(span.attributes['http.method']).must_equal 'N/A' _(span.attributes['http.status_code']).must_be_nil _(span.attributes['http.url']).must_equal 'http://example.com/test' + _(span.attributes['net.peer.name']).must_equal 'example.com' end end end @@ -275,8 +276,10 @@ def stub_response(options) multi.perform _(exporter.finished_spans.size).must_equal 2 - _(exporter.finished_spans[0].attributes['http.url']).must_equal nil + _(exporter.finished_spans[0].attributes['http.url']).must_be_nil + _(exporter.finished_spans[0].attributes['net.peer.name']).must_be_nil _(exporter.finished_spans[1].attributes['http.url']).must_equal 'test' + _(exporter.finished_spans[1].attributes['net.peer.name']).must_be_nil end end end diff --git a/instrumentation/excon/Appraisals b/instrumentation/excon/Appraisals index f84de92ff..4538446e4 100644 --- a/instrumentation/excon/Appraisals +++ b/instrumentation/excon/Appraisals @@ -2,6 +2,12 @@ # add more tests for excon -appraise 'excon-0.71' do - gem 'excon', '~> 0.71.0' +%w[0.71 0.109].each do |version| + appraise "excon-#{version}" do + gem 'excon', "~> #{version}.0" + end +end + +appraise 'excon-latest' do + gem 'excon' end diff --git a/instrumentation/excon/CHANGELOG.md b/instrumentation/excon/CHANGELOG.md index 34b6c1b74..dbef84bab 100644 --- a/instrumentation/excon/CHANGELOG.md +++ b/instrumentation/excon/CHANGELOG.md @@ -1,5 +1,28 @@ # Release History: opentelemetry-instrumentation-excon +### v0.22.3 / 2024-06-18 + +* FIXED: Relax otel common gem constraints +* FIXED: Add `http.url` to Excon instrumentation + +### v0.22.2 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.22.1 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.22.0 / 2023-11-28 + +* BREAKING CHANGE: Add a connect span to excon + +* ADDED: Add a connect span to excon + +### v0.21.3 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.21.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/excon/lib/opentelemetry/instrumentation/concerns/untraced_hosts.rb b/instrumentation/excon/lib/opentelemetry/instrumentation/concerns/untraced_hosts.rb new file mode 100644 index 000000000..81acdd6a3 --- /dev/null +++ b/instrumentation/excon/lib/opentelemetry/instrumentation/concerns/untraced_hosts.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module Concerns + # The untraced hosts concerns allows instrumentation to skip traces on hostnames in an exclusion list. + # If the current OpenTelemetry context is untraced, all hosts will be treated as untraced. + # When included in a class that extends OpenTelemetry::Instrumentation::Base, this module defines an option named :untraced_hosts. + module UntracedHosts + def self.included(klass) + klass.instance_eval do + # untraced_hosts: if a request's address matches any of the `String` + # or `Regexp` in this array, the instrumentation will not record a + # `kind = :client` representing the request and will not propagate + # context in the request. + option :untraced_hosts, default: [], validate: :array + end + end + + # Checks whether the given host should be treated as untraced. + # If the current OpenTelemetry context is untraced, all hosts will be treated as untraced. + # The given host must be a String. + def untraced?(host) + OpenTelemetry::Common::Utilities.untraced? || untraced_host?(host) + end + + private + + def untraced_host?(host) + config[:untraced_hosts].any? do |rule| + rule.is_a?(Regexp) ? rule.match?(host) : rule == host + end + end + end + end + end +end diff --git a/instrumentation/excon/lib/opentelemetry/instrumentation/excon/instrumentation.rb b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/instrumentation.rb index 1a8a4d334..e20ca9b2d 100644 --- a/instrumentation/excon/lib/opentelemetry/instrumentation/excon/instrumentation.rb +++ b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/instrumentation.rb @@ -4,15 +4,20 @@ # # SPDX-License-Identifier: Apache-2.0 +require_relative '../concerns/untraced_hosts' + module OpenTelemetry module Instrumentation module Excon # The Instrumentation class contains logic to detect and install the Excon # instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base + include OpenTelemetry::Instrumentation::Concerns::UntracedHosts + install do |_config| require_dependencies add_middleware + patch end present do @@ -25,11 +30,15 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base def require_dependencies require_relative 'middlewares/tracer_middleware' + require_relative 'patches/socket' end def add_middleware - ::Excon.defaults[:middlewares] = - Middlewares::TracerMiddleware.around_default_stack + ::Excon.defaults[:middlewares] = Middlewares::TracerMiddleware.around_default_stack + end + + def patch + ::Excon::Socket.prepend(Patches::Socket) end end end diff --git a/instrumentation/excon/lib/opentelemetry/instrumentation/excon/middlewares/tracer_middleware.rb b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/middlewares/tracer_middleware.rb index 264bf5067..592fdb793 100644 --- a/instrumentation/excon/lib/opentelemetry/instrumentation/excon/middlewares/tracer_middleware.rb +++ b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/middlewares/tracer_middleware.rb @@ -22,24 +22,31 @@ class TracerMiddleware < ::Excon::Middleware::Base end.freeze def request_call(datum) - begin - unless datum.key?(:otel_span) - http_method = HTTP_METHODS_TO_UPPERCASE[datum[:method]] - attributes = span_creation_attributes(datum, http_method) - tracer.start_span( - HTTP_METHODS_TO_SPAN_NAMES[http_method], - attributes: attributes, - kind: :client - ).tap do |span| - datum[:otel_span] = span - OpenTelemetry::Trace.with_span(span) do - OpenTelemetry.propagation.inject(datum[:headers]) - end - end - end - rescue StandardError => e - OpenTelemetry.logger.debug(e.message) - end + return @stack.request_call(datum) if untraced?(datum) + + http_method = HTTP_METHODS_TO_UPPERCASE[datum[:method]] + + attributes = { + OpenTelemetry::SemanticConventions::Trace::HTTP_HOST => datum[:host], + OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => http_method, + OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => datum[:scheme], + OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => datum[:path], + OpenTelemetry::SemanticConventions::Trace::HTTP_URL => OpenTelemetry::Common::Utilities.cleanse_url(::Excon::Utils.request_uri(datum)), + OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => datum[:hostname], + OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => datum[:port] + } + + peer_service = Excon::Instrumentation.instance.config[:peer_service] + attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = peer_service if peer_service + attributes.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes) + + span = tracer.start_span(HTTP_METHODS_TO_SPAN_NAMES[http_method], attributes: attributes, kind: :client) + ctx = OpenTelemetry::Trace.context_with_span(span) + + datum[:otel_span] = span + datum[:otel_token] = OpenTelemetry::Context.attach(ctx) + + OpenTelemetry.propagation.inject(datum[:headers]) @stack.request_call(datum) end @@ -71,43 +78,35 @@ def self.around_default_stack private def handle_response(datum) - if datum.key?(:otel_span) - datum[:otel_span].tap do |span| - return span if span.end_timestamp - - if datum.key?(:response) - response = datum[:response] - span.set_attribute('http.status_code', response[:status]) - span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(response[:status].to_i) - end + datum.delete(:otel_span)&.tap do |span| + return unless span.recording? - span.status = OpenTelemetry::Trace::Status.error("Request has failed: #{datum[:error]}") if datum.key?(:error) + if datum.key?(:response) + response = datum[:response] + span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, response[:status]) + span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(response[:status].to_i) + end - span.finish - datum.delete(:otel_span) + if datum.key?(:error) + span.status = OpenTelemetry::Trace::Status.error('Request has failed') + span.record_exception(datum[:error]) end + + span.finish + + OpenTelemetry::Context.detach(datum.delete(:otel_token)) if datum.include?(:otel_token) end rescue StandardError => e - OpenTelemetry.logger.debug(e.message) - end - - def span_creation_attributes(datum, http_method) - instrumentation_attrs = { - 'http.host' => datum[:host], - 'http.method' => http_method, - 'http.scheme' => datum[:scheme], - 'http.target' => datum[:path] - } - config = Excon::Instrumentation.instance.config - instrumentation_attrs['peer.service'] = config[:peer_service] if config[:peer_service] - instrumentation_attrs.merge!( - OpenTelemetry::Common::HTTP::ClientContext.attributes - ) + OpenTelemetry.handle_error(e) end def tracer Excon::Instrumentation.instance.tracer end + + def untraced?(datum) + datum.key?(:otel_span) || Excon::Instrumentation.instance.untraced?(datum[:host]) + end end end end diff --git a/instrumentation/excon/lib/opentelemetry/instrumentation/excon/patches/socket.rb b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/patches/socket.rb new file mode 100644 index 000000000..b96ed8833 --- /dev/null +++ b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/patches/socket.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module Excon + module Patches + # Module to prepend to an Excon Socket for instrumentation + module Socket + private + + def connect + return super if untraced? + + if @data[:proxy] + conn_address = @data.dig(:proxy, :hostname) + conn_port = @data.dig(:proxy, :port) + else + conn_address = @data[:hostname] + conn_port = @port + end + + attributes = { OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => conn_address, OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => conn_port }.merge!(OpenTelemetry::Common::HTTP::ClientContext.attributes) + + if is_a?(::Excon::SSLSocket) && @data[:proxy] + span_name = 'HTTP CONNECT' + span_kind = :client + else + span_name = 'connect' + span_kind = :internal + end + + tracer.in_span(span_name, attributes: attributes, kind: span_kind) do + super + end + end + + def tracer + Excon::Instrumentation.instance.tracer + end + + def untraced? + address = if @data[:proxy] + @data.dig(:proxy, :hostname) + else + @data[:hostname] + end + + Excon::Instrumentation.instance.untraced?(address) + end + end + end + end + end +end diff --git a/instrumentation/excon/lib/opentelemetry/instrumentation/excon/version.rb b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/version.rb index eb739fe32..486b0200a 100644 --- a/instrumentation/excon/lib/opentelemetry/instrumentation/excon/version.rb +++ b/instrumentation/excon/lib/opentelemetry/instrumentation/excon/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Excon - VERSION = '0.21.2' + VERSION = '0.22.3' end end end diff --git a/instrumentation/excon/opentelemetry-instrumentation-excon.gemspec b/instrumentation/excon/opentelemetry-instrumentation-excon.gemspec index 117c4e6f1..ce3de4604 100644 --- a/instrumentation/excon/opentelemetry-instrumentation-excon.gemspec +++ b/instrumentation/excon/opentelemetry-instrumentation-excon.gemspec @@ -26,16 +26,15 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'excon', '~> 0.71.0' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb b/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb index 2e1ba7bea..6f085689b 100644 --- a/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb +++ b/instrumentation/excon/test/opentelemetry/instrumentation/excon/instrumentation_test.rb @@ -8,6 +8,7 @@ require_relative '../../../../lib/opentelemetry/instrumentation/excon' require_relative '../../../../lib/opentelemetry/instrumentation/excon/middlewares/tracer_middleware' +require_relative '../../../../lib/opentelemetry/instrumentation/excon/patches/socket' describe OpenTelemetry::Instrumentation::Excon::Instrumentation do let(:instrumentation) { OpenTelemetry::Instrumentation::Excon::Instrumentation.instance } @@ -39,7 +40,7 @@ end it 'before request' do - _(exporter.finished_spans.size).must_equal 0 + _(exporter.finished_spans).must_be_empty end it 'after request with success code' do @@ -47,11 +48,12 @@ _(exporter.finished_spans.size).must_equal 1 _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.host']).must_equal 'example.com' _(span.attributes['http.method']).must_equal 'GET' - _(span.attributes['http.status_code']).must_equal 200 _(span.attributes['http.scheme']).must_equal 'http' - _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.status_code']).must_equal 200 _(span.attributes['http.target']).must_equal '/success' + _(span.attributes['http.url']).must_equal 'http://example.com/success' assert_requested( :get, 'http://example.com/success', @@ -70,11 +72,12 @@ _(exporter.finished_spans.size).must_equal 1 _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.host']).must_equal 'example.com' _(span.attributes['http.method']).must_equal 'GET' - _(span.attributes['http.status_code']).must_equal 500 _(span.attributes['http.scheme']).must_equal 'http' - _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.status_code']).must_equal 500 _(span.attributes['http.target']).must_equal '/failure' + _(span.attributes['http.url']).must_equal 'http://example.com/failure' assert_requested( :get, 'http://example.com/failure', @@ -89,21 +92,24 @@ _(exporter.finished_spans.size).must_equal 1 _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.host']).must_equal 'example.com' _(span.attributes['http.method']).must_equal 'GET' _(span.attributes['http.scheme']).must_equal 'http' - _(span.attributes['http.host']).must_equal 'example.com' _(span.attributes['http.target']).must_equal '/timeout' + _(span.attributes['http.url']).must_equal 'http://example.com/timeout' _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) - _(span.status.description).must_equal( - 'Request has failed: Excon::Error::Timeout' - ) + _(span.status.description).must_equal('Request has failed') assert_requested( :get, 'http://example.com/timeout', headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } ) + + exception_event = span.events.first + _(exception_event.attributes['exception.type']).must_equal('Excon::Error::Timeout') + _(exception_event.attributes['exception.message']).must_equal('Excon::Error::Timeout') end it 'merges HTTP client context' do @@ -116,11 +122,12 @@ _(exporter.finished_spans.size).must_equal 1 _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.host']).must_equal 'example.com' _(span.attributes['http.method']).must_equal 'OVERRIDE' - _(span.attributes['http.status_code']).must_equal 200 _(span.attributes['http.scheme']).must_equal 'http' - _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.status_code']).must_equal 200 _(span.attributes['http.target']).must_equal '/success' + _(span.attributes['http.url']).must_equal 'http://example.com/success' _(span.attributes['test.attribute']).must_equal 'test.value' assert_requested( :get, @@ -150,4 +157,168 @@ _(span.attributes['peer.service']).must_equal 'example:custom' end end + + describe 'untraced?' do + before do + instrumentation.install(untraced_hosts: ['foobar.com', /bazqux\.com/]) + + stub_request(:get, 'http://example.com/body').to_return(status: 200) + stub_request(:get, 'http://foobar.com/body').to_return(status: 200) + stub_request(:get, 'http://bazqux.com/body').to_return(status: 200) + end + + it 'does not create a span when request ignored using a string' do + Excon.get('http://foobar.com/body') + _(exporter.finished_spans).must_be_empty + end + + it 'does not create a span when request ignored using a regexp' do + Excon.get('http://bazqux.com/body') + _(exporter.finished_spans).must_be_empty + end + + it 'does not create a span on connect when request ignored using a regexp' do + uri = URI.parse('http://bazqux.com') + + Excon::Socket.new(hostname: uri.host, port: uri.port) + + _(exporter.finished_spans).must_be_empty + end + + it 'creates a span for a non-ignored request' do + Excon.get('http://example.com/body') + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.method']).must_equal 'GET' + end + + it 'creates a span on connect for a non-ignored request' do + uri = URI.parse('http://example.com') + + Excon::Socket.new(hostname: uri.host, port: uri.port) + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal('connect') + _(span.kind).must_equal(:internal) + _(span.attributes['net.peer.name']).must_equal('example.com') + _(span.attributes['net.peer.port']).must_equal(80) + end + end + + # NOTE: WebMock introduces an extra HTTP request span due to the way the mocking is implemented. + describe '#connect' do + before do + instrumentation.install + WebMock.allow_net_connect! + end + + after do + WebMock.disable_net_connect! + end + + it 'emits span on connect' do + port = nil + + TCPServer.open('localhost', 0) do |server| + Thread.start do + server.accept + rescue IOError + nil + end + + port = server.addr[1] + + _(-> { Excon.get("http://localhost:#{port}/example", read_timeout: 0) }).must_raise(Excon::Error::Timeout) + end + + _(exporter.finished_spans.size).must_equal(3) + _(span.name).must_equal 'connect' + _(span.attributes['net.peer.name']).must_equal('localhost') + _(span.attributes['net.peer.port']).wont_be_nil + _(span.attributes['net.peer.port']).must_equal(port) + + assert_http_spans(port: port, target: '/example', exception: 'Excon::Error::Timeout') + end + + it 'captures errors' do + _(-> { Excon.get('http://invalid.com:99999/example') }).must_raise + + _(exporter.finished_spans.size).must_equal(3) + _(span.name).must_equal 'connect' + _(span.attributes['net.peer.name']).must_equal('invalid.com') + _(span.attributes['net.peer.port']).must_equal(99_999) + + span_event = span.events.first + _(span_event.name).must_equal 'exception' + # Depending on the Ruby and Excon Version this will be a SocketError, Socket::ResolutionError or Resolv::ResolvError + _(span_event.attributes['exception.type']).must_match(/(Socket|Resolv)/) + + assert_http_spans(host: 'invalid.com', port: 99_999, target: '/example') + end + + it '[BUG] fails to emit an HTTP CONNECT span when connecting through an SSL proxy for an HTTP service' do + _(-> { Excon.get('http://localhost/', proxy: 'https://proxy_user:proxy_pass@localhost') }).must_raise(Excon::Error::Socket) + + _(exporter.finished_spans.size).must_equal(3) + _(span.name).must_equal 'connect' + _(span.kind).must_equal(:internal) + _(span.attributes['net.peer.name']).must_equal('localhost') + _(span.attributes['net.peer.port']).must_equal(443) + + assert_http_spans + end + + it 'emits an HTTP CONNECT span when connecting through an SSL proxy' do + _(-> { Excon.get('https://localhost/', proxy: 'https://proxy_user:proxy_pass@localhost') }).must_raise(Excon::Error::Socket) + + _(exporter.finished_spans.size).must_equal(3) + _(span.name).must_equal 'HTTP CONNECT' + _(span.kind).must_equal(:client) + _(span.attributes['net.peer.name']).must_equal('localhost') + _(span.attributes['net.peer.port']).must_equal(443) + + assert_http_spans(scheme: 'https') + end + + it 'emits a "connect" span when connecting through an non-ssl proxy' do + _(-> { Excon.get('http://localhost', proxy: 'https://proxy_user:proxy_pass@localhost') }).must_raise(Excon::Error::Socket) + + _(exporter.finished_spans.size).must_equal(3) + _(span.name).must_equal 'connect' + _(span.kind).must_equal(:internal) + _(span.attributes['net.peer.name']).must_equal('localhost') + _(span.attributes['net.peer.port']).must_equal(443) + + assert_http_spans(exception: 'Excon::Error::Socket') + end + + it 'emits no spans when untraced' do + OpenTelemetry::Common::Utilities.untraced do + _(-> { Excon.get('http://localhost', proxy: 'https://proxy_user:proxy_pass@localhost') }).must_raise(Excon::Error::Socket) + + _(exporter.finished_spans.size).must_equal(0) + end + end + end + + def assert_http_spans(scheme: 'http', host: 'localhost', port: nil, target: '/', exception: nil) + exporter.finished_spans[1..].each do |http_span| + _(http_span.name).must_equal 'HTTP GET' + _(http_span.attributes['http.host']).must_equal host + _(http_span.attributes['http.method']).must_equal 'GET' + _(http_span.attributes['http.scheme']).must_equal scheme + _(http_span.attributes['http.target']).must_equal target + _(http_span.attributes['http.url']).must_equal "#{scheme}://#{host}#{port&.to_s&.prepend(':')}#{target}" + _(http_span.status.code).must_equal( + OpenTelemetry::Trace::Status::ERROR + ) + + if exception + exception_event = http_span.events.first + _(exception_event.attributes['exception.type']).must_equal(exception) + end + end + end end diff --git a/instrumentation/faraday/CHANGELOG.md b/instrumentation/faraday/CHANGELOG.md index 6c9384049..a3e04733d 100644 --- a/instrumentation/faraday/CHANGELOG.md +++ b/instrumentation/faraday/CHANGELOG.md @@ -1,5 +1,37 @@ # Release History: opentelemetry-instrumentation-faraday +### v0.24.5 / 2024-06-20 + +* FIXED: Compatibility with Faraday v1 + +### v0.24.4 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.24.3 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.24.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.24.1 / 2024-03-22 + +* FIXED: Propagate response attributes on Faraday::Error. + +### v0.24.0 / 2024-02-20 + +* ADDED: Faraday add support for internal spans + +### v0.23.4 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.23.3 / 2023-10-16 + +* FIXED: Omit `nil` `net.peer.name` attributes + ### v0.23.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/instrumentation.rb b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/instrumentation.rb index 0c98ea8f5..17d4826e8 100644 --- a/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/instrumentation.rb +++ b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/instrumentation.rb @@ -20,12 +20,14 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base defined?(::Faraday) end + option :span_kind, default: :client, validate: %i[client internal] option :peer_service, default: nil, validate: :string private def require_dependencies require_relative 'middlewares/tracer_middleware' + require_relative 'patches/connection' require_relative 'patches/rack_builder' end @@ -36,7 +38,11 @@ def register_tracer_middleware end def use_middleware_by_default - ::Faraday::RackBuilder.prepend(Patches::RackBuilder) + if Gem::Version.new(::Faraday::VERSION) >= Gem::Version.new('1') + ::Faraday::Connection.prepend(Patches::Connection) + else + ::Faraday::RackBuilder.prepend(Patches::RackBuilder) + end end end end diff --git a/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware.rb b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware.rb index be880c0ae..1b4452ac3 100644 --- a/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware.rb +++ b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware.rb @@ -25,42 +25,50 @@ class TracerMiddleware < ::Faraday::Middleware def call(env) http_method = HTTP_METHODS_SYMBOL_TO_STRING[env.method] + config = Faraday::Instrumentation.instance.config + attributes = span_creation_attributes( - http_method: http_method, url: env.url + http_method: http_method, url: env.url, config: config ) tracer.in_span( - "HTTP #{http_method}", attributes: attributes, kind: :client + "HTTP #{http_method}", attributes: attributes, kind: config.fetch(:span_kind) ) do |span| OpenTelemetry.propagation.inject(env.request_headers) - app.call(env).on_complete { |resp| trace_response(span, resp) } + app.call(env).on_complete { |resp| trace_response(span, resp.status) } + rescue ::Faraday::Error => e + trace_response(span, e.response[:status]) if e.response + + raise end end private - attr_reader :app - - def span_creation_attributes(http_method:, url:) + def span_creation_attributes(http_method:, url:, config:) instrumentation_attrs = { 'http.method' => http_method, 'http.url' => OpenTelemetry::Common::Utilities.cleanse_url(url.to_s), - 'net.peer.name' => url.host + 'faraday.adapter.name' => app.class.name } - config = Faraday::Instrumentation.instance.config + instrumentation_attrs['net.peer.name'] = url.host if url.host instrumentation_attrs['peer.service'] = config[:peer_service] if config[:peer_service] + instrumentation_attrs.merge!( OpenTelemetry::Common::HTTP::ClientContext.attributes ) end + # Versions prior to 1.0 do not define an accessor for app + attr_reader :app if Gem::Version.new(Faraday::VERSION) < Gem::Version.new('1.0.0') + def tracer Faraday::Instrumentation.instance.tracer end - def trace_response(span, response) - span.set_attribute('http.status_code', response.status) - span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(response.status.to_i) + def trace_response(span, status) + span.set_attribute('http.status_code', status) + span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(status.to_i) end end end diff --git a/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/patches/connection.rb b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/patches/connection.rb new file mode 100644 index 000000000..51d8ad0b5 --- /dev/null +++ b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/patches/connection.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module Faraday + module Patches + # Module to be prepended to force Faraday to use the middleware by + # default so the user doesn't have to call `use` for every connection. + module Connection + # Wraps Faraday::Connection#initialize: + # /~https://github.com/lostisland/faraday/blob/ff9dc1d1219a1bbdba95a9a4cf5d135b97247ee2/lib/faraday/connection.rb#L62-L92 + def initialize(*args) + super.tap do + use(:open_telemetry) unless builder.handlers.any? do |handler| + handler.klass == Middlewares::TracerMiddleware + end + end + end + end + end + end + end +end diff --git a/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/version.rb b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/version.rb index 2df7a1f27..f049c317f 100644 --- a/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/version.rb +++ b/instrumentation/faraday/lib/opentelemetry/instrumentation/faraday/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Faraday - VERSION = '0.23.2' + VERSION = '0.24.5' end end end diff --git a/instrumentation/faraday/opentelemetry-instrumentation-faraday.gemspec b/instrumentation/faraday/opentelemetry-instrumentation-faraday.gemspec index d89bcaf74..ecea6f673 100644 --- a/instrumentation/faraday/opentelemetry-instrumentation-faraday.gemspec +++ b/instrumentation/faraday/opentelemetry-instrumentation-faraday.gemspec @@ -26,16 +26,15 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'faraday', '~> 0.17.0' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb b/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb index f9f9617fb..8a0def73c 100644 --- a/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb +++ b/instrumentation/faraday/test/opentelemetry/instrumentation/faraday/middlewares/tracer_middleware_test.rb @@ -42,89 +42,174 @@ instrumentation.install end - it 'has http 200 attributes' do - response = client.get('/success') - - _(span.name).must_equal 'HTTP GET' - _(span.attributes['http.method']).must_equal 'GET' - _(span.attributes['http.status_code']).must_equal 200 - _(span.attributes['http.url']).must_equal 'http://example.com/success' - _(span.attributes['net.peer.name']).must_equal 'example.com' - _(response.env.request_headers['Traceparent']).must_equal( - "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" - ) - end + describe 'given a client with a base url' do + it 'has http 200 attributes' do + response = client.get('/success') + + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 200 + _(span.attributes['http.url']).must_equal 'http://example.com/success' + _(span.attributes['net.peer.name']).must_equal 'example.com' + _(response.env.request_headers['Traceparent']).must_equal( + "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" + ) + end - it 'has http.status_code 404' do - response = client.get('/not_found') - - _(span.name).must_equal 'HTTP GET' - _(span.attributes['http.method']).must_equal 'GET' - _(span.attributes['http.status_code']).must_equal 404 - _(span.attributes['http.url']).must_equal 'http://example.com/not_found' - _(span.attributes['net.peer.name']).must_equal 'example.com' - _(response.env.request_headers['Traceparent']).must_equal( - "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" - ) - end + it 'has http.status_code 404' do + response = client.get('/not_found') + + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 404 + _(span.attributes['http.url']).must_equal 'http://example.com/not_found' + _(span.attributes['net.peer.name']).must_equal 'example.com' + _(response.env.request_headers['Traceparent']).must_equal( + "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" + ) + end - it 'has http.status_code 500' do - response = client.get('/failure') - - _(span.name).must_equal 'HTTP GET' - _(span.attributes['http.method']).must_equal 'GET' - _(span.attributes['http.status_code']).must_equal 500 - _(span.attributes['http.url']).must_equal 'http://example.com/failure' - _(span.attributes['net.peer.name']).must_equal 'example.com' - _(response.env.request_headers['Traceparent']).must_equal( - "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" - ) - end + it 'has http.status_code 500' do + response = client.get('/failure') + + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 500 + _(span.attributes['http.url']).must_equal 'http://example.com/failure' + _(span.attributes['net.peer.name']).must_equal 'example.com' + _(response.env.request_headers['Traceparent']).must_equal( + "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" + ) + end + + it 'merges http client attributes' do + client_context_attrs = { + 'test.attribute' => 'test.value', 'http.method' => 'OVERRIDE' + } + response = OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + client.get('/success') + end + + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'OVERRIDE' + _(span.attributes['http.status_code']).must_equal 200 + _(span.attributes['http.url']).must_equal 'http://example.com/success' + _(span.attributes['net.peer.name']).must_equal 'example.com' + _(span.attributes['test.attribute']).must_equal 'test.value' + _(response.env.request_headers['Traceparent']).must_equal( + "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" + ) + end + + it 'accepts peer service name from config' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:faraday') - it 'merges http client attributes' do - client_context_attrs = { - 'test.attribute' => 'test.value', 'http.method' => 'OVERRIDE' - } - response = OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do client.get('/success') + + _(span.attributes['peer.service']).must_equal 'example:faraday' end - _(span.name).must_equal 'HTTP GET' - _(span.attributes['http.method']).must_equal 'OVERRIDE' - _(span.attributes['http.status_code']).must_equal 200 - _(span.attributes['http.url']).must_equal 'http://example.com/success' - _(span.attributes['net.peer.name']).must_equal 'example.com' - _(span.attributes['test.attribute']).must_equal 'test.value' - _(response.env.request_headers['Traceparent']).must_equal( - "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" - ) - end + it 'defaults to span kind client' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install - it 'accepts peer service name from config' do - instrumentation.instance_variable_set(:@installed, false) - instrumentation.install(peer_service: 'example:faraday') + client.get('/success') - client.get('/success') + _(span.kind).must_equal :client + end - _(span.attributes['peer.service']).must_equal 'example:faraday' - end + it 'allows overriding the span kind to internal' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(span_kind: :internal) - it 'prioritizes context attributes over config for peer service name' do - instrumentation.instance_variable_set(:@installed, false) - instrumentation.install(peer_service: 'example:faraday') + client.get('/success') + + _(span.kind).must_equal :internal + end + + it 'reports the name of the configured adapter' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install - client_context_attrs = { 'peer.service' => 'example:custom' } - OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do client.get('/success') + + _(span.attributes.fetch('faraday.adapter.name')).must_equal Faraday::Adapter::Test.name end - _(span.attributes['peer.service']).must_equal 'example:custom' + it 'prioritizes context attributes over config for peer service name' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:faraday') + + client_context_attrs = { 'peer.service' => 'example:custom' } + OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + client.get('/success') + end + + _(span.attributes['peer.service']).must_equal 'example:custom' + end + + it 'does not leak authentication credentials' do + client.run_request(:get, 'http://username:password@example.com/success', nil, {}) + + _(span.attributes['http.url']).must_equal 'http://example.com/success' + end end - it 'does not leak authentication credentials' do - client.run_request(:get, 'http://username:password@example.com/success', nil, {}) + describe 'given a client without a base url' do + let(:client) do + Faraday.new do |builder| + builder.adapter(:test) do |stub| + stub.get('/success') { |_| [200, {}, 'OK'] } + end + end + end + + it 'omits missing attributes' do + response = client.get('/success') + + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 200 + _(span.attributes['http.url']).must_equal 'http:/success' + _(span.attributes).wont_include('net.peer.name') + _(response.env.request_headers['Traceparent']).must_equal( + "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" + ) + end + end - _(span.attributes['http.url']).must_equal 'http://example.com/success' + describe 'when faraday raises an error' do + let(:client) do + Faraday.new do |builder| + builder.response :raise_error + builder.adapter(:test) do |stub| + stub.get('/not_found') { |_| [404, {}, 'NOT FOUND'] } + end + end + end + + it 'adds response attributes' do + assert_raises Faraday::Error do + client.get('/not_found') + end + + _(span.attributes['http.status_code']).must_equal 404 + _(span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR + end + end + + describe 'when explicitly adding the tracer middleware' do + let(:client) do + Faraday.new do |builder| + builder.use :open_telemetry + end + end + + it 'only adds the middleware once' do + tracers = client.builder.handlers.count(OpenTelemetry::Instrumentation::Faraday::Middlewares::TracerMiddleware) + _(tracers).must_equal 1 + end end end end diff --git a/instrumentation/grape/Appraisals b/instrumentation/grape/Appraisals index f12843271..0ada7ab1e 100644 --- a/instrumentation/grape/Appraisals +++ b/instrumentation/grape/Appraisals @@ -4,26 +4,15 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'grape-1.2' do - gem 'grape', '~> 1.2' +appraise 'grape-1.x' do + gem 'rack', '~> 2.0' + gem 'grape', '~> 1.8' end -appraise 'grape-1.3' do - gem 'grape', '~> 1.3' +appraise 'grape-2.0.x' do + gem 'grape', '~> 2.0.0' end -appraise 'grape-1.4' do - gem 'grape', '~> 1.4' -end - -appraise 'grape-1.5' do - gem 'grape', '~> 1.5' -end - -appraise 'grape-1.6' do - gem 'grape', '~> 1.6' -end - -appraise 'grape-1.7' do - gem 'grape', '~> 1.7' +appraise 'grape-latest' do + gem 'grape' end diff --git a/instrumentation/grape/CHANGELOG.md b/instrumentation/grape/CHANGELOG.md index f1fe30822..324d29dbf 100644 --- a/instrumentation/grape/CHANGELOG.md +++ b/instrumentation/grape/CHANGELOG.md @@ -1,5 +1,25 @@ # Release History: opentelemetry-instrumentation-grape +### v0.2.0 / 2024-07-02 + +* ADDED: Make the install of rack instrumentation by grape instrumentation optional + +### v0.1.8 / 2024-05-14 + +* DOCS: Instrumentation Authors Guide + +### v0.1.7 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.1.6 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.1.5 / 2023-10-31 + +* FIXED: Remove dependency on ActiveSupport core extensions from Grape instrumentation + ### v0.1.4 / 2023-08-02 * FIXED: Fix opentelemetry-api version constraint in grape gemspec diff --git a/instrumentation/grape/Gemfile b/instrumentation/grape/Gemfile index ed1ec924e..a97f3cccf 100644 --- a/instrumentation/grape/Gemfile +++ b/instrumentation/grape/Gemfile @@ -15,4 +15,5 @@ gemspec group :test do gem 'opentelemetry-instrumentation-base', path: '../base' gem 'opentelemetry-instrumentation-rack', path: '../rack' + gem 'builder' end diff --git a/instrumentation/grape/example/trace_demonstration.rb b/instrumentation/grape/example/trace_demonstration.rb index ca93f5eea..7f67a93d1 100644 --- a/instrumentation/grape/example/trace_demonstration.rb +++ b/instrumentation/grape/example/trace_demonstration.rb @@ -4,20 +4,22 @@ gemfile(true) do source 'https://rubygems.org' - gem 'opentelemetry-api' - gem 'opentelemetry-instrumentation-grape' + gem 'grape', '~> 1.2' gem 'opentelemetry-sdk' - gem 'grape' + gem 'opentelemetry-instrumentation-rack' + gem 'opentelemetry-instrumentation-grape' end -require 'opentelemetry-instrumentation-rack' - # Export traces to console ENV['OTEL_TRACES_EXPORTER'] ||= 'console' OpenTelemetry::SDK.configure do |c| c.service_name = 'trace_demonstration' - c.use_all # this will only require instrumentation gems it finds that are installed by bundler. + c.use_all # this will only require instrumentation gems it finds that are installed by bundler. +end + +at_exit do + OpenTelemetry.tracer_provider.shutdown end # A basic Grape API example @@ -30,9 +32,6 @@ class ExampleAPI < Grape::API end desc 'Return information about a user' - # Filters - before { sleep(0.01) } - after { sleep(0.01) } params do requires :id, type: Integer, desc: 'User ID' end @@ -45,7 +44,7 @@ class ExampleAPI < Grape::API builder = Rack::Builder.app do # Integration is automatic in web frameworks but plain Rack applications require this line. # Enable it in your config.ru. - use OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware + use *OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args run ExampleAPI end app = Rack::MockRequest.new(builder) diff --git a/instrumentation/grape/lib/opentelemetry/instrumentation/grape/event_handler.rb b/instrumentation/grape/lib/opentelemetry/instrumentation/grape/event_handler.rb index b39fafe33..1e5d2b9e2 100644 --- a/instrumentation/grape/lib/opentelemetry/instrumentation/grape/event_handler.rb +++ b/instrumentation/grape/lib/opentelemetry/instrumentation/grape/event_handler.rb @@ -79,7 +79,7 @@ def handle_payload_exception(span, exception) # Only record exceptions if they were not raised (i.e. do not have a status code in Grape) # or do not have a 5xx status code. These exceptions are recorded by Rack. # See instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/tracer_middleware.rb#L155 - return unless exception.respond_to?('status') && ::Rack::Utils.status_code(exception.status) < 500 + return unless exception.respond_to?(:status) && ::Rack::Utils.status_code(exception.status) < 500 span.record_exception(exception) span.status = OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{exception.class}") @@ -93,10 +93,10 @@ def path(endpoint) return '' unless endpoint.routes namespace = endpoint.routes.first.namespace - version = endpoint.routes.first.options[:version] || '' - prefix = endpoint.routes.first.options[:prefix]&.to_s || '' + version = endpoint.routes.first.options[:version]&.to_s + prefix = endpoint.routes.first.options[:prefix]&.to_s parts = [prefix, version] + namespace.split('/') + endpoint.options[:path] - parts.reject { |p| p.blank? || p.eql?('/') }.join('/').prepend('/') + parts.reject { |p| p.nil? || p.empty? || p.eql?('/') }.join('/').prepend('/') end def formatter_type(formatter) @@ -108,7 +108,7 @@ def formatter_type(formatter) end def built_in_grape_formatter?(formatter) - formatter.respond_to?('name') && formatter.name.include?('Grape::Formatter') + formatter.respond_to?(:name) && formatter.name.include?('Grape::Formatter') end end end diff --git a/instrumentation/grape/lib/opentelemetry/instrumentation/grape/instrumentation.rb b/instrumentation/grape/lib/opentelemetry/instrumentation/grape/instrumentation.rb index d31def440..66c89c23a 100644 --- a/instrumentation/grape/lib/opentelemetry/instrumentation/grape/instrumentation.rb +++ b/instrumentation/grape/lib/opentelemetry/instrumentation/grape/instrumentation.rb @@ -8,6 +8,16 @@ module OpenTelemetry module Instrumentation module Grape # The Instrumentation class contains logic to detect and install the Grape instrumentation + # # Configuration keys and options + # ## `:ignored_events` + # + # Default is `[]`. Specifies which ActiveSupport::Notifications events published by Grape to ignore. + # Ignored events will not be published as Span events. + # + # ## `:install_rack` + # + # Default is `true`. Specifies whether or not to install the Rack instrumentation as part of installing the Grape instrumentation. + # This is useful in cases where you have multiple Rack applications but want to manually specify where to insert the tracing middleware. class Instrumentation < OpenTelemetry::Instrumentation::Base # Minimum Grape version needed for compatibility with this instrumentation MINIMUM_VERSION = Gem::Version.new('1.2.0') @@ -27,6 +37,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end option :ignored_events, default: [], validate: :array + option :install_rack, default: true, validate: :boolean private @@ -35,6 +46,8 @@ def gem_version end def install_rack_instrumentation + return unless config[:install_rack] + OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install({}) end diff --git a/instrumentation/grape/lib/opentelemetry/instrumentation/grape/version.rb b/instrumentation/grape/lib/opentelemetry/instrumentation/grape/version.rb index 48400673f..c2a7ca5f4 100644 --- a/instrumentation/grape/lib/opentelemetry/instrumentation/grape/version.rb +++ b/instrumentation/grape/lib/opentelemetry/instrumentation/grape/version.rb @@ -8,7 +8,7 @@ module OpenTelemetry module Instrumentation module Grape # Current gem version - VERSION = '0.1.4' + VERSION = '0.2.0' end end end diff --git a/instrumentation/grape/opentelemetry-instrumentation-grape.gemspec b/instrumentation/grape/opentelemetry-instrumentation-grape.gemspec index 1d59fb9a6..8d194aa6d 100644 --- a/instrumentation/grape/opentelemetry-instrumentation-grape.gemspec +++ b/instrumentation/grape/opentelemetry-instrumentation-grape.gemspec @@ -32,14 +32,14 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'activesupport' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'grape', '~> 1.2' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.0' spec.add_development_dependency 'opentelemetry-test-helpers' spec.add_development_dependency 'rack-test' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/grape/test/opentelemetry/instrumentation/grape_test.rb b/instrumentation/grape/test/opentelemetry/instrumentation/grape_test.rb index 0e7ab4850..782dcc8d5 100644 --- a/instrumentation/grape/test/opentelemetry/instrumentation/grape_test.rb +++ b/instrumentation/grape/test/opentelemetry/instrumentation/grape_test.rb @@ -272,7 +272,7 @@ class RaisedErrorAPI < Grape::API it 'sets span status to error' do _(span.name).must_equal expected_span_name _(span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR - _(span.status.description).must_equal "Unhandled exception of type: #{expected_error_type}" + _(span.status.description).must_equal expected_error_type end it 'records the exception event' do @@ -316,7 +316,7 @@ class ErrorInFilterAPI < Grape::API it 'sets span status to error' do _(span.name).must_equal expected_span_name _(span.status.code).must_equal OpenTelemetry::Trace::Status::ERROR - _(span.status.description).must_equal "Unhandled exception of type: #{expected_error_type}" + _(span.status.description).must_equal expected_error_type end it 'records the exception event' do @@ -418,5 +418,56 @@ class IgnoredEventAPI < Grape::API _(events_per_name('grape.endpoint_render').length).must_equal 0 end end + + describe 'when install_rack is set to false' do + class BasicAPI < Grape::API + format :json + get :hello do + { message: 'Hello, world!' } + end + end + + let(:config) { { install_rack: false } } + + let(:app) do + builder = Rack::Builder.app do + run BasicAPI + end + Rack::MockRequest.new(builder) + end + + let(:request_path) { '/hello' } + let(:expected_span_name) { 'HTTP GET /hello' } + + describe 'missing rack installation' do + it 'disables tracing' do + app.get request_path + _(exporter.finished_spans).must_be_empty + end + end + + describe 'when rack is manually installed' do + let(:app) do + build_rack_app(BasicAPI) + end + + before do + OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install + end + + it 'creates a span' do + app.get request_path + _(exporter.finished_spans.first.attributes).must_equal( + 'code.namespace' => 'BasicAPI', + 'http.method' => 'GET', + 'http.host' => 'unknown', + 'http.scheme' => 'http', + 'http.target' => '/hello', + 'http.route' => '/hello', + 'http.status_code' => 200 + ) + end + end + end end end diff --git a/instrumentation/grape/test/test_helper.rb b/instrumentation/grape/test/test_helper.rb index b079d1571..7b12c181c 100644 --- a/instrumentation/grape/test/test_helper.rb +++ b/instrumentation/grape/test/test_helper.rb @@ -23,7 +23,7 @@ # Helper functions def uninstall_and_cleanup - instrumentation.instance_variable_set('@installed', false) + instrumentation.instance_variable_set(:@installed, false) unsubscribe EXPORTER.reset end diff --git a/instrumentation/graphql/Appraisals b/instrumentation/graphql/Appraisals index d478f591b..05298c160 100644 --- a/instrumentation/graphql/Appraisals +++ b/instrumentation/graphql/Appraisals @@ -4,18 +4,39 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'graphql-1.13' do +# Max compatible version of 1.x +appraise 'graphql-1.x' do gem 'graphql', '~> 1.13' end -appraise 'graphql-2.0.17' do - gem 'graphql', '2.0.17' -end - +# A bug was introduced in 2.0.18 that was fixed in 2.0.19 appraise 'graphql-2.0.18' do gem 'graphql', '2.0.18' end -appraise 'graphql-2.x' do - gem 'graphql', '>= 2.0.18', '< 3.0.0' +# Max compatible version of 2.0.x +appraise 'graphql-2.0' do + gem 'graphql', '~> 2.0.27' +end + +# Max compatible version of 2.1.x +appraise 'graphql-2.1' do + gem 'graphql', '~> 2.1.8' +end + +appraise 'graphql-c_parser-2.2.x' do + gem 'graphql', '~> 2.2.1' + gem 'graphql-c_parser', '~> 1.0.7' +end + +appraise 'graphql-2.2.x' do + gem 'graphql', '~> 2.2.1', '< 3.0.0' +end + +appraise 'graphql-c_parser-latest' do + gem 'graphql-c_parser' +end + +appraise 'graphql-latest' do + gem 'graphql' end diff --git a/instrumentation/graphql/CHANGELOG.md b/instrumentation/graphql/CHANGELOG.md index 1fb6b31a0..327842397 100644 --- a/instrumentation/graphql/CHANGELOG.md +++ b/instrumentation/graphql/CHANGELOG.md @@ -1,5 +1,27 @@ # Release History: opentelemetry-instrumentation-graphql +### v0.28.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.28.1 / 2024-04-10 + +* FIXED: Analyze span names in GraphQL instrumentation + +### v0.28.0 / 2024-02-16 + +* BREAKING CHANGE: GraphQL Legacy Tracer perf improvements [#867](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/867). + +* ADDED: GraphQL Legacy Tracer perf improvements [#867](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/867). + +### v0.27.0 / 2023-11-28 + +* CHANGED: Performance optimization cache attribute hashes [#723](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/723) + +### v0.26.8 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.26.7 / 2023-09-27 * FIXED: Micro optimization: build Hash w/ {} (/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/665) diff --git a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_trace.rb b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_trace.rb index 881aa10ec..70c966623 100644 --- a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_trace.rb +++ b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_trace.rb @@ -16,8 +16,46 @@ module GraphQLTrace # rubocop:disable Metrics/ModuleLength def initialize(trace_scalars: false, **_options) @trace_scalars = trace_scalars @_otel_field_key_cache = Hash.new { |h, k| h[k] = _otel_field_key(k) } + @_otel_field_key_cache.compare_by_identity @_otel_authorized_key_cache = Hash.new { |h, k| h[k] = _otel_authorized_key(k) } + @_otel_authorized_key_cache.compare_by_identity @_otel_resolve_type_key_cache = Hash.new { |h, k| h[k] = _otel_resolve_type_key(k) } + @_otel_resolve_type_key_cache.compare_by_identity + + @_otel_type_attrs_cache = Hash.new do |h, type| + h[type] = { + 'graphql.type.name' => type.graphql_name, + 'graphql.lazy' => false + }.freeze + end + @_otel_type_attrs_cache.compare_by_identity + + @_otel_lazy_type_attrs_cache = Hash.new do |h, type| + h[type] = { + 'graphql.type.name' => type.graphql_name, + 'graphql.lazy' => true + }.freeze + end + @_otel_lazy_type_attrs_cache.compare_by_identity + + @_otel_field_attrs_cache = Hash.new do |h, field| + h[field] = { + 'graphql.field.parent' => field.owner&.graphql_name, + 'graphql.field.name' => field.graphql_name, + 'graphql.lazy' => false + }.freeze + end + @_otel_field_attrs_cache.compare_by_identity + + @_otel_lazy_field_attrs_cache = Hash.new do |h, field| + h[field] = { + 'graphql.field.parent' => field.owner&.graphql_name, + 'graphql.field.name' => field.graphql_name, + 'graphql.lazy' => true + }.freeze + end + @_otel_lazy_field_attrs_cache.compare_by_identity + super end @@ -51,11 +89,11 @@ def validate(query:, validate:, &block) end def analyze_multiplex(multiplex:, &block) - tracer.in_span('graphql.analyze_query', &block) + tracer.in_span('graphql.analyze_multiplex', &block) end def analyze_query(query:, &block) - tracer.in_span('graphql.analyze_multiplex', &block) + tracer.in_span('graphql.analyze_query', &block) end def execute_query(query:, &block) @@ -73,26 +111,18 @@ def execute_query_lazy(query:, multiplex:, &block) def execute_field(field:, query:, ast_node:, arguments:, object:, &block) platform_key = _otel_execute_field_key(field: field) - return super unless platform_key + return super(field: field, query: query, ast_node: ast_node, object: object, arguments: arguments, &block) unless platform_key - attributes = { - 'graphql.field.parent' => field.owner&.graphql_name, - 'graphql.field.name' => field.graphql_name, - 'graphql.lazy' => false - } + attributes = @_otel_field_attrs_cache[field] tracer.in_span(platform_key, attributes: attributes, &block) end def execute_field_lazy(field:, query:, ast_node:, arguments:, object:, &block) platform_key = _otel_execute_field_key(field: field) - return super unless platform_key + return super(field: field, query: query, ast_node: ast_node, object: object, arguments: arguments, &block) unless platform_key - attributes = { - 'graphql.field.parent' => field.owner&.graphql_name, - 'graphql.field.name' => field.graphql_name, - 'graphql.lazy' => true - } + attributes = @_otel_lazy_field_attrs_cache[field] tracer.in_span(platform_key, attributes: attributes, &block) end @@ -101,10 +131,7 @@ def authorized(query:, type:, object:, &block) platform_key = @_otel_authorized_key_cache[type] return super unless platform_key - attributes = { - 'graphql.type.name' => type.graphql_name, - 'graphql.lazy' => false - } + attributes = @_otel_type_attrs_cache[type] tracer.in_span(platform_key, attributes: attributes, &block) end @@ -113,33 +140,19 @@ def authorized_lazy(query:, type:, object:, &block) platform_key = @_otel_authorized_key_cache[type] return super unless platform_key - attributes = { - 'graphql.type.name' => type.graphql_name, - 'graphql.lazy' => true - } - + attributes = @_otel_lazy_type_attrs_cache[type] tracer.in_span(platform_key, attributes: attributes, &block) end def resolve_type(query:, type:, object:, &block) platform_key = @_otel_resolve_type_key_cache[type] - - attributes = { - 'graphql.type.name' => type.graphql_name, - 'graphql.lazy' => false - } - + attributes = @_otel_type_attrs_cache[type] tracer.in_span(platform_key, attributes: attributes, &block) end def resolve_type_lazy(query:, type:, object:, &block) platform_key = @_otel_resolve_type_key_cache[type] - - attributes = { - 'graphql.type.name' => type.graphql_name, - 'graphql.lazy' => true - } - + attributes = @_otel_lazy_type_attrs_cache[type] tracer.in_span(platform_key, attributes: attributes, &block) end diff --git a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb index bd65f7d88..0d4c3b0ad 100644 --- a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb +++ b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/tracers/graphql_tracer.rb @@ -13,6 +13,8 @@ module Tracers # GraphQLTracer contains the OpenTelemetry tracer implementation compatible with # the GraphQL tracer API class GraphQLTracer < ::GraphQL::Tracing::PlatformTracing + DEFAULT_HASH = {}.freeze + self.platform_keys = { 'lex' => 'graphql.lex', 'parse' => 'graphql.parse', @@ -86,24 +88,60 @@ def config end def attributes_for(key, data) - attributes = {} case key - when 'execute_field', 'execute_field_lazy' - attributes['graphql.field.parent'] = data[:owner]&.graphql_name # owner is the concrete type, not interface - attributes['graphql.field.name'] = data[:field]&.graphql_name - attributes['graphql.lazy'] = key == 'execute_field_lazy' - when 'authorized', 'authorized_lazy' - attributes['graphql.type.name'] = data[:type]&.graphql_name - attributes['graphql.lazy'] = key == 'authorized_lazy' - when 'resolve_type', 'resolve_type_lazy' - attributes['graphql.type.name'] = data[:type]&.graphql_name - attributes['graphql.lazy'] = key == 'resolve_type_lazy' + when 'execute_field' + field_attr_cache = data[:query].context.namespace(:otel_attrs)[:execute_field_attrs] ||= attr_cache do |field| + attrs = {} + attrs['graphql.field.parent'] = field.owner.graphql_name if field.owner.graphql_name + attrs['graphql.field.name'] = field.graphql_name if field.graphql_name + attrs['graphql.lazy'] = false + attrs.freeze + end + field_attr_cache[data[:field]] + when 'execute_field_lazy' + lazy_field_attr_cache = data[:query].context.namespace(:otel_attrs)[:execute_field_lazy_attrs] ||= attr_cache do |field| + attrs = {} + attrs['graphql.field.parent'] = field.owner.graphql_name if field.owner.graphql_name + attrs['graphql.field.name'] = field.graphql_name if field.graphql_name + attrs['graphql.lazy'] = true + attrs.freeze + end + lazy_field_attr_cache[data[:field]] + when 'authorized', 'resolve_type' + type_attrs_cache = data[:context].namespace(:otel_attrs)[:type_attrs] ||= attr_cache do |type| + attrs = {} + attrs['graphql.type.name'] = type.graphql_name if type.graphql_name + attrs['graphql.lazy'] = false + attrs.freeze + end + type_attrs_cache[data[:type]] + when 'authorized_lazy', 'resolve_type_lazy' + type_lazy_attrs_cache = data[:context].namespace(:otel_attrs)[:type_lazy_attrs] ||= attr_cache do |type| + attrs = {} + attrs['graphql.type.name'] = type.graphql_name if type.graphql_name + attrs['graphql.lazy'] = true + attrs.freeze + end + type_lazy_attrs_cache[data[:type]] when 'execute_query' - attributes['graphql.operation.name'] = data[:query].selected_operation_name if data[:query].selected_operation_name - attributes['graphql.operation.type'] = data[:query].selected_operation.operation_type - attributes['graphql.document'] = data[:query].query_string + attrs = {} + attrs['graphql.document'] = data[:query].query_string if data[:query].query_string + # rubocop:disable Style/SafeNavigation - using safe navigation creates more objects, we want to avoid this + attrs['graphql.operation.type'] = data[:query].selected_operation.operation_type if data[:query].selected_operation && data[:query].selected_operation.operation_type + # rubocop:enable Style/SafeNavigation + attrs['graphql.operation.name'] = data[:query].selected_operation_name || 'anonymous' + attrs.freeze + else + DEFAULT_HASH + end + end + + def attr_cache + cache_h = Hash.new do |h, k| + h[k] = yield(k) end - attributes + cache_h.compare_by_identity + cache_h end end end diff --git a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/version.rb b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/version.rb index aca6963d2..03976e332 100644 --- a/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/version.rb +++ b/instrumentation/graphql/lib/opentelemetry/instrumentation/graphql/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module GraphQL - VERSION = '0.26.7' + VERSION = '0.28.2' end end end diff --git a/instrumentation/graphql/opentelemetry-instrumentation-graphql.gemspec b/instrumentation/graphql/opentelemetry-instrumentation-graphql.gemspec index c5a8678c3..061c34518 100644 --- a/instrumentation/graphql/opentelemetry-instrumentation-graphql.gemspec +++ b/instrumentation/graphql/opentelemetry-instrumentation-graphql.gemspec @@ -30,12 +30,12 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'graphql', '>= 1.9.0', '< 3.0.0' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/graphql/test/instrumentation/graphql/instrumentation_test.rb b/instrumentation/graphql/test/instrumentation/graphql/instrumentation_test.rb index 916beaca5..580918fb8 100644 --- a/instrumentation/graphql/test/instrumentation/graphql/instrumentation_test.rb +++ b/instrumentation/graphql/test/instrumentation/graphql/instrumentation_test.rb @@ -54,7 +54,7 @@ OpenTelemetry::TestHelpers.with_test_logger do |log| instrumentation.install(config) _(log.string).must_match( - / Unable to patch schema Old::Truck: undefined method `trace_with' for Old::Truck:Class/ + /undefined method `trace_with'.*Old::Truck/ ) end end @@ -90,7 +90,7 @@ instrumentation.install(config) _(log.string).must_match( - /Unable to patch schema Old::Truck: undefined method `use' for Old::Truck:Class/ + /undefined method `use'.*Old::Truck/ ) end end diff --git a/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_trace_test.rb b/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_trace_test.rb index 26b2cf6a2..22e194036 100644 --- a/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_trace_test.rb +++ b/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_trace_test.rb @@ -47,13 +47,15 @@ 'graphql.lex', 'graphql.parse', 'graphql.validate', - 'graphql.analyze_multiplex', 'graphql.analyze_query', + 'graphql.analyze_multiplex', 'graphql.execute_query', 'graphql.execute_query_lazy', 'graphql.execute_multiplex' ] + expected_spans.delete('graphql.lex') unless trace_lex_supported? + expected_result = { 'simpleField' => 'Hello.', 'resolvedField' => { 'originalValue' => 'testing=1', 'uppercasedValue' => 'TESTING=1' } @@ -97,14 +99,13 @@ after do # Reset various instance variables to clear state between tests - SomeOtherGraphQLAppSchema.instance_variable_set(:@own_tracers, []) - SomeOtherGraphQLAppSchema.instance_variable_set(:@own_plugins, SomeOtherGraphQLAppSchema.plugins[0..1]) + [GraphQL::Schema, SomeOtherGraphQLAppSchema, SomeGraphQLAppSchema].each(&:_reset_tracer_for_testing) end it 'traces the provided schemas' do SomeOtherGraphQLAppSchema.execute('query SimpleQuery{ __typename }') - _(spans.size).must_equal(8) + _(spans.select { |s| s.name.start_with?('graphql.') }).wont_be(:empty?) end it 'does not trace all schemas' do @@ -292,7 +293,7 @@ describe 'compatibility with other tracers' do let(:config) { { enable_platform_field: true } } - if GraphQL::Tracing.const_defined?('PlatformTrace') + if GraphQL::Tracing.const_defined?(:PlatformTrace) module CustomPlatformTracer include ::GraphQL::Tracing::PlatformTrace diff --git a/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb b/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb index 496580bee..61908a79d 100644 --- a/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb +++ b/instrumentation/graphql/test/instrumentation/graphql/tracers/graphql_tracer_test.rb @@ -53,6 +53,7 @@ 'graphql.execute_query_lazy', 'graphql.execute_multiplex' ] + expected_spans.delete('graphql.lex') unless trace_lex_supported? expected_result = { 'simpleField' => 'Hello.', @@ -82,7 +83,8 @@ it 'omits nil attributes for execute_query' do expected_attributes = { 'graphql.operation.type' => 'query', - 'graphql.document' => '{ simpleField }' + 'graphql.document' => '{ simpleField }', + 'graphql.operation.name' => 'anonymous' } SomeGraphQLAppSchema.execute('{ simpleField }') @@ -97,14 +99,12 @@ after do # Reset various instance variables to clear state between tests - SomeOtherGraphQLAppSchema.instance_variable_set(:@own_tracers, []) - SomeOtherGraphQLAppSchema.instance_variable_set(:@own_plugins, SomeOtherGraphQLAppSchema.plugins[0..1]) + [GraphQL::Schema, SomeOtherGraphQLAppSchema, SomeGraphQLAppSchema].each(&:_reset_tracer_for_testing) end it 'traces the provided schemas' do SomeOtherGraphQLAppSchema.execute('query SimpleQuery{ __typename }') - - _(spans.size).must_equal(8) + _(spans.select { |s| s.name.start_with?('graphql.') }).wont_be(:empty?) end it 'does not trace all schemas' do @@ -142,7 +142,7 @@ it 'includes attributes using platform types' do skip if uses_platform_interfaces? expected_attributes = { - 'graphql.field.parent' => 'Car', # type name, not interface + 'graphql.field.parent' => 'Vehicle', # interface name, not type 'graphql.field.name' => 'model', 'graphql.lazy' => false } diff --git a/instrumentation/graphql/test/test_helper.rb b/instrumentation/graphql/test/test_helper.rb index 330b3d836..7129b1408 100644 --- a/instrumentation/graphql/test/test_helper.rb +++ b/instrumentation/graphql/test/test_helper.rb @@ -25,8 +25,9 @@ module SchemaTestPatches # Reseting @graphql_definition is needed for tests running against version `1.9.x` # Other variables are used by ~> 2.0.19 def _reset_tracer_for_testing - %w[own_tracers trace_modes trace_class tracers graphql_definition].each do |ivar| - remove_instance_variable("@#{ivar}") if instance_variable_defined?("@#{ivar}") + %w[own_tracers trace_modes trace_class tracers graphql_definition own_trace_modes].each do |name| + ivar_name = "@#{name}" + remove_instance_variable(ivar_name) if instance_variable_defined?(ivar_name) end end end @@ -125,3 +126,12 @@ def uses_platform_interfaces? def gem_version Gem::Version.new(GraphQL::VERSION) end + +# When tracing, is the parser expected to call `lex` before `parse` +def trace_lex_supported? + return @trace_lex_supported if defined?(@trace_lex_supported) + + # In GraphQL 2.2, the default parser was changed such that `lex` is no longer called + @trace_lex_supported = Gem::Requirement.new('< 2.2').satisfied_by?(Gem::Version.new(GraphQL::VERSION)) || + (defined?(GraphQL::CParser) == 'constant') +end diff --git a/instrumentation/gruf/Appraisals b/instrumentation/gruf/Appraisals index 8501003fc..c07e47bb9 100644 --- a/instrumentation/gruf/Appraisals +++ b/instrumentation/gruf/Appraisals @@ -4,10 +4,14 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'gruf-2.15.1' do - gem 'gruf', '~> 2.15.1' +appraise 'gruf-2.17' do + gem 'gruf', '~> 2.17.0' end -appraise 'gruf-2.16.1' do - gem 'gruf', '~> 2.16.1' +appraise 'gruf-2.18' do + gem 'gruf', '~> 2.18.0' +end + +appraise 'gruf-2.19' do + gem 'gruf', '~> 2.19.0' end diff --git a/instrumentation/gruf/CHANGELOG.md b/instrumentation/gruf/CHANGELOG.md index 8cdbb4f4a..87dfd51a0 100644 --- a/instrumentation/gruf/CHANGELOG.md +++ b/instrumentation/gruf/CHANGELOG.md @@ -1,5 +1,17 @@ # Release History: opentelemetry-instrumentation-gruf +### v0.2.1 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.2.0 / 2024-02-20 + +* ADDED: Add support gruf 2.19 + +### v0.1.1 / 2023-10-16 + +* FIXED: Remove activesupport dependency + ### v0.1.0 / 2023-08-07 * Initial release! diff --git a/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/client.rb b/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/client.rb index d1c28022f..ffb69973f 100644 --- a/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/client.rb +++ b/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/client.rb @@ -10,7 +10,7 @@ module Gruf module Interceptors class Client < ::Gruf::Interceptors::ClientInterceptor def call(request_context:) - return yield if instrumentation_config.blank? + return yield if instrumentation_config.empty? service = request_context.method.split('/')[1] method = request_context.method_name diff --git a/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/server.rb b/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/server.rb index 5609d9c63..fec68f357 100644 --- a/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/server.rb +++ b/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/interceptors/server.rb @@ -10,7 +10,7 @@ module Gruf module Interceptors class Server < ::Gruf::Interceptors::ServerInterceptor def call - return yield if instrumentation_config.blank? + return yield if instrumentation_config.empty? method = request.method_name diff --git a/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/version.rb b/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/version.rb index 0c87d64fe..8cb09a5a8 100644 --- a/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/version.rb +++ b/instrumentation/gruf/lib/opentelemetry/instrumentation/gruf/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Gruf - VERSION = '0.1.0' + VERSION = '0.2.1' end end end diff --git a/instrumentation/gruf/opentelemetry-instrumentation-gruf.gemspec b/instrumentation/gruf/opentelemetry-instrumentation-gruf.gemspec index 4a7a0b550..f1c494593 100644 --- a/instrumentation/gruf/opentelemetry-instrumentation-gruf.gemspec +++ b/instrumentation/gruf/opentelemetry-instrumentation-gruf.gemspec @@ -31,12 +31,12 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '>= 1.17' spec.add_development_dependency 'grpc_mock' - spec.add_development_dependency 'gruf', '>= 2.15.1' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.0' spec.add_development_dependency 'opentelemetry-test-helpers' spec.add_development_dependency 'rake', '~> 12.3.3' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/http/CHANGELOG.md b/instrumentation/http/CHANGELOG.md index e27725612..59ab58139 100644 --- a/instrumentation/http/CHANGELOG.md +++ b/instrumentation/http/CHANGELOG.md @@ -1,5 +1,13 @@ # Release History: opentelemetry-instrumentation-http +### v0.23.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.23.2 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.23.1 / 2023-06-05 * FIXED: Base config options diff --git a/instrumentation/http/lib/opentelemetry/instrumentation/http/patches/client.rb b/instrumentation/http/lib/opentelemetry/instrumentation/http/patches/client.rb index 21153a926..f814e1a65 100644 --- a/instrumentation/http/lib/opentelemetry/instrumentation/http/patches/client.rb +++ b/instrumentation/http/lib/opentelemetry/instrumentation/http/patches/client.rb @@ -43,7 +43,7 @@ def annotate_span_with_response!(span, response) status_code = response.status.to_i span.set_attribute('http.status_code', status_code) - span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(status_code.to_i) + span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(status_code.to_i) end def create_request_span_name(request_method, request_path) diff --git a/instrumentation/http/lib/opentelemetry/instrumentation/http/version.rb b/instrumentation/http/lib/opentelemetry/instrumentation/http/version.rb index 482b29b2f..9e392212c 100644 --- a/instrumentation/http/lib/opentelemetry/instrumentation/http/version.rb +++ b/instrumentation/http/lib/opentelemetry/instrumentation/http/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module HTTP - VERSION = '0.23.1' + VERSION = '0.23.3' end end end diff --git a/instrumentation/http/opentelemetry-instrumentation-http.gemspec b/instrumentation/http/opentelemetry-instrumentation-http.gemspec index c8cd137b4..68f6bad7e 100644 --- a/instrumentation/http/opentelemetry-instrumentation-http.gemspec +++ b/instrumentation/http/opentelemetry-instrumentation-http.gemspec @@ -30,13 +30,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'http' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/http/test/instrumentation/http/patches/client_test.rb b/instrumentation/http/test/instrumentation/http/patches/client_test.rb index 65ba6569c..8d62a5b07 100644 --- a/instrumentation/http/test/instrumentation/http/patches/client_test.rb +++ b/instrumentation/http/test/instrumentation/http/patches/client_test.rb @@ -26,7 +26,7 @@ propagator = OpenTelemetry::Trace::Propagation::TraceContext.text_map_propagator OpenTelemetry.propagation = propagator # simulate a fresh install: - instrumentation.instance_variable_set('@installed', false) + instrumentation.instance_variable_set(:@installed, false) instrumentation.install(config) stub_request(:get, 'http://example.com/success').to_return(status: 200) stub_request(:post, 'http://example.com/failure').to_return(status: 500) @@ -128,7 +128,7 @@ let(:span_name_formatter) do # demonstrate simple addition of path and string to span name: lambda { |request_method, request_path| - return "HTTP #{request_method} #{request_path} miniswan" + "HTTP #{request_method} #{request_path} miniswan" } end diff --git a/instrumentation/http_client/CHANGELOG.md b/instrumentation/http_client/CHANGELOG.md index cd3058832..419dfcfa4 100644 --- a/instrumentation/http_client/CHANGELOG.md +++ b/instrumentation/http_client/CHANGELOG.md @@ -1,5 +1,21 @@ # Release History: opentelemetry-instrumentation-http_client +### v0.22.6 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.22.5 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.22.4 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.22.3 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.22.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/patches/client.rb b/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/patches/client.rb index fcdae5e70..a71c8ad7b 100644 --- a/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/patches/client.rb +++ b/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/patches/client.rb @@ -42,7 +42,7 @@ def annotate_span_with_response!(span, response) status_code = response.status_code.to_i span.set_attribute('http.status_code', status_code) - span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(status_code.to_i) + span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(status_code.to_i) end def tracer diff --git a/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/version.rb b/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/version.rb index b5c7bdd29..5f263a756 100644 --- a/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/version.rb +++ b/instrumentation/http_client/lib/opentelemetry/instrumentation/http_client/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module HttpClient - VERSION = '0.22.2' + VERSION = '0.22.6' end end end diff --git a/instrumentation/http_client/opentelemetry-instrumentation-http_client.gemspec b/instrumentation/http_client/opentelemetry-instrumentation-http_client.gemspec index 6bd4328fe..d393f10e0 100644 --- a/instrumentation/http_client/opentelemetry-instrumentation-http_client.gemspec +++ b/instrumentation/http_client/opentelemetry-instrumentation-http_client.gemspec @@ -26,17 +26,16 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'httpclient' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/httpx/.yardopts b/instrumentation/httpx/.yardopts new file mode 100644 index 000000000..66530b20f --- /dev/null +++ b/instrumentation/httpx/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry HTTPX Instrumentation +--markup=markdown +--main=README.md +./lib/opentelemetry/instrumentation/**/*.rb +./lib/opentelemetry/instrumentation.rb +- +README.md +CHANGELOG.md diff --git a/instrumentation/httpx/Appraisals b/instrumentation/httpx/Appraisals new file mode 100644 index 000000000..6d5b4793c --- /dev/null +++ b/instrumentation/httpx/Appraisals @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +appraise 'httpx-1' do + gem 'httpx', '~> 1.0' +end + +appraise 'httpx-0' do + gem 'httpx', '~> 0.24' +end diff --git a/instrumentation/httpx/CHANGELOG.md b/instrumentation/httpx/CHANGELOG.md new file mode 100644 index 000000000..74a508b72 --- /dev/null +++ b/instrumentation/httpx/CHANGELOG.md @@ -0,0 +1,13 @@ +# Release History: opentelemetry-instrumentation-httpx + +### v0.1.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.1.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.1.0 / 2023-11-06 + +* Initial Release. diff --git a/instrumentation/httpx/Gemfile b/instrumentation/httpx/Gemfile new file mode 100644 index 000000000..8b038cdcb --- /dev/null +++ b/instrumentation/httpx/Gemfile @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +source 'https://rubygems.org' + +gemspec + +group :test do + gem 'opentelemetry-instrumentation-base', path: '../base' + gem 'pry-byebug', platform: 'ruby' +end diff --git a/instrumentation/httpx/LICENSE b/instrumentation/httpx/LICENSE new file mode 100644 index 000000000..1ef7dad2c --- /dev/null +++ b/instrumentation/httpx/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/instrumentation/httpx/README.md b/instrumentation/httpx/README.md new file mode 100644 index 000000000..2053e8ab1 --- /dev/null +++ b/instrumentation/httpx/README.md @@ -0,0 +1,49 @@ +# OpenTelemetry Http Instrumentation + +The HTTPX instrumentation is a community-maintained instrumentation for the [HTTPX][httpx-home] gem. + +## How do I get started? + +Install the gem using: + +``` +gem install opentelemetry-instrumentation-httpx +``` + +Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-httpx` in your `Gemfile`. + +## Usage + +To use the instrumentation, call `use` with the name of the instrumentation: + +```ruby +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::HTTPX' +end +``` + +Alternatively, you can also call `use_all` to install all the available instrumentation. + +```ruby +OpenTelemetry::SDK.configure do |c| + c.use_all +end +``` + +## How can I get involved? + +The `opentelemetry-instrumentation-httpx` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us in [GitHub Discussions][discussions-url] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +The `opentelemetry-instrumentation-httpx` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + +[http-home]: https://gitlab.com/os85/httpx +[bundler-home]: https://bundler.io +[repo-github]: /~https://github.com/open-telemetry/opentelemetry-ruby +[license-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/LICENSE +[ruby-sig]: /~https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: /~https://github.com/open-telemetry/community#community-meetings +[discussions-url]: /~https://github.com/open-telemetry/opentelemetry-ruby/discussions diff --git a/instrumentation/httpx/Rakefile b/instrumentation/httpx/Rakefile new file mode 100644 index 000000000..1a64ba842 --- /dev/null +++ b/instrumentation/httpx/Rakefile @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] +end + +YARD::Rake::YardocTask.new do |t| + t.stats_options = ['--list-undoc'] +end + +if RUBY_ENGINE == 'truffleruby' + task default: %i[test] +else + task default: %i[test rubocop yard] +end diff --git a/instrumentation/httpx/example/trace_demonstration.rb b/instrumentation/httpx/example/trace_demonstration.rb new file mode 100644 index 000000000..f0afdbf11 --- /dev/null +++ b/instrumentation/httpx/example/trace_demonstration.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'bundler/inline' + +gemfile(true) do + source 'https://rubygems.org' + gem 'opentelemetry-api' + gem 'opentelemetry-instrumentation-base' + gem 'opentelemetry-instrumentation-httpx' + gem 'opentelemetry-sdk' + gem 'httpx' +end + +require 'opentelemetry-api' +require 'opentelemetry-sdk' +require 'opentelemetry-instrumentation-httpx' +require 'httpx' + +# Export traces to console by default +ENV['OTEL_TRACES_EXPORTER'] ||= 'console' + +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::HTTPX' +end + +# A basic HTTP example +HTTPX.get('https://github.com') diff --git a/instrumentation/httpx/lib/opentelemetry-instrumentation-httpx.rb b/instrumentation/httpx/lib/opentelemetry-instrumentation-httpx.rb new file mode 100644 index 000000000..c034f140f --- /dev/null +++ b/instrumentation/httpx/lib/opentelemetry-instrumentation-httpx.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'opentelemetry/instrumentation' diff --git a/instrumentation/httpx/lib/opentelemetry/instrumentation.rb b/instrumentation/httpx/lib/opentelemetry/instrumentation.rb new file mode 100644 index 000000000..b9459a853 --- /dev/null +++ b/instrumentation/httpx/lib/opentelemetry/instrumentation.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# OpenTelemetry is an open source observability framework, providing a +# general-purpose API, SDK, and related tools required for the instrumentation +# of cloud-native software, frameworks, and libraries. +# +# The OpenTelemetry module provides global accessors for telemetry objects. +# See the documentation for the `opentelemetry-api` gem for details. +module OpenTelemetry + # Instrumentation should be able to handle the case when the library is not installed on a user's system. + module Instrumentation + end +end + +require_relative 'instrumentation/httpx' diff --git a/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx.rb b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx.rb new file mode 100644 index 000000000..70e00f6cc --- /dev/null +++ b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry' +require 'opentelemetry-instrumentation-base' + +module OpenTelemetry + module Instrumentation + # Contains the OpenTelemetry instrumentation for the Http gem + module HTTPX + end + end +end + +require_relative 'httpx/instrumentation' +require_relative 'httpx/version' diff --git a/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/instrumentation.rb b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/instrumentation.rb new file mode 100644 index 000000000..9174343b4 --- /dev/null +++ b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/instrumentation.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module HTTPX + # The Instrumentation class contains logic to detect and install the Http instrumentation + class Instrumentation < OpenTelemetry::Instrumentation::Base + install do |_config| + require_dependencies + patch + end + + compatible do + Gem::Version.new(::HTTPX::VERSION) >= Gem::Version.new('0.24.7') + end + + present do + defined?(::HTTPX) + end + + option :peer_service, default: nil, validate: :string + + def patch + otel_session = ::HTTPX.plugin(Plugin) + + ::HTTPX.send(:remove_const, :Session) + ::HTTPX.send(:const_set, :Session, otel_session.class) + end + + def require_dependencies + require_relative 'plugin' + end + end + end + end +end diff --git a/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/plugin.rb b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/plugin.rb new file mode 100644 index 000000000..137fca976 --- /dev/null +++ b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/plugin.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module HTTPX + module Plugin + # Instruments around HTTPX's request/response lifecycle in order to generate + # an OTEL trace. + class RequestTracer + def initialize(request) + @request = request + end + + def call + @request.on(:response, &method(:finish)) # rubocop:disable Performance/MethodObjectAsBlock + + uri = @request.uri + request_method = @request.verb + span_name = "HTTP #{request_method}" + + attributes = { + OpenTelemetry::SemanticConventions::Trace::HTTP_HOST => uri.host, + OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => request_method, + OpenTelemetry::SemanticConventions::Trace::HTTP_SCHEME => uri.scheme, + OpenTelemetry::SemanticConventions::Trace::HTTP_TARGET => uri.path, + OpenTelemetry::SemanticConventions::Trace::HTTP_URL => "#{uri.scheme}://#{uri.host}", + OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => uri.host, + OpenTelemetry::SemanticConventions::Trace::NET_PEER_PORT => uri.port + } + config = HTTPX::Instrumentation.instance.config + attributes[OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service] + attributes.merge!( + OpenTelemetry::Common::HTTP::ClientContext.attributes + ) + + @span = tracer.start_span(span_name, attributes: attributes, kind: :client) + trace_ctx = OpenTelemetry::Trace.context_with_span(@span) + @trace_token = OpenTelemetry::Context.attach(trace_ctx) + + OpenTelemetry.propagation.inject(@request.headers) + rescue StandardError => e + OpenTelemetry.handle_error(exception: e) + end + + def finish(response) + return unless @span + + if response.is_a?(::HTTPX::ErrorResponse) + @span.record_exception(response.error) + @span.status = Trace::Status.error("Unhandled exception of type: #{response.error.class}") + else + @span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, response.status) + @span.status = Trace::Status.error unless (100..399).cover?(response.status) + end + + OpenTelemetry::Context.detach(@trace_token) if @trace_token + @span.finish + end + + private + + def tracer + HTTPX::Instrumentation.instance.tracer + end + end + + # HTTPX::Request overrides + module RequestMethods + def __otel_enable_trace! + return if @__otel_enable_trace + + RequestTracer.new(self).call + @__otel_enable_trace = true + end + end + + # HTTPX::Connection overrides + module ConnectionMethods + def send(request) + request.__otel_enable_trace! + + super + end + end + end + end + end +end diff --git a/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/version.rb b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/version.rb new file mode 100644 index 000000000..d5df1b2da --- /dev/null +++ b/instrumentation/httpx/lib/opentelemetry/instrumentation/httpx/version.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Instrumentation + module HTTPX + VERSION = '0.1.2' + end + end +end diff --git a/instrumentation/httpx/opentelemetry-instrumentation-httpx.gemspec b/instrumentation/httpx/opentelemetry-instrumentation-httpx.gemspec new file mode 100644 index 000000000..9d7858466 --- /dev/null +++ b/instrumentation/httpx/opentelemetry-instrumentation-httpx.gemspec @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +lib = File.expand_path('lib', __dir__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'opentelemetry/instrumentation/httpx/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-instrumentation-httpx' + spec.version = OpenTelemetry::Instrumentation::HTTPX::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'HTTPX instrumentation for the OpenTelemetry framework' + spec.description = 'HTTPX instrumentation for the OpenTelemetry framework' + spec.homepage = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib' + spec.license = 'Apache-2.0' + + spec.files = Dir.glob('lib/**/*.rb') + + Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.0' + + spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' + + spec.add_development_dependency 'appraisal', '~> 2.5' + spec.add_development_dependency 'bundler', '~> 2.4' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' + spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' + spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rspec-mocks' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' + spec.add_development_dependency 'simplecov', '~> 0.17.1' + spec.add_development_dependency 'webmock', '~> 3.19' + spec.add_development_dependency 'yard', '~> 0.9' + + if spec.respond_to?(:metadata) + spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md" + spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/instrumentation/http' + spec.metadata['bug_tracker_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues' + spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}" + end +end diff --git a/instrumentation/httpx/test/instrumentation/httpx/instrumentation_test.rb b/instrumentation/httpx/test/instrumentation/httpx/instrumentation_test.rb new file mode 100644 index 000000000..70dda9013 --- /dev/null +++ b/instrumentation/httpx/test/instrumentation/httpx/instrumentation_test.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../../lib/opentelemetry/instrumentation/httpx' + +describe OpenTelemetry::Instrumentation::HTTPX do + let(:instrumentation) { OpenTelemetry::Instrumentation::HTTPX::Instrumentation.instance } + + it 'has #name' do + _(instrumentation.name).must_equal 'OpenTelemetry::Instrumentation::HTTPX' + end + + it 'has #version' do + _(instrumentation.version).wont_be_nil + _(instrumentation.version).wont_be_empty + end + + describe '#install' do + it 'accepts argument' do + _(instrumentation.install({})).must_equal(true) + end + end +end diff --git a/instrumentation/httpx/test/instrumentation/plugin_test.rb b/instrumentation/httpx/test/instrumentation/plugin_test.rb new file mode 100644 index 000000000..585e8f50f --- /dev/null +++ b/instrumentation/httpx/test/instrumentation/plugin_test.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require_relative '../../lib/opentelemetry/instrumentation/httpx' +require_relative '../../lib/opentelemetry/instrumentation/httpx/plugin' + +describe OpenTelemetry::Instrumentation::HTTPX::Plugin do + let(:instrumentation) { OpenTelemetry::Instrumentation::HTTPX::Instrumentation.instance } + let(:exporter) { EXPORTER } + let(:span) { exporter.finished_spans.first } + + before do + exporter.reset + stub_request(:get, 'http://example.com/success').to_return(status: 200) + stub_request(:get, 'http://example.com/failure').to_return(status: 500) + stub_request(:get, 'http://example.com/timeout').to_timeout + end + + # Force re-install of instrumentation + after { instrumentation.instance_variable_set(:@installed, false) } + + describe 'tracing' do + before do + instrumentation.install + end + + it 'before request' do + _(exporter.finished_spans.size).must_equal 0 + end + + it 'after request with success code' do + HTTPX.get('http://example.com/success') + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 200 + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.target']).must_equal '/success' + assert_requested( + :get, + 'http://example.com/success', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'after request with failure code' do + HTTPX.get('http://example.com/failure') + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.status_code']).must_equal 500 + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.target']).must_equal '/failure' + assert_requested( + :get, + 'http://example.com/failure', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'after request timeout' do + response = HTTPX.get('http://example.com/timeout') + assert response.is_a?(HTTPX::ErrorResponse) + assert response.error.is_a?(HTTPX::TimeoutError) + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'GET' + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.target']).must_equal '/timeout' + _(span.status.code).must_equal( + OpenTelemetry::Trace::Status::ERROR + ) + _(span.status.description).must_equal( + 'Unhandled exception of type: HTTPX::TimeoutError' + ) + assert_requested( + :get, + 'http://example.com/timeout', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'merges HTTP client context' do + client_context_attrs = { + 'test.attribute' => 'test.value', 'http.method' => 'OVERRIDE' + } + + OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + HTTPX.get('http://example.com/success') + end + + _(exporter.finished_spans.size).must_equal 1 + _(span.name).must_equal 'HTTP GET' + _(span.attributes['http.method']).must_equal 'OVERRIDE' + _(span.attributes['http.status_code']).must_equal 200 + _(span.attributes['http.scheme']).must_equal 'http' + _(span.attributes['http.host']).must_equal 'example.com' + _(span.attributes['http.target']).must_equal '/success' + _(span.attributes['test.attribute']).must_equal 'test.value' + assert_requested( + :get, + 'http://example.com/success', + headers: { 'Traceparent' => "00-#{span.hex_trace_id}-#{span.hex_span_id}-01" } + ) + end + + it 'accepts peer service name from config' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:httpx') + + HTTPX.get('http://example.com/success') + + _(span.attributes['peer.service']).must_equal 'example:httpx' + end + + it 'prioritizes context attributes over config for peer service name' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'example:static') + + client_context_attrs = { 'peer.service' => 'example:custom' } + OpenTelemetry::Common::HTTP::ClientContext.with_attributes(client_context_attrs) do + HTTPX.get('http://example.com/success') + end + + _(span.attributes['peer.service']).must_equal 'example:custom' + end + end +end diff --git a/instrumentation/httpx/test/test_helper.rb b/instrumentation/httpx/test/test_helper.rb new file mode 100644 index 000000000..10c09f2da --- /dev/null +++ b/instrumentation/httpx/test/test_helper.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/setup' +Bundler.require(:default, :development, :test) + +require 'minitest/autorun' +require 'httpx/adapters/webmock' +require 'webmock/minitest' + +# global opentelemetry-sdk setup: +EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new +span_processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(EXPORTER) + +OpenTelemetry::SDK.configure do |c| + c.error_handler = ->(exception:, message:) { raise(exception || message) } + c.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym) + c.add_span_processor span_processor +end diff --git a/instrumentation/koala/CHANGELOG.md b/instrumentation/koala/CHANGELOG.md index 3c4b93039..1cd0e5d62 100644 --- a/instrumentation/koala/CHANGELOG.md +++ b/instrumentation/koala/CHANGELOG.md @@ -1,5 +1,17 @@ # Release History: opentelemetry-instrumentation-koala +### v0.20.5 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.20.4 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.20.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + ### v0.20.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/koala/lib/opentelemetry/instrumentation/koala/version.rb b/instrumentation/koala/lib/opentelemetry/instrumentation/koala/version.rb index 3786defde..7d2588e92 100644 --- a/instrumentation/koala/lib/opentelemetry/instrumentation/koala/version.rb +++ b/instrumentation/koala/lib/opentelemetry/instrumentation/koala/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Koala - VERSION = '0.20.2' + VERSION = '0.20.5' end end end diff --git a/instrumentation/koala/opentelemetry-instrumentation-koala.gemspec b/instrumentation/koala/opentelemetry-instrumentation-koala.gemspec index 578fd1e1c..708f34484 100644 --- a/instrumentation/koala/opentelemetry-instrumentation-koala.gemspec +++ b/instrumentation/koala/opentelemetry-instrumentation-koala.gemspec @@ -26,18 +26,17 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'koala', '~> 3.0.0' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/lmdb/CHANGELOG.md b/instrumentation/lmdb/CHANGELOG.md index 3a2998617..83403f938 100644 --- a/instrumentation/lmdb/CHANGELOG.md +++ b/instrumentation/lmdb/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History: opentelemetry-instrumentation-lmdb +### v0.22.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + ### v0.22.1 / 2023-06-05 * FIXED: Base config options diff --git a/instrumentation/lmdb/lib/opentelemetry/instrumentation/lmdb/version.rb b/instrumentation/lmdb/lib/opentelemetry/instrumentation/lmdb/version.rb index b89bdf81e..998d9a01e 100644 --- a/instrumentation/lmdb/lib/opentelemetry/instrumentation/lmdb/version.rb +++ b/instrumentation/lmdb/lib/opentelemetry/instrumentation/lmdb/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module LMDB - VERSION = '0.22.1' + VERSION = '0.22.2' end end end diff --git a/instrumentation/lmdb/opentelemetry-instrumentation-lmdb.gemspec b/instrumentation/lmdb/opentelemetry-instrumentation-lmdb.gemspec index f5c24419d..d91e80267 100644 --- a/instrumentation/lmdb/opentelemetry-instrumentation-lmdb.gemspec +++ b/instrumentation/lmdb/opentelemetry-instrumentation-lmdb.gemspec @@ -30,12 +30,12 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' - spec.add_development_dependency 'lmdb' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/mongo/CHANGELOG.md b/instrumentation/mongo/CHANGELOG.md index ec1ebe964..6c8e51b2a 100644 --- a/instrumentation/mongo/CHANGELOG.md +++ b/instrumentation/mongo/CHANGELOG.md @@ -1,5 +1,13 @@ # Release History: opentelemetry-instrumentation-mongo +### v0.22.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.22.2 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.22.1 / 2023-06-05 * FIXED: Base config options diff --git a/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/command_serializer.rb b/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/command_serializer.rb index 3d3c217e6..3696c102f 100644 --- a/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/command_serializer.rb +++ b/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/command_serializer.rb @@ -75,7 +75,7 @@ def build_pipeline pipeline = command['pipeline'] return unless pipeline - payload['pipeline'] = pipeline.map(&method(:mask)) + payload['pipeline'] = pipeline.map { |x| mask(x) } end def add_val(payload, command, key) diff --git a/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/version.rb b/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/version.rb index 97c32b4e8..57c5f676f 100644 --- a/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/version.rb +++ b/instrumentation/mongo/lib/opentelemetry/instrumentation/mongo/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Mongo - VERSION = '0.22.1' + VERSION = '0.22.3' end end end diff --git a/instrumentation/mongo/opentelemetry-instrumentation-mongo.gemspec b/instrumentation/mongo/opentelemetry-instrumentation-mongo.gemspec index 38fa563e1..92e49d180 100644 --- a/instrumentation/mongo/opentelemetry-instrumentation-mongo.gemspec +++ b/instrumentation/mongo/opentelemetry-instrumentation-mongo.gemspec @@ -31,13 +31,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' - spec.add_development_dependency 'mongo', '~> 2.5' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-byebug' unless RUBY_ENGINE == 'jruby' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/mysql2/CHANGELOG.md b/instrumentation/mysql2/CHANGELOG.md index 4b56ee1a2..bf4abc390 100644 --- a/instrumentation/mysql2/CHANGELOG.md +++ b/instrumentation/mysql2/CHANGELOG.md @@ -1,5 +1,28 @@ # Release History: opentelemetry-instrumentation-mysql2 +### v0.27.1 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.27.0 / 2024-02-15 + +* ADDED: Instrument mysql2 prepare statement + +### v0.26.1 / 2024-02-08 + +* FIXED: Add missing requires for sql-helpers to mysql, pg, and trilogy instrumentation + +### v0.26.0 / 2024-02-08 + +* BREAKING CHANGE: Move shared sql behavior to helper gems + + +### v0.25.0 / 2023-10-16 + +* BREAKING CHANGE: Obfuscation for mysql2, dalli and postgresql as default option for db_statement + +* ADDED: Obfuscation for mysql2, dalli and postgresql as default option for db_statement + ### v0.24.3 / 2023-08-03 * FIXED: Remove inline linter rules diff --git a/instrumentation/mysql2/Gemfile b/instrumentation/mysql2/Gemfile index b2f83af8d..6ba31761a 100644 --- a/instrumentation/mysql2/Gemfile +++ b/instrumentation/mysql2/Gemfile @@ -10,4 +10,6 @@ gemspec group :test, :development do gem 'opentelemetry-instrumentation-base', path: '../base' + gem 'opentelemetry-helpers-mysql', path: '../../helpers/mysql' + gem 'opentelemetry-helpers-sql-obfuscation', path: '../../helpers/sql-obfuscation' end diff --git a/instrumentation/mysql2/README.md b/instrumentation/mysql2/README.md index 66af9c6bb..7ba249e74 100644 --- a/instrumentation/mysql2/README.md +++ b/instrumentation/mysql2/README.md @@ -46,9 +46,9 @@ end ```ruby OpenTelemetry::SDK.configure do |c| c.use 'OpenTelemetry::Instrumentation::Mysql2', { - # The obfuscation of SQL in the db.statement attribute is disabled by default. - # To enable, set db_statement to :obfuscate. - db_statement: :obfuscate, + # The obfuscation of SQL in the db.statement attribute is enabled by default. + # To disable, set db_statement to :include; to omit the query completely, set db_statement to :omit + db_statement: :include, } end ``` diff --git a/instrumentation/mysql2/example/mysql2.rb b/instrumentation/mysql2/example/mysql2.rb index 437cec6dc..294e9575e 100644 --- a/instrumentation/mysql2/example/mysql2.rb +++ b/instrumentation/mysql2/example/mysql2.rb @@ -18,4 +18,6 @@ password: ENV.fetch('TEST_MYSQL_PASSWORD') { 'root' } ) -client.query("SELECT * FROM users WHERE group='x'") +client.query("SELECT * from information_schema.INNODB_TABLES; /**Dé**/").each do |row| + puts row +end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2.rb index bb2b5fac4..186523915 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2.rb @@ -20,18 +20,18 @@ module Mysql2 # Returns the attributes hash representing the Mysql2 context found # in the optional context or the current context if none is provided. # - # @param [optional Context] context The context to lookup the current + # @param context [optional Context] The context to lookup the current # attributes hash. Defaults to Context.current def attributes(context = nil) context ||= Context.current context.value(CURRENT_ATTRIBUTES_KEY) || {} end - # Returns a context containing the merged attributes hash, derived from the - # optional parent context, or the current context if one was not provided. - # - # @param [optional Context] context The context to use as the parent for + # @param attributes_hash [Hash] The attributes to add to the context + # @param parent_context [optional Context] The context to use as the parent for # the returned context + # @return A context containing the merged attributes hash, derived from the + # optional parent context, or the current context if one was not provided. def context_with_attributes(attributes_hash, parent_context: Context.current) attributes_hash = attributes(parent_context).merge(attributes_hash) parent_context.set_value(CURRENT_ATTRIBUTES_KEY, attributes_hash) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb index 3e85c3ff3..9a7b78ccb 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb @@ -20,7 +20,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end option :peer_service, default: nil, validate: :string - option :db_statement, default: :include, validate: %I[omit include obfuscate] + option :db_statement, default: :obfuscate, validate: %I[omit include obfuscate] option :span_name, default: :statement_type, validate: %I[statement_type db_name db_operation_and_name] option :obfuscation_limit, default: 2000, validate: :integer diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb index 0e4025d5b..c9dbdaddd 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb @@ -4,129 +4,71 @@ # # SPDX-License-Identifier: Apache-2.0 +require 'opentelemetry-helpers-mysql' +require 'opentelemetry-helpers-sql-obfuscation' + module OpenTelemetry module Instrumentation module Mysql2 module Patches # Module to prepend to Mysql2::Client for instrumentation - module Client # rubocop:disable Metrics/ModuleLength - QUERY_NAMES = [ - 'set names', - 'select', - 'insert', - 'update', - 'delete', - 'begin', - 'commit', - 'rollback', - 'savepoint', - 'release savepoint', - 'explain', - 'drop database', - 'drop table', - 'create database', - 'create table' - ].freeze - - QUERY_NAME_RE = Regexp.new("^(#{QUERY_NAMES.join('|')})", Regexp::IGNORECASE) - - # From: /~https://github.com/newrelic/newrelic-ruby-agent/blob/0235b288d85b8bc795bdc1a24621dd9f84cfef45/lib/new_relic/agent/database/obfuscation_helpers.rb#L9-L34 - COMPONENTS_REGEX_MAP = { - single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/, - double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/, - numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/, - boolean_literals: /\b(?:true|false|null)\b/i, - hexadecimal_literals: /0x[0-9a-fA-F]+/, - comments: /(?:#|--).*?(?=\r|\n|$)/i, - multi_line_comments: %r{\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)} - }.freeze - - MYSQL_COMPONENTS = %i[ - single_quotes - double_quotes - numeric_literals - boolean_literals - hexadecimal_literals - comments - multi_line_comments - ].freeze - + module Client def query(sql, options = {}) - attributes = client_attributes - case config[:db_statement] - when :include - attributes[SemanticConventions::Trace::DB_STATEMENT] = sql - when :obfuscate - attributes[SemanticConventions::Trace::DB_STATEMENT] = obfuscate_sql(sql) - end tracer.in_span( - database_span_name(sql), - attributes: attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes), + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), kind: :client ) do - super(sql, options) + super end end - private - - def obfuscate_sql(sql) - if sql.size > config[:obfuscation_limit] - first_match_index = sql.index(generated_mysql_regex) - truncation_message = "SQL truncated (> #{config[:obfuscation_limit]} characters)" - return truncation_message unless first_match_index - - truncated_sql = sql[..first_match_index - 1] - "#{truncated_sql}...\n#{truncation_message}" - else - obfuscated = OpenTelemetry::Common::Utilities.utf8_encode(sql, binary: true) - obfuscated = obfuscated.gsub(generated_mysql_regex, '?') - obfuscated = 'Failed to obfuscate SQL query - quote characters remained after obfuscation' if detect_unmatched_pairs(obfuscated) - obfuscated + def prepare(sql) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super end - rescue StandardError => e - OpenTelemetry.handle_error(message: 'Failed to obfuscate SQL', exception: e) - 'OpenTelemetry error: failed to obfuscate sql' end - def generated_mysql_regex - @generated_mysql_regex ||= Regexp.union(MYSQL_COMPONENTS.map { |component| COMPONENTS_REGEX_MAP[component] }) - end + private - def detect_unmatched_pairs(obfuscated) - # We use this to check whether the query contains any quote characters - # after obfuscation. If so, that's a good indication that the original - # query was malformed, and so our obfuscation can't reliably find - # literals. In such a case, we'll replace the entire query with a - # placeholder. - %r{'|"|\/\*|\*\/}.match(obfuscated) + def _otel_span_name(sql) + OpenTelemetry::Helpers::MySQL.database_span_name( + sql, + OpenTelemetry::Instrumentation::Mysql2.attributes[ + SemanticConventions::Trace::DB_OPERATION + ], + _otel_database_name, + config + ) end - def database_span_name(sql) - case config[:span_name] - when :statement_type - extract_statement_type(sql) - when :db_name - database_name - when :db_operation_and_name - op = OpenTelemetry::Instrumentation::Mysql2.attributes[SemanticConventions::Trace::DB_OPERATION] - name = database_name - if op && name - "#{op} #{name}" - elsif op - op - elsif name - name - end - end || 'mysql' + def _otel_span_attributes(sql) + attributes = _otel_client_attributes + case config[:db_statement] + when :include + attributes[SemanticConventions::Trace::DB_STATEMENT] = sql + when :obfuscate + attributes[SemanticConventions::Trace::DB_STATEMENT] = + OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql + ) + end + + attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes) + attributes.compact! + attributes end - def database_name + def _otel_database_name # /~https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L78 (query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s end - def client_attributes + def _otel_client_attributes # The client specific attributes can be found via the query_options instance variable # exposed on the mysql2 Client # /~https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 @@ -138,8 +80,9 @@ def client_attributes SemanticConventions::Trace::NET_PEER_NAME => host, SemanticConventions::Trace::NET_PEER_PORT => port } - attributes[SemanticConventions::Trace::DB_NAME] = database_name if database_name - attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service] + + attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name + attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] attributes end @@ -150,13 +93,6 @@ def tracer def config Mysql2::Instrumentation.instance.config end - - def extract_statement_type(sql) - QUERY_NAME_RE.match(sql) { |match| match[1].downcase } unless sql.nil? - rescue StandardError => e - OpenTelemetry.logger.debug("Error extracting sql statement type: #{e.message}") - nil - end end end end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/version.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/version.rb index 8c5258e97..13538dc20 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/version.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Mysql2 - VERSION = '0.24.3' + VERSION = '0.27.1' end end end diff --git a/instrumentation/mysql2/opentelemetry-instrumentation-mysql2.gemspec b/instrumentation/mysql2/opentelemetry-instrumentation-mysql2.gemspec index e4cdc1c55..50069b04f 100644 --- a/instrumentation/mysql2/opentelemetry-instrumentation-mysql2.gemspec +++ b/instrumentation/mysql2/opentelemetry-instrumentation-mysql2.gemspec @@ -26,17 +26,19 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-helpers-mysql' + spec.add_dependency 'opentelemetry-helpers-sql-obfuscation' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' - spec.add_development_dependency 'mysql2', '>= 0.4.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-byebug' unless RUBY_ENGINE == 'jruby' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb index e363980c7..319e138d8 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb @@ -22,7 +22,7 @@ let(:instrumentation) { OpenTelemetry::Instrumentation::Mysql2::Instrumentation.instance } let(:exporter) { EXPORTER } let(:span) { exporter.finished_spans.first } - let(:config) { {} } + let(:config) { { db_statement: :include } } before do exporter.reset @@ -88,6 +88,53 @@ end end + describe 'prepare statement' do + it 'after requests with prepare' do + client.prepare('SELECT 1') + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT 1' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + end + + it 'after requests with prepare select ?' do + client.prepare('SELECT ?') + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT ?' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + end + + it 'query ? sequences for db.statement with prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.prepare(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.statement']).must_equal sql + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + end + + it 'query invalid byte sequences for db.statement without prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.events[0].attributes['exception.message'].slice(0, 37)).must_equal 'You have an error in your SQL syntax;' + end + end + it 'after requests' do client.query('SELECT 1') @@ -182,7 +229,7 @@ client.query(sql) end.must_raise Mysql2::Error - _(span.name).must_equal 'mysql' + _(span.name).must_equal 'select' _(span.attributes['db.statement']).must_equal obfuscated_sql end diff --git a/instrumentation/net_http/CHANGELOG.md b/instrumentation/net_http/CHANGELOG.md index 8347cb2e1..50cb64973 100644 --- a/instrumentation/net_http/CHANGELOG.md +++ b/instrumentation/net_http/CHANGELOG.md @@ -1,5 +1,21 @@ # Release History: opentelemetry-instrumentation-net_http +### v0.22.6 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.22.5 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.22.4 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.22.3 / 2023-11-22 + +* FIXED: Update `Net::HTTP` instrumentation to no-op on untraced contexts + ### v0.22.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/patches/instrumentation.rb b/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/patches/instrumentation.rb index 32a67122c..725d4c546 100644 --- a/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/patches/instrumentation.rb +++ b/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/patches/instrumentation.rb @@ -16,9 +16,9 @@ module Instrumentation def request(req, body = nil, &block) # Do not trace recursive call for starting the connection - return super(req, body, &block) unless started? + return super unless started? - return super(req, body, &block) if untraced? + return super if untraced? attributes = { OpenTelemetry::SemanticConventions::Trace::HTTP_METHOD => req.method, @@ -35,7 +35,7 @@ def request(req, body = nil, &block) ) do |span| OpenTelemetry.propagation.inject(req) - super(req, body, &block).tap do |response| + super.tap do |response| annotate_span_with_response!(span, response) end end @@ -78,7 +78,7 @@ def annotate_span_with_response!(span, response) status_code = response.code.to_i span.set_attribute(OpenTelemetry::SemanticConventions::Trace::HTTP_STATUS_CODE, status_code) - span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(status_code.to_i) + span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(status_code.to_i) end def tracer @@ -86,12 +86,20 @@ def tracer end def untraced? + untraced_context? || untraced_host? + end + + def untraced_host? return true if Net::HTTP::Instrumentation.instance.config[:untraced_hosts]&.any? do |host| host.is_a?(Regexp) ? host.match?(@address) : host == @address end false end + + def untraced_context? + OpenTelemetry::Common::Utilities.untraced? + end end end end diff --git a/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/version.rb b/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/version.rb index 567ff1aa0..f8b0c8666 100644 --- a/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/version.rb +++ b/instrumentation/net_http/lib/opentelemetry/instrumentation/net/http/version.rb @@ -8,7 +8,7 @@ module OpenTelemetry module Instrumentation module Net module HTTP - VERSION = '0.22.2' + VERSION = '0.22.6' end end end diff --git a/instrumentation/net_http/opentelemetry-instrumentation-net_http.gemspec b/instrumentation/net_http/opentelemetry-instrumentation-net_http.gemspec index d61af1b88..8352bcf70 100644 --- a/instrumentation/net_http/opentelemetry-instrumentation-net_http.gemspec +++ b/instrumentation/net_http/opentelemetry-instrumentation-net_http.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'bundler', '~> 2.4' @@ -34,7 +33,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0.1' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rspec-mocks' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb b/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb index 2ab764299..fb4d61a92 100644 --- a/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb +++ b/instrumentation/net_http/test/opentelemetry/instrumentation/net/http/instrumentation_test.rb @@ -178,6 +178,34 @@ _(span.attributes['net.peer.port']).must_equal(80) end end + + describe 'untraced context' do + it 'no-ops on #request' do + # Calling `tracer.in_span` within an untraced context causes the logging of "called + # finish on an ended Span" messages. To avoid log noise, the instrumentation must + # no-op (i.e., not call `tracer.in_span`) when the context is untraced. + expect(instrumentation.tracer).not_to receive(:in_span) + + OpenTelemetry::Common::Utilities.untraced do + Net::HTTP.get('example.com', '/body') + end + + _(exporter.finished_spans.size).must_equal 0 + end + + it 'no-ops on #connect' do + expect(instrumentation.tracer).not_to receive(:in_span) + + OpenTelemetry::Common::Utilities.untraced do + uri = URI.parse('http://example.com/body') + http = Net::HTTP.new(uri.host, uri.port) + http.send(:connect) + http.send(:do_finish) + end + + _(exporter.finished_spans.size).must_equal 0 + end + end end describe '#connect' do diff --git a/instrumentation/net_http/test/test_helper.rb b/instrumentation/net_http/test/test_helper.rb index d617b91c2..921a7e11c 100644 --- a/instrumentation/net_http/test/test_helper.rb +++ b/instrumentation/net_http/test/test_helper.rb @@ -9,6 +9,7 @@ Bundler.require(:default, :development, :test) require 'minitest/autorun' +require 'rspec/mocks/minitest_integration' require 'webmock/minitest' # global opentelemetry-sdk setup: diff --git a/instrumentation/pg/CHANGELOG.md b/instrumentation/pg/CHANGELOG.md index a85f110f6..fe3d3eb37 100644 --- a/instrumentation/pg/CHANGELOG.md +++ b/instrumentation/pg/CHANGELOG.md @@ -1,5 +1,32 @@ # Release History: opentelemetry-instrumentation-pg +### v0.27.3 / 2024-05-11 + +* ADDED: Support prepend SQL comment for PG instrumentation + +### v0.27.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.27.1 / 2024-02-08 + +* FIXED: Add missing requires for sql-helpers to mysql, pg, and trilogy instrumentation + +### v0.27.0 / 2024-02-08 + +* BREAKING CHANGE: Move shared sql behavior to helper gems + + +### v0.26.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.26.0 / 2023-10-16 + +* BREAKING CHANGE: Obfuscation for mysql2, dalli and postgresql as default option for db_statement + +* ADDED: Obfuscation for mysql2, dalli and postgresql as default option for db_statement + ### v0.25.3 / 2023-07-29 * FIXED: Pass block explicitly in `define_method` calls for PG instrumentation query methods diff --git a/instrumentation/pg/Gemfile b/instrumentation/pg/Gemfile index 2baf57ac4..6c8072b49 100644 --- a/instrumentation/pg/Gemfile +++ b/instrumentation/pg/Gemfile @@ -10,4 +10,5 @@ gemspec group :test do gem 'opentelemetry-instrumentation-base', path: '../base' + gem 'opentelemetry-helpers-sql-obfuscation', path: '../../helpers/sql-obfuscation' end diff --git a/instrumentation/pg/README.md b/instrumentation/pg/README.md index a9261dce6..12c4c167d 100644 --- a/instrumentation/pg/README.md +++ b/instrumentation/pg/README.md @@ -50,9 +50,9 @@ OpenTelemetry::SDK.configure do |c| # will be included on all spans from this instrumentation: peer_service: 'postgres:readonly', - # By default, this instrumentation includes the executed SQL as the `db.statement` + # By default, this instrumentation obfuscate/sanitize the executed SQL as the `db.statement` # semantic attribute. Optionally, you may disable the inclusion of this attribute entirely by - # setting this option to :omit or sanitize the attribute by setting to :obfuscate + # setting this option to :omit or disbale sanitization the attribute by setting to :include db_statement: :include, } end diff --git a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/constants.rb b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/constants.rb index 6a324f148..73d9c8710 100644 --- a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/constants.rb +++ b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/constants.rb @@ -65,29 +65,6 @@ module Constants VALUES ].freeze - # From: /~https://github.com/newrelic/newrelic-ruby-agent/blob/9787095d4b5b2d8fcaf2fdbd964ed07c731a8b6b/lib/new_relic/agent/database/obfuscation_helpers.rb#L9-L34 - COMPONENTS_REGEX_MAP = { - single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/, - dollar_quotes: /(\$(?!\d)[^$]*?\$).*?(?:\1|$)/, - uuids: /\{?(?:[0-9a-fA-F]\-*){32}\}?/, - numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/, - boolean_literals: /\b(?:true|false|null)\b/i, - comments: /(?:#|--).*?(?=\r|\n|$)/i, - multi_line_comments: %r{\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)} - }.freeze - - POSTGRES_COMPONENTS = %i[ - single_quotes - dollar_quotes - uuids - numeric_literals - boolean_literals - comments - multi_line_comments - ].freeze - - UNMATCHED_PAIRS_REGEX = %r{'|\/\*|\*\/|\$(?!\?)} - # These are all alike in that they will have a SQL statement as the first parameter. # That statement may possibly be parameterized, but we can still use it - the # obfuscation code will just transform $1 -> $? in that case (which is fine enough). diff --git a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/instrumentation.rb b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/instrumentation.rb index bf6a4dcdc..2eeb6ac66 100644 --- a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/instrumentation.rb +++ b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/instrumentation.rb @@ -25,7 +25,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end option :peer_service, default: nil, validate: :string - option :db_statement, default: :include, validate: %I[omit include obfuscate] + option :db_statement, default: :obfuscate, validate: %I[omit include obfuscate] option :obfuscation_limit, default: 2000, validate: :integer private diff --git a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/patches/connection.rb b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/patches/connection.rb index 75f000f14..631610acf 100644 --- a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/patches/connection.rb +++ b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/patches/connection.rb @@ -4,6 +4,7 @@ # # SPDX-License-Identifier: Apache-2.0 +require 'opentelemetry-helpers-sql-obfuscation' require_relative '../constants' require_relative '../lru_cache' @@ -50,6 +51,16 @@ module Connection # rubocop:disable Metrics/ModuleLength private + def obfuscate_sql(sql) + return sql unless config[:db_statement] == :obfuscate + + OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + sql, + obfuscation_limit: config[:obfuscation_limit], + adapter: :postgres + ) + end + def tracer PG::Instrumentation.instance.tracer end @@ -101,7 +112,9 @@ def span_attrs(kind, *args) def extract_operation(sql) # From: /~https://github.com/open-telemetry/opentelemetry-js-contrib/blob/9244a08a8d014afe26b82b91cf86e407c2599d73/plugins/node/opentelemetry-instrumentation-pg/src/utils.ts#L35 - sql.to_s.split[0].to_s.upcase + # Ignores prepend comment + comment_regex = %r{\A\/\*.*?\*\/}m + sql.to_s.sub(comment_regex, '').split[0].to_s.upcase end def span_name(operation) @@ -112,34 +125,6 @@ def validated_operation(operation) operation if PG::Constants::SQL_COMMANDS.include?(operation) end - def obfuscate_sql(sql) - return sql unless config[:db_statement] == :obfuscate - - if sql.size > config[:obfuscation_limit] - first_match_index = sql.index(generated_postgres_regex) - truncation_message = "SQL truncated (> #{config[:obfuscation_limit]} characters)" - return truncation_message unless first_match_index - - truncated_sql = sql[..first_match_index - 1] - return "#{truncated_sql}...\n#{truncation_message}" - end - - # From: - # /~https://github.com/newrelic/newrelic-ruby-agent/blob/9787095d4b5b2d8fcaf2fdbd964ed07c731a8b6b/lib/new_relic/agent/database/obfuscator.rb - # /~https://github.com/newrelic/newrelic-ruby-agent/blob/9787095d4b5b2d8fcaf2fdbd964ed07c731a8b6b/lib/new_relic/agent/database/obfuscation_helpers.rb - obfuscated = sql.gsub(generated_postgres_regex, '?') - obfuscated = 'Failed to obfuscate SQL query - quote characters remained after obfuscation' if PG::Constants::UNMATCHED_PAIRS_REGEX.match(obfuscated) - - obfuscated - rescue StandardError => e - OpenTelemetry.handle_error(message: 'Failed to obfuscate SQL', exception: e) - 'OpenTelemetry error: failed to obfuscate sql' - end - - def generated_postgres_regex - @generated_postgres_regex ||= Regexp.union(PG::Constants::POSTGRES_COMPONENTS.map { |component| PG::Constants::COMPONENTS_REGEX_MAP[component] }) - end - def client_attributes attributes = { 'db.system' => 'postgresql', diff --git a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/version.rb b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/version.rb index 7e5b54983..105f4152e 100644 --- a/instrumentation/pg/lib/opentelemetry/instrumentation/pg/version.rb +++ b/instrumentation/pg/lib/opentelemetry/instrumentation/pg/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module PG - VERSION = '0.25.3' + VERSION = '0.27.3' end end end diff --git a/instrumentation/pg/opentelemetry-instrumentation-pg.gemspec b/instrumentation/pg/opentelemetry-instrumentation-pg.gemspec index 2c77b6629..1b6cd3389 100644 --- a/instrumentation/pg/opentelemetry-instrumentation-pg.gemspec +++ b/instrumentation/pg/opentelemetry-instrumentation-pg.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-helpers-sql-obfuscation' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'activerecord' @@ -34,10 +35,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'pg', '>= 1.1.0' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-byebug' unless RUBY_ENGINE == 'jruby' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/pg/test/opentelemetry/instrumentation/pg/instrumentation_test.rb b/instrumentation/pg/test/opentelemetry/instrumentation/pg/instrumentation_test.rb index 57bef4797..b7e97b9ac 100644 --- a/instrumentation/pg/test/opentelemetry/instrumentation/pg/instrumentation_test.rb +++ b/instrumentation/pg/test/opentelemetry/instrumentation/pg/instrumentation_test.rb @@ -51,7 +51,7 @@ let(:user) { ENV.fetch('TEST_POSTGRES_USER', 'postgres') } let(:dbname) { ENV.fetch('TEST_POSTGRES_DB', 'postgres') } let(:password) { ENV.fetch('TEST_POSTGRES_PASSWORD', 'postgres') } - + let(:config) { { db_statement: :include } } before do instrumentation.install(config) end @@ -177,6 +177,18 @@ end end + it 'ignores prepend comment to extract operation' do + client.query('/* comment */ SELECT 1') + + _(span.name).must_equal 'SELECT postgres' + _(span.attributes['db.system']).must_equal 'postgresql' + _(span.attributes['db.name']).must_equal 'postgres' + _(span.attributes['db.statement']).must_equal '/* comment */ SELECT 1' + _(span.attributes['db.operation']).must_equal 'SELECT' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_i + end + it 'only caches 50 prepared statement names' do 51.times { |i| client.prepare("foo#{i}", "SELECT $1 AS foo#{i}") } client.exec_prepared('foo0', [1]) @@ -346,7 +358,7 @@ client.query('SELECT 1') _(span.attributes['net.peer.name']).must_equal host - _(span.attributes['net.peer.port']).must_equal port.to_i if PG.const_defined?('DEF_PORT') + _(span.attributes['net.peer.port']).must_equal port.to_i if PG.const_defined?(:DEF_PORT) end end end unless ENV['OMIT_SERVICES'] diff --git a/instrumentation/que/Appraisals b/instrumentation/que/Appraisals index d5b7c5b72..7792e4821 100644 --- a/instrumentation/que/Appraisals +++ b/instrumentation/que/Appraisals @@ -4,11 +4,14 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'que-1.2' do - gem 'que', '1.2.0' +appraise 'que-1.x' do + gem 'que', '~> 1.4' end -appraise 'que-2.1' do - ruby '>= 2.7' - gem 'que', '2.1.0' +appraise 'que-2.x' do + gem 'que', '~> 2.3' +end + +appraise 'que-latest' do + gem 'que' end diff --git a/instrumentation/que/CHANGELOG.md b/instrumentation/que/CHANGELOG.md index 91c8a240c..7debd66c3 100644 --- a/instrumentation/que/CHANGELOG.md +++ b/instrumentation/que/CHANGELOG.md @@ -1,8 +1,25 @@ # Release History: opentelemetry-instrumentation-que +### v0.8.2 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.8.1 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.8.0 / 2024-02-08 + +* BREAKING CHANGE: Move shared sql behavior to helper gems + + +### v0.7.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.7.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.6.2 / 2023-08-07 diff --git a/instrumentation/que/Gemfile b/instrumentation/que/Gemfile index b1ea20ba3..46bfbfb01 100644 --- a/instrumentation/que/Gemfile +++ b/instrumentation/que/Gemfile @@ -9,7 +9,9 @@ source 'https://rubygems.org' gemspec group :test do + gem 'activerecord' + gem 'pg' + gem 'opentelemetry-helpers-sql-obfuscation', path: '../../helpers/sql-obfuscation' gem 'opentelemetry-instrumentation-base', path: '../base' - gem 'opentelemetry-instrumentation-pg', path: '../../instrumentation/pg' - gem 'appraisal' + gem 'opentelemetry-instrumentation-pg', path: '../pg' end diff --git a/instrumentation/que/lib/opentelemetry/instrumentation/que/middlewares/server_middleware.rb b/instrumentation/que/lib/opentelemetry/instrumentation/que/middlewares/server_middleware.rb index 1e5c0c15d..386a6d5cd 100644 --- a/instrumentation/que/lib/opentelemetry/instrumentation/que/middlewares/server_middleware.rb +++ b/instrumentation/que/lib/opentelemetry/instrumentation/que/middlewares/server_middleware.rb @@ -20,7 +20,7 @@ def self.call(job, &block) OpenTelemetry::Context.with_current(extracted_context) do if otel_config[:propagation_style] == :child tracer.in_span(span_name, attributes: attributes, kind: :consumer) do |span| - block.call + yield enhance_span_after_job_completion(span, job) end else @@ -28,7 +28,7 @@ def self.call(job, &block) root_span = tracer.start_root_span(span_name, attributes: attributes, links: span_links, kind: :consumer) OpenTelemetry::Trace.with_span(root_span) do |span| - block.call + yield enhance_span_after_job_completion(span, job) ensure root_span.finish diff --git a/instrumentation/que/lib/opentelemetry/instrumentation/que/patches/poller.rb b/instrumentation/que/lib/opentelemetry/instrumentation/que/patches/poller.rb index c2786e2e2..b4681b3b6 100644 --- a/instrumentation/que/lib/opentelemetry/instrumentation/que/patches/poller.rb +++ b/instrumentation/que/lib/opentelemetry/instrumentation/que/patches/poller.rb @@ -17,9 +17,9 @@ def poll(*args, **kwargs) return unless should_poll? if Que::Instrumentation.instance.config[:trace_poller] - Que::Instrumentation.instance.tracer.in_span('Que::Poller#poll') { super(*args, **kwargs) } + Que::Instrumentation.instance.tracer.in_span('Que::Poller#poll') { super } else - OpenTelemetry::Common::Utilities.untraced { super(*args, **kwargs) } + OpenTelemetry::Common::Utilities.untraced { super } end end end diff --git a/instrumentation/que/lib/opentelemetry/instrumentation/que/version.rb b/instrumentation/que/lib/opentelemetry/instrumentation/que/version.rb index b49a45f06..b96f6aa0e 100644 --- a/instrumentation/que/lib/opentelemetry/instrumentation/que/version.rb +++ b/instrumentation/que/lib/opentelemetry/instrumentation/que/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Que - VERSION = '0.7.0' + VERSION = '0.8.2' end end end diff --git a/instrumentation/que/opentelemetry-instrumentation-que.gemspec b/instrumentation/que/opentelemetry-instrumentation-que.gemspec index af9982566..d0e13a240 100644 --- a/instrumentation/que/opentelemetry-instrumentation-que.gemspec +++ b/instrumentation/que/opentelemetry-instrumentation-que.gemspec @@ -28,17 +28,14 @@ Gem::Specification.new do |spec| spec.add_dependency 'opentelemetry-api', '~> 1.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' - spec.add_development_dependency 'activerecord', '~> 6.0' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' - spec.add_development_dependency 'opentelemetry-instrumentation-pg', '~> 0.20' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'pg', '~> 1.1' - spec.add_development_dependency 'que', '~> 1.2.0' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/que/test/test_helper.rb b/instrumentation/que/test/test_helper.rb index 5660b9bda..27414d2f3 100644 --- a/instrumentation/que/test/test_helper.rb +++ b/instrumentation/que/test/test_helper.rb @@ -54,12 +54,7 @@ def prepare_que ActiveRecord::Base.connection.execute('CREATE SCHEMA public') Que.connection = ActiveRecord - - if que_version >= Gem::Version.new('2.1.0') - Que.migrate!(version: 7) - else - Que.migrate!(version: 4) - end + Que.migrate!(version: Que::Migrations::CURRENT_VERSION) end def database_name diff --git a/instrumentation/racecar/CHANGELOG.md b/instrumentation/racecar/CHANGELOG.md index cb0cce4a4..814b45b81 100644 --- a/instrumentation/racecar/CHANGELOG.md +++ b/instrumentation/racecar/CHANGELOG.md @@ -1,8 +1,24 @@ # Release History: opentelemetry-instrumentation-racecar +### v0.3.4 / 2024-07-09 + +* FIXED: Suppress header access with symbol key deprecation warning in Racecar Instrumentation + +### v0.3.3 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.3.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.3.1 / 2024-04-05 + +* FIXED: Fix markdown header + ### v0.3.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.2.1 / 2023-06-05 diff --git a/instrumentation/racecar/README.md b/instrumentation/racecar/README.md index cd41cdaf1..393f32069 100644 --- a/instrumentation/racecar/README.md +++ b/instrumentation/racecar/README.md @@ -13,7 +13,7 @@ gem install opentelemetry-instrumentation-racecar Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-racecar` in your `Gemfile`. -## Runtime requirements +## Runtime requirements This instrumentation is built on top of Racecar's integration with `ActiveSupport::Notifications`. `ActiveSupport::Notification` will need to be loaded before the instrumentation is installed (as below) or the installation will cancel. diff --git a/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/process_message_subscriber.rb b/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/process_message_subscriber.rb index 31b5c8aa8..8b2f8392b 100644 --- a/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/process_message_subscriber.rb +++ b/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/process_message_subscriber.rb @@ -4,6 +4,13 @@ module OpenTelemetry module Instrumentation # This class contains the ASN subsciber that instruments message processing class ProcessMessageSubscriber + GETTER = if Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.13.0') + Context::Propagation.text_map_getter + else + OpenTelemetry::Common::Propagation.symbol_key_getter + end + private_constant :GETTER + def tracer Racecar::Instrumentation.instance.tracer end @@ -11,7 +18,7 @@ def tracer def start(_name, _id, payload) attrs = attributes(payload) - parent_context = OpenTelemetry.propagation.extract(payload[:headers], getter: OpenTelemetry::Common::Propagation.symbol_key_getter) + parent_context = OpenTelemetry.propagation.extract(payload[:headers], getter: GETTER) parent_token = OpenTelemetry::Context.attach(parent_context) span_context = OpenTelemetry::Trace.current_span(parent_context).context diff --git a/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/version.rb b/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/version.rb index 86a2d94b4..4e77f3498 100644 --- a/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/version.rb +++ b/instrumentation/racecar/lib/opentelemetry/instrumentation/racecar/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Racecar - VERSION = '0.3.0' + VERSION = '0.3.4' end end end diff --git a/instrumentation/racecar/opentelemetry-instrumentation-racecar.gemspec b/instrumentation/racecar/opentelemetry-instrumentation-racecar.gemspec index cb52a1d09..d238fe84f 100644 --- a/instrumentation/racecar/opentelemetry-instrumentation-racecar.gemspec +++ b/instrumentation/racecar/opentelemetry-instrumentation-racecar.gemspec @@ -28,16 +28,15 @@ Gem::Specification.new do |spec| spec.add_dependency 'opentelemetry-api', '~> 1.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' - spec.add_development_dependency 'activesupport' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.0' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'racecar', '~> 2.7' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/rack/Appraisals b/instrumentation/rack/Appraisals index a62cdf541..6f96b3584 100644 --- a/instrumentation/rack/Appraisals +++ b/instrumentation/rack/Appraisals @@ -4,6 +4,10 @@ # # SPDX-License-Identifier: Apache-2.0 +appraise 'rack-latest' do + gem 'rack' +end + appraise 'rack-3.0' do gem 'rack', '~> 3.0.0' end diff --git a/instrumentation/rack/CHANGELOG.md b/instrumentation/rack/CHANGELOG.md index bc40bd3bb..540499280 100644 --- a/instrumentation/rack/CHANGELOG.md +++ b/instrumentation/rack/CHANGELOG.md @@ -1,5 +1,36 @@ # Release History: opentelemetry-instrumentation-rack +### v0.24.5 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.24.4 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.24.3 / 2024-05-08 + +* FIXED: Rack event baggage handling + +### v0.24.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.24.1 / 2024-04-05 + +* DOCS: Fix typo where Rake is mentioned instead of Rack + +### v0.24.0 / 2024-01-06 + +* BREAKING CHANGE: Use Rack Events By Default + +* ADDED: Use Rack Events By Default +* FIXED: Backport Rack proxy event to middleware + +### v0.23.5 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.23.4 / 2023-08-03 * FIXED: Remove inline linter rules diff --git a/instrumentation/rack/Gemfile b/instrumentation/rack/Gemfile index 5b7d54b64..c8b2ad72f 100644 --- a/instrumentation/rack/Gemfile +++ b/instrumentation/rack/Gemfile @@ -10,5 +10,6 @@ gemspec group :test do gem 'opentelemetry-instrumentation-base', path: '../base' + gem 'rack-test', '~> 2.1.0' gem 'pry-byebug' end diff --git a/instrumentation/rack/README.md b/instrumentation/rack/README.md index e63fc02bc..290ad59bc 100644 --- a/instrumentation/rack/README.md +++ b/instrumentation/rack/README.md @@ -12,6 +12,19 @@ gem install opentelemetry-instrumentation-rack Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-rack` in your `Gemfile`. +### Version Compatibility + +Older versions of Rack are not supported by the latest version of this instrumentation. If you are using an older version of Rack and need an earlier version of this instrumentation, then consider installing and pinning the compatible gem version, e.g.: + +```console +gem opentelemetry-instrumentation-rack, "" +``` + +| Rack Version | Instrumentation Version | +| ------------ | ----------------------- | +| `< 2.0` | `= 0.22.1` | +| `>= 2.0` | `~> 0.23` | + ## Usage To use the instrumentation, call `use` with the name of the instrumentation: @@ -29,6 +42,23 @@ OpenTelemetry::SDK.configure do |c| c.use_all end ``` + +## Rack Middleware vs Rack Events + +Since `v0.24.0`, this instrumentation uses `Rack::Events` as opposed to `Middleware` to support Requests that use Buffered Response Bodies. + +If your application does not support `Rack::Events`, you may disable it by setting `use_rack_events: false`, e.g. + +```ruby +OpenTelemetry::SDK.configure do |c| + c.use 'OpenTelemetry::Instrumentation::Rack', use_rack_events: false +end +``` + +This will switch to using `Rack::Middleware` by default in dependent instrumentations. + +See [#342](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/342) for more details. + ## Controlling span name cardinality By default we will set the rack span name to match the format "HTTP #{method}" (ie. HTTP GET). There are different ways to control span names with this instrumentation. diff --git a/instrumentation/rack/example/trace_demonstration.rb b/instrumentation/rack/example/trace_demonstration.rb index e6253f57c..02fc8a712 100644 --- a/instrumentation/rack/example/trace_demonstration.rb +++ b/instrumentation/rack/example/trace_demonstration.rb @@ -18,7 +18,7 @@ builder = Rack::Builder.app do # integration should be automatic in web frameworks (like rails), # but for a plain Rack application, enable it in your config.ru, e.g., - use OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware + use ::Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::EventHandler.new] app = ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['All responses are OK']] } run app diff --git a/instrumentation/rack/example/trace_demonstration2.rb b/instrumentation/rack/example/trace_demonstration2.rb index c6ac649e9..66941cfb0 100644 --- a/instrumentation/rack/example/trace_demonstration2.rb +++ b/instrumentation/rack/example/trace_demonstration2.rb @@ -24,7 +24,7 @@ end # integrate instrumentation explicitly: -builder.use OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware +builder.use ::Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::EventHandler.new] # demonstrate tracing (span output to console): puts Rack::MockRequest.new(builder).get('/') diff --git a/instrumentation/rack/example/trace_demonstration3.rb b/instrumentation/rack/example/trace_demonstration3.rb index a444ecfeb..38226a72a 100644 --- a/instrumentation/rack/example/trace_demonstration3.rb +++ b/instrumentation/rack/example/trace_demonstration3.rb @@ -19,7 +19,7 @@ builder = Rack::Builder.app do # integration should be automatic in web frameworks (like rails), # but for a plain Rack application, enable it in your config.ru, e.g., - use OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware + use ::Rack::Events, [OpenTelemetry::Instrumentation::Rack::Middlewares::EventHandler.new] app = ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['All responses are OK']] } run app diff --git a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/instrumentation.rb b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/instrumentation.rb index 35be3a519..3bfb68a3c 100644 --- a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/instrumentation.rb +++ b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/instrumentation.rb @@ -28,8 +28,8 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base option :url_quantization, default: nil, validate: :callable option :untraced_requests, default: nil, validate: :callable option :response_propagators, default: [], validate: :array - # This option is only valid for applicaitons using Rack 2.0 or greater - option :use_rack_events, default: false, validate: :boolean + # This option is only valid for applications using Rack 2.0 or greater + option :use_rack_events, default: true, validate: :boolean # Temporary Helper for Sinatra and ActionPack middleware to use during installation # @@ -55,7 +55,7 @@ def require_dependencies end def config_options(user_config) - config = super(user_config) + config = super config[:allowed_rack_request_headers] = config[:allowed_request_headers].compact.each_with_object({}) do |header, memo| key = header.to_s.upcase.gsub(/[-\s]/, '_') case key diff --git a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/event_handler.rb b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/event_handler.rb index 80096c77f..5475a4fc5 100644 --- a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/event_handler.rb +++ b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/event_handler.rb @@ -42,7 +42,7 @@ module Middlewares class EventHandler include ::Rack::Events::Abstract - TOKENS_KEY = 'otel.context.tokens' + OTEL_TOKEN_AND_SPAN = 'otel.rack.token_and_span' GOOD_HTTP_STATUSES = (100..499) # Creates a server span for this current request using the incoming parent context @@ -52,11 +52,16 @@ class EventHandler # @param [Rack::Response] This is nil in practice # @return [void] def on_start(request, _) - return if untraced_request?(request.env) + parent_context = if untraced_request?(request.env) + extract_remote_context(request, OpenTelemetry::Common::Utilities.untraced) + else + extract_remote_context(request) + end - parent_context = extract_remote_context(request) span = create_span(parent_context, request) - request.env[TOKENS_KEY] = register_current_span(span) + span_ctx = OpenTelemetry::Trace.context_with_span(span, parent_context: parent_context) + rack_ctx = OpenTelemetry::Instrumentation::Rack.context_with_span(span, parent_context: span_ctx) + request.env[OTEL_TOKEN_AND_SPAN] = [OpenTelemetry::Context.attach(rack_ctx), span] rescue StandardError => e OpenTelemetry.handle_error(exception: e) end @@ -90,7 +95,7 @@ def on_error(request, _, error) return unless span.recording? span.record_exception(error) - span.status = OpenTelemetry::Trace::Status.error + span.status = OpenTelemetry::Trace::Status.error(error.class.name) rescue StandardError => e OpenTelemetry.handle_error(exception: e) end @@ -108,7 +113,7 @@ def on_finish(request, response) rescue StandardError => e OpenTelemetry.handle_error(exception: e) ensure - detach_contexts(request) + detach_context(request) end private @@ -171,9 +176,10 @@ def create_request_span_name(request) end end - def extract_remote_context(request) + def extract_remote_context(request, context = Context.current) OpenTelemetry.propagation.extract( request.env, + context: context, getter: OpenTelemetry::Common::Propagation.rack_env_getter ) end @@ -191,11 +197,12 @@ def request_span_attributes(env) attributes end - def detach_contexts(request) - request.env[TOKENS_KEY]&.reverse&.each do |token| - OpenTelemetry::Context.detach(token) - OpenTelemetry::Trace.current_span.finish - end + def detach_context(request) + return nil unless request.env[OTEL_TOKEN_AND_SPAN] + + token, span = request.env[OTEL_TOKEN_AND_SPAN] + span.finish + OpenTelemetry::Context.detach(token) rescue StandardError => e OpenTelemetry.handle_error(exception: e) end @@ -244,15 +251,6 @@ def config OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.config end - def register_current_span(span) - ctx = OpenTelemetry::Trace.context_with_span(span) - rack_ctx = OpenTelemetry::Instrumentation::Rack.context_with_span(span, parent_context: ctx) - - contexts = [ctx, rack_ctx] - contexts.compact! - contexts.map { |context| OpenTelemetry::Context.attach(context) } - end - def create_span(parent_context, request) span = tracer.start_span( create_request_span_name(request), diff --git a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/tracer_middleware.rb b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/tracer_middleware.rb index d68c7400a..608728ac6 100644 --- a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/tracer_middleware.rb +++ b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/middlewares/tracer_middleware.rb @@ -77,6 +77,8 @@ def call(env) tracer.in_span(request_span_name, attributes: request_span_attributes(env: env), kind: request_span_kind) do |request_span| + request_start_time = OpenTelemetry::Instrumentation::Rack::Util::QueueTime.get_request_start(env) + request_span.add_event('http.proxy.request.started', timestamp: request_start_time) unless request_start_time.nil? OpenTelemetry::Instrumentation::Rack.with_span(request_span) do @app.call(env).tap do |status, headers, response| set_attributes_after_request(request_span, status, headers, response) @@ -150,7 +152,7 @@ def create_request_span_name(request_uri_or_path_info, env) end def set_attributes_after_request(span, status, headers, _response) - span.status = OpenTelemetry::Trace::Status.error unless (100..499).include?(status.to_i) + span.status = OpenTelemetry::Trace::Status.error unless (100..499).cover?(status.to_i) span.set_attribute('http.status_code', status) # NOTE: if data is available, it would be good to do this: diff --git a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/version.rb b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/version.rb index ae0a2aa0f..082779a2e 100644 --- a/instrumentation/rack/lib/opentelemetry/instrumentation/rack/version.rb +++ b/instrumentation/rack/lib/opentelemetry/instrumentation/rack/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Rack - VERSION = '0.23.4' + VERSION = '0.24.5' end end end diff --git a/instrumentation/rack/opentelemetry-instrumentation-rack.gemspec b/instrumentation/rack/opentelemetry-instrumentation-rack.gemspec index 35d20b559..6381e7f71 100644 --- a/instrumentation/rack/opentelemetry-instrumentation-rack.gemspec +++ b/instrumentation/rack/opentelemetry-instrumentation-rack.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' @@ -35,11 +34,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-sdk-experimental', '~> 0.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rack' - spec.add_development_dependency 'rack-test' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/rack/test/opentelemetry/instrumentation/rack/instrumentation_test.rb b/instrumentation/rack/test/opentelemetry/instrumentation/rack/instrumentation_test.rb index 2814bde0d..2a0cf5d6d 100644 --- a/instrumentation/rack/test/opentelemetry/instrumentation/rack/instrumentation_test.rb +++ b/instrumentation/rack/test/opentelemetry/instrumentation/rack/instrumentation_test.rb @@ -13,7 +13,7 @@ before do # simulate a fresh install: - instrumentation.instance_variable_set('@installed', false) + instrumentation.instance_variable_set(:@installed, false) instrumentation.config.clear end @@ -32,7 +32,7 @@ _(instrumentation.config[:url_quantization]).must_be_nil _(instrumentation.config[:untraced_requests]).must_be_nil _(instrumentation.config[:response_propagators]).must_be_empty - _(instrumentation.config[:use_rack_events]).must_equal false + _(instrumentation.config[:use_rack_events]).must_equal true end end diff --git a/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_reseliency_test.rb b/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_resiliency_test.rb similarity index 100% rename from instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_reseliency_test.rb rename to instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_resiliency_test.rb diff --git a/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_test.rb b/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_test.rb index ff43da7d1..b106a5d0d 100644 --- a/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_test.rb +++ b/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/event_handler_test.rb @@ -63,10 +63,31 @@ exporter.reset # simulate a fresh install: - instrumentation.instance_variable_set('@installed', false) + instrumentation.instance_variable_set(:@installed, false) instrumentation.install(config) end + # Simulating buggy instrumentation that starts a span, sets the ctx + # but fails to detach or close the span + describe 'broken instrumentation' do + let(:service) do + lambda do |_env| + span = OpenTelemetry.tracer_provider.tracer('buggy').start_span('I never close') + OpenTelemetry::Context.attach(OpenTelemetry::Trace.context_with_span(span)) + [200, { 'Content-Type' => 'text/plain' }, response_body] + end + end + + it 'still closes the rack span' do + assert_raises OpenTelemetry::Context::DetachError do + get uri, {}, headers + end + _(finished_spans.size).must_equal 1 + _(rack_span.name).must_equal 'HTTP GET' + OpenTelemetry::Context.clear + end + end + describe '#call' do before do get uri, {}, headers @@ -84,6 +105,25 @@ _(proxy_event).must_be_nil end + describe 'when baggage is set' do + let(:headers) do + Hash( + 'baggage' => 'foo=123' + ) + end + + let(:service) do + lambda do |_env| + _(OpenTelemetry::Baggage.raw_entries['foo'].value).must_equal('123') + [200, { 'Content-Type' => 'text/plain' }, response_body] + end + end + + it 'sets baggage in the request context' do + _(rack_span.name).must_equal 'HTTP GET' + end + end + describe 'when a query is passed in' do let(:uri) { '/endpoint?query=true' } @@ -94,17 +134,22 @@ end describe 'config[:untraced_endpoints]' do + let(:service) do + lambda do |_env| + OpenTelemetry.tracer_provider.tracer('req').in_span('in_req_span') {} + [200, { 'Content-Type' => 'text/plain' }, response_body] + end + end + describe 'when an array is passed in' do + let(:uri) { '/ping' } let(:untraced_endpoints) { ['/ping'] } it 'does not trace paths listed in the array' do - get '/ping' - ping_span = finished_spans.find { |s| s.attributes['http.target'] == '/ping' } _(ping_span).must_be_nil - root_span = finished_spans.find { |s| s.attributes['http.target'] == '/' } - _(root_span).wont_be_nil + _(finished_spans.size).must_equal 0 end end @@ -124,19 +169,24 @@ end describe 'config[:untraced_requests]' do + let(:service) do + lambda do |_env| + OpenTelemetry.tracer_provider.tracer('req').in_span('in_req_span') {} + [200, { 'Content-Type' => 'text/plain' }, response_body] + end + end + describe 'when a callable is passed in' do + let(:uri) { '/assets' } let(:untraced_requests) do ->(env) { env['PATH_INFO'] =~ %r{^\/assets} } end it 'does not trace requests in which the callable returns true' do - get '/assets' - assets_span = finished_spans.find { |s| s.attributes['http.target'] == '/assets' } _(assets_span).must_be_nil - root_span = finished_spans.find { |s| s.attributes['http.target'] == '/' } - _(root_span).wont_be_nil + _(finished_spans.size).must_equal 0 end end diff --git a/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/tracer_middleware_test.rb b/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/tracer_middleware_test.rb index 5e9df36e8..89c68c328 100644 --- a/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/tracer_middleware_test.rb +++ b/instrumentation/rack/test/opentelemetry/instrumentation/rack/middlewares/tracer_middleware_test.rb @@ -25,6 +25,7 @@ let(:exporter) { EXPORTER } let(:finished_spans) { exporter.finished_spans } let(:first_span) { exporter.finished_spans.first } + let(:proxy_event) { first_span.events&.first } let(:default_config) { {} } let(:config) { default_config } @@ -36,7 +37,7 @@ exporter.reset # simulate a fresh install: - instrumentation.instance_variable_set('@installed', false) + instrumentation.instance_variable_set(:@installed, false) instrumentation.install(config) # clear out cached config: @@ -49,7 +50,7 @@ after do # installation is 'global', so it should be reset: - instrumentation.instance_variable_set('@installed', false) + instrumentation.instance_variable_set(:@installed, false) instrumentation.install(default_config) end @@ -84,6 +85,15 @@ end end + describe 'given request proxy headers' do + let(:env) { Hash('HTTP_X_REQUEST_START' => '1677723466') } + + it 'records an event' do + _(proxy_event.name).must_equal 'http.proxy.request.started' + _(proxy_event.timestamp).must_equal 1_677_723_466_000_000_000 + end + end + describe 'config[:untraced_endpoints]' do describe 'when an array is passed in' do let(:config) { { untraced_endpoints: ['/ping'] } } diff --git a/instrumentation/rails/Appraisals b/instrumentation/rails/Appraisals index 03f29edc9..018ce75b2 100644 --- a/instrumentation/rails/Appraisals +++ b/instrumentation/rails/Appraisals @@ -4,10 +4,6 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'rails-6.0' do - gem 'rails', '~> 6.0.0' -end - appraise 'rails-6.1' do gem 'rails', '~> 6.1.0' end @@ -15,3 +11,7 @@ end appraise 'rails-7.0' do gem 'rails', '~> 7.0.0' end + +appraise 'rails-7.1' do + gem 'rails', '~> 7.1.0' +end diff --git a/instrumentation/rails/CHANGELOG.md b/instrumentation/rails/CHANGELOG.md index 2ff2b00e9..b2dc96104 100644 --- a/instrumentation/rails/CHANGELOG.md +++ b/instrumentation/rails/CHANGELOG.md @@ -1,8 +1,39 @@ # Release History: opentelemetry-instrumentation-rails +### v0.31.0 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change +* CHANGED: Update ActiveSupport Instrumentation + +### v0.30.2 / 2024-06-04 + +* FIXED: Add action_mailer to rails and all + +### v0.30.1 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.30.0 / 2024-01-09 + +* BREAKING CHANGE: Use ActiveSupport instead of patches #703 + +### v0.29.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + +### v0.29.0 / 2023-11-22 + +* BREAKING CHANGE: Drop Rails 6.0 EOL + +* ADDED: Drop Rails 6.0 EOL + +### v0.28.1 / 2023-10-16 + +* FIXED: Add Rails 7.1 compatibility + ### v0.28.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.27.1 / 2023-06-05 diff --git a/instrumentation/rails/README.md b/instrumentation/rails/README.md index aec56238f..330db609f 100644 --- a/instrumentation/rails/README.md +++ b/instrumentation/rails/README.md @@ -14,7 +14,7 @@ Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-r ### Version Compatibility -EOL versions of Rails are not supported by the latest version of this instrumentation. If you are using an EOL version of Rails and need an earlier version of this instrumentation, then consider installing and pinning the compatible gem version, e.g.: +EOL versions of Rails are not supported by the latest version of this instrumentation. If you are using an EOL version of Rails and need an earlier version of this instrumentation, then consider installing and pinning the compatible gem version, e.g.: ```console gem opentelemetry-instrumentation-rails, "" @@ -23,7 +23,7 @@ gem opentelemetry-instrumentation-rails, "" | Rails Version | Instrumentation Version | | --- | --- | | `5.2` | `= 0.24.1` | -| `6.0` | `~> 0.24.1` | +| `6.0` | `= 0.28.0` | | `6.1` | `~> 0.24` | | `7.x` | `~> 0.24` | diff --git a/instrumentation/rails/example/trace_request_demonstration.ru b/instrumentation/rails/example/trace_request_demonstration.ru index 1a6f2b16d..1c74c0f39 100644 --- a/instrumentation/rails/example/trace_request_demonstration.ru +++ b/instrumentation/rails/example/trace_request_demonstration.ru @@ -23,7 +23,7 @@ require 'action_controller/railtie' class TraceRequestApp < Rails::Application config.root = __dir__ config.hosts << 'example.org' - secrets.secret_key_base = 'secret_key_base' + credentials.secret_key_base = 'secret_key_base' config.eager_load = false config.logger = Logger.new($stdout) Rails.logger = config.logger diff --git a/instrumentation/rails/lib/opentelemetry/instrumentation/rails.rb b/instrumentation/rails/lib/opentelemetry/instrumentation/rails.rb index ce8ee826b..f421f3a29 100644 --- a/instrumentation/rails/lib/opentelemetry/instrumentation/rails.rb +++ b/instrumentation/rails/lib/opentelemetry/instrumentation/rails.rb @@ -18,6 +18,7 @@ module Rails require 'opentelemetry-instrumentation-action_pack' require 'opentelemetry-instrumentation-active_support' require 'opentelemetry-instrumentation-action_view' +require 'opentelemetry-instrumentation-action_mailer' require 'opentelemetry-instrumentation-active_record' require_relative 'rails/instrumentation' require_relative 'rails/version' diff --git a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb index c424dc58f..c928c0192 100644 --- a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb +++ b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/instrumentation.rb @@ -12,7 +12,7 @@ module Rails # The Instrumentation class contains logic to detect and install the Rails # instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - MINIMUM_VERSION = Gem::Version.new('6.0.0') + MINIMUM_VERSION = Gem::Version.new('6.1.0') # This gem requires the instrumentantion gems for the different # components of Rails, as a result it does not have any explicit @@ -24,7 +24,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base private def gem_version - ::ActionPack.version + ::Rails.gem_version end end end diff --git a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/version.rb b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/version.rb index f5d46002f..334dc1f60 100644 --- a/instrumentation/rails/lib/opentelemetry/instrumentation/rails/version.rb +++ b/instrumentation/rails/lib/opentelemetry/instrumentation/rails/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Rails - VERSION = '0.28.0' + VERSION = '0.31.0' end end end diff --git a/instrumentation/rails/opentelemetry-instrumentation-rails.gemspec b/instrumentation/rails/opentelemetry-instrumentation-rails.gemspec index c49f43e70..045e45aae 100644 --- a/instrumentation/rails/opentelemetry-instrumentation-rails.gemspec +++ b/instrumentation/rails/opentelemetry-instrumentation-rails.gemspec @@ -26,11 +26,12 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-instrumentation-action_pack', '~> 0.7.0' - spec.add_dependency 'opentelemetry-instrumentation-action_view', '~> 0.6.0' - spec.add_dependency 'opentelemetry-instrumentation-active_job', '~> 0.6.0' - spec.add_dependency 'opentelemetry-instrumentation-active_record', '~> 0.6.1' - spec.add_dependency 'opentelemetry-instrumentation-active_support', '~> 0.4.1' + spec.add_dependency 'opentelemetry-instrumentation-action_mailer', '~> 0.1.0' + spec.add_dependency 'opentelemetry-instrumentation-action_pack', '~> 0.9.0' + spec.add_dependency 'opentelemetry-instrumentation-action_view', '~> 0.7.0' + spec.add_dependency 'opentelemetry-instrumentation-active_job', '~> 0.7.0' + spec.add_dependency 'opentelemetry-instrumentation-active_record', '~> 0.7.0' + spec.add_dependency 'opentelemetry-instrumentation-active_support', '~> 0.6.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' @@ -39,9 +40,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rack-test', '~> 2.1.0' - spec.add_development_dependency 'rails', '>= 6' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.22.0' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/rails/test/instrumentation/opentelemetry/instrumentation/rails/instrumentation_test.rb b/instrumentation/rails/test/instrumentation/opentelemetry/instrumentation/rails/instrumentation_test.rb deleted file mode 100644 index bae64a5b6..000000000 --- a/instrumentation/rails/test/instrumentation/opentelemetry/instrumentation/rails/instrumentation_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Instrumentation::Rails::Instrumentation do - it 'adds the rack tracing middleware' do - _(DEFAULT_RAILS_APP.config.middleware).must_include OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware - end -end diff --git a/instrumentation/rails/test/instrumentation/test_helpers/app_config.rb b/instrumentation/rails/test/instrumentation/test_helpers/app_config.rb index 1bb3fe564..3e8c23ee1 100644 --- a/instrumentation/rails/test/instrumentation/test_helpers/app_config.rb +++ b/instrumentation/rails/test/instrumentation/test_helpers/app_config.rb @@ -26,8 +26,6 @@ def initialize_app(use_exceptions_app: false, remove_rack_tracer_middleware: fal new_app.config.log_level = level case Rails.version - when /^6\.0/ - apply_rails_6_0_configs(new_app) when /^6\.1/ apply_rails_6_1_configs(new_app) when /^7\./ @@ -71,13 +69,6 @@ def add_middlewares(application) ) end - def apply_rails_6_0_configs(application) - # Required in Rails 6 - application.config.hosts << 'example.org' - # Creates a lot of deprecation warnings on subsequent app initializations if not explicitly set. - application.config.action_view.finalize_compiled_template_methods = ActionView::Railtie::NULL_OPTION - end - def apply_rails_6_1_configs(application) # Required in Rails 6 application.config.hosts << 'example.org' diff --git a/instrumentation/rails/test/railtie/dummy/config/initializers/content_security_policy.rb b/instrumentation/rails/test/railtie/dummy/config/initializers/content_security_policy.rb index f37ed8de4..96bd22282 100644 --- a/instrumentation/rails/test/railtie/dummy/config/initializers/content_security_policy.rb +++ b/instrumentation/rails/test/railtie/dummy/config/initializers/content_security_policy.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Define an application-wide content security policy diff --git a/instrumentation/rails/test/railtie/dummy/config/initializers/inflections.rb b/instrumentation/rails/test/railtie/dummy/config/initializers/inflections.rb index 6c78420e7..9e049dcc9 100644 --- a/instrumentation/rails/test/railtie/dummy/config/initializers/inflections.rb +++ b/instrumentation/rails/test/railtie/dummy/config/initializers/inflections.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/instrumentation/rails/test/railtie/dummy/config/initializers/permissions_policy.rb b/instrumentation/rails/test/railtie/dummy/config/initializers/permissions_policy.rb index 50bcf4ead..810aadeb9 100644 --- a/instrumentation/rails/test/railtie/dummy/config/initializers/permissions_policy.rb +++ b/instrumentation/rails/test/railtie/dummy/config/initializers/permissions_policy.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Define an application-wide HTTP permissions policy. For further # information see https://developers.google.com/web/updates/2018/06/feature-policy # diff --git a/instrumentation/rails/test/railtie/opentelemetry/instrumentation/rails/railtie_test.rb b/instrumentation/rails/test/railtie/opentelemetry/instrumentation/rails/railtie_test.rb index b3465aaf8..53911790d 100644 --- a/instrumentation/rails/test/railtie/opentelemetry/instrumentation/rails/railtie_test.rb +++ b/instrumentation/rails/test/railtie/opentelemetry/instrumentation/rails/railtie_test.rb @@ -9,10 +9,10 @@ class OpenTelemetry::Instrumentation::Rails::RailtieTest < ActiveSupport::TestCa include OpenTelemetry::SemanticConventions setup do - OpenTelemetry::Instrumentation.registry.instance_variable_get('@instrumentation').each do |i| - i.instance_variable_set('@instance', nil) + OpenTelemetry::Instrumentation.registry.instance_variable_get(:@instrumentation).each do |i| + i.instance_variable_set(:@instance, nil) end - OpenTelemetry::SDK::Resources::Resource.instance_variable_set('@default', nil) + OpenTelemetry::SDK::Resources::Resource.instance_variable_set(:@default, nil) OpenTelemetry.tracer_provider = OpenTelemetry::Internal::ProxyTracerProvider.new end @@ -20,7 +20,7 @@ class OpenTelemetry::Instrumentation::Rails::RailtieTest < ActiveSupport::TestCa OpenTelemetry::TestHelpers.with_env('OTEL_SERVICE_NAME' => nil) do run_initializer assert_instance_of(OpenTelemetry::SDK::Trace::TracerProvider, OpenTelemetry.tracer_provider) - assert_same(Rails.logger, OpenTelemetry.logger.instance_variable_get('@logger')) + assert_same(Rails.logger, OpenTelemetry.logger.instance_variable_get(:@logger)) end end diff --git a/instrumentation/rake/CHANGELOG.md b/instrumentation/rake/CHANGELOG.md index 342835891..65c061e76 100644 --- a/instrumentation/rake/CHANGELOG.md +++ b/instrumentation/rake/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History: opentelemetry-instrumentation-rake +### v0.2.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + ### v0.2.1 / 2023-06-05 * FIXED: Base config options diff --git a/instrumentation/rake/lib/opentelemetry/instrumentation/rake/version.rb b/instrumentation/rake/lib/opentelemetry/instrumentation/rake/version.rb index b0aeebb26..2d86b5213 100644 --- a/instrumentation/rake/lib/opentelemetry/instrumentation/rake/version.rb +++ b/instrumentation/rake/lib/opentelemetry/instrumentation/rake/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Rake - VERSION = '0.2.1' + VERSION = '0.2.2' end end end diff --git a/instrumentation/rake/opentelemetry-instrumentation-rake.gemspec b/instrumentation/rake/opentelemetry-instrumentation-rake.gemspec index 086244590..b689c8fce 100644 --- a/instrumentation/rake/opentelemetry-instrumentation-rake.gemspec +++ b/instrumentation/rake/opentelemetry-instrumentation-rake.gemspec @@ -33,8 +33,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.0' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rake', '>= 0.9.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/rdkafka/CHANGELOG.md b/instrumentation/rdkafka/CHANGELOG.md index 4b66d4fd6..58d8f5431 100644 --- a/instrumentation/rdkafka/CHANGELOG.md +++ b/instrumentation/rdkafka/CHANGELOG.md @@ -1,8 +1,36 @@ # Release History: opentelemetry-instrumentation-rdkafka +### v0.4.7 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.4.6 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.4.5 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.4.4 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.4.3 / 2024-04-05 + +* FIXED: Suppress deprecation warning in Rdkafka Instrumentation + +### v0.4.2 / 2023-11-23 + +* FIXED: Retry Release of 0.4.1 [#730](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues/730) + +### v0.4.1 / 2023-11-22 + +* FIXED: Get Rdkafka version from VERSION contant + ### v0.4.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.3.2 / 2023-07-21 diff --git a/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/instrumentation.rb b/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/instrumentation.rb index 34c0ac27c..4d9919145 100644 --- a/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/instrumentation.rb +++ b/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/instrumentation.rb @@ -12,7 +12,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base MINIMUM_VERSION = Gem::Version.new('0.10.0') compatible do - Gem.loaded_specs['rdkafka'].version >= MINIMUM_VERSION + Gem::Version.new(::Rdkafka::VERSION) >= MINIMUM_VERSION end install do |_config| diff --git a/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/patches/consumer.rb b/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/patches/consumer.rb index 97f3dd2b5..0186128a3 100644 --- a/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/patches/consumer.rb +++ b/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/patches/consumer.rb @@ -10,6 +10,13 @@ module Rdkafka module Patches # The Consumer module contains the instrumentation patch for the Consumer class module Consumer + GETTER = if Gem::Version.new(::Rdkafka::VERSION) >= Gem::Version.new('0.13.0') + Context::Propagation.text_map_getter + else + OpenTelemetry::Common::Propagation.symbol_key_getter + end + private_constant :GETTER + def each super do |message| attributes = { @@ -23,7 +30,7 @@ def each message_key = extract_message_key(message.key) attributes['messaging.kafka.message_key'] = message_key if message_key - parent_context = OpenTelemetry.propagation.extract(message.headers, getter: OpenTelemetry::Common::Propagation.symbol_key_getter) + parent_context = OpenTelemetry.propagation.extract(message.headers, getter: GETTER) span_context = OpenTelemetry::Trace.current_span(parent_context).context links = [OpenTelemetry::Trace::Link.new(span_context)] if span_context.valid? @@ -47,7 +54,7 @@ def each_batch(max_items: 100, bytes_threshold: Float::INFINITY, timeout_ms: 250 } links = messages.map do |message| - trace_context = OpenTelemetry.propagation.extract(message.headers, getter: OpenTelemetry::Common::Propagation.symbol_key_getter) + trace_context = OpenTelemetry.propagation.extract(message.headers, getter: GETTER) span_context = OpenTelemetry::Trace.current_span(trace_context).context OpenTelemetry::Trace::Link.new(span_context) if span_context.valid? end diff --git a/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/version.rb b/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/version.rb index 150141cec..556700353 100644 --- a/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/version.rb +++ b/instrumentation/rdkafka/lib/opentelemetry/instrumentation/rdkafka/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Rdkafka - VERSION = '0.4.0' + VERSION = '0.4.7' end end end diff --git a/instrumentation/rdkafka/opentelemetry-instrumentation-rdkafka.gemspec b/instrumentation/rdkafka/opentelemetry-instrumentation-rdkafka.gemspec index 9dbd122db..afd16cdf0 100644 --- a/instrumentation/rdkafka/opentelemetry-instrumentation-rdkafka.gemspec +++ b/instrumentation/rdkafka/opentelemetry-instrumentation-rdkafka.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' @@ -36,8 +35,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'pry-byebug' unless RUBY_ENGINE == 'jruby' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rdkafka', '>= 0.12' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/redis/Appraisals b/instrumentation/redis/Appraisals index 0f8374dc8..7939892cf 100644 --- a/instrumentation/redis/Appraisals +++ b/instrumentation/redis/Appraisals @@ -1,29 +1,6 @@ # frozen_string_literal: true -appraise 'redis-4.6' do - gem 'redis', '~> 4.1.0' -end - -appraise 'redis-4.5' do - gem 'redis', '~> 4.1.0' -end - -appraise 'redis-4.4' do - gem 'redis', '~> 4.1.0' -end - -appraise 'redis-4.3' do - gem 'redis', '~> 4.1.0' -end - -appraise 'redis-4.2' do - gem 'redis', '~> 4.1.0' -end - -appraise 'redis-4.1' do - gem 'redis', '~> 4.1.0' -end - -appraise 'redis-4.0' do - gem 'redis', '~> 4.0.0' +appraise 'redis-4.x' do + gem 'redis-client', '~> 0.22' + gem 'redis', '~> 4.8' end diff --git a/instrumentation/redis/CHANGELOG.md b/instrumentation/redis/CHANGELOG.md index 1b116a11a..39cf15af2 100644 --- a/instrumentation/redis/CHANGELOG.md +++ b/instrumentation/redis/CHANGELOG.md @@ -1,5 +1,17 @@ # Release History: opentelemetry-instrumentation-redis +### v0.25.6 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.25.5 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.25.4 / 2024-04-30 + +* FIXED: Bundler conflict warnings + ### v0.25.3 / 2023-08-03 * FIXED: Remove inline linter rules diff --git a/instrumentation/redis/lib/opentelemetry/instrumentation/redis/patches/redis_v4_client.rb b/instrumentation/redis/lib/opentelemetry/instrumentation/redis/patches/redis_v4_client.rb index 5cf32ec1a..6bd473c09 100644 --- a/instrumentation/redis/lib/opentelemetry/instrumentation/redis/patches/redis_v4_client.rb +++ b/instrumentation/redis/lib/opentelemetry/instrumentation/redis/patches/redis_v4_client.rb @@ -43,7 +43,7 @@ def process(commands) end instrumentation_tracer.in_span(span_name, attributes: attributes, kind: :client) do |s| - super(commands).tap do |reply| + super.tap do |reply| if reply.is_a?(::Redis::CommandError) s.record_exception(reply) s.status = Trace::Status.error(reply.message) diff --git a/instrumentation/redis/lib/opentelemetry/instrumentation/redis/version.rb b/instrumentation/redis/lib/opentelemetry/instrumentation/redis/version.rb index 22eef6963..f386662f2 100644 --- a/instrumentation/redis/lib/opentelemetry/instrumentation/redis/version.rb +++ b/instrumentation/redis/lib/opentelemetry/instrumentation/redis/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Redis - VERSION = '0.25.3' + VERSION = '0.25.6' end end end diff --git a/instrumentation/redis/opentelemetry-instrumentation-redis.gemspec b/instrumentation/redis/opentelemetry-instrumentation-redis.gemspec index 3b8b39298..811f3e09f 100644 --- a/instrumentation/redis/opentelemetry-instrumentation-redis.gemspec +++ b/instrumentation/redis/opentelemetry-instrumentation-redis.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' @@ -34,9 +33,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'redis', '~> 4.1.0' - spec.add_development_dependency 'redis-client', '~> 0.7' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/resque/CHANGELOG.md b/instrumentation/resque/CHANGELOG.md index 5225be2c0..7f8682447 100644 --- a/instrumentation/resque/CHANGELOG.md +++ b/instrumentation/resque/CHANGELOG.md @@ -1,5 +1,13 @@ # Release History: opentelemetry-instrumentation-resque +### v0.5.2 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.5.1 / 2024-02-08 + +* DOCS: Relocate Resque config option comments to render in Yard docs + ### v0.5.0 / 2023-09-07 * FIXED: Align messaging instrumentation operation names diff --git a/instrumentation/resque/lib/opentelemetry/instrumentation/resque.rb b/instrumentation/resque/lib/opentelemetry/instrumentation/resque.rb index b1c3d3da7..0ffa565b6 100644 --- a/instrumentation/resque/lib/opentelemetry/instrumentation/resque.rb +++ b/instrumentation/resque/lib/opentelemetry/instrumentation/resque.rb @@ -9,7 +9,7 @@ module OpenTelemetry module Instrumentation - # Contains the OpenTelemetry instrumentation for the Resque gem + # (see OpenTelemetry::Instrumentation::Resque::Instrumentation) module Resque end end diff --git a/instrumentation/resque/lib/opentelemetry/instrumentation/resque/instrumentation.rb b/instrumentation/resque/lib/opentelemetry/instrumentation/resque/instrumentation.rb index 15cec6cea..f5f966c31 100644 --- a/instrumentation/resque/lib/opentelemetry/instrumentation/resque/instrumentation.rb +++ b/instrumentation/resque/lib/opentelemetry/instrumentation/resque/instrumentation.rb @@ -7,7 +7,61 @@ module OpenTelemetry module Instrumentation module Resque - # The Instrumentation class contains logic to detect and install the Resque instrumentation + # The {OpenTelemetry::Instrumentation::Resque::Instrumentation} class contains logic to detect and install the Resque instrumentation + # + # Installation and configuration of this instrumentation is done within the + # {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry/SDK#configure-instance_method OpenTelemetry::SDK#configure} + # block, calling {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use use()} + # or {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use_all use_all()}. + # + # ## Configuration keys and options + # + # ### `:span_naming` + # + # Specifies how the span names are set. Can be one of: + # + # - `:queue` **(default)** - The job span name will appear as ' ', + # for example `default process`. + # - `:job_class` - The job span name will appear as ' ', + # for example `SimpleJob process`. + # + # ### `:propagation_style` + # + # Specifies how the job's execution is traced and related to the trace where the job was enqueued. + # + # - `:link` **(default)** - The job will be represented by a separate trace from the span that enqueued the job. + # - The initial span of the job trace will be associated with the span that enqueued the job, via a + # {https://opentelemetry.io/docs/concepts/signals/traces/#span-links Span Link}. + # - `:child` - The job will be represented within the same logical trace, as a direct + # child of the span that enqueued the job. + # - `:none` - The job will be represented by a separate trace from the span that enqueued the job. + # There will be no explicit relationship between the job trace and the trace containing the span that + # enqueued the job. + # + # ### `:force_flush` + # + # Specifies whether spans are forcibly flushed (exported out of process) upon every job completion. + # + # - `:ask_the_job` **(default)** - Synchronously flush completed spans when a job completes if workers are + # forked for each job. + # - Determined by checking if {https://www.rubydoc.info/gems/resque/Resque%2FWorker:fork_per_job%3F Resque::Worker#fork_per_job?} + # is true. Spans must be flushed and exported before a worker process terminates or no telemetry will be sent. + # - `:always` - All completed spans will be synchronously flushed at the end of a job's execution. + # - `:never` - Job completion will not affect the export of spans out of worker processes. + # - Selecting this option will result in spans being lost if the worker process ends before + # the spans are flushed. You might select this option if you wish to coordinate the timing for flushing + # completed spans yourself. + # + # @example An explicit default configuration + # OpenTelemetry::SDK.configure do |c| + # c.use_all({ + # 'OpenTelemetry::Instrumentation::Resque' => { + # span_naming: :queue, + # propagation_style: :link + # force_flush: :ask_the_job, + # }, + # }) + # end class Instrumentation < OpenTelemetry::Instrumentation::Base install do |_config| require_dependencies @@ -18,30 +72,6 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base defined?(::Resque) end - ## Supported configuration keys for the install config hash: - # - # force_flush: controls if spans are forcibly flushed upon job completion - # - :ask_the_job (default) - if `Resque::Worker#fork_per_job?` is set, - # all completed spans will be synchronously flushed at the end of a - # job's execution - # - :always - all completed spans will be synchronously flushed at the - # end of a job's execution - # - :never - the job will not intervene with the processing of spans - # - # span_naming: when `:job_class`, the span names will be set to - # ' '. When `:queue`, the span names - # will be set to ' ' - # - # propagation_style: controls how the job's execution is traced and related - # to the trace where the job was enqueued. Can be one of: - # - :link (default) - the job will be executed in a separate trace. The - # initial span of the execution trace will be linked to the span that - # enqueued the job, via a Span Link. - # - :child - the job will be executed in the same logical trace, as a direct - # child of the span that enqueued the job. - # - :none - the job's execution will not be explicitly linked to the span that - # enqueued the job. - option :force_flush, default: :ask_the_job, validate: %I[ask_the_job always never] option :span_naming, default: :queue, validate: %I[job_class queue] option :propagation_style, default: :link, validate: %i[link child none] diff --git a/instrumentation/resque/lib/opentelemetry/instrumentation/resque/version.rb b/instrumentation/resque/lib/opentelemetry/instrumentation/resque/version.rb index 284cf059f..54309f2cd 100644 --- a/instrumentation/resque/lib/opentelemetry/instrumentation/resque/version.rb +++ b/instrumentation/resque/lib/opentelemetry/instrumentation/resque/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Resque - VERSION = '0.5.0' + VERSION = '0.5.2' end end end diff --git a/instrumentation/resque/opentelemetry-instrumentation-resque.gemspec b/instrumentation/resque/opentelemetry-instrumentation-resque.gemspec index 6294c5453..0978cb188 100644 --- a/instrumentation/resque/opentelemetry-instrumentation-resque.gemspec +++ b/instrumentation/resque/opentelemetry-instrumentation-resque.gemspec @@ -34,8 +34,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'resque' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/restclient/CHANGELOG.md b/instrumentation/restclient/CHANGELOG.md index 3575888c7..101fa104d 100644 --- a/instrumentation/restclient/CHANGELOG.md +++ b/instrumentation/restclient/CHANGELOG.md @@ -1,5 +1,21 @@ # Release History: opentelemetry-instrumentation-restclient +### v0.22.6 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.22.5 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.22.4 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.22.3 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.22.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb index dd98bf912..afb2fdf11 100644 --- a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb +++ b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/patches/request.rb @@ -12,7 +12,7 @@ module Patches module Request def execute(&block) trace_request do |_span| - super(&block) + super end end @@ -49,12 +49,12 @@ def trace_request # If so, add additional attributes. if response.is_a?(::RestClient::Response) span.set_attribute('http.status_code', response.code) - span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(response.code.to_i) + span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(response.code.to_i) end end rescue ::RestClient::ExceptionWithResponse => e span.set_attribute('http.status_code', e.http_code) - span.status = OpenTelemetry::Trace::Status.error unless (100..399).include?(e.http_code.to_i) + span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(e.http_code.to_i) raise e ensure diff --git a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/version.rb b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/version.rb index a9d94fbfc..00ddf501a 100644 --- a/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/version.rb +++ b/instrumentation/restclient/lib/opentelemetry/instrumentation/restclient/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module RestClient - VERSION = '0.22.2' + VERSION = '0.22.6' end end end diff --git a/instrumentation/restclient/opentelemetry-instrumentation-restclient.gemspec b/instrumentation/restclient/opentelemetry-instrumentation-restclient.gemspec index 42aaaa0fe..41d031184 100644 --- a/instrumentation/restclient/opentelemetry-instrumentation-restclient.gemspec +++ b/instrumentation/restclient/opentelemetry-instrumentation-restclient.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'appraisal', '~> 2.5' @@ -34,8 +33,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rest-client', '~> 2.1.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/rspec/CHANGELOG.md b/instrumentation/rspec/CHANGELOG.md index f246adfa2..6c22f2db1 100644 --- a/instrumentation/rspec/CHANGELOG.md +++ b/instrumentation/rspec/CHANGELOG.md @@ -1,5 +1,13 @@ # Release History: opentelemetry-instrumentation-rspec +### v0.3.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.3.2 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.3.1 / 2023-06-05 * FIXED: Base config options diff --git a/instrumentation/rspec/Gemfile b/instrumentation/rspec/Gemfile index 2baf57ac4..74a39f083 100644 --- a/instrumentation/rspec/Gemfile +++ b/instrumentation/rspec/Gemfile @@ -9,5 +9,6 @@ source 'https://rubygems.org' gemspec group :test do + gem 'rspec', '~> 3.0' gem 'opentelemetry-instrumentation-base', path: '../base' end diff --git a/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb b/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb index 538625c23..11a37b8f8 100644 --- a/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb +++ b/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/formatter.rb @@ -121,7 +121,7 @@ def strip_console_codes(string) end def multiple_failure_message(exception) - exception.failures.map(&:message).map(&method(:strip_console_codes)).join("\n\n") + exception.failures.map(&:message).map { |x| strip_console_codes(x) }.join("\n\n") end end end diff --git a/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/version.rb b/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/version.rb index dc6826d98..57f53a5e7 100644 --- a/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/version.rb +++ b/instrumentation/rspec/lib/opentelemetry/instrumentation/rspec/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module RSpec - VERSION = '0.3.1' + VERSION = '0.3.3' end end end diff --git a/instrumentation/rspec/opentelemetry-instrumentation-rspec.gemspec b/instrumentation/rspec/opentelemetry-instrumentation-rspec.gemspec index 976bd7c10..375f55166 100644 --- a/instrumentation/rspec/opentelemetry-instrumentation-rspec.gemspec +++ b/instrumentation/rspec/opentelemetry-instrumentation-rspec.gemspec @@ -34,8 +34,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rspec', '~> 3.10.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb b/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb index 656626333..d54e6a96d 100644 --- a/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb +++ b/instrumentation/rspec/test/opentelemetry/instrumentation/rspec/formatter_test.rb @@ -103,10 +103,10 @@ def run_rspec_with_tracing end describe 'exports spans for examples' do - def run_example(&blk) + def run_example(...) spans = run_rspec_with_tracing do RSpec.describe('group one') do - instance_eval(&blk) + instance_eval(...) end end spans.first diff --git a/instrumentation/ruby_kafka/CHANGELOG.md b/instrumentation/ruby_kafka/CHANGELOG.md index cad2b514d..f67fc3cf6 100644 --- a/instrumentation/ruby_kafka/CHANGELOG.md +++ b/instrumentation/ruby_kafka/CHANGELOG.md @@ -1,8 +1,16 @@ # Release History: opentelemetry-instrumentation-ruby_kafka +### v0.21.2 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.21.1 / 2024-04-30 + +* FIXED: Bundler conflict warnings + ### v0.21.0 / 2023-09-07 -* FIXED: Align messaging instrumentation operation names +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) ### v0.20.2 / 2023-08-09 diff --git a/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/instrumentation.rb b/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/instrumentation.rb index 2fd61a413..c2c045e3e 100644 --- a/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/instrumentation.rb +++ b/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/instrumentation.rb @@ -22,7 +22,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base end compatible do - (!gem_version.nil? && gem_version >= MINIMUM_VERSION) + !gem_version.nil? && gem_version >= MINIMUM_VERSION end private diff --git a/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/version.rb b/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/version.rb index ec39acf71..8939e202c 100644 --- a/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/version.rb +++ b/instrumentation/ruby_kafka/lib/opentelemetry/instrumentation/ruby_kafka/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module RubyKafka - VERSION = '0.21.0' + VERSION = '0.21.2' end end end diff --git a/instrumentation/ruby_kafka/opentelemetry-instrumentation-ruby_kafka.gemspec b/instrumentation/ruby_kafka/opentelemetry-instrumentation-ruby_kafka.gemspec index 740399830..9d5e2502a 100644 --- a/instrumentation/ruby_kafka/opentelemetry-instrumentation-ruby_kafka.gemspec +++ b/instrumentation/ruby_kafka/opentelemetry-instrumentation-ruby_kafka.gemspec @@ -34,8 +34,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' - spec.add_development_dependency 'ruby-kafka' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/sidekiq/.yardopts b/instrumentation/sidekiq/.yardopts index 9f610acac..79e64ffc0 100644 --- a/instrumentation/sidekiq/.yardopts +++ b/instrumentation/sidekiq/.yardopts @@ -2,6 +2,7 @@ --title=OpenTelemetry Sidekiq Instrumentation --markup=markdown --main=README.md +--tag=instrumentation_option_default:"Default" ./lib/opentelemetry/instrumentation/**/*.rb ./lib/opentelemetry/instrumentation.rb - diff --git a/instrumentation/sidekiq/CHANGELOG.md b/instrumentation/sidekiq/CHANGELOG.md index 8fb267d12..007b05113 100644 --- a/instrumentation/sidekiq/CHANGELOG.md +++ b/instrumentation/sidekiq/CHANGELOG.md @@ -1,9 +1,33 @@ # Release History: opentelemetry-instrumentation-sidekiq -### v0.25.0 / 2023-09-07 +### v0.25.6 / 2024-07-02 + +* DOCS: Fix CHANGELOGs to reflect a past breaking change + +### v0.25.5 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.25.4 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.25.3 / 2024-04-30 -* FIXED: Align messaging instrumentation operation names +* FIXED: Bundler conflict warnings + +### v0.25.2 / 2024-02-08 + +* DOCS: Fix doc for sidekiq options. + +### v0.25.1 / 2024-02-08 + +* DOCS: ✏️ Sidekiq instrumentation options + +### v0.25.0 / 2023-09-07 +* BREAKING CHANGE: Align messaging instrumentation operation names [#648](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/648) +* ### v0.24.4 / 2023-08-07 * FIXED: Allow traces inside jobs while avoiding Redis noise diff --git a/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq.rb b/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq.rb index 77fc78d76..530464dde 100644 --- a/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq.rb +++ b/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq.rb @@ -9,7 +9,7 @@ module OpenTelemetry module Instrumentation - # Contains the OpenTelemetry instrumentation for the Sidekiq gem + # (see {OpenTelemetry::Instrumentation::Sidekiq::Instrumentation}) module Sidekiq end end diff --git a/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/instrumentation.rb b/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/instrumentation.rb index b1ce910b4..1f3fe2720 100644 --- a/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/instrumentation.rb +++ b/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/instrumentation.rb @@ -7,8 +7,81 @@ module OpenTelemetry module Instrumentation module Sidekiq - # The Instrumentation class contains logic to detect and install the Sidekiq - # instrumentation + # The `OpenTelemetry::Instrumentation::Sidekiq::Instrumentation` class contains logic to detect and install the Sidekiq instrumentation + # + # Installation and configuration of this instrumentation is done within the + # {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry/SDK#configure-instance_method OpenTelemetry::SDK#configure} + # block, calling {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use use()} + # or {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use_all use_all()}. + # + # ## Configuration keys and options + # + # ### `:span_naming` + # + # Specifies how the span names are set. Can be one of: + # + # - `:queue` **(default)** - The job span name will appear as ' ', + # for example `default process`. + # - `:job_class` - The job span name will appear as ' ', + # for example `SimpleJob process`. + # + # ### `:propagation_style` + # + # Specifies how the job's execution is traced and related to the trace where the job was enqueued. + # + # - `:link` **(default)** - The job will be represented by a separate trace from the span that enqueued the job. + # - The initial span of the job trace will be associated with the span that enqueued the job, via a + # {https://opentelemetry.io/docs/concepts/signals/traces/#span-links Span Link}. + # - `:child` - The job will be represented within the same logical trace, as a direct + # child of the span that enqueued the job. + # - `:none` - The job will be represented by a separate trace from the span that enqueued the job. + # There will be no explicit relationship between the job trace and the trace containing the span that + # enqueued the job. + # + # ### `:trace_launcher_heartbeat` + # + # Allows tracing Sidekiq::Launcher#heartbeat. + # + # - `false` **(default)** - Sidekiq::Launcher#heartbeat will not be traced. + # - `true` - Sidekiq::Launcher#heartbeat will be traced. + # + # ### `:trace_poller_enqueue` + # + # Allows tracing Sidekiq::Scheduled#enqueue. + # + # - `false` **(default)** - Sidekiq::Scheduled#enqueue will not be traced. + # - `true` - Sidekiq::Scheduled#enqueue will be traced. + # + # ### `:trace_poller_wait` + # + # Allows tracing Sidekiq::Scheduled#wait. + # + # - `false` **(default)** - Sidekiq::Scheduled#wait will not be traced. + # - `true` - Sidekiq::Scheduled#wait will be traced. + # + # ### `:trace_processor_process_one` + # + # Allows tracing Sidekiq::Processor#process_one. + # + # - `false` **(default)** - Sidekiq::Processor#process_one will not be traced. + # - `true` - Sidekiq::Processor#process_one will be traced. + # + # ### `:peer_service` + # + # Sets service name of the remote service. + # + # - `nil` **(default)** + # + # @example An explicit default configuration + # OpenTelemetry::SDK.configure do |c| + # c.use_all({ + # 'OpenTelemetry::Instrumentation::Sidekiq' => { + # span_naming: :queue, + # propagation_style: :link, + # trace_launcher_heartbeat: true, + # }, + # }) + # end class Instrumentation < OpenTelemetry::Instrumentation::Base MINIMUM_VERSION = Gem::Version.new('4.2.10') diff --git a/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/version.rb b/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/version.rb index bde8730d8..553f0de90 100644 --- a/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/version.rb +++ b/instrumentation/sidekiq/lib/opentelemetry/instrumentation/sidekiq/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Sidekiq - VERSION = '0.25.0' + VERSION = '0.25.6' end end end diff --git a/instrumentation/sidekiq/opentelemetry-instrumentation-sidekiq.gemspec b/instrumentation/sidekiq/opentelemetry-instrumentation-sidekiq.gemspec index 009da600c..7c1c4c184 100644 --- a/instrumentation/sidekiq/opentelemetry-instrumentation-sidekiq.gemspec +++ b/instrumentation/sidekiq/opentelemetry-instrumentation-sidekiq.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_development_dependency 'activejob', '>= 6.0' @@ -36,8 +35,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' - spec.add_development_dependency 'sidekiq', '~> 5.2.0' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/sinatra/Appraisals b/instrumentation/sinatra/Appraisals index 77bc21d6f..f04c6775b 100644 --- a/instrumentation/sinatra/Appraisals +++ b/instrumentation/sinatra/Appraisals @@ -4,14 +4,18 @@ # # SPDX-License-Identifier: Apache-2.0 -appraise 'sinatra-3.0.x' do - gem 'sinatra', '~> 3.0.0' +appraise 'sinatra-4.x' do + gem 'sinatra', '~> 4.0' +end + +appraise 'sinatra-3.x' do + gem 'sinatra', '~> 3.0' end appraise 'sinatra-2.x' do gem 'sinatra', '~> 2.1' end -appraise 'sinatra-1.x' do - gem 'sinatra', '~> 1.4' +appraise 'sinatra-latest' do + gem 'sinatra' end diff --git a/instrumentation/sinatra/CHANGELOG.md b/instrumentation/sinatra/CHANGELOG.md index 4ca2ae8ac..331835a9b 100644 --- a/instrumentation/sinatra/CHANGELOG.md +++ b/instrumentation/sinatra/CHANGELOG.md @@ -1,5 +1,21 @@ # Release History: opentelemetry-instrumentation-sinatra +### v0.24.0 / 2024-07-02 + +* ADDED: Make Rack install optional for sinatra + +### v0.23.5 / 2024-06-18 + +* FIXED: Relax otel common gem constraints + +### v0.23.4 / 2024-05-09 + +* FIXED: Untrace entire request + +### v0.23.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + ### v0.23.2 / 2023-07-21 * ADDED: Update `opentelemetry-common` from [0.19.3 to 0.20.0](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/537) diff --git a/instrumentation/sinatra/README.md b/instrumentation/sinatra/README.md index 69a77984a..ddbad1f95 100644 --- a/instrumentation/sinatra/README.md +++ b/instrumentation/sinatra/README.md @@ -10,8 +10,22 @@ Install the gem using: gem install opentelemetry-instrumentation-sinatra ``` + Or, if you use [bundler][bundler-home], include `opentelemetry-instrumentation-sinatra` to your `Gemfile`. +### Version Compatibility + +Older versions of Sinatra depend on older version of Rack, which are not supported by the latest version of Rack instrumentation. If you are using an older version of Sinatra and need an earlier version of Rack instrumentation, then consider installing and pinning the compatible gem version, e.g.: + +```console +gem opentelemetry-instrumentation-rack, "" +``` + +| Sinatra Version | Rack Instrumentation Version | +| --------------- | ---------------------------- | +| `< 2.0` | `= 0.22.1` | +| `>= 2.0` | `~> 0.22` | + ## Usage To install the instrumentation, add the gem to your Gemfile: diff --git a/instrumentation/sinatra/example/Gemfile b/instrumentation/sinatra/example/Gemfile index 0ddf65b87..c6bb91d5d 100644 --- a/instrumentation/sinatra/example/Gemfile +++ b/instrumentation/sinatra/example/Gemfile @@ -5,9 +5,10 @@ # SPDX-License-Identifier: Apache-2.0 source 'https://rubygems.org' -gem 'puma' gem 'opentelemetry-api' gem 'opentelemetry-common' gem 'opentelemetry-instrumentation-sinatra' gem 'opentelemetry-sdk' +gem 'puma' +gem 'rackup' gem 'sinatra' diff --git a/instrumentation/sinatra/example/config.ru b/instrumentation/sinatra/example/config.ru new file mode 100755 index 000000000..0de51a949 --- /dev/null +++ b/instrumentation/sinatra/example/config.ru @@ -0,0 +1,51 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# Example rack application that manually manages tracer middleware + +require 'rubygems' +require 'bundler/setup' + +Bundler.require + +ENV['OTEL_TRACES_EXPORTER'] = 'console' +OpenTelemetry::SDK.configure do |c| + c.use_all({ + 'OpenTelemetry::Instrumentation::Rack' => { }, + 'OpenTelemetry::Instrumentation::Sinatra' => { install_rack: false } + }) +end + +# Example application for the Sinatra instrumentation +class App < Sinatra::Base + set :show_exceptions, false + + template :example_render do + 'Example Render' + end + + get '/example' do + 'Sinatra Instrumentation Example' + end + + # Uses `render` method + get '/example_render' do + erb :example_render + end + + get '/thing/:id' do + 'Thing 1' + end + + get '/error' do + raise 'Panic!' + end +end + +use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args) +run App diff --git a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra.rb b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra.rb index 5b6147252..c6bf01670 100644 --- a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra.rb +++ b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra.rb @@ -9,7 +9,7 @@ module OpenTelemetry module Instrumentation - # Contains the OpenTelemetry instrumentation for the Sinatra gem + # (see OpenTelemetry::Instrumentation::Sinatra::Instrumentation) module Sinatra end end diff --git a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/extensions/tracer_extension.rb b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/extensions/tracer_extension.rb index 2a2ca4743..4205326da 100644 --- a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/extensions/tracer_extension.rb +++ b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/extensions/tracer_extension.rb @@ -13,23 +13,25 @@ module Extensions # Sinatra extension that installs TracerMiddleware and provides # tracing for template rendering module TracerExtension - # Sinatra hook after extension is registered - def self.registered(app) - # Create tracing `render` method - ::Sinatra::Base.module_eval do - def render(_engine, data, *) - template_name = data.is_a?(Symbol) ? data : :literal + # Contants patches for `render` method + module RenderPatches + def render(_engine, data, *) + template_name = data.is_a?(Symbol) ? data : :literal - Sinatra::Instrumentation.instance.tracer.in_span( - 'sinatra.render_template', - attributes: { 'sinatra.template_name' => template_name.to_s } - ) do - super - end + Sinatra::Instrumentation.instance.tracer.in_span( + 'sinatra.render_template', + attributes: { 'sinatra.template_name' => template_name.to_s } + ) do + super end end - app.use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args) - app.use(Middlewares::TracerMiddleware) + end + + # Sinatra hook after extension is registered + def self.registered(app) + # Create tracing `render` method + ::Sinatra::Base.prepend(RenderPatches) + Sinatra::Instrumentation.instance.install_middleware(app) end end end diff --git a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/instrumentation.rb b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/instrumentation.rb index b7127838e..bd7b85ad6 100644 --- a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/instrumentation.rb +++ b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/instrumentation.rb @@ -9,18 +9,51 @@ module OpenTelemetry module Instrumentation module Sinatra - # The Instrumentation class contains logic to detect and install the Sinatra - # instrumentation + # The {OpenTelemetry::Instrumentation::Sinatra::Instrumentation} class contains logic to detect and install the Sinatra instrumentation + # + # Installation and configuration of this instrumentation is done within the + # {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry/SDK#configure-instance_method OpenTelemetry::SDK#configure} + # block, calling {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use use()} + # or {https://www.rubydoc.info/gems/opentelemetry-sdk/OpenTelemetry%2FSDK%2FConfigurator:use_all use_all()}. + # + # ## Configuration keys and options + # + # ### `:install_rack` + # + # Default is `true`. Specifies whether or not to install the Rack instrumentation as part of installing the Sinatra instrumentation. + # This is useful in cases where you have multiple Rack applications but want to manually specify where to insert the tracing middleware. + # + # @example Manually install Rack instrumentation. + # OpenTelemetry::SDK.configure do |c| + # c.use_all({ + # 'OpenTelemetry::Instrumentation::Rack' => { }, + # 'OpenTelemetry::Instrumentation::Sinatra' => { + # install_rack: false + # }, + # }) + # end + # class Instrumentation < OpenTelemetry::Instrumentation::Base - install do |_| - OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install({}) + install do |config| + OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install({}) if config[:install_rack] + + unless OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.installed? + OpenTelemetry.logger.warn('Rack instrumentation is required for Sinatra but not installed. Please see the docs for more details: https://opentelemetry.io/docs/languages/ruby/libraries/') + end ::Sinatra::Base.register Extensions::TracerExtension end + option :install_rack, default: true, validate: :boolean + present do defined?(::Sinatra) end + + def install_middleware(app) + app.use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args) if config[:install_rack] + app.use(Middlewares::TracerMiddleware) + end end end end diff --git a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/version.rb b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/version.rb index 8bb6b015b..1411df42f 100644 --- a/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/version.rb +++ b/instrumentation/sinatra/lib/opentelemetry/instrumentation/sinatra/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Sinatra - VERSION = '0.23.2' + VERSION = '0.24.0' end end end diff --git a/instrumentation/sinatra/opentelemetry-instrumentation-sinatra.gemspec b/instrumentation/sinatra/opentelemetry-instrumentation-sinatra.gemspec index 3bc862704..20da3876f 100644 --- a/instrumentation/sinatra/opentelemetry-instrumentation-sinatra.gemspec +++ b/instrumentation/sinatra/opentelemetry-instrumentation-sinatra.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' - spec.add_dependency 'opentelemetry-common', '~> 0.20.0' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_dependency 'opentelemetry-instrumentation-rack', '~> 0.21' @@ -35,10 +34,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' - spec.add_development_dependency 'rack-test', '~> 1.1.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rack-test', '~> 2.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' - spec.add_development_dependency 'sinatra', '~> 2.0.7' spec.add_development_dependency 'webmock', '~> 3.19' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/instrumentation/sinatra/test/opentelemetry/instrumentation/sinatra_test.rb b/instrumentation/sinatra/test/opentelemetry/instrumentation/sinatra_test.rb index e83efbc91..0425595b8 100644 --- a/instrumentation/sinatra/test/opentelemetry/instrumentation/sinatra_test.rb +++ b/instrumentation/sinatra/test/opentelemetry/instrumentation/sinatra_test.rb @@ -11,6 +11,7 @@ let(:instrumentation) { OpenTelemetry::Instrumentation::Sinatra::Instrumentation.instance } let(:exporter) { EXPORTER } + let(:config) { {} } class CustomError < StandardError; end @@ -66,7 +67,14 @@ class CustomError < StandardError; end end before do - instrumentation.install + Sinatra::Base.reset! + + OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.instance_variable_set(:@installed, false) + OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.config.clear + + instrumentation.instance_variable_set(:@installed, false) + instrumentation.config.clear + instrumentation.install(config) exporter.reset end @@ -169,4 +177,48 @@ class CustomError < StandardError; end _(exporter.finished_spans.first.events[0].attributes['exception.message']).must_equal('custom message') end end + + describe 'when install_rack is set to false' do + let(:config) { { install_rack: false } } + + describe 'missing rack installation' do + it 'disables tracing' do + get '/one/endpoint' + + _(exporter.finished_spans).must_be_empty + end + end + + describe 'when rack is manually installed' do + let(:app) do + apps_to_build = apps + Rack::Builder.new do + use(*OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.middleware_args) + + apps_to_build.each do |root, app| + map root do + run app + end + end + end.to_app + end + + before do + OpenTelemetry::Instrumentation::Rack::Instrumentation.instance.install + end + + it 'creates a span' do + get '/one/endpoint' + + _(exporter.finished_spans.first.attributes).must_equal( + 'http.method' => 'GET', + 'http.host' => 'example.org', + 'http.scheme' => 'http', + 'http.target' => '/one/endpoint', + 'http.route' => '/endpoint', + 'http.status_code' => 200 + ) + end + end + end end diff --git a/instrumentation/trilogy/CHANGELOG.md b/instrumentation/trilogy/CHANGELOG.md index f2da9da4d..6b9fe2acf 100644 --- a/instrumentation/trilogy/CHANGELOG.md +++ b/instrumentation/trilogy/CHANGELOG.md @@ -1,5 +1,34 @@ # Release History: opentelemetry-instrumentation-trilogy +### v0.59.3 / 2024-04-30 + +* FIXED: Bundler conflict warnings + +### v0.59.2 / 2024-02-20 + +* FIXED: Dup string if frozen in trilogy query + +### v0.59.1 / 2024-02-08 + +* FIXED: Add missing requires for sql-helpers to mysql, pg, and trilogy instrumentation + +### v0.59.0 / 2024-02-08 + +* BREAKING CHANGE: Move shared sql behavior to helper gems + +* ADDED: Propagate context to Vitess + +### v0.58.0 / 2024-01-06 + +* BREAKING CHANGE: Change db.mysql.instance.address to db.instance.id + +* ADDED: Change db.mysql.instance.address to db.instance.id +* FIXED: Trilogy only set db.instance.id attribute if there is a value + +### v0.57.0 / 2023-10-27 + +* ADDED: Instrument connect and ping + ### v0.56.3 / 2023-08-03 * FIXED: Remove inline linter rules diff --git a/instrumentation/trilogy/Gemfile b/instrumentation/trilogy/Gemfile index 2baf57ac4..de6a67cae 100644 --- a/instrumentation/trilogy/Gemfile +++ b/instrumentation/trilogy/Gemfile @@ -10,4 +10,7 @@ gemspec group :test do gem 'opentelemetry-instrumentation-base', path: '../base' + gem 'opentelemetry-helpers-mysql', path: '../../helpers/mysql' + gem 'opentelemetry-helpers-sql-obfuscation', path: '../../helpers/sql-obfuscation' + gem 'opentelemetry-propagator-vitess', path: '../../propagator/vitess' end diff --git a/instrumentation/trilogy/README.md b/instrumentation/trilogy/README.md index 10259f628..4bbd0be7a 100644 --- a/instrumentation/trilogy/README.md +++ b/instrumentation/trilogy/README.md @@ -51,6 +51,19 @@ OpenTelemetry::Instrumentation::Trilogy.with_attributes('pizzatoppings' => 'mush end ``` +## Semantic Conventions + +This instrumentation generally uses [Database semantic conventions](https://opentelemetry.io/docs/specs/semconv/database/database-spans/). + +| Attribute Name | Type | Notes | +| - | - | - | +| `db.instance.id` | String | The name of the DB host executing the query e.g. `SELECT @@hostname` | +| `db.name` | String | The name of the database from connection_options | +| `db.statement` | String | SQL statement being executed | +| `db.user` | String | The username from connection_options | +| `db.system` | String | `mysql` | +| `net.peer.name` | String | The name of the remote host from connection_options | + ## How can I get involved? The `opentelemetry-instrumentation-trilogy` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. diff --git a/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/instrumentation.rb b/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/instrumentation.rb index 9fc9c7a5d..684fbe660 100644 --- a/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/instrumentation.rb +++ b/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/instrumentation.rb @@ -9,9 +9,10 @@ module Instrumentation module Trilogy # The Instrumentation class contains logic to detect and install the Trilogy instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - install do |_config| + install do |config| require_dependencies patch_client + configure_propagator(config) end present do @@ -26,6 +27,9 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base option :db_statement, default: :obfuscate, validate: %I[omit include obfuscate] option :span_name, default: :statement_type, validate: %I[statement_type db_name db_operation_and_name] option :obfuscation_limit, default: 2000, validate: :integer + option :propagator, default: nil, validate: :string + + attr_reader :propagator private @@ -36,6 +40,23 @@ def require_dependencies def patch_client ::Trilogy.prepend(Patches::Client) end + + def configure_propagator(config) + propagator = config[:propagator] + @propagator = case propagator + when 'vitess' then fetch_propagator(propagator, 'OpenTelemetry::Propagator::Vitess') + when 'none', nil then nil + else + OpenTelemetry.logger.warn "The #{propagator} propagator is unknown and cannot be configured" + end + end + + def fetch_propagator(name, class_name, gem_suffix = name) + Kernel.const_get(class_name).sql_query_propagator + rescue NameError + OpenTelemetry.logger.warn "The #{name} propagator cannot be configured - please add opentelemetry-propagator-#{gem_suffix} to your Gemfile" + nil + end end end end diff --git a/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/patches/client.rb b/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/patches/client.rb index 1efccd029..8f15dd89d 100644 --- a/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/patches/client.rb +++ b/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/patches/client.rb @@ -1,133 +1,93 @@ # frozen_string_literal: true +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry-helpers-mysql' +require 'opentelemetry-helpers-sql-obfuscation' + module OpenTelemetry module Instrumentation module Trilogy module Patches # Module to prepend to Trilogy for instrumentation - module Client # rubocop:disable Metrics/ModuleLength - QUERY_NAMES = [ - 'set names', - 'select', - 'insert', - 'update', - 'delete', - 'begin', - 'commit', - 'rollback', - 'savepoint', - 'release savepoint', - 'explain', - 'drop database', - 'drop table', - 'create database', - 'create table' - ].freeze - - QUERY_NAME_RE = Regexp.new("^(#{QUERY_NAMES.join('|')})", Regexp::IGNORECASE) - - COMPONENTS_REGEX_MAP = { - single_quotes: /'(?:[^']|'')*?(?:\\'.*|'(?!'))/, - double_quotes: /"(?:[^"]|"")*?(?:\\".*|"(?!"))/, - numeric_literals: /-?\b(?:[0-9]+\.)?[0-9]+([eE][+-]?[0-9]+)?\b/, - boolean_literals: /\b(?:true|false|null)\b/i, - hexadecimal_literals: /0x[0-9a-fA-F]+/, - comments: /(?:#|--).*?(?=\r|\n|$)/i, - multi_line_comments: %r{\/\*(?:[^\/]|\/[^*])*?(?:\*\/|\/\*.*)} - }.freeze - - MYSQL_COMPONENTS = %i[ - single_quotes - double_quotes - numeric_literals - boolean_literals - hexadecimal_literals - comments - multi_line_comments - ].freeze - - FULL_SQL_REGEXP = Regexp.union(MYSQL_COMPONENTS.map { |component| COMPONENTS_REGEX_MAP[component] }) + module Client + def initialize(options = {}) + @connection_options = options # This is normally done by Trilogy#initialize - def query(sql) tracer.in_span( - database_span_name(sql), - attributes: client_attributes(sql).merge!(OpenTelemetry::Instrumentation::Trilogy.attributes), + 'connect', + attributes: client_attributes.merge!(OpenTelemetry::Instrumentation::Trilogy.attributes), + kind: :client + ) do + super + end + end + + def ping(...) + tracer.in_span( + 'ping', + attributes: client_attributes.merge!(OpenTelemetry::Instrumentation::Trilogy.attributes), kind: :client ) do - super(sql) + super + end + end + + def query(sql) + tracer.in_span( + OpenTelemetry::Helpers::MySQL.database_span_name( + sql, + OpenTelemetry::Instrumentation::Trilogy.attributes[ + OpenTelemetry::SemanticConventions::Trace::DB_OPERATION + ], + database_name, + config + ), + attributes: client_attributes(sql).merge!( + OpenTelemetry::Instrumentation::Trilogy.attributes + ), + kind: :client + ) do |_span, context| + if propagator && sql.frozen? + sql = +sql + propagator.inject(sql, context: context) + sql.freeze + elsif propagator + propagator.inject(sql, context: context) + end + + super end end private - def client_attributes(sql) + def client_attributes(sql = nil) attributes = { ::OpenTelemetry::SemanticConventions::Trace::DB_SYSTEM => 'mysql', - ::OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => connection_options.fetch(:host, 'unknown sock') + ::OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME => connection_options&.fetch(:host, 'unknown sock') || 'unknown sock' } attributes[::OpenTelemetry::SemanticConventions::Trace::DB_NAME] = database_name if database_name attributes[::OpenTelemetry::SemanticConventions::Trace::DB_USER] = database_user if database_user attributes[::OpenTelemetry::SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] unless config[:peer_service].nil? - attributes['db.mysql.instance.address'] = @connected_host if defined?(@connected_host) - - case config[:db_statement] - when :obfuscate - attributes[::OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT] = obfuscate_sql(sql) - when :include - attributes[::OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT] = sql + attributes['db.instance.id'] = @connected_host unless @connected_host.nil? + + if sql + case config[:db_statement] + when :obfuscate + attributes[::OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT] = + OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql(sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql) + when :include + attributes[::OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT] = sql + end end attributes end - def obfuscate_sql(sql) - if sql.size > config[:obfuscation_limit] - first_match_index = sql.index(FULL_SQL_REGEXP) - truncation_message = "SQL truncated (> #{config[:obfuscation_limit]} characters)" - return truncation_message unless first_match_index - - truncated_sql = sql[..first_match_index - 1] - "#{truncated_sql}...\n#{truncation_message}" - else - obfuscated = OpenTelemetry::Common::Utilities.utf8_encode(sql, binary: true) - obfuscated = obfuscated.gsub(FULL_SQL_REGEXP, '?') - obfuscated = 'Failed to obfuscate SQL query - quote characters remained after obfuscation' if detect_unmatched_pairs(obfuscated) - obfuscated - end - rescue StandardError => e - OpenTelemetry.handle_error(message: 'Failed to obfuscate SQL', exception: e) - 'OpenTelemetry error: failed to obfuscate sql' - end - - def detect_unmatched_pairs(obfuscated) - # We use this to check whether the query contains any quote characters - # after obfuscation. If so, that's a good indication that the original - # query was malformed, and so our obfuscation can't reliably find - # literals. In such a case, we'll replace the entire query with a - # placeholder. - %r{'|"|\/\*|\*\/}.match(obfuscated) - end - - def database_span_name(sql) - case config[:span_name] - when :statement_type - extract_statement_type(sql) - when :db_name - database_name - when :db_operation_and_name - op = OpenTelemetry::Instrumentation::Trilogy.attributes['db.operation'] - name = database_name - if op && name - "#{op} #{name}" - elsif op - op - elsif name - name - end - end || 'mysql' - end - def database_name connection_options[:database] end @@ -144,11 +104,8 @@ def config Trilogy::Instrumentation.instance.config end - def extract_statement_type(sql) - QUERY_NAME_RE.match(sql) { |match| match[1].downcase } unless sql.nil? - rescue StandardError => e - OpenTelemetry.logger.error("Error extracting sql statement type: #{e.message}") - nil + def propagator + Trilogy::Instrumentation.instance.propagator end end end diff --git a/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/version.rb b/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/version.rb index 8d71c1ee3..24cedba17 100644 --- a/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/version.rb +++ b/instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Instrumentation module Trilogy - VERSION = '0.56.3' + VERSION = '0.59.3' end end end diff --git a/instrumentation/trilogy/opentelemetry-instrumentation-trilogy.gemspec b/instrumentation/trilogy/opentelemetry-instrumentation-trilogy.gemspec index d3cad653a..2e7bac304 100644 --- a/instrumentation/trilogy/opentelemetry-instrumentation-trilogy.gemspec +++ b/instrumentation/trilogy/opentelemetry-instrumentation-trilogy.gemspec @@ -26,21 +26,24 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 3.0' spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-helpers-mysql' + spec.add_dependency 'opentelemetry-helpers-sql-obfuscation' spec.add_dependency 'opentelemetry-instrumentation-base', '~> 0.22.1' spec.add_dependency 'opentelemetry-semantic_conventions', '>= 1.8.0' spec.add_development_dependency 'appraisal', '~> 2.5' spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-propagator-vitess', '~> 0.1' spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' spec.add_development_dependency 'opentelemetry-test-helpers', '~> 0.3' spec.add_development_dependency 'pry' spec.add_development_dependency 'pry-byebug' unless RUBY_ENGINE == 'jruby' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec-mocks' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17.1' - spec.add_development_dependency 'trilogy', '>= 2.0', '< 3.0' spec.add_development_dependency 'yard', '~> 0.9' if spec.respond_to?(:metadata) diff --git a/instrumentation/trilogy/test/opentelemetry/instrumentation/trilogy/instrumentation_test.rb b/instrumentation/trilogy/test/opentelemetry/instrumentation/trilogy/instrumentation_test.rb index 1e395f03f..9b1b7b845 100644 --- a/instrumentation/trilogy/test/opentelemetry/instrumentation/trilogy/instrumentation_test.rb +++ b/instrumentation/trilogy/test/opentelemetry/instrumentation/trilogy/instrumentation_test.rb @@ -12,7 +12,7 @@ describe OpenTelemetry::Instrumentation::Trilogy do let(:instrumentation) { OpenTelemetry::Instrumentation::Trilogy::Instrumentation.instance } let(:exporter) { EXPORTER } - let(:span) { exporter.finished_spans.first } + let(:span) { exporter.finished_spans[1] } let(:config) { {} } let(:driver_options) do { @@ -139,7 +139,7 @@ _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_SYSTEM]).must_equal 'mysql' _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT]).must_equal 'SELECT ?' _(span.attributes[OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME]).must_equal(host) - _(span.attributes['db.mysql.instance.address']).must_be_nil + _(span.attributes['db.instance.id']).must_be_nil end it 'extracts statement type' do @@ -166,6 +166,37 @@ end end + describe 'when connecting' do + let(:span) { exporter.finished_spans.first } + + it 'spans will include database name' do + _(client.connected_host).wont_be_nil + + _(span.name).must_equal 'connect' + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_NAME]).must_equal(database) + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_USER]).must_equal(username) + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_SYSTEM]).must_equal 'mysql' + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME]).must_equal(host) + _(span.attributes['db.instance.id']).must_be_nil + end + end + + describe 'when pinging' do + let(:span) { exporter.finished_spans[2] } + + it 'spans will include database name' do + _(client.connected_host).wont_be_nil + + client.ping + + _(span.name).must_equal 'ping' + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_NAME]).must_equal(database) + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_USER]).must_equal(username) + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_SYSTEM]).must_equal 'mysql' + _(span.attributes[OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME]).must_equal(host) + end + end + describe 'when quering for the connected host' do it 'spans will include the net.peer.name attribute' do _(client.connected_host).wont_be_nil @@ -176,7 +207,7 @@ _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_SYSTEM]).must_equal 'mysql' _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT]).must_equal 'select @@hostname' _(span.attributes[OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME]).must_equal(host) - _(span.attributes['db.mysql.instance.address']).must_be_nil + _(span.attributes['db.instance.id']).must_be_nil client.query('SELECT 1') @@ -188,7 +219,7 @@ _(last_span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_SYSTEM]).must_equal 'mysql' _(last_span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT]).must_equal 'SELECT ?' _(last_span.attributes[OpenTelemetry::SemanticConventions::Trace::NET_PEER_NAME]).must_equal(host) - _(last_span.attributes['db.mysql.instance.address']).must_equal client.connected_host + _(last_span.attributes['db.instance.id']).must_equal client.connected_host end end @@ -285,7 +316,7 @@ client.query(sql) end.must_raise Trilogy::Error - _(span.name).must_equal 'mysql' + _(span.name).must_equal 'select' _(span.attributes[OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT]).must_equal obfuscated_sql end @@ -315,6 +346,84 @@ end end + describe 'when propagator is set to none' do + let(:config) { { propagator: :none } } + + it 'does not inject context' do + sql = +'SELECT * from users where users.id = 1 and users.email = "test@test.com"' + original_sql = sql.dup + expect do + client.query(sql) + end.must_raise Trilogy::Error + _(sql).must_equal original_sql + end + end + + describe 'when propagator is set to nil' do + let(:config) { { propagator: nil } } + + it 'does not inject context' do + sql = +'SELECT * from users where users.id = 1 and users.email = "test@test.com"' + original_sql = sql.dup + expect do + client.query(sql) + end.must_raise Trilogy::Error + _(sql).must_equal original_sql + end + end + + describe 'when propagator is set to vitess' do + let(:config) { { propagator: 'vitess' } } + + it 'does inject context on frozen strings' do + sql = 'SELECT * from users where users.id = 1 and users.email = "test@test.com"' + assert(sql.frozen?) + propagator = OpenTelemetry::Instrumentation::Trilogy::Instrumentation.instance.propagator + + arg_cache = {} # maintain handles to args + allow(client).to receive(:query).and_wrap_original do |m, *args| + arg_cache[:query_input] = args[0] + assert(args[0].frozen?) + m.call(args[0]) + end + + allow(propagator).to receive(:inject).and_wrap_original do |m, *args| + arg_cache[:inject_input] = args[0] + refute(args[0].frozen?) + assert_match(sql, args[0]) + m.call(args[0], context: args[1][:context]) + end + + expect do + client.query(sql) + end.must_raise Trilogy::Error + + # arg_cache[:inject_input] _was_ a mutable string, so it has the context injected + encoded = Base64.strict_encode64("{\"uber-trace-id\":\"#{span.hex_trace_id}:#{span.hex_span_id}:0:1\"}") + assert_equal(arg_cache[:inject_input], "/*VT_SPAN_CONTEXT=#{encoded}*/#{sql}") + + # arg_cache[:inject_input] is now frozen + assert(arg_cache[:inject_input].frozen?) + end + + it 'does inject context on unfrozen strings' do + # inbound SQL is not frozen (string prefixed with +) + sql = +'SELECT * from users where users.id = 1 and users.email = "test@test.com"' + refute(sql.frozen?) + + # dup sql for comparison purposes, since propagator mutates it + cached_sql = sql.dup + + expect do + client.query(sql) + end.must_raise Trilogy::Error + + encoded = Base64.strict_encode64("{\"uber-trace-id\":\"#{span.hex_trace_id}:#{span.hex_span_id}:0:1\"}") + assert_equal(sql, "/*VT_SPAN_CONTEXT=#{encoded}*/#{cached_sql}") + refute(sql.frozen?) + end + end + describe 'when db_statement is set to omit' do let(:config) { { db_statement: :omit } } diff --git a/processor/baggage/.rubocop.yml b/processor/baggage/.rubocop.yml new file mode 100644 index 000000000..1248a2f82 --- /dev/null +++ b/processor/baggage/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: ../../.rubocop.yml diff --git a/processor/baggage/.yardopts b/processor/baggage/.yardopts new file mode 100644 index 000000000..6046da39e --- /dev/null +++ b/processor/baggage/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry Processor Baggage +--markup=markdown +--main=README.md +./lib/opentelemetry/processor/baggage/**/*.rb +./lib/opentelemetry/processor/baggage.rb +- +README.md +CHANGELOG.md diff --git a/processor/baggage/CHANGELOG.md b/processor/baggage/CHANGELOG.md new file mode 100644 index 000000000..9b948ba54 --- /dev/null +++ b/processor/baggage/CHANGELOG.md @@ -0,0 +1,11 @@ +# Release History: opentelemetry-processor-baggage + +### v0.2.0 / 2024-06-18 + +* BREAKING CHANGE: Add baggage key predicate func to baggage span processor + +* ADDED: Add baggage key predicate func to baggage span processor + +### v0.1.0 / 2024-04-18 + +* Initial release. diff --git a/processor/baggage/Gemfile b/processor/baggage/Gemfile new file mode 100644 index 000000000..52eb0ed2e --- /dev/null +++ b/processor/baggage/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +# Specify your gem's dependencies in opentelemetry-processor-baggage.gemspec +gemspec diff --git a/processor/baggage/LICENSE b/processor/baggage/LICENSE new file mode 100644 index 000000000..1ef7dad2c --- /dev/null +++ b/processor/baggage/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/processor/baggage/README.md b/processor/baggage/README.md new file mode 100644 index 000000000..0c5bc8b93 --- /dev/null +++ b/processor/baggage/README.md @@ -0,0 +1,99 @@ +# Baggage Span Processor + +This is an OpenTelemetry [span processor](https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor) that reads key/values stored in [Baggage](https://opentelemetry.io/docs/specs/otel/baggage/api/) in the starting span's parent context and adds them as attributes to the span. + +Keys and values added to Baggage will appear on all subsequent child spans, not the current active span, for a trace within this service *and* will be propagated to external services via propagation headers. +If the external services also have a Baggage span processor, the keys and values will appear in those child spans as well. + +⚠️ Waning ⚠️ +To repeat: a consequence of adding data to Baggage is that the keys and values will appear in all outgoing HTTP headers from the application. +Do not put sensitive information in Baggage. + +## How do I get started? + +Install the gem using: + +```shell +gem install opentelemetry-processor-baggage +``` + +Or, if you use [bundler][bundler-home], include `opentelemetry-processor-baggage` to your `Gemfile`. + +### Version Compatibility + +* OpenTelemetry API v1.0+ + +## Usage + +To install the instrumentation, add the gem to your Gemfile: + +```ruby +gem 'opentelemetry-processor-baggage' +``` + +Then configure the span processor to copy all baggage entries: + +```ruby +require 'rubygems' +require 'bundler/setup' + +Bundler.require + +OpenTelemetry::SDK.configure do |c| + # Add the BaggageSpanProcessor to the collection of span processors and + # copy all baggage entries + c.add_span_processor(OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS + )) + + # Because the span processor list is no longer empty, the SDK will not use the + # values in OTEL_TRACES_EXPORTER to instantiate exporters. + # You'll need to declare your own here in the configure block. + # + # These lines setup the default: a batching OTLP exporter. + c.add_span_processor( + # these constructors without arguments will pull config from the environment + OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new( + OpenTelemetry::Exporter::OTLP::Exporter.new() + ) + ) +end +``` + +Alternatively, you can provide a custom baggage key predicate to select which baggage keys you want to copy. + +For example, to only copy baggage entries that start with `myapp.`: + +```ruby +OUR_BAGGAGE_KEY_PREFIX = 'myapp.'.freeze +OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + # a constant here improves performance + ->(baggage_key) { baggage_key.start_with?(OUR_BAGGAGE_KEY_PREFIX) } +) +``` + +For example, to only copy baggage entries that match `myapp.`, `myapp1.` and `myapp42.`: + +```ruby +OUR_BAGGAGE_KEY_MATCHER = /\Amyapp\d*\./ +OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + ->(baggage_key) { OUR_BAGGAGE_KEY_MATCHER.match?(baggage_key) } +) +``` + +## How can I get involved? + +The `opentelemetry-processor-baggage` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us in [GitHub Discussions][discussions-url] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +The `opentelemetry-instrumentation-sinatra` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + +[bundler-home]: https://bundler.io +[repo-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib +[license-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/LICENSE +[ruby-sig]: /~https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: /~https://github.com/open-telemetry/community#community-meetings +[discussions-url]: /~https://github.com/open-telemetry/opentelemetry-ruby/discussions diff --git a/processor/baggage/Rakefile b/processor/baggage/Rakefile new file mode 100644 index 000000000..88f3670a3 --- /dev/null +++ b/processor/baggage/Rakefile @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' +require 'rubocop/rake_task' + +ENV['ENABLE_COVERAGE'] ||= '1' + +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] +end + +YARD::Rake::YardocTask.new do |t| + t.stats_options = ['--list-undoc'] +end + +if RUBY_ENGINE == 'truffleruby' + task default: %i[test] +else + task default: %i[test rubocop yard] +end diff --git a/processor/baggage/lib/opentelemetry-processor-baggage.rb b/processor/baggage/lib/opentelemetry-processor-baggage.rb new file mode 100644 index 000000000..a1b22b5a5 --- /dev/null +++ b/processor/baggage/lib/opentelemetry-processor-baggage.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'opentelemetry/processor/baggage/baggage_span_processor' diff --git a/processor/baggage/lib/opentelemetry/processor/baggage/baggage_span_processor.rb b/processor/baggage/lib/opentelemetry/processor/baggage/baggage_span_processor.rb new file mode 100644 index 000000000..9721f8517 --- /dev/null +++ b/processor/baggage/lib/opentelemetry/processor/baggage/baggage_span_processor.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry-api' +require 'opentelemetry-sdk' + +module OpenTelemetry + module Processor + module Baggage + # A baggage key predicate that allows all keys to be added to the span as attributes. + ALLOW_ALL_BAGGAGE_KEYS = ->(_) { true } + + # The BaggageSpanProcessor reads key/values stored in Baggage in the + # starting span's parent context and adds them as attributes to the span, + # if a key matches a provided predicate lambda. + # + # Keys and values added to Baggage will appear on all subsequent child spans, + # not the current active span, for a trace within this service *and* will be + # propagated to external services via propagation headers. + # + # If the external services also have a Baggage span processor, the keys and + # values will appear in those child spans as well. + # + # ⚠️ + # To repeat: a consequence of adding data to Baggage is that the keys and + # values will appear in all outgoing HTTP headers from the application. + # Do not put sensitive information in Baggage. + # ⚠️ + # + # @example Adding the BaggageSpanProcessor to the SDK, only add attributes for keys that start with 'myapp.' + # OUR_BAGGAGE_KEY_PREFIX = 'myapp.'.freeze + # + # OpenTelemetry::SDK.configure do |c| + # # Add the BaggageSpanProcessor to the collection of span processors + # c.add_span_processor( + # OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + # ->(key) { key.start_with?(OUR_BAGGAGE_KEY_PREFIX) } # a constant here improves performance + # ) + # ) + # + # # Because the span processor list is no longer empty, the SDK will not use the + # # values in OTEL_TRACES_EXPORTER to instantiate exporters. + # # You'll need to declare your own here in the configure block. + # # + # # These lines setup the default: a batching OTLP exporter. + # c.add_span_processor( + # # these constructors without arguments will pull config from the environment + # OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new( + # OpenTelemetry::Exporter::OTLP::Exporter.new() + # ) + # ) + # end + # + # @example Allow all Baggage keys to be added to the span as attributes + # OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + # # This processor provides a convenience predicate that allows all keys to be added as attributes. + # OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS + # ) + class BaggageSpanProcessor < OpenTelemetry::SDK::Trace::SpanProcessor + # Create a new BaggageSpanProcessor that reads Baggage keys and values from the parent context + # and adds them as attributes to the span. + # + # @param [lambda] baggage_key_predicate A lambda that takes a baggage key [String] and returns true if + # the key should be added to the span as an attribute, false otherwise. + # + # @example Only add attributes for keys that start with a specific prefix + # OUR_BAGGAGE_KEY_PREFIX = 'myapp.'.freeze + # OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + # ->(key) { key.start_with?(OUR_BAGGAGE_KEY_PREFIX) } # a constant here improves performance + # ) + def initialize(baggage_key_predicate) + raise ArgumentError, 'baggage_key_predicate must respond to :call (lambda/Proc)' unless baggage_key_predicate.respond_to?(:call) + + @baggage_key_predicate = baggage_key_predicate + super() + end + + # Called when a `Span` is started, adds Baggage keys/values to the span as attributes. + # + # @param [Span] span the `Span` that just started, expected to conform + # to the concrete `Span` interface from the SDK and respond to :add_attributes. + # @param [Context] parent_context the parent `Context` of the newly + # started span. + def on_start(span, parent_context) + return unless span.respond_to?(:add_attributes) && parent_context.is_a?(::OpenTelemetry::Context) + + span.add_attributes( + ::OpenTelemetry::Baggage + .values(context: parent_context) + .select { |k, _v| @baggage_key_predicate.call(k) } + ) + end + + # Called when a Span is ended, does nothing. + # + # NO-OP method to satisfy the SpanProcessor duck type. + # + # @param [Span] span the {OpenTelemetry::Trace::Span} that just ended. + def on_finish(span); end + + # Always successful; this processor does not maintain any state to flush. + # + # NO-OP method to satisfy the `SpanProcessor` duck type. + # + # @param [optional Numeric] timeout An optional timeout in seconds. + # @return [Integer] 0 for success and there is nothing to flush so always successful. + def force_flush(timeout: nil) + 0 + end + + # Always successful; this processor does not maintain any state to clean up or processes to close on shutdown. + # + # NO-OP method to satisfy the `SpanProcessor` duck type. + # + # @param [optional Numeric] timeout An optional timeout in seconds. + # @return [Integer] 0 for success and there is nothing to stop so always successful. + def shutdown(timeout: nil) + 0 + end + end + end + end +end diff --git a/resource_detectors/lib/opentelemetry/resource/detectors.rb b/processor/baggage/lib/opentelemetry/processor/baggage/version.rb similarity index 53% rename from resource_detectors/lib/opentelemetry/resource/detectors.rb rename to processor/baggage/lib/opentelemetry/processor/baggage/version.rb index 78494b65a..f3711695e 100644 --- a/resource_detectors/lib/opentelemetry/resource/detectors.rb +++ b/processor/baggage/lib/opentelemetry/processor/baggage/version.rb @@ -4,12 +4,6 @@ # # SPDX-License-Identifier: Apache-2.0 -require 'opentelemetry/sdk' -require 'opentelemetry/resource/detectors/version' -require 'opentelemetry/resource/detectors/google_cloud_platform' -require 'opentelemetry/resource/detectors/azure' -require 'opentelemetry/resource/detectors/auto_detector' - # OpenTelemetry is an open source observability framework, providing a # general-purpose API, SDK, and related tools required for the instrumentation # of cloud-native software, frameworks, and libraries. @@ -17,10 +11,11 @@ # The OpenTelemetry module provides global accessors for telemetry objects. # See the documentation for the `opentelemetry-api` gem for details. module OpenTelemetry - module Resource - # Detectors contains the resource detectors as well as the AutoDetector - # that can run all the detectors and return an accumlated resource - module Detectors + # Namespace for OpenTelemetry processor extension libraries + module Processor + # Namespace for OpenTelemetry Baggage processor + module Baggage + VERSION = '0.2.0' end end end diff --git a/processor/baggage/opentelemetry-processor-baggage.gemspec b/processor/baggage/opentelemetry-processor-baggage.gemspec new file mode 100644 index 000000000..0a1950fb3 --- /dev/null +++ b/processor/baggage/opentelemetry-processor-baggage.gemspec @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'lib/opentelemetry/processor/baggage/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-processor-baggage' + spec.version = OpenTelemetry::Processor::Baggage::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'Baggage Span Processor for the OpenTelemetry framework' + spec.description = 'Baggage Span Processor for the OpenTelemetry framework' + spec.homepage = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib' + spec.license = 'Apache-2.0' + + spec.files = Dir.glob('lib/**/*.rb') + + Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.0' + + spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-sdk', '~> 1.0' + + spec.add_development_dependency 'bundler', '~> 2.4' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'opentelemetry-sdk', '~> 1.1' + spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' + spec.add_development_dependency 'simplecov', '~> 0.22.0' + spec.add_development_dependency 'yard', '~> 0.9' + + if spec.respond_to?(:metadata) + spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md" + spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/processor/baggage' + spec.metadata['bug_tracker_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues' + spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}" + end +end diff --git a/processor/baggage/test/opentelemetry/processor/baggage/baggage_span_processor_test.rb b/processor/baggage/test/opentelemetry/processor/baggage/baggage_span_processor_test.rb new file mode 100644 index 000000000..73687f0e4 --- /dev/null +++ b/processor/baggage/test/opentelemetry/processor/baggage/baggage_span_processor_test.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +require 'opentelemetry/sdk' + +TEST_EXPORTER = OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new + +OpenTelemetry::SDK.configure do |c| + # the baggage processor getting wired in for integration testing + c.add_span_processor OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS + ) + + # use a simple processor and in-memory export for testing sent spans + c.add_span_processor( + OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(TEST_EXPORTER) + ) + + c.error_handler = ->(exception:, message:) { raise(exception || message) } + c.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym) +end + +describe OpenTelemetry::Processor::Baggage::BaggageSpanProcessor do + let(:processor) do + OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + OpenTelemetry::Processor::Baggage::ALLOW_ALL_BAGGAGE_KEYS + ) + end + let(:span) { Minitest::Mock.new } + let(:context_with_baggage) do + OpenTelemetry::Baggage.build(context: OpenTelemetry::Context.empty) do |baggage| + baggage.set_value('a_key', 'a_value') + baggage.set_value('b_key', 'b_value') + end + end + + describe '#new' do + it 'requires a callable baggage_key_predicate' do + _(-> { OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new }).must_raise(ArgumentError) + err = _(-> { OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new(:not_a_callable) }).must_raise(ArgumentError) + _(err.message).must_match(/must respond to :call/) + end + end + + describe '#on_start' do + describe 'with the ALLOW_ALL_BAGGAGE_KEYS predicate' do + it 'adds current baggage keys/values as attributes when a span starts' do + span.expect(:add_attributes, span, [{ 'a_key' => 'a_value', 'b_key' => 'b_value' }]) + + processor.on_start(span, context_with_baggage) + + span.verify + end + end + + describe 'with a start_with? key predicate' do + let(:start_with_processor) do + OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + ->(baggage_key) { baggage_key.start_with?('a') } + ) + end + + it 'only adds attributes that pass the keyfilter' do + span.expect(:add_attributes, span, [{ 'a_key' => 'a_value' }]) + + start_with_processor.on_start(span, context_with_baggage) + + span.verify + end + end + + describe 'with a regex key predicate' do + let(:regex_processor) do + OpenTelemetry::Processor::Baggage::BaggageSpanProcessor.new( + ->(baggage_key) { baggage_key.match?(/^b_ke.+/) } + ) + end + + it 'only adds attributes that pass the keyfilter' do + span.expect(:add_attributes, span, [{ 'b_key' => 'b_value' }]) + + regex_processor.on_start(span, context_with_baggage) + + span.verify + end + end + + it 'does not blow up when given nil context' do + processor.on_start(span, nil) + assert true # nothing above raised an exception + end + it 'does not blow up when given nil span' do + processor.on_start(nil, context_with_baggage) + assert true # nothing above raised an exception + end + it 'does not blow up when given nil span and context' do + processor.on_start(nil, nil) + assert true # nothing above raised an exception + end + it 'does not blow up when given a context that is not a Context' do + processor.on_start(span, :not_a_context) + assert true # nothing above raised an exception + end + it 'does not blow up when given a span that is not a Span' do + processor.on_start(:not_a_span, context_with_baggage) + assert true # nothing above raised an exception + end + end + + describe 'satisfies the SpanProcessor duck type with no-op methods' do + it 'implements #on_finish' do + processor.on_finish(span) + assert true # nothing above raised an exception + end + + it 'implements #force_flush' do + _(processor.force_flush).must_equal(OpenTelemetry::SDK::Trace::Export::SUCCESS) + end + + it 'implements #shutdown' do + _(processor.shutdown).must_equal(OpenTelemetry::SDK::Trace::Export::SUCCESS) + end + end + + describe 'integration test with an exporter' do + let(:tracer) { OpenTelemetry.tracer_provider.tracer('🧳') } + let(:exporter) { TEST_EXPORTER } + + before do + exporter.reset + end + + it 'adds baggage attributes to spans' do + tracer + .start_span('integration test span', with_parent: context_with_baggage) + .finish + + _(exporter.finished_spans.size).must_equal(1) + _(exporter.finished_spans.first.name).must_equal('integration test span') + _(exporter.finished_spans.first.attributes).must_equal('a_key' => 'a_value', 'b_key' => 'b_value') + end + end +end diff --git a/processor/baggage/test/test_helper.rb b/processor/baggage/test/test_helper.rb new file mode 100644 index 000000000..8c1eb5932 --- /dev/null +++ b/processor/baggage/test/test_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/setup' +Bundler.require(:default, :development, :test) + +require 'minitest/autorun' +require 'opentelemetry-processor-baggage' diff --git a/propagator/ottrace/CHANGELOG.md b/propagator/ottrace/CHANGELOG.md index 874a340f3..0b8547296 100644 --- a/propagator/ottrace/CHANGELOG.md +++ b/propagator/ottrace/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History: opentelemetry-propagator-ottrace +### v0.21.2 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.21.1 / 2023-07-19 * DOCS: Add some clarity to ottrace docs [#522](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/522) diff --git a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_propagator.rb b/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_propagator.rb index bbbfd19e0..40604fba0 100644 --- a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_propagator.rb +++ b/propagator/ottrace/lib/opentelemetry/propagator/ottrace/text_map_propagator.rb @@ -116,10 +116,10 @@ def set_baggage(carrier:, context:, getter:) getter.keys(carrier).each do |carrier_key| baggage_key = carrier_key.start_with?(prefix) && carrier_key[prefix.length..-1] next unless baggage_key - next unless VALID_BAGGAGE_HEADER_NAME_CHARS =~ baggage_key + next unless VALID_BAGGAGE_HEADER_NAME_CHARS.match?(baggage_key) value = getter.get(carrier, carrier_key) - next unless INVALID_BAGGAGE_HEADER_VALUE_CHARS !~ value + next if INVALID_BAGGAGE_HEADER_VALUE_CHARS.match?(value) builder.set_value(baggage_key, value) end diff --git a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/version.rb b/propagator/ottrace/lib/opentelemetry/propagator/ottrace/version.rb index fe289a7ac..83215e678 100644 --- a/propagator/ottrace/lib/opentelemetry/propagator/ottrace/version.rb +++ b/propagator/ottrace/lib/opentelemetry/propagator/ottrace/version.rb @@ -15,7 +15,7 @@ module OpenTelemetry module Propagator # Namespace for OpenTelemetry OTTrace propagation module OTTrace - VERSION = '0.21.1' + VERSION = '0.21.2' end end end diff --git a/propagator/ottrace/opentelemetry-propagator-ottrace.gemspec b/propagator/ottrace/opentelemetry-propagator-ottrace.gemspec index 7e4c36003..ca10be3e1 100644 --- a/propagator/ottrace/opentelemetry-propagator-ottrace.gemspec +++ b/propagator/ottrace/opentelemetry-propagator-ottrace.gemspec @@ -28,7 +28,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.22.0' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/propagator/vitess/.rubocop.yml b/propagator/vitess/.rubocop.yml new file mode 100644 index 000000000..1248a2f82 --- /dev/null +++ b/propagator/vitess/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: ../../.rubocop.yml diff --git a/propagator/vitess/.yardopts b/propagator/vitess/.yardopts new file mode 100644 index 000000000..58f4d0b60 --- /dev/null +++ b/propagator/vitess/.yardopts @@ -0,0 +1,9 @@ +--no-private +--title=OpenTelemetry Propagator Vitess +--markup=markdown +--main=README.md +./lib/opentelemetry/propagator/vitess/**/*.rb +./lib/opentelemetry/propagator/vitess.rb +- +README.md +CHANGELOG.md diff --git a/propagator/vitess/CHANGELOG.md b/propagator/vitess/CHANGELOG.md new file mode 100644 index 000000000..234f991cc --- /dev/null +++ b/propagator/vitess/CHANGELOG.md @@ -0,0 +1,5 @@ +# Release History: opentelemetry-propagator-vitess + +### v0.1.0 / 2024-02-08 + +Initial release. diff --git a/propagator/vitess/Gemfile b/propagator/vitess/Gemfile new file mode 100644 index 000000000..b79007ade --- /dev/null +++ b/propagator/vitess/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +# Specify your gem's dependencies in opentelemetry-propragator-vitess.gemspec +gemspec diff --git a/propagator/vitess/LICENSE b/propagator/vitess/LICENSE new file mode 100644 index 000000000..1ef7dad2c --- /dev/null +++ b/propagator/vitess/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright The OpenTelemetry Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/propagator/vitess/README.md b/propagator/vitess/README.md new file mode 100644 index 000000000..d95561637 --- /dev/null +++ b/propagator/vitess/README.md @@ -0,0 +1,64 @@ +# opentelemetry-propagator-vitess + +The `opentelemetry-propagator-vitess` gem contains injectors and extractors for the +[Vitess context propagation format][vitess-spec]. + +## Vitess trace context Format + +Vitess encodes trace context in a special SQL comment style. The format is a base64 string encoding of a JSON object that, at it simplest, looks something like this: + +```json +{"uber-trace-id":"{trace-id}:{span-id}:{parent-span-id}:{flags}"} +``` + +To inform Vitess of the trace context, the context is prepended to a SQL query, e.g.: + +```sql +/*VT_SPAN_CONTEXT=*/ SELECT * from product; +``` + +## What is OpenTelemetry? + +[OpenTelemetry][opentelemetry-home] is an open source observability framework, providing a general-purpose API, SDK, and related tools required for the instrumentation of cloud-native software, frameworks, and libraries. + +OpenTelemetry provides a single set of APIs, libraries, agents, and collector services to capture distributed traces and metrics from your application. You can analyze them using Prometheus, Jaeger, and other observability tools. + +## How does this gem fit in? + +This gem can be used with any OpenTelemetry SDK implementation. This can be the official `opentelemetry-sdk` gem or any other concrete implementation. It is intended to be used with SQL client instrumentation, such as the `opentelemetry-instrumentation-trilogy` gem. + +## How do I get started? + +Install the gem using: + +``` +gem install opentelemetry-propagator-vitess +``` + +Or, if you use [bundler][bundler-home], include `opentelemetry-propagator-vitess` in your `Gemfile`. + +Configure your application to use this propagator with the Trilogy client instrumentation by setting the following [environment variable][envars]: + +``` +OTEL_RUBY_INSTRUMENTATION_TRILOGY_PROPAGATOR=vitess +``` + +## How can I get involved? + +The `opentelemetry-propagator-vitess` gem source is [on github][repo-github], along with related gems including `opentelemetry-api` and `opentelemetry-sdk`. + +The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us in [GitHub Discussions][discussions-url] or attending our weekly meeting. See the [meeting calendar][community-meetings] for dates and times. For more information on this and other language SIGs, see the OpenTelemetry [community page][ruby-sig]. + +## License + +The `opentelemetry-propagator-vitess` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + +[opentelemetry-home]: https://opentelemetry.io +[bundler-home]: https://bundler.io +[repo-github]: /~https://github.com/open-telemetry/opentelemetry-ruby +[license-github]: /~https://github.com/open-telemetry/opentelemetry-ruby-contrib/blob/main/LICENSE +[ruby-sig]: /~https://github.com/open-telemetry/community#ruby-sig +[community-meetings]: /~https://github.com/open-telemetry/community#community-meetings +[discussions-url]: /~https://github.com/open-telemetry/opentelemetry-ruby/discussions +[vitess-spec]: https://vitess.io/docs/16.0/user-guides/configuration-advanced/tracing/#instrumenting-queries +[envars]: /~https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#general-sdk-configuration diff --git a/propagator/vitess/Rakefile b/propagator/vitess/Rakefile new file mode 100644 index 000000000..88f3670a3 --- /dev/null +++ b/propagator/vitess/Rakefile @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'yard' +require 'rubocop/rake_task' + +ENV['ENABLE_COVERAGE'] ||= '1' + +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] +end + +YARD::Rake::YardocTask.new do |t| + t.stats_options = ['--list-undoc'] +end + +if RUBY_ENGINE == 'truffleruby' + task default: %i[test] +else + task default: %i[test rubocop yard] +end diff --git a/propagator/vitess/lib/opentelemetry-propagator-vitess.rb b/propagator/vitess/lib/opentelemetry-propagator-vitess.rb new file mode 100644 index 000000000..67aa436b5 --- /dev/null +++ b/propagator/vitess/lib/opentelemetry-propagator-vitess.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry-api' +require_relative 'opentelemetry/propagator/vitess' diff --git a/propagator/vitess/lib/opentelemetry/propagator/vitess.rb b/propagator/vitess/lib/opentelemetry/propagator/vitess.rb new file mode 100644 index 000000000..a3557cb41 --- /dev/null +++ b/propagator/vitess/lib/opentelemetry/propagator/vitess.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'json' +require 'base64' +require 'opentelemetry-api' +require 'opentelemetry/propagator/jaeger' +require 'opentelemetry/propagator/vitess/version' +require 'opentelemetry/propagator/vitess/sql_query_propagator' + +# OpenTelemetry is an open source observability framework, providing a +# general-purpose API, SDK, and related tools required for the instrumentation +# of cloud-native software, frameworks, and libraries. +# +# The OpenTelemetry module provides global accessors for telemetry objects. +# See the documentation for the `opentelemetry-api` gem for details. +module OpenTelemetry + # Namespace for OpenTelemetry propagator extension libraries + module Propagator + # Namespace for OpenTelemetry Vitess propagation + module Vitess + extend self + + SQL_QUERY_PROPAGATOR = SqlQueryPropagator.new + + private_constant :SQL_QUERY_PROPAGATOR + + # Returns a sql query propagator that propagates context using the + # Vitess format. + def sql_query_propagator + SQL_QUERY_PROPAGATOR + end + end + end +end diff --git a/propagator/vitess/lib/opentelemetry/propagator/vitess/sql_query_propagator.rb b/propagator/vitess/lib/opentelemetry/propagator/vitess/sql_query_propagator.rb new file mode 100644 index 000000000..10133c434 --- /dev/null +++ b/propagator/vitess/lib/opentelemetry/propagator/vitess/sql_query_propagator.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# OpenTelemetry is an open source observability framework, providing a +# general-purpose API, SDK, and related tools required for the instrumentation +# of cloud-native software, frameworks, and libraries. +# +# The OpenTelemetry module provides global accessors for telemetry objects. +# See the documentation for the `opentelemetry-api` gem for details. +module OpenTelemetry + # Namespace for OpenTelemetry propagator extension libraries + module Propagator + # Namespace for OpenTelemetry Vitess propagation + module Vitess + # Setter for Vitess SQL query propagation + module SqlQuerySetter + extend self + + # Set a key and value on the carrier. Assumes the carrier is a string. + # The key and value will be wrapped in a comment block prepended to the carrier. + # + # @param [String] carrier The carrier to set the key and value on + # @param [String] key The key to set + # @param [String] value The value to set + def set(carrier, key, value) + carrier.gsub!(/\A/, "/*#{key}=#{value}*/") + rescue FrozenError # rubocop:disable Lint/SuppressedException + end + end + + # Propagates context using Vitess header format: + # https://vitess.io/docs/16.0/user-guides/configuration-advanced/tracing/#instrumenting-queries + class SqlQueryPropagator + VT_SPAN_CONTEXT = 'VT_SPAN_CONTEXT' + FIELDS = [VT_SPAN_CONTEXT].freeze + + private_constant :VT_SPAN_CONTEXT, :FIELDS + + def initialize + @jaeger = OpenTelemetry::Propagator::Jaeger.text_map_propagator + end + + # No-op extractor. + def extract(carrier, context: Context.current, getter: nil) + context + end + + # @param [Object] carrier to update with context. + # @param [optional Context] context The active Context. + # @param [optional Setter] setter If the optional setter is provided, it + # will be used to write context into the carrier, otherwise the default + # setter will be used. + def inject(carrier, context: Context.current, setter: SqlQuerySetter) + span_context = Trace.current_span(context).context + return unless span_context.valid? + + jaeger = {} + @jaeger.inject(jaeger, context: context) + encoded = Base64.strict_encode64(jaeger.to_json) + setter.set(carrier, VT_SPAN_CONTEXT, encoded) + + nil + end + + # Returns the predefined propagation fields. If your carrier is reused, you + # should delete the fields returned by this method before calling +inject+. + # + # @return [Array] a list of fields that will be used by this propagator. + def fields + FIELDS + end + end + end + end +end diff --git a/propagator/vitess/lib/opentelemetry/propagator/vitess/version.rb b/propagator/vitess/lib/opentelemetry/propagator/vitess/version.rb new file mode 100644 index 000000000..6e5616b3f --- /dev/null +++ b/propagator/vitess/lib/opentelemetry/propagator/vitess/version.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# OpenTelemetry is an open source observability framework, providing a +# general-purpose API, SDK, and related tools required for the instrumentation +# of cloud-native software, frameworks, and libraries. +# +# The OpenTelemetry module provides global accessors for telemetry objects. +# See the documentation for the `opentelemetry-api` gem for details. +module OpenTelemetry + # Namespace for OpenTelemetry propagator extension libraries + module Propagator + # Namespace for OpenTelemetry Vitess propagation + module Vitess + VERSION = '0.1.0' + end + end +end diff --git a/propagator/vitess/opentelemetry-propagator-vitess.gemspec b/propagator/vitess/opentelemetry-propagator-vitess.gemspec new file mode 100644 index 000000000..8894b5816 --- /dev/null +++ b/propagator/vitess/opentelemetry-propagator-vitess.gemspec @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require_relative 'lib/opentelemetry/propagator/vitess/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-propagator-vitess' + spec.version = OpenTelemetry::Propagator::Vitess::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'Vitess Context Propagation Extension for the OpenTelemetry framework' + spec.description = 'Vitess Context Propagation Extension for the OpenTelemetry framework' + spec.homepage = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib' + spec.license = 'Apache-2.0' + + spec.files = Dir.glob('lib/**/*.rb') + + Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.0' + + spec.add_dependency 'opentelemetry-api', '~> 1.0' + spec.add_dependency 'opentelemetry-propagator-jaeger', '~> 0.21' + + spec.add_development_dependency 'bundler', '~> 2.4' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' + spec.add_development_dependency 'simplecov', '~> 0.22.0' + spec.add_development_dependency 'yard', '~> 0.9' + + if spec.respond_to?(:metadata) + spec.metadata['changelog_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}/file/CHANGELOG.md" + spec.metadata['source_code_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/tree/main/propagator/vitess' + spec.metadata['bug_tracker_uri'] = '/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/issues' + spec.metadata['documentation_uri'] = "https://rubydoc.info/gems/#{spec.name}/#{spec.version}" + end +end diff --git a/propagator/vitess/test/opentelemetry/propagator/vitess/sql_query_propagator_test.rb b/propagator/vitess/test/opentelemetry/propagator/vitess/sql_query_propagator_test.rb new file mode 100644 index 000000000..cc46cbec0 --- /dev/null +++ b/propagator/vitess/test/opentelemetry/propagator/vitess/sql_query_propagator_test.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Propagator::Vitess::SqlQueryPropagator do + let(:span_id) do + 'e457b5a2e4d86bd1' + end + + let(:trace_id) do + '80f198ee56343ba864fe8b2a57d3eff7' + end + + let(:trace_flags) do + OpenTelemetry::Trace::TraceFlags::DEFAULT + end + + let(:context) do + OpenTelemetry::Trace.context_with_span( + OpenTelemetry::Trace.non_recording_span( + OpenTelemetry::Trace::SpanContext.new( + trace_id: Array(trace_id).pack('H*'), + span_id: Array(span_id).pack('H*'), + trace_flags: trace_flags + ) + ) + ) + end + + let(:propagator) do + OpenTelemetry::Propagator::Vitess::SqlQueryPropagator.new + end + + let(:parent_context) do + OpenTelemetry::Context.empty + end + + let(:base64_encoded_jaeger_context) do + Base64.strict_encode64({ 'uber-trace-id' => "#{trace_id}:#{span_id}:0:#{trace_flags.sampled? ? 1 : 0}" }.to_json) + end + + let(:carrier) do + +'SELECT * FROM users' + end + + describe '#extract' do + it 'returns the context' do + context = propagator.extract(carrier, context: parent_context) + _(context).must_equal(parent_context) + end + end + + describe '#inject' do + describe 'when provided invalid trace ids' do + let(:trace_id) do + '0' * 32 + end + + it 'skips injecting context' do + carrier = +'' + propagator.inject(carrier, context: context) + + _(carrier).must_be_empty + end + end + + describe 'when provided invalid span ids' do + let(:span_id) do + '0' * 16 + end + + it 'skips injecting context' do + carrier = +'' + propagator.inject(carrier, context: context) + + _(carrier).must_be_empty + end + end + + describe 'given a minimal context' do + it 'injects a SQL comment' do + carrier = +'SELECT * FROM users' + propagator.inject(carrier, context: context) + + _(carrier).must_equal("/*VT_SPAN_CONTEXT=#{base64_encoded_jaeger_context}*/SELECT * FROM users") + end + end + + describe 'when the carrier is frozen' do + it 'does not raise an error' do + carrier = -'' + propagator.inject(carrier, context: context) + + _(carrier).must_be_empty + end + end + + describe 'given a sampled trace flag' do + let(:trace_flags) do + OpenTelemetry::Trace::TraceFlags::SAMPLED + end + + it 'injects a SQL comment' do + carrier = +'SELECT * FROM users' + propagator.inject(carrier, context: context) + + _(carrier).must_equal("/*VT_SPAN_CONTEXT=#{base64_encoded_jaeger_context}*/SELECT * FROM users") + end + end + + describe 'given an alternative setter parameter' do + it 'will use the alternative setter instead of the constructor provided one' do + carrier = {} + + propagator.inject(carrier, context: context, setter: OpenTelemetry::Context::Propagation::TextMapSetter.new) + + _(carrier.fetch('VT_SPAN_CONTEXT')).must_equal(base64_encoded_jaeger_context) + end + end + end +end diff --git a/propagator/vitess/test/opentelemetry/propagator/vitess_test.rb b/propagator/vitess/test/opentelemetry/propagator/vitess_test.rb new file mode 100644 index 000000000..c149fa5f0 --- /dev/null +++ b/propagator/vitess/test/opentelemetry/propagator/vitess_test.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Propagator::Vitess do + describe '#sql_query_propagator' do + it 'returns an instance of SqlQueryPropagator' do + propagator = OpenTelemetry::Propagator::Vitess.sql_query_propagator + _(propagator).must_be_instance_of( + OpenTelemetry::Propagator::Vitess::SqlQueryPropagator + ) + end + end +end diff --git a/propagator/vitess/test/test_helper.rb b/propagator/vitess/test/test_helper.rb new file mode 100644 index 000000000..681e85d48 --- /dev/null +++ b/propagator/vitess/test/test_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'bundler/setup' +Bundler.require(:default, :development, :test) + +require 'opentelemetry-propagator-vitess' +require 'minitest/autorun' + +if ENV['ENABLE_COVERAGE'].to_i.positive? + require 'simplecov' + SimpleCov.start + SimpleCov.minimum_coverage 85 +end + +OpenTelemetry.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym) diff --git a/propagator/xray/CHANGELOG.md b/propagator/xray/CHANGELOG.md index fda517ff3..5448ad7b9 100644 --- a/propagator/xray/CHANGELOG.md +++ b/propagator/xray/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History: opentelemetry-propagator-xray +### v0.22.1 / 2023-11-23 + +* CHANGED: Applied Rubocop Performance Recommendations [#727](/~https://github.com/open-telemetry/opentelemetry-ruby-contrib/pull/727) + ### v0.22.0 / 2023-04-17 * BREAKING CHANGE: Drop support for EoL Ruby 2.7 diff --git a/propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb b/propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb index ee1f8ce05..1041643ac 100644 --- a/propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb +++ b/propagator/xray/lib/opentelemetry/propagator/xray/text_map_propagator.rb @@ -119,7 +119,7 @@ def to_trace_flags(sampling_state) def to_trace_state(trace_state) return nil unless trace_state - Trace::Tracestate.from_string(trace_state.gsub(';', ',')) + Trace::Tracestate.from_string(trace_state.tr(';', ',')) end end end diff --git a/propagator/xray/lib/opentelemetry/propagator/xray/version.rb b/propagator/xray/lib/opentelemetry/propagator/xray/version.rb index d0d932009..0984efef4 100644 --- a/propagator/xray/lib/opentelemetry/propagator/xray/version.rb +++ b/propagator/xray/lib/opentelemetry/propagator/xray/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Propagator module XRay - VERSION = '0.22.0' + VERSION = '0.22.1' end end end diff --git a/propagator/xray/opentelemetry-propagator-xray.gemspec b/propagator/xray/opentelemetry-propagator-xray.gemspec index b5fb7b330..6ad021a6e 100644 --- a/propagator/xray/opentelemetry-propagator-xray.gemspec +++ b/propagator/xray/opentelemetry-propagator-xray.gemspec @@ -31,7 +31,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.22.0' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 000000000..056677671 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,226 @@ +{ + "bootstrap-sha": "c7686e532a7348b504ad43024735b12d795756cd", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "draft": true, + "draft-pull-request": true, + "include-component-in-tag": true, + "include-v-in-tag": true, + "prerelease": true, + "release-type": "ruby", + "separate-pull-requests": false, + "sequential-calls": true, + "signoff": "OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com>", + "skip-github-release": false, + "tag-separator": " ", + "packages": { + "helpers/mysql": { + "package-name": "opentelemetry-helpers-mysql", + "version-file": "lib/opentelemetry/helpers/mysql/version.rb" + }, + "helpers/sql_obfuscation": { + "package-name": "opentelemetry-helpers-sql-obfuscation", + "version-file": "lib/opentelemetry/helpers/sql_obfuscation/version.rb" + }, + "instrumentation/gruf": { + "package-name": "opentelemetry-instrumentation-gruf", + "version-file": "lib/opentelemetry/instrumentation/gruf/version.rb" + }, + "instrumentation/grape": { + "package-name": "opentelemetry-instrumentation-grape", + "version-file": "lib/opentelemetry/instrumentation/grape/version.rb" + }, + "instrumentation/racecar": { + "package-name": "opentelemetry-instrumentation-racecar", + "version-file": "lib/opentelemetry/instrumentation/racecar/version.rb" + }, + "instrumentation/rake": { + "package-name": "opentelemetry-instrumentation-rake", + "version-file": "lib/opentelemetry/instrumentation/rake/version.rb" + }, + "instrumentation/rdkafka": { + "package-name": "opentelemetry-instrumentation-rdkafka", + "version-file": "lib/opentelemetry/instrumentation/rdkafka/version.rb" + }, + "instrumentation/trilogy": { + "package-name": "opentelemetry-instrumentation-trilogy", + "version-file": "lib/opentelemetry/instrumentation/trilogy/version.rb" + }, + "instrumentation/active_support": { + "package-name": "opentelemetry-instrumentation-active_support", + "version-file": "lib/opentelemetry/instrumentation/active_support/version.rb" + }, + "instrumentation/action_mailer": { + "package-name": "opentelemetry-instrumentation-action_mailer", + "version-file": "lib/opentelemetry/instrumentation/action_mailer/version.rb" + }, + "instrumentation/action_view": { + "package-name": "opentelemetry-instrumentation-action_view", + "version-file": "lib/opentelemetry/instrumentation/action_view/version.rb" + }, + "instrumentation/action_pack": { + "package-name": "opentelemetry-instrumentation-action_pack", + "version-file": "lib/opentelemetry/instrumentation/action_pack/version.rb" + }, + "instrumentation/active_job": { + "package-name": "opentelemetry-instrumentation-active_job", + "version-file": "lib/opentelemetry/instrumentation/active_job/version.rb" + }, + "instrumentation/resque": { + "package-name": "opentelemetry-instrumentation-resque", + "version-file": "lib/opentelemetry/instrumentation/resque/version.rb" + }, + "instrumentation/bunny": { + "package-name": "opentelemetry-instrumentation-bunny", + "version-file": "lib/opentelemetry/instrumentation/bunny/version.rb" + }, + "instrumentation/base": { + "package-name": "opentelemetry-instrumentation-base", + "version-file": "lib/opentelemetry/instrumentation/version.rb" + }, + "instrumentation/active_record": { + "package-name": "opentelemetry-instrumentation-active_record", + "version-file": "lib/opentelemetry/instrumentation/active_record/version.rb" + }, + "instrumentation/aws_sdk": { + "package-name": "opentelemetry-instrumentation-aws_sdk", + "version-file": "lib/opentelemetry/instrumentation/aws_sdk/version.rb" + }, + "instrumentation/aws_lambda": { + "package-name": "opentelemetry-instrumentation-aws_lambda", + "version-file": "lib/opentelemetry/instrumentation/aws_lambda/version.rb" + }, + "instrumentation/lmdb": { + "package-name": "opentelemetry-instrumentation-lmdb", + "version-file": "lib/opentelemetry/instrumentation/lmdb/version.rb" + }, + "instrumentation/http": { + "package-name": "opentelemetry-instrumentation-http", + "version-file": "lib/opentelemetry/instrumentation/http/version.rb" + }, + "instrumentation/graphql": { + "package-name": "opentelemetry-instrumentation-graphql", + "version-file": "lib/opentelemetry/instrumentation/graphql/version.rb" + }, + "instrumentation/http_client": { + "package-name": "opentelemetry-instrumentation-http_client", + "version-file": "lib/opentelemetry/instrumentation/http_client/version.rb" + }, + "instrumentation/httpx": { + "package-name": "opentelemetry-instrumentation-httpx", + "version-file": "lib/opentelemetry/instrumentation/httpx/version.rb" + }, + "instrumentation/koala": { + "package-name": "opentelemetry-instrumentation-koala", + "version-file": "lib/opentelemetry/instrumentation/koala/version.rb" + }, + "instrumentation/active_model_serializers": { + "package-name": "opentelemetry-instrumentation-active_model_serializers", + "version-file": "lib/opentelemetry/instrumentation/active_model_serializers/version.rb" + }, + "instrumentation/concurrent_ruby": { + "package-name": "opentelemetry-instrumentation-concurrent_ruby", + "version-file": "lib/opentelemetry/instrumentation/concurrent_ruby/version.rb" + }, + "instrumentation/dalli": { + "package-name": "opentelemetry-instrumentation-dalli", + "version-file": "lib/opentelemetry/instrumentation/dalli/version.rb" + }, + "instrumentation/delayed_job": { + "package-name": "opentelemetry-instrumentation-delayed_job", + "version-file": "lib/opentelemetry/instrumentation/delayed_job/version.rb" + }, + "instrumentation/ethon": { + "package-name": "opentelemetry-instrumentation-ethon", + "version-file": "lib/opentelemetry/instrumentation/ethon/version.rb" + }, + "instrumentation/excon": { + "package-name": "opentelemetry-instrumentation-excon", + "version-file": "lib/opentelemetry/instrumentation/excon/version.rb" + }, + "instrumentation/faraday": { + "package-name": "opentelemetry-instrumentation-faraday", + "version-file": "lib/opentelemetry/instrumentation/faraday/version.rb" + }, + "instrumentation/mongo": { + "package-name": "opentelemetry-instrumentation-mongo", + "version-file": "lib/opentelemetry/instrumentation/mongo/version.rb" + }, + "instrumentation/mysql2": { + "package-name": "opentelemetry-instrumentation-mysql2", + "version-file": "lib/opentelemetry/instrumentation/mysql2/version.rb" + }, + "instrumentation/net_http": { + "package-name": "opentelemetry-instrumentation-net_http", + "version-file": "lib/opentelemetry/instrumentation/net/http/version.rb" + }, + "instrumentation/pg": { + "package-name": "opentelemetry-instrumentation-pg", + "version-file": "lib/opentelemetry/instrumentation/pg/version.rb" + }, + "instrumentation/que": { + "package-name": "opentelemetry-instrumentation-que", + "version-file": "lib/opentelemetry/instrumentation/que/version.rb" + }, + "instrumentation/rack": { + "package-name": "opentelemetry-instrumentation-rack", + "version-file": "lib/opentelemetry/instrumentation/rack/version.rb" + }, + "instrumentation/rails": { + "package-name": "opentelemetry-instrumentation-rails", + "version-file": "lib/opentelemetry/instrumentation/rails/version.rb" + }, + "instrumentation/redis": { + "package-name": "opentelemetry-instrumentation-redis", + "version-file": "lib/opentelemetry/instrumentation/redis/version.rb" + }, + "instrumentation/restclient": { + "package-name": "opentelemetry-instrumentation-restclient", + "version-file": "lib/opentelemetry/instrumentation/restclient/version.rb" + }, + "instrumentation/rspec": { + "package-name": "opentelemetry-instrumentation-rspec", + "version-file": "lib/opentelemetry/instrumentation/rspec/version.rb" + }, + "instrumentation/ruby_kafka": { + "package-name": "opentelemetry-instrumentation-ruby_kafka", + "version-file": "lib/opentelemetry/instrumentation/ruby_kafka/version.rb" + }, + "instrumentation/sidekiq": { + "package-name": "opentelemetry-instrumentation-sidekiq", + "version-file": "lib/opentelemetry/instrumentation/sidekiq/version.rb" + }, + "instrumentation/sinatra": { + "package-name": "opentelemetry-instrumentation-sinatra", + "version-file": "lib/opentelemetry/instrumentation/sinatra/version.rb" + }, + "instrumentation/all": { + "package-name": "opentelemetry-instrumentation-all", + "version-file": "lib/opentelemetry/instrumentation/all/version.rb" + }, + "propagator/ottrace": { + "package-name": "opentelemetry-propagator-ottrace", + "version-file": "lib/opentelemetry/propagator/ottrace/version.rb" + }, + "propagator/vitess": { + "package-name": "opentelemetry-propagator-vitess", + "version-file": "lib/opentelemetry/propagator/vitess/version.rb" + }, + "propagator/xray": { + "package-name": "opentelemetry-propagator-xray", + "version-file": "lib/opentelemetry/propagator/xray/version.rb" + }, + "resources/azure": { + "package-name": "opentelemetry-resource-detector-azure", + "version-file": "lib/opentelemetry/resource/detector/azure/version.rb" + }, + "resources/container": { + "package-name": "opentelemetry-resource-detector-container", + "version-file": "lib/opentelemetry/resource/detector/container/version.rb" + }, + "resources/google_cloud_platform": { + "package-name": "opentelemetry-resource-detector-google_cloud_platform", + "version-file": "lib/opentelemetry/resource/detector/google_cloud_platform/version.rb" + } + } +} diff --git a/releases/Gemfile b/releases/Gemfile index bfc49848d..c9cd38765 100644 --- a/releases/Gemfile +++ b/releases/Gemfile @@ -36,7 +36,6 @@ gem 'trilogy' gem 'opentelemetry-api' gem 'opentelemetry-sdk' -gem 'opentelemetry-resource_detectors' Dir['../propagator/**/version.rb'].each do |f| name = f.match(%r{propagator/(\w+)/lib})[1] diff --git a/resource_detectors/.rubocop.yml b/resource_detectors/.rubocop.yml deleted file mode 100644 index fc2019d46..000000000 --- a/resource_detectors/.rubocop.yml +++ /dev/null @@ -1 +0,0 @@ -inherit_from: ../.rubocop.yml diff --git a/resource_detectors/.yardopts b/resource_detectors/.yardopts deleted file mode 100644 index 1b25699e2..000000000 --- a/resource_detectors/.yardopts +++ /dev/null @@ -1,9 +0,0 @@ ---no-private ---title=OpenTelemetry Resource Detectors ---markup=markdown ---main=README.md -./lib/opentelemetry/resource/detectors/**/*.rb -./lib/opentelemetry/resource/detectors.rb -- -README.md -CHANGELOG.md diff --git a/resource_detectors/CHANGELOG.md b/resource_detectors/CHANGELOG.md deleted file mode 100644 index 5d6b2c0e6..000000000 --- a/resource_detectors/CHANGELOG.md +++ /dev/null @@ -1,106 +0,0 @@ -# Release History: opentelemetry-resource_detectors - -### v0.24.2 / 2023-09-07 - -* CHANGED: split resource_detectors into their own gems - -### v0.24.1 / 2023-08-03 - -* FIXED: Remove inline linter rules - -### v0.24.0 / 2023-08-02 - -* ADDED: Add container resource detector - -### v0.23.0 / 2023-04-17 - -* BREAKING CHANGE: Drop support for EoL Ruby 2.7 - -* ADDED: Drop support for EoL Ruby 2.7 - -### v0.22.0 / 2023-01-14 - -* ADDED: Add azure resource detector. -* DOCS: Fix gem homepage -* DOCS: More gem documentation fixes - -### v0.21.0 / 2022-06-09 - -* BREAKING CHANGE: This requires upgrading both the SDK and Instrumentation gem in tandem - - -### v0.20.0 / 2022-05-02 - -* ADDED: Added Google Cloud Function Resource Detection - -### v0.19.1 / 2021-09-29 - -* (No significant changes) - -### v0.19.0 / 2021-08-12 - -* BREAKING CHANGE: Use auto-generated resource constants in sdk and resource_detectors - -* ADDED: Use auto-generated resource constants in sdk and resource_detectors - -### v0.18.1 / 2021-06-23 - -* (No significant changes) - -### v0.18.0 / 2021-05-21 - -* FIXED: Rename cloud.zone to cloud.availability_zone - -### v0.17.0 / 2021-04-22 - -* (No significant changes) - -### v0.16.0 / 2021-03-17 - -* ADDED: Add k8s node to gcp resource detector -* DOCS: Replace Gitter with GitHub Discussions - -### v0.15.0 / 2021-02-18 - -* (No significant changes) - -### v0.14.0 / 2021-02-03 - -* DOCS: Updated gem name to match gemspec - -### v0.13.0 / 2021-01-29 - -* (No significant changes) - -### v0.12.0 / 2020-12-24 - -* (No significant changes) - -### v0.11.0 / 2020-12-11 - -* FIXED: Copyright comments to not reference year - -### v0.10.0 / 2020-12-03 - -* (No significant changes) - -### v0.9.0 / 2020-11-27 - -* BREAKING CHANGE: Add timeout for force_flush and shutdown - -* ADDED: Add timeout for force_flush and shutdown - -### v0.8.0 / 2020-10-27 - -* (No significant changes) - -### v0.7.0 / 2020-10-07 - -* DOCS: Standardize toplevel docs structure and readme - -### v0.6.0 / 2020-09-10 - -* BREAKING CHANGE: Rename Resource labels to attributes - -* FIXED: Rename Resource labels to attributes -* ADDED: Environment variable resource detection diff --git a/resource_detectors/README.md b/resource_detectors/README.md deleted file mode 100644 index a0ffaa622..000000000 --- a/resource_detectors/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Opentelemetry::Resource::Detectors - -The `opentelemetry-resource_detectors` gem provides resource detectors for OpenTelemetry. - -## What is OpenTelemetry? - -OpenTelemetry is an open source observability framework, providing a general-purpose API, SDK, and related tools required for the instrumentation of cloud-native software, frameworks, and libraries. - -OpenTelemetry provides a single set of APIs, libraries, agents, and collector services to capture distributed traces and metrics from your application. You can analyze them using Prometheus, Jaeger, and other observability tools. - -## How does this gem fit in? - -The `opentelemetry-resource-detectors` gem provides a means of retrieving a resource for supported environments following the resource semantic conventions. - -## How do I get started? - -Install the gem using: - -``` -gem install opentelemetry-sdk -gem install opentelemetry-resource_detectors -``` - -Or, if you use Bundler, include `opentelemetry-sdk` and `opentelemetry-resource_detectors` in your `Gemfile`. - -```rb -require 'opentelemetry/sdk' -require 'opentelemetry/resource/detectors' - -# For a specific platform -OpenTelemetry::SDK.configure do |c| - c.resource = OpenTelemetry::Resource::Detectors::GoogleCloudPlatform.detect -end - -# Or if you would like for it to run all detectors available -OpenTelemetry::SDK.configure do |c| - c.resource = OpenTelemetry::Resource::Detectors::AutoDetector.detect -end -``` - -## How can I get involved? - -The `opentelemetry-resource_detectors` gem source is on github, along with related gems. - -The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special interest group (SIG). You can get involved by joining us in [GitHub Discussions][discussions-url] or attending our weekly meeting. See the meeting calendar for dates and times. For more information on this and other language SIGs, see the OpenTelemetry community page. - -## License - -The `opentelemetry-resource_detectors` gem is distributed under the Apache 2.0 license. See LICENSE for more information. - -[discussions-url]: /~https://github.com/open-telemetry/opentelemetry-ruby/discussions diff --git a/resource_detectors/lib/opentelemetry/resource/detectors/auto_detector.rb b/resource_detectors/lib/opentelemetry/resource/detectors/auto_detector.rb deleted file mode 100644 index 2eb873bdc..000000000 --- a/resource_detectors/lib/opentelemetry/resource/detectors/auto_detector.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - module Resource - module Detectors - # AutoDetector contains detect class method for running all detectors - module AutoDetector - extend self - - DETECTORS = [ - OpenTelemetry::Resource::Detectors::Azure, - OpenTelemetry::Resource::Detectors::GoogleCloudPlatform - ].freeze - - def detect - DETECTORS.map(&:detect).reduce(:merge) - end - end - end - end -end diff --git a/resource_detectors/lib/opentelemetry/resource/detectors/azure.rb b/resource_detectors/lib/opentelemetry/resource/detectors/azure.rb deleted file mode 100644 index c27ccc012..000000000 --- a/resource_detectors/lib/opentelemetry/resource/detectors/azure.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'net/http' - -module OpenTelemetry - module Resource - module Detectors - # Azure contains detect class method for determining Azure environment resource attributes - # - # This gem has been moved into a separate gem: - # opentelemetry-resource-detector-azure - # - # Log a warning if someone still uses this gem for Azure Resource Detection - module Azure - extend self - - AZURE_METADATA_URI = 'http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15' - - def detect - OpenTelemetry.logger.warn('Azure resource detector - The Azure resource detector has been moved to a separate gem. ' \ - 'Please use the "opentelemetry-resource-detector-azure" gem onwards.') - - metadata = azure_metadata - resource_attributes = {} - - unless metadata.nil? - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER] = 'azure' - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID] = metadata['subscriptionId'] - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PLATFORM] = cloud_platform(metadata['provider']) - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_REGION] = metadata['location'] - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_AVAILABILITY_ZONE] = metadata['zone'] - - resource_attributes[OpenTelemetry::SemanticConventions::Resource::HOST_ID] = metadata['vmId'] - resource_attributes[OpenTelemetry::SemanticConventions::Resource::HOST_IMAGE_ID] = metadata.dig('storageProfile', 'imageReference', 'id') - resource_attributes[OpenTelemetry::SemanticConventions::Resource::HOST_TYPE] = metadata['vmSize'] - resource_attributes[OpenTelemetry::SemanticConventions::Resource::HOST_NAME] = metadata['name'] - end - - resource_attributes.delete_if { |_key, value| value.nil? || value.empty? } - OpenTelemetry::SDK::Resources::Resource.create(resource_attributes) - end - - private - - def azure_metadata - uri = URI(AZURE_METADATA_URI) - - req = Net::HTTP::Get.new(uri) - req['Metadata'] = 'true' - - response = Net::HTTP.start(uri.hostname, uri.port, open_timeout: 2) do |http| - http.request(req) - end - - return unless response.code == '200' - - JSON.parse(response.body) - rescue Errno::EHOSTDOWN, Net::OpenTimeout, SocketError - nil - end - - def cloud_platform(metadata) - case metadata - when 'Microsoft.Compute' - 'azure_vm' - else - '' - end - end - end - end - end -end diff --git a/resource_detectors/lib/opentelemetry/resource/detectors/google_cloud_platform.rb b/resource_detectors/lib/opentelemetry/resource/detectors/google_cloud_platform.rb deleted file mode 100644 index 17795ebf4..000000000 --- a/resource_detectors/lib/opentelemetry/resource/detectors/google_cloud_platform.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'google-cloud-env' - -module OpenTelemetry - module Resource - module Detectors - # GoogleCloudPlatform contains detect class method for determining gcp environment resource attributes - # - # This gem has been moved into a separate gem: - # opentelemetry-resource-detector-google_cloud_platform - # - # Log a warning if someone still uses this gem for GoogleCloudPlatform Resource Detection - module GoogleCloudPlatform - extend self - - def detect - OpenTelemetry.logger.warn('GoogleCloudPlatform resource detector - The GoogleCloudPlatform resource detector has been moved to a separate gem. ' \ - 'Please use the "opentelemetry-resource-detector-google_cloud_platform" gem onwards.') - - gcp_env = Google::Cloud::Env.new - resource_attributes = {} - - if gcp_env.compute_engine? - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER] = 'gcp' - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID] = gcp_env.project_id - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_REGION] = gcp_env.instance_attribute('cluster-location') - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_AVAILABILITY_ZONE] = gcp_env.instance_zone - - resource_attributes[OpenTelemetry::SemanticConventions::Resource::HOST_ID] = gcp_env.lookup_metadata('instance', 'id') - resource_attributes[OpenTelemetry::SemanticConventions::Resource::HOST_NAME] = ENV['HOSTNAME'] || - gcp_env.lookup_metadata('instance', 'hostname') || - safe_gethostname - end - - if gcp_env.kubernetes_engine? - resource_attributes[OpenTelemetry::SemanticConventions::Resource::K8S_CLUSTER_NAME] = gcp_env.instance_attribute('cluster-name') - resource_attributes[OpenTelemetry::SemanticConventions::Resource::K8S_NAMESPACE_NAME] = gcp_env.kubernetes_engine_namespace_id - resource_attributes[OpenTelemetry::SemanticConventions::Resource::K8S_POD_NAME] = ENV['HOSTNAME'] || safe_gethostname - resource_attributes[OpenTelemetry::SemanticConventions::Resource::K8S_NODE_NAME] = gcp_env.lookup_metadata('instance', 'hostname') - - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CONTAINER_NAME] = ENV.fetch('CONTAINER_NAME', nil) - end - - if gcp_env.knative? - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_PROVIDER] = 'gcp' - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_ACCOUNT_ID] = gcp_env.project_id - resource_attributes[OpenTelemetry::SemanticConventions::Resource::FAAS_NAME] = gcp_env.knative_service_id - resource_attributes[OpenTelemetry::SemanticConventions::Resource::FAAS_VERSION] = gcp_env.knative_service_revision - zone = gcp_env.instance_zone - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_REGION] = get_region zone - resource_attributes[OpenTelemetry::SemanticConventions::Resource::CLOUD_AVAILABILITY_ZONE] = zone - end - - resource_attributes.delete_if { |_key, value| value.nil? || value.empty? } - OpenTelemetry::SDK::Resources::Resource.create(resource_attributes) - end - - private - - def get_region(zone) - return if zone.nil? || zone.empty? - - split_arr = zone.split('-', 3) - split_arr[0].concat('-', split_arr[1]) - end - - def safe_gethostname - Socket.gethostname - rescue StandardError - '' - end - end - end - end -end diff --git a/resource_detectors/test/opentelemetry/detectors/auto_detector_test.rb b/resource_detectors/test/opentelemetry/detectors/auto_detector_test.rb deleted file mode 100644 index c9b583601..000000000 --- a/resource_detectors/test/opentelemetry/detectors/auto_detector_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Resource::Detectors::AutoDetector do - before do - WebMock.disable_net_connect! - # Azure stub - stub_request(:get, 'http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15') - .with( - headers: { - 'Accept' => '*/*', - 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', - 'Host' => '169.254.169.254', - 'Metadata' => 'true', - 'User-Agent' => 'Ruby' - } - ).to_raise(SocketError) - - # GCP stub - stub_request(:get, 'http://169.254.169.254/') - .with( - headers: { - 'Accept' => '*/*', - 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', - 'Metadata-Flavor' => 'Google', - 'User-Agent' => 'Ruby' - } - ) - .to_return(status: 200, body: '', headers: {}) - end - - after do - WebMock.allow_net_connect! - end - - let(:auto_detector) { OpenTelemetry::Resource::Detectors::AutoDetector } - let(:detected_resource) { auto_detector.detect } - let(:detected_resource_attributes) { detected_resource.attribute_enumerator.to_h } - let(:expected_resource_attributes) { {} } - - describe '.detect' do - it 'returns detected resources' do - _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) - _(detected_resource_attributes).must_equal(expected_resource_attributes) - end - end -end diff --git a/resource_detectors/test/opentelemetry/detectors/azure_test.rb b/resource_detectors/test/opentelemetry/detectors/azure_test.rb deleted file mode 100644 index 8da61cb20..000000000 --- a/resource_detectors/test/opentelemetry/detectors/azure_test.rb +++ /dev/null @@ -1,120 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Resource::Detectors::Azure do - before do - WebMock.disable_net_connect! - stub_request(:get, 'http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15') - .with( - headers: { - 'Accept' => '*/*', - 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', - 'Host' => '169.254.169.254', - 'Metadata' => 'true', - 'User-Agent' => 'Ruby' - } - ).to_raise(SocketError) - end - - after do - WebMock.allow_net_connect! - end - - let(:detector) { OpenTelemetry::Resource::Detectors::Azure } - - describe '.detect' do - let(:detected_resource) { detector.detect } - let(:detected_resource_attributes) { detected_resource.attribute_enumerator.to_h } - let(:expected_resource_attributes) { {} } - - describe 'when NOT in an azure environment' do - it 'returns an empty resource' do - _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) - _(detected_resource_attributes).must_equal(expected_resource_attributes) - end - end - - describe 'when in an azure VM environment' do - let(:project_id) { 'opentelemetry' } - let(:azure_metadata) do - { - 'subscriptionId' => project_id, - 'provider' => 'Microsoft.Compute', - 'location' => 'westeurope', - 'zone' => '2', - 'vmId' => '012345671234-abcd-1234-0123456789ab', - 'storageProfile' => { - 'imageReference' => { - 'id' => '/subscriptions/12345678-abcd-1234-abcd-0123456789ab/resourceGroups/AKS-Ubuntu/providers/Microsoft.Compute/galleries/AKSUbuntu/images/1804gen2containerd/versions/2022.06.22' - } - }, - 'vmSize' => 'Standard_D2s_v3', - 'name' => 'opentelemetry' - }.to_json - end - - before do - metadata = Minitest::Mock.new - metadata.expect(:code, 200) - metadata.expect(:body, azure_metadata) - metadata.expect(:nil?, false) - - WebMock.disable_net_connect! - stub_request(:get, 'http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15') - .with( - headers: { - 'Accept' => '*/*', - 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', - 'Host' => '169.254.169.254', - 'Metadata' => 'true', - 'User-Agent' => 'Ruby' - } - ).to_return(status: 200, body: azure_metadata, headers: {}) - end - - after do - WebMock.allow_net_connect! - end - - let(:expected_resource_attributes) do - { - 'cloud.provider' => 'azure', - 'cloud.account.id' => 'opentelemetry', - 'cloud.platform' => 'azure_vm', - 'cloud.region' => 'westeurope', - 'cloud.availability_zone' => '2', - 'host.id' => '012345671234-abcd-1234-0123456789ab', - 'host.image.id' => '/subscriptions/12345678-abcd-1234-abcd-0123456789ab/resourceGroups/AKS-Ubuntu/providers/Microsoft.Compute/galleries/AKSUbuntu/images/1804gen2containerd/versions/2022.06.22', - 'host.name' => 'opentelemetry', - 'host.type' => 'Standard_D2s_v3' - } - end - - it 'returns a resource with azure attributes' do - _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) - _(detected_resource_attributes).must_equal(expected_resource_attributes) - end - - describe 'and a nil resource value is detected' do - let(:project_id) { nil } - - it 'returns a resource without that attribute' do - _(detected_resource_attributes.key?('cloud.account.id')).must_equal(false) - end - end - - describe 'and an empty string resource value is detected' do - let(:project_id) { '' } - - it 'returns a resource without that attribute' do - _(detected_resource_attributes.key?('cloud.account.id')).must_equal(false) - end - end - end - end -end diff --git a/resource_detectors/test/opentelemetry/detectors/google_cloud_platform_test.rb b/resource_detectors/test/opentelemetry/detectors/google_cloud_platform_test.rb deleted file mode 100644 index c537bef2d..000000000 --- a/resource_detectors/test/opentelemetry/detectors/google_cloud_platform_test.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'test_helper' - -describe OpenTelemetry::Resource::Detectors::GoogleCloudPlatform do - let(:detector) { OpenTelemetry::Resource::Detectors::GoogleCloudPlatform } - - describe '.detect' do - before do - WebMock.disable_net_connect! - stub_request(:get, 'http://169.254.169.254/') - .with( - headers: { - 'Accept' => '*/*', - 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', - 'Metadata-Flavor' => 'Google', - 'User-Agent' => 'Ruby' - } - ) - .to_return(status: 200, body: '', headers: {}) - end - - after do - WebMock.allow_net_connect! - end - - let(:detected_resource) { detector.detect } - let(:detected_resource_attributes) { detected_resource.attribute_enumerator.to_h } - let(:expected_resource_attributes) { {} } - - it 'returns an empty resource' do - _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) - _(detected_resource_attributes).must_equal(expected_resource_attributes) - end - - describe 'when in a gcp environment' do - let(:project_id) { 'opentelemetry' } - - before do - gcp_env_mock = Minitest::Mock.new - gcp_env_mock.expect(:compute_engine?, true) - gcp_env_mock.expect(:project_id, project_id) - gcp_env_mock.expect(:instance_attribute, 'us-central1', %w[cluster-location]) - gcp_env_mock.expect(:instance_zone, 'us-central1-a') - gcp_env_mock.expect(:lookup_metadata, 'opentelemetry-test', %w[instance id]) - gcp_env_mock.expect(:lookup_metadata, 'opentelemetry-node-1', %w[instance hostname]) - gcp_env_mock.expect(:instance_attribute, 'opentelemetry-cluster', %w[cluster-name]) - gcp_env_mock.expect(:kubernetes_engine?, true) - gcp_env_mock.expect(:kubernetes_engine_namespace_id, 'default') - gcp_env_mock.expect(:knative?, true) - gcp_env_mock.expect(:project_id, project_id) - gcp_env_mock.expect(:knative_service_id, 'test-google-cloud-function') - gcp_env_mock.expect(:knative_service_revision, '2') - gcp_env_mock.expect(:instance_zone, 'us-central1-a') - - Socket.stub(:gethostname, 'opentelemetry-test') do - old_hostname = ENV.fetch('HOSTNAME', nil) - ENV['HOSTNAME'] = 'opentelemetry-host-name-1' - begin - Google::Cloud::Env.stub(:new, gcp_env_mock) { detected_resource } - ensure - ENV['HOSTNAME'] = old_hostname - end - end - end - - let(:expected_resource_attributes) do - { - 'cloud.provider' => 'gcp', - 'cloud.account.id' => 'opentelemetry', - 'cloud.region' => 'us-central1', - 'cloud.availability_zone' => 'us-central1-a', - 'host.id' => 'opentelemetry-test', - 'host.name' => 'opentelemetry-host-name-1', - 'k8s.cluster.name' => 'opentelemetry-cluster', - 'k8s.namespace.name' => 'default', - 'k8s.pod.name' => 'opentelemetry-host-name-1', - 'k8s.node.name' => 'opentelemetry-node-1', - 'faas.name' => 'test-google-cloud-function', - 'faas.version' => '2' - } - end - - it 'returns a resource with gcp attributes' do - _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) - _(detected_resource_attributes).must_equal(expected_resource_attributes) - end - - describe 'and a nil resource value is detected' do - let(:project_id) { nil } - - it 'returns a resource without that attribute' do - _(detected_resource_attributes.key?('cloud.account.id')).must_equal(false) - end - end - - describe 'and an empty string resource value is detected' do - let(:project_id) { '' } - - it 'returns a resource without that attribute' do - _(detected_resource_attributes.key?('cloud.account.id')).must_equal(false) - end - end - end - end -end diff --git a/resources/azure/opentelemetry-resource-detector-azure.gemspec b/resources/azure/opentelemetry-resource-detector-azure.gemspec index 0c3d09755..823972833 100644 --- a/resources/azure/opentelemetry-resource-detector-azure.gemspec +++ b/resources/azure/opentelemetry-resource-detector-azure.gemspec @@ -30,9 +30,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.55.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17' - spec.add_development_dependency 'webmock', '~> 3.19.1' + spec.add_development_dependency 'webmock', '~> 3.23.0' spec.add_development_dependency 'yard', '~> 0.9' if spec.respond_to?(:metadata) diff --git a/resources/container/opentelemetry-resource-detector-container.gemspec b/resources/container/opentelemetry-resource-detector-container.gemspec index baf1db746..e7f0abcea 100644 --- a/resources/container/opentelemetry-resource-detector-container.gemspec +++ b/resources/container/opentelemetry-resource-detector-container.gemspec @@ -30,7 +30,8 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.56.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17' spec.add_development_dependency 'yard', '~> 0.9' diff --git a/resources/google_cloud_platform/opentelemetry-resource-detector-google_cloud_platform.gemspec b/resources/google_cloud_platform/opentelemetry-resource-detector-google_cloud_platform.gemspec index 931751f63..51cebccc9 100644 --- a/resources/google_cloud_platform/opentelemetry-resource-detector-google_cloud_platform.gemspec +++ b/resources/google_cloud_platform/opentelemetry-resource-detector-google_cloud_platform.gemspec @@ -31,9 +31,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'bundler', '~> 2.4' spec.add_development_dependency 'minitest', '~> 5.0' spec.add_development_dependency 'rake', '~> 13.0' - spec.add_development_dependency 'rubocop', '~> 1.55.1' + spec.add_development_dependency 'rubocop', '~> 1.64.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.20' spec.add_development_dependency 'simplecov', '~> 0.17' - spec.add_development_dependency 'webmock', '~> 3.19.1' + spec.add_development_dependency 'webmock', '~> 3.23.0' spec.add_development_dependency 'yard', '~> 0.9' if spec.respond_to?(:metadata)