From 0099668e9ad7eedf32bb496e135e8220f1e49c61 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 22 Jun 2020 21:19:40 -0400 Subject: [PATCH 1/3] Add support for automated resource detection (#263) * Add support for automated resource detection This adds support for populating a resource with with Telemetry data as well as Google Cloud Platform environment metata data. For #230 * Fix typo typo in correlation context module * Extract resource detection into a gem To avoid introducing third party dependencies to the SDK for optional functionality resource detection has been extracted into its own gem. * Add resource-detectors README * Add default Telemetry SDK resource create method The specification requires a Telemetry::SDK resource, this adds a method for create it directly from the resource class. This also initializes it by default on the configurator. The resource setter on the configurator class will merge the default resource with the one provided. * Add comment to disable Style/AccessModifierDeclarations in resource class * Adjust tracer to use instrumentation library * Re-add tracer_tests for name and version * Add method documentation, update comments Add resource documentation for configurator. Update comments for resource constants. Simplify resource merging in auto detector. * Update copyright year, revert incorrect comment * Fix typo in gcp resource detector test * Set tracer resource in initializer * Add tracer_provider reference to tracer The tracer carries a reference to the tracer_provider that initialized it to begin supporting multiple tracer providers. --- resource-detectors/.rubocop.yml | 26 +++ resource-detectors/Gemfile | 14 ++ resource-detectors/LICENSE | 201 ++++++++++++++++++ resource-detectors/README.md | 59 +++++ resource-detectors/Rakefile | 30 +++ .../lib/opentelemetry/resource/detectors.rb | 19 ++ .../resource/detectors/auto_detector.rb | 24 +++ .../detectors/google_cloud_platform.rb | 54 +++++ .../resource/detectors/version.rb | 13 ++ .../opentelemetry-resource-detectors.gemspec | 37 ++++ .../detectors/auto_detector_test.rb | 21 ++ .../detectors/google_cloud_platform_test.rb | 79 +++++++ resource-detectors/test/test_helper.rb | 12 ++ sdk/lib/opentelemetry/sdk/configurator.rb | 21 +- .../opentelemetry/sdk/correlation_context.rb | 2 +- sdk/lib/opentelemetry/sdk/resources.rb | 1 + .../opentelemetry/sdk/resources/constants.rb | 124 +++++++++++ .../opentelemetry/sdk/resources/resource.rb | 8 + sdk/lib/opentelemetry/sdk/trace/tracer.rb | 29 ++- .../sdk/trace/tracer_provider.rb | 8 +- .../opentelemetry/sdk/configurator_test.rb | 18 ++ .../sdk/resources/resource_test.rb | 9 + .../opentelemetry/sdk/trace/tracer_test.rb | 10 +- 23 files changed, 799 insertions(+), 20 deletions(-) create mode 100644 resource-detectors/.rubocop.yml create mode 100644 resource-detectors/Gemfile create mode 100644 resource-detectors/LICENSE create mode 100644 resource-detectors/README.md create mode 100644 resource-detectors/Rakefile create mode 100644 resource-detectors/lib/opentelemetry/resource/detectors.rb create mode 100644 resource-detectors/lib/opentelemetry/resource/detectors/auto_detector.rb create mode 100644 resource-detectors/lib/opentelemetry/resource/detectors/google_cloud_platform.rb create mode 100644 resource-detectors/lib/opentelemetry/resource/detectors/version.rb create mode 100644 resource-detectors/opentelemetry-resource-detectors.gemspec create mode 100644 resource-detectors/test/opentelemetry/detectors/auto_detector_test.rb create mode 100644 resource-detectors/test/opentelemetry/detectors/google_cloud_platform_test.rb create mode 100644 resource-detectors/test/test_helper.rb create mode 100644 sdk/lib/opentelemetry/sdk/resources/constants.rb diff --git a/resource-detectors/.rubocop.yml b/resource-detectors/.rubocop.yml new file mode 100644 index 0000000000..7de9ab67fa --- /dev/null +++ b/resource-detectors/.rubocop.yml @@ -0,0 +1,26 @@ +AllCops: + TargetRubyVersion: '2.5.0' + +Bundler/OrderedGems: + Exclude: + - gemfiles/**/* +Lint/UnusedMethodArgument: + Enabled: false +Metrics/AbcSize: + Max: 18 +Metrics/LineLength: + Enabled: false +Metrics/MethodLength: + Max: 20 +Metrics/ParameterLists: + Enabled: false +Style/FrozenStringLiteralComment: + Exclude: + - gemfiles/**/* +Style/ModuleFunction: + Enabled: false +Style/StringLiterals: + Exclude: + - gemfiles/**/* +Metrics/BlockLength: + Enabled: false diff --git a/resource-detectors/Gemfile b/resource-detectors/Gemfile new file mode 100644 index 0000000000..4ef4addd61 --- /dev/null +++ b/resource-detectors/Gemfile @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Copyright 2020 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +source 'https://rubygems.org' + +gemspec + +group :development, :test do + gem 'byebug' + gem 'pry' +end diff --git a/resource-detectors/LICENSE b/resource-detectors/LICENSE new file mode 100644 index 0000000000..b7fbe3acd1 --- /dev/null +++ b/resource-detectors/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 2020 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/resource-detectors/README.md b/resource-detectors/README.md new file mode 100644 index 0000000000..7d12fcb865 --- /dev/null +++ b/resource-detectors/README.md @@ -0,0 +1,59 @@ +# Opentelemetry::Resource::Detectors + +The `opentelemetry-resource-detectors` gem provides resource detectors for OpenTelemetry. + +## 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? + +The `opentelemetry-resource-detectors` gem provides a means of retrieving a [resource](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/sdk.md) for supported environments following the [resource semantic conventions](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/resource/semantic_conventions/README.md). + +## How do I get started? + +Install the gem using: + +``` +gem install opentelemetry-sdk +gem install opentelemetry-resource-detectors +``` + +Or, if you use [bundler][bundler-home], 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][repo-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 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-resource-detectors` 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/blob/master/LICENSE +[examples-github]: https://github.com/open-telemetry/opentelemetry-ruby/tree/master/examples +[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/resource-detectors/Rakefile new file mode 100644 index 0000000000..a9de144083 --- /dev/null +++ b/resource-detectors/Rakefile @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# Copyright 2020 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 << '../sdk/lib' + t.libs << '../api/lib' + 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/resource-detectors/lib/opentelemetry/resource/detectors.rb b/resource-detectors/lib/opentelemetry/resource/detectors.rb new file mode 100644 index 0000000000..acb08aacd5 --- /dev/null +++ b/resource-detectors/lib/opentelemetry/resource/detectors.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Copyright 2020 OpenTelemetry Authors +# +# 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/auto_detector' + +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 + end + end +end diff --git a/resource-detectors/lib/opentelemetry/resource/detectors/auto_detector.rb b/resource-detectors/lib/opentelemetry/resource/detectors/auto_detector.rb new file mode 100644 index 0000000000..98cb23e69a --- /dev/null +++ b/resource-detectors/lib/opentelemetry/resource/detectors/auto_detector.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright 2020 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::GoogleCloudPlatform + ].freeze + + def detect + DETECTORS.map(&:detect).reduce(:merge) + 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 new file mode 100644 index 0000000000..612e90fc88 --- /dev/null +++ b/resource-detectors/lib/opentelemetry/resource/detectors/google_cloud_platform.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# Copyright 2020 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 labels + module GoogleCloudPlatform + extend self + + def detect # rubocop:disable Metrics/AbcSize + gcp_env = Google::Cloud::Env.new + resource_labels = {} + resource_constants = OpenTelemetry::SDK::Resources::Constants + + if gcp_env.compute_engine? + resource_labels[resource_constants::CLOUD_RESOURCE[:provider]] = 'gcp' + resource_labels[resource_constants::CLOUD_RESOURCE[:account_id]] = gcp_env.project_id + resource_labels[resource_constants::CLOUD_RESOURCE[:region]] = gcp_env.instance_attribute('cluster-location') + resource_labels[resource_constants::CLOUD_RESOURCE[:zone]] = gcp_env.instance_zone + + resource_labels[resource_constants::HOST_RESOURCE[:hostname]] = hostname + resource_labels[resource_constants::HOST_RESOURCE[:id]] = gcp_env.lookup_metadata('instance', 'id') + resource_labels[resource_constants::HOST_RESOURCE[:name]] = gcp_env.lookup_metadata('instance', 'hostname') + end + + if gcp_env.kubernetes_engine? + resource_labels[resource_constants::K8S_RESOURCE[:cluster_name]] = gcp_env.instance_attribute('cluster-name') + resource_labels[resource_constants::K8S_RESOURCE[:namespace_name]] = gcp_env.kubernetes_engine_namespace_id + resource_labels[resource_constants::K8S_RESOURCE[:pod_name]] = hostname + + resource_labels[resource_constants::CONTAINER_RESOURCE[:name]] = ENV['CONTAINER_NAME'] + end + + resource_labels.delete_if { |_key, value| value.nil? || value.empty? } + OpenTelemetry::SDK::Resources::Resource.create(resource_labels) + end + + private + + def hostname + ENV['HOSTNAME'] || Socket.gethostname + rescue StandardError + '' + end + end + end + end +end diff --git a/resource-detectors/lib/opentelemetry/resource/detectors/version.rb b/resource-detectors/lib/opentelemetry/resource/detectors/version.rb new file mode 100644 index 0000000000..85a01c109b --- /dev/null +++ b/resource-detectors/lib/opentelemetry/resource/detectors/version.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright 2020 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Resource + module Detectors + VERSION = '0.0.0' + end + end +end diff --git a/resource-detectors/opentelemetry-resource-detectors.gemspec b/resource-detectors/opentelemetry-resource-detectors.gemspec new file mode 100644 index 0000000000..d612ebf19d --- /dev/null +++ b/resource-detectors/opentelemetry-resource-detectors.gemspec @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Copyright 2020 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/resource/detectors/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-resource-detectors' + spec.version = OpenTelemetry::Resource::Detectors::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.homepage = 'https://github.com/open-telemetry/opentelemetry-ruby' + spec.license = 'Apache-2.0' + + spec.files = ::Dir.glob('lib/**/*.rb') + + ::Dir.glob('*.md') + + ['LICENSE', '.yardopts'] + spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 2.5.0' + + spec.add_dependency 'google-cloud-env' + + spec.add_development_dependency 'bundler', '>= 1.17' + spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'rake', '~> 12.0' + spec.add_development_dependency 'rubocop', '~> 0.73.0' + spec.add_development_dependency 'simplecov', '~> 0.17' + spec.add_development_dependency 'yard', '~> 0.9' + spec.add_development_dependency 'yard-doctest', '~> 0.1.6' +end diff --git a/resource-detectors/test/opentelemetry/detectors/auto_detector_test.rb b/resource-detectors/test/opentelemetry/detectors/auto_detector_test.rb new file mode 100644 index 0000000000..ce26530891 --- /dev/null +++ b/resource-detectors/test/opentelemetry/detectors/auto_detector_test.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# Copyright 2020 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::Resource::Detectors::AutoDetector do + let(:auto_detector) { OpenTelemetry::Resource::Detectors::AutoDetector } + let(:detected_resource) { auto_detector.detect } + let(:detected_resource_labels) { detected_resource.label_enumerator.to_h } + let(:expected_resource_labels) { {} } + + describe '.detect' do + it 'returns detected resources' do + _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) + _(detected_resource_labels).must_equal(expected_resource_labels) + 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 new file mode 100644 index 0000000000..38aef91444 --- /dev/null +++ b/resource-detectors/test/opentelemetry/detectors/google_cloud_platform_test.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +# Copyright 2020 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 + let(:detected_resource) { detector.detect } + let(:detected_resource_labels) { detected_resource.label_enumerator.to_h } + let(:expected_resource_labels) { {} } + + it 'returns an empty resource' do + _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) + _(detected_resource_labels).must_equal(expected_resource_labels) + 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-test', %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') + + Socket.stub(:gethostname, 'opentelemetry-test') do + Google::Cloud::Env.stub(:new, gcp_env_mock) { detected_resource } + end + end + + let(:expected_resource_labels) do + { + 'cloud.provider' => 'gcp', + 'cloud.account.id' => 'opentelemetry', + 'cloud.region' => 'us-central1', + 'cloud.zone' => 'us-central1-a', + 'host.hostname' => 'opentelemetry-test', + 'host.id' => 'opentelemetry-test', + 'host.name' => 'opentelemetry-test', + 'k8s.cluster.name' => 'opentelemetry-cluster', + 'k8s.namespace.name' => 'default', + 'k8s.pod.name' => 'opentelemetry-test' + } + end + + it 'returns a resource with gcp attributes' do + _(detected_resource).must_be_instance_of(OpenTelemetry::SDK::Resources::Resource) + _(detected_resource_labels).must_equal(expected_resource_labels) + end + + describe 'and a nil resource value is detected' do + let(:project_id) { nil } + + it 'returns a resource without that label' do + _(detected_resource_labels.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 label' do + _(detected_resource_labels.key?('cloud.account.id')).must_equal(false) + end + end + end + end +end diff --git a/resource-detectors/test/test_helper.rb b/resource-detectors/test/test_helper.rb new file mode 100644 index 0000000000..4d73d382e3 --- /dev/null +++ b/resource-detectors/test/test_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Copyright 2020 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'simplecov' +SimpleCov.start + +require 'opentelemetry/resource/detectors' +require 'minitest/autorun' +require 'pry' diff --git a/sdk/lib/opentelemetry/sdk/configurator.rb b/sdk/lib/opentelemetry/sdk/configurator.rb index 1713e8ae44..4dab018b6c 100644 --- a/sdk/lib/opentelemetry/sdk/configurator.rb +++ b/sdk/lib/opentelemetry/sdk/configurator.rb @@ -8,7 +8,7 @@ module OpenTelemetry module SDK # The configurator provides defaults and facilitates configuring the # SDK for use. - class Configurator + class Configurator # rubocop:disable Metrics/ClassLength USE_MODE_UNSPECIFIED = 0 USE_MODE_ONE = 1 USE_MODE_ALL = 2 @@ -27,13 +27,22 @@ def initialize @text_injectors = nil @span_processors = [] @use_mode = USE_MODE_UNSPECIFIED - @tracer_provider = Trace::TracerProvider.new + @resource = Resources::Resource.telemetry_sdk end def logger @logger ||= Logger.new(STDOUT) end + # Accepts a resource object that is merged with the default telemetry sdk + # resource. The use of this method is optional, and is provided as means + # to add additional resource information. + # + # @param [Resource] new_resource The resource to be merged + def resource=(new_resource) + @resource = @resource.merge(new_resource) + end + # Install an instrumentation adapter with specificied optional +config+. # Use can be called multiple times to install multiple instrumentation # adapters. Only +use+ or +use_all+, but not both when installing @@ -83,12 +92,16 @@ def configure OpenTelemetry.correlations = CorrelationContext::Manager.new configure_propagation configure_span_processors - OpenTelemetry.tracer_provider = @tracer_provider + OpenTelemetry.tracer_provider = tracer_provider install_instrumentation end private + def tracer_provider + @tracer_provider ||= Trace::TracerProvider.new(@resource) + end + def check_use_mode!(mode) @use_mode = mode if @use_mode == USE_MODE_UNSPECIFIED raise 'Use either `use_all` or `use`, but not both' unless @use_mode == mode @@ -105,7 +118,7 @@ def install_instrumentation def configure_span_processors processors = @span_processors.empty? ? [default_span_processor] : @span_processors - processors.each { |p| @tracer_provider.add_span_processor(p) } + processors.each { |p| tracer_provider.add_span_processor(p) } end def default_span_processor diff --git a/sdk/lib/opentelemetry/sdk/correlation_context.rb b/sdk/lib/opentelemetry/sdk/correlation_context.rb index 2b0976846e..66508368da 100644 --- a/sdk/lib/opentelemetry/sdk/correlation_context.rb +++ b/sdk/lib/opentelemetry/sdk/correlation_context.rb @@ -9,7 +9,7 @@ module OpenTelemetry module SDK - # Contains operational implementataions of the CorrelationContext::Manager + # Contains operational implementations of the CorrelationContext::Manager module CorrelationContext end end diff --git a/sdk/lib/opentelemetry/sdk/resources.rb b/sdk/lib/opentelemetry/sdk/resources.rb index 7155df90c6..07a98dc479 100644 --- a/sdk/lib/opentelemetry/sdk/resources.rb +++ b/sdk/lib/opentelemetry/sdk/resources.rb @@ -13,3 +13,4 @@ module Resources end require 'opentelemetry/sdk/resources/resource' +require 'opentelemetry/sdk/resources/constants' diff --git a/sdk/lib/opentelemetry/sdk/resources/constants.rb b/sdk/lib/opentelemetry/sdk/resources/constants.rb new file mode 100644 index 0000000000..6f76372925 --- /dev/null +++ b/sdk/lib/opentelemetry/sdk/resources/constants.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +# Copyright 2020 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module SDK + module Resources + module Constants + # Attributes describing a service instance. + SERVICE_RESOURCE = { + # Logical name of the service. + name: 'service.name', + + # A namespace for `service.name`. + namespace: 'service.namespace', + + # The string ID of the service instance. + instance_id: 'service.instance.id', + + # The version string of the service API or implementation. + version: 'service.version' + }.freeze + + # Attributes describing the telemetry library. + TELEMETRY_SDK_RESOURCE = { + # The name of the telemetry library. + name: 'telemetry.sdk.name', + + # The language of the telemetry library and of the code instrumented with it. + language: 'telemetry.sdk.language', + + # The version string of the telemetry library + version: 'telemetry.sdk.version' + }.freeze + + # Attributes defining a compute unit (e.g. Container, Process, Lambda + # Function). + CONTAINER_RESOURCE = { + # The container name. + name: 'container.name', + + # The name of the image the container was built on. + image_name: 'container.image.name', + + # The container image tag. + image_tag: 'container.image.tag' + }.freeze + + FAAS_RESOURCE = { + # The name of the function being executed. + name: 'faas.name', + + # The unique name of the function being executed. + id: 'faas.id', + + # The version string of the function being executed. + version: 'faas.version', + + # The execution environment ID as a string. + instance: 'faas.instance' + }.freeze + + # Attributes defining a deployment service (e.g. Kubernetes). + K8S_RESOURCE = { + # The name of the cluster that the pod is running in. + cluster_name: 'k8s.cluster.name', + + # The name of the namespace that the pod is running in. + namespace_name: 'k8s.namespace.name', + + # The name of the pod. + pod_name: 'k8s.pod.name', + + # The name of the deployment. + deployment_name: 'k8s.deployment.name' + }.freeze + + # Attributes defining a computing instance (e.g. host). + HOST_RESOURCE = { + # Hostname of the host. It contains what the hostname command returns on the + # host machine. + hostname: 'host.hostname', + + # Unique host id. For Cloud this must be the instance_id assigned by the + # cloud provider + id: 'host.id', + + # Name of the host. It may contain what hostname returns on Unix systems, + # the fully qualified, or a name specified by the user. + name: 'host.name', + + # Type of host. For Cloud this must be the machine type. + type: 'host.type', + + # Name of the VM image or OS install the host was instantiated from. + image_name: 'host.image.name', + + # VM image id. For Cloud, this value is from the provider. + image_id: 'host.image.id', + + # The version string of the VM image. + image_version: 'host.image.version' + }.freeze + + # Attributes defining a running environment (e.g. Cloud, Data Center). + CLOUD_RESOURCE = { + # Name of the cloud provider. Example values are aws, azure, gcp. + provider: 'cloud.provider', + + # The cloud account id used to identify different entities. + account_id: 'cloud.account.id', + + # A specific geographical location where different entities can run. + region: 'cloud.region', + + # Zones are a sub set of the region connected through low-latency links. + zone: 'cloud.zone' + }.freeze + end + end + end +end diff --git a/sdk/lib/opentelemetry/sdk/resources/resource.rb b/sdk/lib/opentelemetry/sdk/resources/resource.rb index ebaf32e376..9981c96c2d 100644 --- a/sdk/lib/opentelemetry/sdk/resources/resource.rb +++ b/sdk/lib/opentelemetry/sdk/resources/resource.rb @@ -29,6 +29,14 @@ def create(labels = {}) new(frozen_labels) end + + def telemetry_sdk + create( + Constants::TELEMETRY_SDK_RESOURCE[:name] => 'opentelemetry', + Constants::TELEMETRY_SDK_RESOURCE[:language] => 'ruby', + Constants::TELEMETRY_SDK_RESOURCE[:version] => "semver:#{OpenTelemetry::SDK::VERSION}" + ) + end end # @api private diff --git a/sdk/lib/opentelemetry/sdk/trace/tracer.rb b/sdk/lib/opentelemetry/sdk/trace/tracer.rb index b12d7f8ac5..d840a4fe3b 100644 --- a/sdk/lib/opentelemetry/sdk/trace/tracer.rb +++ b/sdk/lib/opentelemetry/sdk/trace/tracer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright 2019 OpenTelemetry Authors +# Copyright 2020 OpenTelemetry Authors # # SPDX-License-Identifier: Apache-2.0 @@ -11,6 +11,7 @@ module Trace class Tracer < OpenTelemetry::Trace::Tracer attr_reader :name attr_reader :version + attr_reader :tracer_provider # @api private # @@ -18,13 +19,14 @@ class Tracer < OpenTelemetry::Trace::Tracer # # @param [String] name Instrumentation package name # @param [String] version Instrumentation package version + # @param [TracerProvider] tracer_provider TracerProvider that initialized the tracer # # @return [Tracer] - def initialize(name, version) + def initialize(name, version, tracer_provider) @name = name @version = version - @resource = Resources::Resource.create('name' => name, 'version' => version) @instrumentation_library = InstrumentationLibrary.new(name, version) + @tracer_provider = tracer_provider end def start_root_span(name, attributes: nil, links: nil, start_timestamp: nil, kind: nil) @@ -41,22 +43,31 @@ def start_span(name, with_parent: nil, with_parent_context: nil, attributes: nil trace_id = parent_span_context&.trace_id trace_id ||= OpenTelemetry::Trace.generate_trace_id span_id = OpenTelemetry::Trace.generate_span_id - sampler = OpenTelemetry.tracer_provider.active_trace_config.sampler + sampler = tracer_provider.active_trace_config.sampler result = sampler.should_sample?(trace_id: trace_id, parent_context: parent_span_context, links: links, name: name, kind: kind, attributes: attributes) - internal_create_span(result, name, kind, trace_id, span_id, parent_span_id, attributes, links, start_timestamp, tracestate) end private def internal_create_span(result, name, kind, trace_id, span_id, parent_span_id, attributes, links, start_timestamp, tracestate) # rubocop:disable Metrics/AbcSize - if result.recording? && !OpenTelemetry.tracer_provider.stopped? + if result.recording? && !tracer_provider.stopped? trace_flags = result.sampled? ? OpenTelemetry::Trace::TraceFlags::SAMPLED : OpenTelemetry::Trace::TraceFlags::DEFAULT context = OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, trace_flags: trace_flags, tracestate: tracestate) attributes = attributes&.merge(result.attributes) || result.attributes - active_trace_config = OpenTelemetry.tracer_provider.active_trace_config - active_span_processor = OpenTelemetry.tracer_provider.active_span_processor - Span.new(context, name, kind, parent_span_id, active_trace_config, active_span_processor, attributes, links, start_timestamp || Time.now, @resource, @instrumentation_library) + Span.new( + context, + name, + kind, + parent_span_id, + tracer_provider.active_trace_config, + tracer_provider.active_span_processor, + attributes, + links, + start_timestamp || Time.now, + tracer_provider.resource, + @instrumentation_library + ) else OpenTelemetry::Trace::Span.new(span_context: OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id)) end diff --git a/sdk/lib/opentelemetry/sdk/trace/tracer_provider.rb b/sdk/lib/opentelemetry/sdk/trace/tracer_provider.rb index d7da2f1b9e..8492b15c34 100644 --- a/sdk/lib/opentelemetry/sdk/trace/tracer_provider.rb +++ b/sdk/lib/opentelemetry/sdk/trace/tracer_provider.rb @@ -13,20 +13,20 @@ class TracerProvider < OpenTelemetry::Trace::TracerProvider private_constant(:Key) attr_accessor :active_trace_config - attr_reader :active_span_processor - attr_reader :stopped + attr_reader :active_span_processor, :stopped, :resource alias stopped? stopped # Returns a new {TracerProvider} instance. # # @return [TracerProvider] - def initialize + def initialize(resource = OpenTelemetry::SDK::Resources::Resource.create) @mutex = Mutex.new @registry = {} @active_span_processor = NoopSpanProcessor.instance @active_trace_config = Config::TraceConfig::DEFAULT @registered_span_processors = [] @stopped = false + @resource = resource end # Returns a {Tracer} instance. @@ -38,7 +38,7 @@ def initialize def tracer(name = nil, version = nil) name ||= '' version ||= '' - @mutex.synchronize { @registry[Key.new(name, version)] ||= Tracer.new(name, version) } + @mutex.synchronize { @registry[Key.new(name, version)] ||= Tracer.new(name, version, self) } end # Attempts to stop all the activity for this {Tracer}. Calls diff --git a/sdk/test/opentelemetry/sdk/configurator_test.rb b/sdk/test/opentelemetry/sdk/configurator_test.rb index 26bf22b97a..c332720b9c 100644 --- a/sdk/test/opentelemetry/sdk/configurator_test.rb +++ b/sdk/test/opentelemetry/sdk/configurator_test.rb @@ -15,6 +15,24 @@ end end + describe '#resource=' do + let(:configurator_resource) { configurator.instance_variable_get(:@resource) } + let(:configurator_resource_labels) { configurator_resource.label_enumerator.to_h } + let(:expected_resource_labels) do + { + 'telemetry.sdk.name' => 'opentelemetry', + 'telemetry.sdk.language' => 'ruby', + 'telemetry.sdk.version' => "semver:#{OpenTelemetry::SDK::VERSION}", + 'test_key' => 'test_value' + } + end + + it 'merges the resource' do + configurator.resource = OpenTelemetry::SDK::Resources::Resource.create('test_key' => 'test_value') + _(configurator_resource_labels).must_equal(expected_resource_labels) + end + end + describe '#use' do it 'can be called multiple times' do configurator.use('TestAdapter', enabled: true) diff --git a/sdk/test/opentelemetry/sdk/resources/resource_test.rb b/sdk/test/opentelemetry/sdk/resources/resource_test.rb index 4896f02c1c..e1d27be11c 100644 --- a/sdk/test/opentelemetry/sdk/resources/resource_test.rb +++ b/sdk/test/opentelemetry/sdk/resources/resource_test.rb @@ -41,6 +41,15 @@ end end + describe '.telemetry_sdk' do + it 'returns a resource for the telemetry sdk' do + resource_labels = Resource.telemetry_sdk.label_enumerator.to_h + _(resource_labels['telemetry.sdk.name']).must_equal('opentelemetry') + _(resource_labels['telemetry.sdk.language']).must_equal('ruby') + _(resource_labels['telemetry.sdk.version']).must_match(/semver:\b\d{1,3}\.\d{1,3}\.\d{1,3}/) + end + end + describe '#merge' do it 'merges two resources into a third' do res1 = Resource.create('k1' => 'v1', 'k2' => 'v2') diff --git a/sdk/test/opentelemetry/sdk/trace/tracer_test.rb b/sdk/test/opentelemetry/sdk/trace/tracer_test.rb index 5cca8540d3..4ce31fe13a 100644 --- a/sdk/test/opentelemetry/sdk/trace/tracer_test.rb +++ b/sdk/test/opentelemetry/sdk/trace/tracer_test.rb @@ -20,13 +20,19 @@ describe '#name' do it 'reflects the name passed in' do - _(Tracer.new('component', 'semver:1.0').name).must_equal('component') + _(Tracer.new('component', 'semver:1.0', tracer_provider).name).must_equal('component') end end describe '#version' do it 'reflects the version passed in' do - _(Tracer.new('component', 'semver:1.0').version).must_equal('semver:1.0') + _(Tracer.new('component', 'semver:1.0', tracer_provider).version).must_equal('semver:1.0') + end + end + + describe '#tracer_provider' do + it 'reflects the tracer_provider passed in' do + _(Tracer.new('component', 'semver:1.0', tracer_provider).tracer_provider).must_equal(tracer_provider) end end From 92568adb02f5ff3553fe76fbd919c1a2ad270add Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Wed, 24 Jun 2020 15:13:03 -0700 Subject: [PATCH 2/3] bump versions for sinatra adapter bugfix release (#283) --- adapters/all/lib/opentelemetry/adapters/all/version.rb | 2 +- adapters/all/opentelemetry-adapters-all.gemspec | 2 +- adapters/sinatra/lib/opentelemetry/adapters/sinatra/version.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adapters/all/lib/opentelemetry/adapters/all/version.rb b/adapters/all/lib/opentelemetry/adapters/all/version.rb index 20be964360..d6a50d5c33 100644 --- a/adapters/all/lib/opentelemetry/adapters/all/version.rb +++ b/adapters/all/lib/opentelemetry/adapters/all/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Adapters module All - VERSION = '0.4.0' + VERSION = '0.4.1' end end end diff --git a/adapters/all/opentelemetry-adapters-all.gemspec b/adapters/all/opentelemetry-adapters-all.gemspec index 67b8261320..507a101faa 100644 --- a/adapters/all/opentelemetry-adapters-all.gemspec +++ b/adapters/all/opentelemetry-adapters-all.gemspec @@ -35,7 +35,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'opentelemetry-adapters-redis', '~> 0.4.0' spec.add_dependency 'opentelemetry-adapters-restclient', '~> 0.4.0' spec.add_dependency 'opentelemetry-adapters-sidekiq', '~> 0.4.0' - spec.add_dependency 'opentelemetry-adapters-sinatra', '~> 0.4.0' + spec.add_dependency 'opentelemetry-adapters-sinatra', '~> 0.4.1' spec.add_development_dependency 'bundler', '>= 1.17' spec.add_development_dependency 'minitest', '~> 5.0' diff --git a/adapters/sinatra/lib/opentelemetry/adapters/sinatra/version.rb b/adapters/sinatra/lib/opentelemetry/adapters/sinatra/version.rb index 88d373fb4e..b7339234d3 100644 --- a/adapters/sinatra/lib/opentelemetry/adapters/sinatra/version.rb +++ b/adapters/sinatra/lib/opentelemetry/adapters/sinatra/version.rb @@ -7,7 +7,7 @@ module OpenTelemetry module Adapters module Sinatra - VERSION = '0.4.0' + VERSION = '0.4.1' end end end From 5ec6ff51518ac395a7b5e47c1e5e6fbf5093c5f4 Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 24 Jun 2020 20:31:37 -0400 Subject: [PATCH 3/3] Spec compliance for span and trace IDs (#280) Spec compliance for span and trace IDs --- .../adapters/ethon/adapter_test.rb | 6 ++--- .../adapters/excon/adapter_test.rb | 6 ++--- .../middlewares/tracer_middleware_test.rb | 6 ++--- .../adapters/net/http/adapter_test.rb | 6 ++--- .../middlewares/tracer_middleware_test.rb | 4 +-- .../adapters/restclient/adapter_test.rb | 4 +-- .../adapters/sidekiq/adapter_test.rb | 6 ++--- api/lib/opentelemetry/trace.rb | 26 +++++++++---------- .../propagation/trace_context/trace_parent.rb | 14 ++++++---- .../trace_context/text_extractor_test.rb | 8 +++--- .../trace_context/text_injector_test.rb | 6 ++--- .../trace_context/trace_parent_test.rb | 2 +- .../exporters/jaeger/exporter/span_encoder.rb | 8 +++--- 13 files changed, 52 insertions(+), 50 deletions(-) diff --git a/adapters/ethon/test/opentelemetry/adapters/ethon/adapter_test.rb b/adapters/ethon/test/opentelemetry/adapters/ethon/adapter_test.rb index 170d24f0ff..6227c01dc0 100644 --- a/adapters/ethon/test/opentelemetry/adapters/ethon/adapter_test.rb +++ b/adapters/ethon/test/opentelemetry/adapters/ethon/adapter_test.rb @@ -99,7 +99,7 @@ def stub_response(options) _(easy.instance_eval { @otel_span }).must_be_nil _( easy.instance_eval { @otel_original_headers['traceparent'] } - ).must_equal "00-#{span.trace_id}-#{span.span_id}-01" + ).must_equal "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" end end @@ -112,7 +112,7 @@ def stub_response(options) _(easy.instance_eval { @otel_span }).must_be_nil _( easy.instance_eval { @otel_original_headers['traceparent'] } - ).must_equal "00-#{span.trace_id}-#{span.span_id}-01" + ).must_equal "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" end end @@ -131,7 +131,7 @@ def stub_response(options) _(easy.instance_eval { @otel_span }).must_be_nil _( easy.instance_eval { @otel_original_headers['traceparent'] } - ).must_equal "00-#{span.trace_id}-#{span.span_id}-01" + ).must_equal "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" end end end diff --git a/adapters/excon/test/opentelemetry/adapters/excon/adapter_test.rb b/adapters/excon/test/opentelemetry/adapters/excon/adapter_test.rb index 458811b89a..a579b5b231 100644 --- a/adapters/excon/test/opentelemetry/adapters/excon/adapter_test.rb +++ b/adapters/excon/test/opentelemetry/adapters/excon/adapter_test.rb @@ -58,7 +58,7 @@ assert_requested( :get, 'http://example.com/success', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end @@ -75,7 +75,7 @@ assert_requested( :get, 'http://example.com/failure', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end @@ -99,7 +99,7 @@ assert_requested( :get, 'http://example.com/timeout', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end end diff --git a/adapters/faraday/test/opentelemetry/adapters/faraday/middlewares/tracer_middleware_test.rb b/adapters/faraday/test/opentelemetry/adapters/faraday/middlewares/tracer_middleware_test.rb index c4f4579dad..5b40400e12 100644 --- a/adapters/faraday/test/opentelemetry/adapters/faraday/middlewares/tracer_middleware_test.rb +++ b/adapters/faraday/test/opentelemetry/adapters/faraday/middlewares/tracer_middleware_test.rb @@ -54,7 +54,7 @@ _(span.attributes['http.status_code']).must_equal 200 _(span.attributes['http.url']).must_equal 'http://example.com/success' _(response.env.request_headers['Traceparent']).must_equal( - "00-#{span.trace_id}-#{span.span_id}-01" + "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" ) end @@ -66,7 +66,7 @@ _(span.attributes['http.status_code']).must_equal 404 _(span.attributes['http.url']).must_equal 'http://example.com/not_found' _(response.env.request_headers['Traceparent']).must_equal( - "00-#{span.trace_id}-#{span.span_id}-01" + "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" ) end @@ -78,7 +78,7 @@ _(span.attributes['http.status_code']).must_equal 500 _(span.attributes['http.url']).must_equal 'http://example.com/failure' _(response.env.request_headers['Traceparent']).must_equal( - "00-#{span.trace_id}-#{span.span_id}-01" + "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" ) end end diff --git a/adapters/net_http/test/opentelemetry/adapters/net/http/adapter_test.rb b/adapters/net_http/test/opentelemetry/adapters/net/http/adapter_test.rb index 9196079469..96dcf525d2 100644 --- a/adapters/net_http/test/opentelemetry/adapters/net/http/adapter_test.rb +++ b/adapters/net_http/test/opentelemetry/adapters/net/http/adapter_test.rb @@ -59,7 +59,7 @@ assert_requested( :get, 'http://example.com/success', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end @@ -77,7 +77,7 @@ assert_requested( :post, 'http://example.com/failure', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end @@ -103,7 +103,7 @@ assert_requested( :get, 'https://example.com/timeout', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end end diff --git a/adapters/rack/test/opentelemetry/adapters/rack/middlewares/tracer_middleware_test.rb b/adapters/rack/test/opentelemetry/adapters/rack/middlewares/tracer_middleware_test.rb index dfe33892e6..3e3eaf749a 100644 --- a/adapters/rack/test/opentelemetry/adapters/rack/middlewares/tracer_middleware_test.rb +++ b/adapters/rack/test/opentelemetry/adapters/rack/middlewares/tracer_middleware_test.rb @@ -68,7 +68,7 @@ end it 'has no parent' do - _(first_span.parent_span_id).must_equal '0000000000000000' + _(first_span.parent_span_id).must_equal OpenTelemetry::Trace::INVALID_SPAN_ID end describe 'config[:allowed_request_headers]' do @@ -122,7 +122,7 @@ end it 'does not parent the request_span' do - _(request_span.parent_span_id).must_equal '0000000000000000' + _(request_span.parent_span_id).must_equal OpenTelemetry::Trace::INVALID_SPAN_ID end end diff --git a/adapters/restclient/test/opentelemetry/adapters/restclient/adapter_test.rb b/adapters/restclient/test/opentelemetry/adapters/restclient/adapter_test.rb index c7fdeb193d..177386479f 100644 --- a/adapters/restclient/test/opentelemetry/adapters/restclient/adapter_test.rb +++ b/adapters/restclient/test/opentelemetry/adapters/restclient/adapter_test.rb @@ -55,7 +55,7 @@ assert_requested( :get, 'http://example.com/success', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end @@ -72,7 +72,7 @@ assert_requested( :get, 'http://example.com/failure', - headers: { 'Traceparent' => "00-#{span.trace_id}-#{span.span_id}-01" } + headers: { 'Traceparent' => "00-#{span.trace_id.unpack1('H*')}-#{span.span_id.unpack1('H*')}-01" } ) end end diff --git a/adapters/sidekiq/test/opentelemetry/adapters/sidekiq/adapter_test.rb b/adapters/sidekiq/test/opentelemetry/adapters/sidekiq/adapter_test.rb index fd1fba268b..a6344f2824 100644 --- a/adapters/sidekiq/test/opentelemetry/adapters/sidekiq/adapter_test.rb +++ b/adapters/sidekiq/test/opentelemetry/adapters/sidekiq/adapter_test.rb @@ -26,7 +26,7 @@ def perform let(:adapter) { OpenTelemetry::Adapters::Sidekiq::Adapter.instance } let(:exporter) { EXPORTER } let(:spans) { exporter.finished_spans } - let(:root_span) { spans.find { |s| s.parent_span_id == '0000000000000000' } } + let(:root_span) { spans.find { |s| s.parent_span_id == OpenTelemetry::Trace::INVALID_SPAN_ID } } before { exporter.reset } @@ -47,7 +47,7 @@ def perform _(root_span.name).must_equal 'SimpleJob' _(root_span.kind).must_equal :producer - _(root_span.parent_span_id).must_equal '0000000000000000' + _(root_span.parent_span_id).must_equal OpenTelemetry::Trace::INVALID_SPAN_ID _(root_span.attributes['messaging.message_id']).must_equal job_id _(root_span.attributes['messaging.destination']).must_equal 'default' _(root_span.events.size).must_equal(1) @@ -72,7 +72,7 @@ def perform _(exporter.finished_spans.size).must_equal 4 - _(root_span.parent_span_id).must_equal '0000000000000000' + _(root_span.parent_span_id).must_equal OpenTelemetry::Trace::INVALID_SPAN_ID _(root_span.name).must_equal 'SimpleEnqueueingJob' _(root_span.kind).must_equal :producer diff --git a/api/lib/opentelemetry/trace.rb b/api/lib/opentelemetry/trace.rb index 6e5bb5ef4b..925dc90cf3 100644 --- a/api/lib/opentelemetry/trace.rb +++ b/api/lib/opentelemetry/trace.rb @@ -9,32 +9,30 @@ module OpenTelemetry # single logical operation, consolidated across various components of an # application. module Trace - # An invalid trace identifier, a 16-byte array with all zero bytes, encoded - # as a hexadecimal string. - INVALID_TRACE_ID = ('0' * 32).freeze + # An invalid trace identifier, a 16-byte string with all zero bytes. + INVALID_TRACE_ID = ("\0" * 16).b - # An invalid span identifier, an 8-byte array with all zero bytes, encoded - # as a hexadecimal string. - INVALID_SPAN_ID = ('0' * 16).freeze + # An invalid span identifier, an 8-byte string with all zero bytes. + INVALID_SPAN_ID = ("\0" * 8).b - # Generates a valid trace identifier, a 16-byte array with at least one - # non-zero byte, encoded as a hexadecimal string. + # Generates a valid trace identifier, a 16-byte string with at least one + # non-zero byte. # - # @return [String] a hexadecimal string encoding of a valid trace ID. + # @return [String] a valid trace ID. def self.generate_trace_id loop do - id = Random::DEFAULT.bytes(16).unpack1('H*') + id = Random::DEFAULT.bytes(16) return id unless id == INVALID_TRACE_ID end end - # Generates a valid span identifier, an 8-byte array with at least one - # non-zero byte, encoded as a hexadecimal string. + # Generates a valid span identifier, an 8-byte string with at least one + # non-zero byte. # - # @return [String] a hexadecimal string encoding of a valid span ID. + # @return [String] a valid span ID. def self.generate_span_id loop do - id = Random::DEFAULT.bytes(8).unpack1('H*') + id = Random::DEFAULT.bytes(8) return id unless id == INVALID_SPAN_ID end end diff --git a/api/lib/opentelemetry/trace/propagation/trace_context/trace_parent.rb b/api/lib/opentelemetry/trace/propagation/trace_context/trace_parent.rb index 4d13c18346..3baac074b5 100644 --- a/api/lib/opentelemetry/trace/propagation/trace_context/trace_parent.rb +++ b/api/lib/opentelemetry/trace/propagation/trace_context/trace_parent.rb @@ -25,6 +25,10 @@ class TraceParent REGEXP = /^(?[A-Fa-f0-9]{2})-(?[A-Fa-f0-9]{32})-(?[A-Fa-f0-9]{16})-(?[A-Fa-f0-9]{2})(?-.*)?$/.freeze private_constant :REGEXP + INVALID_TRACE_ID = OpenTelemetry::Trace::INVALID_TRACE_ID.unpack1('H*') + INVALID_SPAN_ID = OpenTelemetry::Trace::INVALID_SPAN_ID.unpack1('H*') + private_constant :INVALID_TRACE_ID, :INVALID_SPAN_ID + class << self # Creates a new {TraceParent} from a supplied {Trace::SpanContext} # @param [SpanContext] ctx The context @@ -71,17 +75,17 @@ def parse_version(string) end def parse_trace_id(string) - raise InvalidTraceIDError, string if string == OpenTelemetry::Trace::INVALID_TRACE_ID + raise InvalidTraceIDError, string if string == INVALID_TRACE_ID string.downcase! - string + Array(string).pack('H*') end def parse_span_id(string) - raise InvalidSpanIDError, string if string == OpenTelemetry::Trace::INVALID_SPAN_ID + raise InvalidSpanIDError, string if string == INVALID_SPAN_ID string.downcase! - string + Array(string).pack('H*') end def parse_flags(string) @@ -102,7 +106,7 @@ def sampled? # converts this object into a string according to the w3c spec # @return [String] the serialized trace_parent def to_s - "00-#{trace_id}-#{span_id}-#{flag_string}" + "00-#{trace_id.unpack1('H*')}-#{span_id.unpack1('H*')}-#{flag_string}" end private diff --git a/api/test/opentelemetry/trace/propagation/trace_context/text_extractor_test.rb b/api/test/opentelemetry/trace/propagation/trace_context/text_extractor_test.rb index 1bfe10c2e3..a8c5f677db 100644 --- a/api/test/opentelemetry/trace/propagation/trace_context/text_extractor_test.rb +++ b/api/test/opentelemetry/trace/propagation/trace_context/text_extractor_test.rb @@ -48,8 +48,8 @@ ctx = extractor.extract(carrier, context) { |c, k| c[k] } span_context = ctx[span_context_key] _(span_context).must_be :remote? - _(span_context.trace_id).must_equal('000000000000000000000000000000aa') - _(span_context.span_id).must_equal('00000000000000ea') + _(span_context.trace_id).must_equal(("\0" * 15 + "\xaa").b) + _(span_context.span_id).must_equal(("\0" * 7 + "\xea").b) _(span_context.trace_flags).must_be :sampled? _(span_context.tracestate).must_equal('vendorname=opaquevalue') end @@ -58,8 +58,8 @@ ctx = extractor.extract(carrier, context) span_context = ctx[span_context_key] _(span_context).must_be :remote? - _(span_context.trace_id).must_equal('000000000000000000000000000000aa') - _(span_context.span_id).must_equal('00000000000000ea') + _(span_context.trace_id).must_equal(("\0" * 15 + "\xaa").b) + _(span_context.span_id).must_equal(("\0" * 7 + "\xea").b) _(span_context.trace_flags).must_be :sampled? _(span_context.tracestate).must_equal('vendorname=opaquevalue') end diff --git a/api/test/opentelemetry/trace/propagation/trace_context/text_injector_test.rb b/api/test/opentelemetry/trace/propagation/trace_context/text_injector_test.rb index c6bed5a5f7..be03dfbfc9 100644 --- a/api/test/opentelemetry/trace/propagation/trace_context/text_injector_test.rb +++ b/api/test/opentelemetry/trace/propagation/trace_context/text_injector_test.rb @@ -32,18 +32,18 @@ end let(:tracestate_header) { 'vendorname=opaquevalue' } let(:context) do - span_context = SpanContext.new(trace_id: 'f' * 32, span_id: '1' * 16) + span_context = SpanContext.new(trace_id: ("\xff" * 16).b, span_id: ("\x11" * 8).b) span = Span.new(span_context: span_context) Context.empty.set_value(current_span_key, span) end let(:context_with_tracestate) do - span_context = SpanContext.new(trace_id: 'f' * 32, span_id: '1' * 16, + span_context = SpanContext.new(trace_id: ("\xff" * 16).b, span_id: ("\x11" * 8).b, tracestate: tracestate_header) span = Span.new(span_context: span_context) Context.empty.set_value(current_span_key, span) end let(:context_without_current_span) do - span_context = SpanContext.new(trace_id: 'f' * 32, span_id: '1' * 16, + span_context = SpanContext.new(trace_id: ("\xff" * 16).b, span_id: ("\x11" * 8).b, tracestate: tracestate_header) Context.empty.set_value(extracted_span_context_key, span_context) end diff --git a/api/test/opentelemetry/trace/propagation/trace_context/trace_parent_test.rb b/api/test/opentelemetry/trace/propagation/trace_context/trace_parent_test.rb index 9b62e6884d..760e9c95d9 100644 --- a/api/test/opentelemetry/trace/propagation/trace_context/trace_parent_test.rb +++ b/api/test/opentelemetry/trace/propagation/trace_context/trace_parent_test.rb @@ -15,7 +15,7 @@ describe '.to_s' do it 'formats it correctly' do - expected = "00-#{good.trace_id}-#{good.span_id}-01" + expected = "00-#{good.trace_id.unpack1('H*')}-#{good.span_id.unpack1('H*')}-01" _(good.to_s).must_equal(expected) end diff --git a/exporters/jaeger/lib/opentelemetry/exporters/jaeger/exporter/span_encoder.rb b/exporters/jaeger/lib/opentelemetry/exporters/jaeger/exporter/span_encoder.rb index bb66cad1ea..0155042bca 100644 --- a/exporters/jaeger/lib/opentelemetry/exporters/jaeger/exporter/span_encoder.rb +++ b/exporters/jaeger/lib/opentelemetry/exporters/jaeger/exporter/span_encoder.rb @@ -15,8 +15,8 @@ def encoded_span(span_data) # rubocop:disable Metrics/AbcSize duration = (span_data.end_timestamp.to_f * 1_000_000).to_i - start_time Thrift::Span.new( - 'traceIdLow' => int64(span_data.trace_id[16, 16]), - 'traceIdHigh' => int64(span_data.trace_id[0, 16]), + 'traceIdLow' => int64(span_data.trace_id[8, 8]), + 'traceIdHigh' => int64(span_data.trace_id[0, 8]), 'spanId' => int64(span_data.span_id), 'parentSpanId' => int64(span_data.parent_span_id), 'operationName' => span_data.name, @@ -106,8 +106,8 @@ def encoded_tag(key, value) ) end - def int64(hex_string) - int = hex_string.to_i(16) + def int64(byte_string) + int = byte_string.unpack1('Q>') int < (1 << 63) ? int : int - (1 << 64) end end