From eda479c60ec9b9d30616c81e94dfb04df351b594 Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Fri, 12 Jun 2020 22:17:35 -0400 Subject: [PATCH 01/10] OTLP exporter --- exporters/otlp/CHANGELOG.md | 1 + exporters/otlp/Gemfile | 12 ++ exporters/otlp/LICENSE | 201 ++++++++++++++++++ exporters/otlp/README.md | 83 ++++++++ exporters/otlp/Rakefile | 30 +++ .../otlp/lib/opentelemetry-exporters-otlp.rb | 7 + .../otlp/lib/opentelemetry/exporters/otlp.rb | 17 ++ .../opentelemetry/exporters/otlp/exporter.rb | 186 ++++++++++++++++ .../opentelemetry-exporters-jaeger.gemspec | 38 ++++ 9 files changed, 575 insertions(+) create mode 100644 exporters/otlp/CHANGELOG.md create mode 100644 exporters/otlp/Gemfile create mode 100644 exporters/otlp/LICENSE create mode 100644 exporters/otlp/README.md create mode 100644 exporters/otlp/Rakefile create mode 100644 exporters/otlp/lib/opentelemetry-exporters-otlp.rb create mode 100644 exporters/otlp/lib/opentelemetry/exporters/otlp.rb create mode 100644 exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb create mode 100644 exporters/otlp/opentelemetry-exporters-jaeger.gemspec diff --git a/exporters/otlp/CHANGELOG.md b/exporters/otlp/CHANGELOG.md new file mode 100644 index 0000000000..bdf76cc5b3 --- /dev/null +++ b/exporters/otlp/CHANGELOG.md @@ -0,0 +1 @@ +# Release History: opentelemetry-exporters-otlp diff --git a/exporters/otlp/Gemfile b/exporters/otlp/Gemfile new file mode 100644 index 0000000000..39f2c26fc9 --- /dev/null +++ b/exporters/otlp/Gemfile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +source 'https://rubygems.org' + +gemspec + +# Use the opentelemetry-api gem from source +gem 'opentelemetry-api', path: '../../api' diff --git a/exporters/otlp/LICENSE b/exporters/otlp/LICENSE new file mode 100644 index 0000000000..b7fbe3acd1 --- /dev/null +++ b/exporters/otlp/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/exporters/otlp/README.md b/exporters/otlp/README.md new file mode 100644 index 0000000000..39d1b71651 --- /dev/null +++ b/exporters/otlp/README.md @@ -0,0 +1,83 @@ +# opentelemetry-exporters-otlp + +The `opentelemetry-exporters-otlp` gem provides an OTLP exporter for OpenTelemetry for Ruby. Using `opentelemetry-exporters-otlp`, an application can configure OpenTelemetry to export collected tracing data to [the OpenTelemetry Collector][opentelemetry-collector-home]. + +## 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-exporters-otlp` gem is a plugin that provides OTLP export. To export to the OpenTelemetry Collector, an application can include this gem along with `opentelemetry-sdk`, and configure the `SDK` to use the provided OTLP exporter as a span processor. + +Generally, *libraries* that produce telemetry data should avoid depending directly on specific exporters, deferring that choice to the application developer. + +## How do I get started? + +Install the gem using: + +``` +gem install opentelemetry-sdk +gem install opentelemetry-exporters-otlp +``` + +Or, if you use [bundler][bundler-home], include `opentelemetry-sdk` in your `Gemfile`. + +Then, configure the SDK to use the OTLP exporter as a span processor, and use the OpenTelemetry interfaces to produces traces and other information. Following is a basic example. + +```ruby +require 'opentelemetry/sdk' +require 'opentelemetry/exporters/otlp' + +# Configure the sdk with custom export +OpenTelemetry::SDK.configure do |c| + c.add_span_processor( + OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new( + OpenTelemetry::Exporters::OTLP::Exporter.new( + host: 'localhost', port: 55680 + ) + ) + ) +end + +# To start a trace you need to get a Tracer from the TracerProvider +tracer = OpenTelemetry.tracer_provider.tracer('my_app_or_gem', '0.1.0') + +# create a span +tracer.in_span('foo') do |span| + # set an attribute + span.set_attribute('platform', 'osx') + # add an event + span.add_event(name: 'event in bar') + # create bar as child of foo + tracer.in_span('bar') do |child_span| + # inspect the span + pp child_span + end +end +``` + +For additional examples, see the [examples on github][examples-github]. + +## How can I get involved? + +The `opentelemetry-exporters-otlp` gem source is [on github][repo-github], along with related gems including `opentelemetry-sdk`. + +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-exporters-otlp` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. + + +[opentelemetry-collector-home]: https://opentelemetry.io/docs/collector/about/ +[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/exporters/otlp/Rakefile b/exporters/otlp/Rakefile new file mode 100644 index 0000000000..b29619af1f --- /dev/null +++ b/exporters/otlp/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' +RuboCop::RakeTask.new + +Rake::TestTask.new :test do |t| + t.libs << 'test' + t.libs << 'lib' + t.libs << '../../api/lib' + t.libs << '../../sdk/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/exporters/otlp/lib/opentelemetry-exporters-otlp.rb b/exporters/otlp/lib/opentelemetry-exporters-otlp.rb new file mode 100644 index 0000000000..52434496ad --- /dev/null +++ b/exporters/otlp/lib/opentelemetry-exporters-otlp.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry/exporters/otlp' diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp.rb b/exporters/otlp/lib/opentelemetry/exporters/otlp.rb new file mode 100644 index 0000000000..fb23165f1f --- /dev/null +++ b/exporters/otlp/lib/opentelemetry/exporters/otlp.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry/exporters/otlp/exporter' +require 'opentelemetry/exporters/otlp/version' + +# 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 +end diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb new file mode 100644 index 0000000000..59f2e259eb --- /dev/null +++ b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry/sdk' +require 'net/http' + +module OpenTelemetry + module Exporters + module OTLP + # An OpenTelemetry trace exporter that sends spans over HTTP as Protobuf encoded OTLP ExportTraceServiceRequests. + class Exporter + SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS + FAILED = OpenTelemetry::SDK::Trace::Export::FAILED + private_constant(:SUCCESS, :FAILED) + + # Default timeouts in seconds. + KEEP_ALIVE_TIMEOUT = 30 + OPEN_TIMEOUT = 5 + READ_TIMEOUT = 5 + PATH = '/v1/traces' + private_constant(:KEEP_ALIVE_TIMEOUT, :OPEN_TIMEOUT, :READ_TIMEOUT, :PATH) + + def initialize(host:, + port:, + path: PATH, + use_ssl: false, + keep_alive_timeout: KEEP_ALIVE_TIMEOUT, + open_timeout: OPEN_TIMEOUT, + read_timeout: READ_TIMEOUT) + @http = Net::HTTP.new(host, port) + @http.use_ssl = use_ssl + @http.keep_alive_timeout = keep_alive_timeout + @http.open_timeout = open_timeout + @http.read_timeout = read_timeout + + @path = path + + @shutdown = false + end + + # Called to export sampled {OpenTelemetry::SDK::Trace::SpanData} structs. + # + # @param [Enumerable] span_data the + # list of recorded {OpenTelemetry::SDK::Trace::SpanData} structs to be + # exported. + # @return [Integer] the result of the export. + def export(span_data) + return FAILED if @shutdown + + etsr = wrap(span_data) + bytes = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode(etsr) + + # Send + request = Net::HTTP::Post.new(@path) + request.body = bytes + request.add_field('Content-Type', 'application/x-protobuf') + request.add_field('Content-Encoding', 'gzip') + + response = untraced do # TODO: how to 'untrace'? + @http.start unless @http.started? + @http.request(request) + rescue Net::OpenTimeout, Net::ReadTimeout + return FAILED # TODO: exponential backoff/retry with jitter. + end + + case response + when Net::HTTPOK + SUCCESS + else # TODO: distinguish errors and retry if appropriate + FAILED + end + end + + # Called when {OpenTelemetry::SDK::Trace::Tracer#shutdown} is called, if + # this exporter is registered to a {OpenTelemetry::SDK::Trace::Tracer} + # object. + def shutdown + @shutdown = true + @http.finish if @http.started? # TODO: is shutdown called concurrently with export? + end + + private + + def wrap(span_data) + rs = Opentelemetry::Proto::Trace::V1::ResourceSpans.new + rs.resource = OpenTelemetry.tracer_provider.resource + span_data + .group_by { |sd| sd.instrumentation_library } + .each do |il, sds| + ils = Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new + ils.instrumentation_library = Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new + ils.instrumentation_library.name = il.name + ils.instrumentation_library.version = il.version + sds.each { |sd| ils.spans.push(as_otlp_span(sd)) } + rs.instrumentation_library_spans.push(ils) + end + etsr = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new + etsr.resource_spans.push(rs) + etsr + end + + def as_otlp_span(span_data) + span = Opentelemetry::Proto::Trace::V1::Span.new + span.trace_id = span_data.trace_id + span.span_id = span_data.span_id + span.trace_state = span_data.trace_state # TODO: add tracestate to SpanData + span.parent_span_id = span_data.parent_span_id + span.name = span_data.name + span.kind = as_otlp_span_kind(span_data.kind) + span.start_time_unix_nano = as_otlp_timestamp(span_data.start_timestamp) + span.end_time_unix_nano = as_otlp_timestamp(span_data.end_timestamp) + span_data.attributes&.each { |k, v| span.attributes.push(as_otlp_attribute(k, v)) } + span.dropped_attributes_count = span_data.total_recorded_attributes - span_data.attributes.size + span_data.events&.each { |event| span.events.push(as_otlp_event(event)) } + span.dropped_events_count = span_data.total_recorded_events - span_data.events.size + span_data.links&.each { |link| span.links.push(as_otlp_link(link)) } + span.dropped_links_count = span_data.total_recorded_links - span_data.links.size + span.status = as_otlp_status(span_data.status) + span + end + + def as_otlp_timestamp(timestamp) + (timestamp.to_r * 1_000_000_000).to_i + end + + def as_otlp_span_kind(kind) + case kind + when :internal then Opentelemetry::Proto::Trace::V1::Span::SpanKind::INTERNAL + when :server then Opentelemetry::Proto::Trace::V1::Span::SpanKind::SERVER + when :client then Opentelemetry::Proto::Trace::V1::Span::SpanKind::CLIENT + when :producer then Opentelemetry::Proto::Trace::V1::Span::SpanKind::PRODUCER + when :consumer then Opentelemetry::Proto::Trace::V1::Span::SpanKind::CONSUMER + else Opentelemetry::Proto::Trace::V1::Span::SpanKind::SPAN_KIND_UNSPECIFIED + end + end + + def as_otlp_attribute(key, value) + kv = Opentelemetry::Proto::Common::V1::AttributeKeyValue.new + kv.key = key + case value + when String + kv.string_value = value + kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::STRING + when Integer + kv.int_value = value + kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::INT + when Float + kv.double_value = value + kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::DOUBLE + when true, false + kv.bool_value = value + kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::BOOL + # TODO: when Array + end + kv + end + + def as_otlp_event(event) + e = Opentelemetry::Proto::Trace::V1::Span::Event.new + e.time_unix_nano = as_otlp_timestamp(event.timestamp) + e.name = event.name + event.attributes&.each { |k, v| e.attributes.push(as_otlp_attribute(k, v)) } + # TODO: enforcement of max_attributes_per_event -> dropped_attributes_count + e + end + + def as_otlp_link(link) + l = Opentelemetry::Proto::Trace::V1::Span::Link.new + l.trace_id = link.context.trace_id + l.span_id = link.context.span_id + l.trace_state = link.context.tracestate + link.attributes&.each { |k, v| l.attributes.push(as_otlp_attribute(k, v)) } + # TODO: enforcement of max_attributes_per_link -> dropped_attributes_count + l + end + + def as_otlp_status(status) + # TODO: status "opentelemetry.proto.trace.v1.Status" + end + end + end + end +end diff --git a/exporters/otlp/opentelemetry-exporters-jaeger.gemspec b/exporters/otlp/opentelemetry-exporters-jaeger.gemspec new file mode 100644 index 0000000000..25878530d5 --- /dev/null +++ b/exporters/otlp/opentelemetry-exporters-jaeger.gemspec @@ -0,0 +1,38 @@ +# 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/exporters/otlp/version' + +Gem::Specification.new do |spec| + spec.name = 'opentelemetry-exporters-otlp' + spec.version = OpenTelemetry::Exporters::OTLP::VERSION + spec.authors = ['OpenTelemetry Authors'] + spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] + + spec.summary = 'OTLP exporter for the OpenTelemetry framework' + spec.description = 'OTLP exporter for the OpenTelemetry framework' + 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 'opentelemetry-api', '~> 0.4.0' + + spec.add_development_dependency 'bundler', '>= 1.17' + spec.add_development_dependency 'faraday', '~> 0.13' + 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 From 482ee4d4badfd0b4ee12de158fcc2b2a2503aec9 Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Tue, 16 Jun 2020 23:46:29 -0400 Subject: [PATCH 02/10] Support proto changes for KeyValue --- .../opentelemetry/exporters/otlp/exporter.rb | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb index 59f2e259eb..81fd6a971d 100644 --- a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb +++ b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb @@ -112,7 +112,7 @@ def as_otlp_span(span_data) span.kind = as_otlp_span_kind(span_data.kind) span.start_time_unix_nano = as_otlp_timestamp(span_data.start_timestamp) span.end_time_unix_nano = as_otlp_timestamp(span_data.end_timestamp) - span_data.attributes&.each { |k, v| span.attributes.push(as_otlp_attribute(k, v)) } + span_data.attributes&.each { |k, v| span.attributes.push(as_otlp_key_value(k, v)) } span.dropped_attributes_count = span_data.total_recorded_attributes - span_data.attributes.size span_data.events&.each { |event| span.events.push(as_otlp_event(event)) } span.dropped_events_count = span_data.total_recorded_events - span_data.events.size @@ -137,22 +137,19 @@ def as_otlp_span_kind(kind) end end - def as_otlp_attribute(key, value) - kv = Opentelemetry::Proto::Common::V1::AttributeKeyValue.new + def as_otlp_key_value(key, value) + kv = Opentelemetry::Proto::Common::V1::KeyValue.new kv.key = key + kv.value = Opentelemetry::Proto::Common::V1::AnyValue.new case value when String - kv.string_value = value - kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::STRING + kv.value.string_value = value when Integer - kv.int_value = value - kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::INT + kv.value.int_value = value when Float - kv.double_value = value - kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::DOUBLE + kv.value.double_value = value when true, false - kv.bool_value = value - kv.type = Opentelemetry::Proto::Common::V1::AttributeKeyValue::ValueType::BOOL + kv.value.bool_value = value # TODO: when Array end kv @@ -162,7 +159,7 @@ def as_otlp_event(event) e = Opentelemetry::Proto::Trace::V1::Span::Event.new e.time_unix_nano = as_otlp_timestamp(event.timestamp) e.name = event.name - event.attributes&.each { |k, v| e.attributes.push(as_otlp_attribute(k, v)) } + event.attributes&.each { |k, v| e.attributes.push(as_otlp_key_value(k, v)) } # TODO: enforcement of max_attributes_per_event -> dropped_attributes_count e end @@ -172,7 +169,7 @@ def as_otlp_link(link) l.trace_id = link.context.trace_id l.span_id = link.context.span_id l.trace_state = link.context.tracestate - link.attributes&.each { |k, v| l.attributes.push(as_otlp_attribute(k, v)) } + link.attributes&.each { |k, v| l.attributes.push(as_otlp_key_value(k, v)) } # TODO: enforcement of max_attributes_per_link -> dropped_attributes_count l end From 049a3c13fb90becf9a444305994517ccbad7d92b Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 29 Jul 2020 14:24:03 -0400 Subject: [PATCH 03/10] Cleanup & start adding tests --- Rakefile | 6 + exporters/otlp/Rakefile | 15 ++ .../opentelemetry/exporters/otlp/exporter.rb | 239 +++++++++++------- .../opentelemetry/exporters/otlp/version.rb | 14 + .../collector/trace/v1/trace_service_pb.rb | 28 ++ .../proto/common/v1/common_pb.rb | 52 ++++ .../proto/resource/v1/resource_pb.rb | 24 ++ .../opentelemetry/proto/trace/v1/trace_pb.rb | 97 +++++++ ...c => opentelemetry-exporters-otlp.gemspec} | 3 +- exporters/otlp/test/.rubocop.yml | 6 + .../exporters/otlp/exporter_test.rb | 70 +++++ exporters/otlp/test/test_helper.rb | 13 + 12 files changed, 481 insertions(+), 86 deletions(-) create mode 100644 exporters/otlp/lib/opentelemetry/exporters/otlp/version.rb create mode 100644 exporters/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb create mode 100644 exporters/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb create mode 100644 exporters/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb create mode 100644 exporters/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb rename exporters/otlp/{opentelemetry-exporters-jaeger.gemspec => opentelemetry-exporters-otlp.gemspec} (92%) create mode 100644 exporters/otlp/test/.rubocop.yml create mode 100644 exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb create mode 100644 exporters/otlp/test/test_helper.rb diff --git a/Rakefile b/Rakefile index 7cfb69412b..2b9016684a 100644 --- a/Rakefile +++ b/Rakefile @@ -57,6 +57,12 @@ GEM_INFO = { OpenTelemetry::Exporters::Jaeger::VERSION } }, + "opentelemetry-exporters-otlp" => { + version_getter: ->() { + require './lib/opentelemetry/exporters/otlp/version.rb' + OpenTelemetry::Exporters::OTLP::VERSION + } + }, "opentelemetry-instrumentation-ethon" => { version_getter: ->() { require './lib/opentelemetry/instrumentation/ethon/version.rb' diff --git a/exporters/otlp/Rakefile b/exporters/otlp/Rakefile index b29619af1f..2e6dcbbd83 100644 --- a/exporters/otlp/Rakefile +++ b/exporters/otlp/Rakefile @@ -28,3 +28,18 @@ if RUBY_ENGINE == 'truffleruby' else task default: %i[test rubocop yard] end + +PROTOBUF_FILES = [ + 'common/v1/common.proto', + 'resource/v1/resource.proto', + 'trace/v1/trace.proto', + 'collector/trace/v1/trace_service.proto', +] + +task :update_protobuf do + system('git clone https://github.com/open-telemetry/opentelemetry-proto') + PROTOBUF_FILES.each do |file| + system("protoc --ruby_out=lib/ --proto_path=opentelemetry-proto opentelemetry/proto/#{file}") + end + system('rm -rf opentelemetry-proto') +end diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb index 81fd6a971d..ab1337b685 100644 --- a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb +++ b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb @@ -7,21 +7,27 @@ require 'opentelemetry/sdk' require 'net/http' +require 'opentelemetry/proto/common/v1/common_pb' +require 'opentelemetry/proto/resource/v1/resource_pb' +require 'opentelemetry/proto/trace/v1/trace_pb' +require 'opentelemetry/proto/collector/trace/v1/trace_service_pb' + module OpenTelemetry module Exporters module OTLP # An OpenTelemetry trace exporter that sends spans over HTTP as Protobuf encoded OTLP ExportTraceServiceRequests. class Exporter SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS - FAILED = OpenTelemetry::SDK::Trace::Export::FAILED - private_constant(:SUCCESS, :FAILED) + FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE + private_constant(:SUCCESS, :FAILURE) # Default timeouts in seconds. KEEP_ALIVE_TIMEOUT = 30 OPEN_TIMEOUT = 5 READ_TIMEOUT = 5 + RETRY_COUNT = 5 PATH = '/v1/traces' - private_constant(:KEEP_ALIVE_TIMEOUT, :OPEN_TIMEOUT, :READ_TIMEOUT, :PATH) + private_constant(:KEEP_ALIVE_TIMEOUT, :OPEN_TIMEOUT, :READ_TIMEOUT, :RETRY_COUNT, :PATH) def initialize(host:, port:, @@ -29,7 +35,8 @@ def initialize(host:, use_ssl: false, keep_alive_timeout: KEEP_ALIVE_TIMEOUT, open_timeout: OPEN_TIMEOUT, - read_timeout: READ_TIMEOUT) + read_timeout: READ_TIMEOUT, + retry_count: RETRY_COUNT) @http = Net::HTTP.new(host, port) @http.use_ssl = use_ssl @http.keep_alive_timeout = keep_alive_timeout @@ -37,6 +44,8 @@ def initialize(host:, @http.read_timeout = read_timeout @path = path + @max_retry_count = retry_count + @tracer = OpenTelemetry.tracer_provider.tracer @shutdown = false end @@ -48,30 +57,9 @@ def initialize(host:, # exported. # @return [Integer] the result of the export. def export(span_data) - return FAILED if @shutdown - - etsr = wrap(span_data) - bytes = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode(etsr) + return FAILURE if @shutdown - # Send - request = Net::HTTP::Post.new(@path) - request.body = bytes - request.add_field('Content-Type', 'application/x-protobuf') - request.add_field('Content-Encoding', 'gzip') - - response = untraced do # TODO: how to 'untrace'? - @http.start unless @http.started? - @http.request(request) - rescue Net::OpenTimeout, Net::ReadTimeout - return FAILED # TODO: exponential backoff/retry with jitter. - end - - case response - when Net::HTTPOK - SUCCESS - else # TODO: distinguish errors and retry if appropriate - FAILED - end + send_bytes(encode(span_data)) end # Called when {OpenTelemetry::SDK::Trace::Tracer#shutdown} is called, if @@ -79,47 +67,152 @@ def export(span_data) # object. def shutdown @shutdown = true - @http.finish if @http.started? # TODO: is shutdown called concurrently with export? + @http.finish if @http.started? end private - def wrap(span_data) - rs = Opentelemetry::Proto::Trace::V1::ResourceSpans.new - rs.resource = OpenTelemetry.tracer_provider.resource - span_data - .group_by { |sd| sd.instrumentation_library } - .each do |il, sds| - ils = Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new - ils.instrumentation_library = Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new - ils.instrumentation_library.name = il.name - ils.instrumentation_library.version = il.version - sds.each { |sd| ils.spans.push(as_otlp_span(sd)) } - rs.instrumentation_library_spans.push(ils) + def send_bytes(bytes) + retry_count = 0 + untraced do + request = Net::HTTP::Post.new(@path) + request.body = bytes + request.add_field('Content-Type', 'application/x-protobuf') + # TODO: enable gzip when https://github.com/open-telemetry/opentelemetry-collector/issues/1344 is fixed. + # request.add_field('Content-Encoding', 'gzip') + + @http.start unless @http.started? + response = @http.request(request) + + case response + when Net::HTTPOK + response.body # Read and discard body + SUCCESS + when Net::HTTPServiceUnavailable, Net::HTTPTooManyRequests + response.body # Read and discard body + redo if backoff?(retry_after: response['Retry-After'], retry_count: retry_count += 1) + FAILURE + when Net::HTTPRequestTimeOut, Net::HTTPGatewayTimeOut, Net::HTTPBadGateway + response.body # Read and discard body + redo if backoff?(retry_count: retry_count += 1) + FAILURE + when Net::HTTPBadRequest, Net::HTTPClientError, Net::HTTPServerError + # TODO: decode the body as a google.rpc.Status Protobuf-encoded message when https://github.com/open-telemetry/opentelemetry-collector/issues/1357 is fixed. + response.body # Read and discard body + FAILURE + when Net::HTTPRedirection + @http.finish + handle_redirect(response['location']) + redo if backoff?(retry_after: 0, retry_count: retry_count += 1) + else + @http.finish + FAILURE end - etsr = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new - etsr.resource_spans.push(rs) - etsr + rescue Net::OpenTimeout, Net::ReadTimeout + retry if backoff?(retry_count: retry_count += 1) + return FAILURE + end + end + + def handle_redirect(location) + # TODO: figure out destination and reinitialize @http and @path + location = response['location'] + location = URI(location).relative? ? (@uri + location).to_s : location + initialize_http(location) # Assume this is our new destination from now on. + end + + def untraced + @tracer.with_span(OpenTelemetry::Trace::Span.new) { yield } + end + + def backoff?(retry_after: nil, retry_count:, reason:) + return false if retry_count > @max_retry_count + + sleep_interval = nil + unless retry_after.nil? + sleep_interval = + begin + Integer(retry_after) + rescue ArgumentError + nil + end + sleep_interval ||= + begin + Time.httpdate(retry_after) - Time.now + rescue + nil + end + sleep_interval = nil unless sleep_interval&.positive? + end + sleep_interval ||= rand(2**retry_count) + + sleep(sleep_interval) + true + end + + def encode(span_data) + Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode( + Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new( + resource_spans: [ + Opentelemetry::Proto::Trace::V1::ResourceSpans.new( + resource: Opentelemetry::Proto::Resource::V1::Resource.new( + attributes: OpenTelemetry.tracer_provider.resource.label_enumerator.map { |key, value| as_otlp_key_value(key, value) }, + ), + instrumentation_library_spans: span_data + .group_by { |sd| sd.instrumentation_library } + .map do |il, sds| + Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( + instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( + name: il.name, + version: il.version, + ), + spans: sds.map { |sd| as_otlp_span(sd) }, + ) + end, + ) + ] + ) + ) end def as_otlp_span(span_data) - span = Opentelemetry::Proto::Trace::V1::Span.new - span.trace_id = span_data.trace_id - span.span_id = span_data.span_id - span.trace_state = span_data.trace_state # TODO: add tracestate to SpanData - span.parent_span_id = span_data.parent_span_id - span.name = span_data.name - span.kind = as_otlp_span_kind(span_data.kind) - span.start_time_unix_nano = as_otlp_timestamp(span_data.start_timestamp) - span.end_time_unix_nano = as_otlp_timestamp(span_data.end_timestamp) - span_data.attributes&.each { |k, v| span.attributes.push(as_otlp_key_value(k, v)) } - span.dropped_attributes_count = span_data.total_recorded_attributes - span_data.attributes.size - span_data.events&.each { |event| span.events.push(as_otlp_event(event)) } - span.dropped_events_count = span_data.total_recorded_events - span_data.events.size - span_data.links&.each { |link| span.links.push(as_otlp_link(link)) } - span.dropped_links_count = span_data.total_recorded_links - span_data.links.size - span.status = as_otlp_status(span_data.status) - span + Opentelemetry::Proto::Trace::V1::Span.new( + trace_id: span_data.trace_id, + span_id: span_data.span_id, + trace_state: span_data.tracestate, + parent_span_id: span_data.parent_span_id, + name: span_data.name, + kind: as_otlp_span_kind(span_data.kind), + start_time_unix_nano: as_otlp_timestamp(span_data.start_timestamp), + end_time_unix_nano: as_otlp_timestamp(span_data.end_timestamp), + attributes: span_data.attributes&.map { |k, v| as_otlp_key_value(k, v) }, + dropped_attributes_count: span_data.total_recorded_attributes - span_data.attributes&.size.to_i, + events: span_data.events&.map do |event| + Opentelemetry::Proto::Trace::V1::Span::Event.new( + time_unix_nano: as_otlp_timestamp(event.timestamp), + name: event.name, + attributes: event.attributes&.map { |k, v| as_otlp_key_value(k, v) }, + # TODO: track dropped_attributes_count in Span#append_event + ) + end, + dropped_events_count: span_data.total_recorded_events - span_data.events&.size.to_i, + links: span_data.links&.map do |link| + Opentelemetry::Proto::Trace::V1::Span::Link.new( + trace_id: link.context.trace_id, + span_id: link.context.span_id, + trace_state: link.context.tracestate, + attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) }, + # TODO: track dropped_attributes_count in Span#trim_links + ) + end, + dropped_links_count: span_data.total_recorded_links - span_data.links&.size.to_i, + status: span_data.status&.yield_self do |status| + Opentelemetry::Proto::Trace::V1::Status.new( + code: status.canonical_code, # TODO: verify that the Integer canonical code can be used here instead of the enum. + message: status.description, + ) + end, + ) end def as_otlp_timestamp(timestamp) @@ -138,8 +231,7 @@ def as_otlp_span_kind(kind) end def as_otlp_key_value(key, value) - kv = Opentelemetry::Proto::Common::V1::KeyValue.new - kv.key = key + kv = Opentelemetry::Proto::Common::V1::KeyValue.new(key: key) kv.value = Opentelemetry::Proto::Common::V1::AnyValue.new case value when String @@ -154,29 +246,6 @@ def as_otlp_key_value(key, value) end kv end - - def as_otlp_event(event) - e = Opentelemetry::Proto::Trace::V1::Span::Event.new - e.time_unix_nano = as_otlp_timestamp(event.timestamp) - e.name = event.name - event.attributes&.each { |k, v| e.attributes.push(as_otlp_key_value(k, v)) } - # TODO: enforcement of max_attributes_per_event -> dropped_attributes_count - e - end - - def as_otlp_link(link) - l = Opentelemetry::Proto::Trace::V1::Span::Link.new - l.trace_id = link.context.trace_id - l.span_id = link.context.span_id - l.trace_state = link.context.tracestate - link.attributes&.each { |k, v| l.attributes.push(as_otlp_key_value(k, v)) } - # TODO: enforcement of max_attributes_per_link -> dropped_attributes_count - l - end - - def as_otlp_status(status) - # TODO: status "opentelemetry.proto.trace.v1.Status" - end end end end diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/version.rb b/exporters/otlp/lib/opentelemetry/exporters/otlp/version.rb new file mode 100644 index 0000000000..ca725aed74 --- /dev/null +++ b/exporters/otlp/lib/opentelemetry/exporters/otlp/version.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Copyright 2019 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Exporters + module OTLP + ## Current OpenTelemetry OTLP exporter version + VERSION = '0.5.0' + end + end +end diff --git a/exporters/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb b/exporters/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb new file mode 100644 index 0000000000..8cfd05470b --- /dev/null +++ b/exporters/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb @@ -0,0 +1,28 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/collector/trace/v1/trace_service.proto + +require 'google/protobuf' + +require 'opentelemetry/proto/trace/v1/trace_pb' +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("opentelemetry/proto/collector/trace/v1/trace_service.proto", :syntax => :proto3) do + add_message "opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest" do + repeated :resource_spans, :message, 1, "opentelemetry.proto.trace.v1.ResourceSpans" + end + add_message "opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse" do + end + end +end + +module Opentelemetry + module Proto + module Collector + module Trace + module V1 + ExportTraceServiceRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest").msgclass + ExportTraceServiceResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse").msgclass + end + end + end + end +end diff --git a/exporters/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb b/exporters/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb new file mode 100644 index 0000000000..3a132e3c4d --- /dev/null +++ b/exporters/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb @@ -0,0 +1,52 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/common/v1/common.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("opentelemetry/proto/common/v1/common.proto", :syntax => :proto3) do + add_message "opentelemetry.proto.common.v1.AnyValue" do + oneof :value do + optional :string_value, :string, 1 + optional :bool_value, :bool, 2 + optional :int_value, :int64, 3 + optional :double_value, :double, 4 + optional :array_value, :message, 5, "opentelemetry.proto.common.v1.ArrayValue" + optional :kvlist_value, :message, 6, "opentelemetry.proto.common.v1.KeyValueList" + end + end + add_message "opentelemetry.proto.common.v1.ArrayValue" do + repeated :values, :message, 1, "opentelemetry.proto.common.v1.AnyValue" + end + add_message "opentelemetry.proto.common.v1.KeyValueList" do + repeated :values, :message, 1, "opentelemetry.proto.common.v1.KeyValue" + end + add_message "opentelemetry.proto.common.v1.KeyValue" do + optional :key, :string, 1 + optional :value, :message, 2, "opentelemetry.proto.common.v1.AnyValue" + end + add_message "opentelemetry.proto.common.v1.StringKeyValue" do + optional :key, :string, 1 + optional :value, :string, 2 + end + add_message "opentelemetry.proto.common.v1.InstrumentationLibrary" do + optional :name, :string, 1 + optional :version, :string, 2 + end + end +end + +module Opentelemetry + module Proto + module Common + module V1 + AnyValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.AnyValue").msgclass + ArrayValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.ArrayValue").msgclass + KeyValueList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.KeyValueList").msgclass + KeyValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.KeyValue").msgclass + StringKeyValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.StringKeyValue").msgclass + InstrumentationLibrary = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.common.v1.InstrumentationLibrary").msgclass + end + end + end +end diff --git a/exporters/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb b/exporters/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb new file mode 100644 index 0000000000..7c0d53e6ee --- /dev/null +++ b/exporters/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb @@ -0,0 +1,24 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/resource/v1/resource.proto + +require 'google/protobuf' + +require 'opentelemetry/proto/common/v1/common_pb' +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("opentelemetry/proto/resource/v1/resource.proto", :syntax => :proto3) do + add_message "opentelemetry.proto.resource.v1.Resource" do + repeated :attributes, :message, 1, "opentelemetry.proto.common.v1.KeyValue" + optional :dropped_attributes_count, :uint32, 2 + end + end +end + +module Opentelemetry + module Proto + module Resource + module V1 + Resource = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.resource.v1.Resource").msgclass + end + end + end +end diff --git a/exporters/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb b/exporters/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb new file mode 100644 index 0000000000..e9e6c93044 --- /dev/null +++ b/exporters/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb @@ -0,0 +1,97 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: opentelemetry/proto/trace/v1/trace.proto + +require 'google/protobuf' + +require 'opentelemetry/proto/common/v1/common_pb' +require 'opentelemetry/proto/resource/v1/resource_pb' +Google::Protobuf::DescriptorPool.generated_pool.build do + add_file("opentelemetry/proto/trace/v1/trace.proto", :syntax => :proto3) do + add_message "opentelemetry.proto.trace.v1.ResourceSpans" do + optional :resource, :message, 1, "opentelemetry.proto.resource.v1.Resource" + repeated :instrumentation_library_spans, :message, 2, "opentelemetry.proto.trace.v1.InstrumentationLibrarySpans" + end + add_message "opentelemetry.proto.trace.v1.InstrumentationLibrarySpans" do + optional :instrumentation_library, :message, 1, "opentelemetry.proto.common.v1.InstrumentationLibrary" + repeated :spans, :message, 2, "opentelemetry.proto.trace.v1.Span" + end + add_message "opentelemetry.proto.trace.v1.Span" do + optional :trace_id, :bytes, 1 + optional :span_id, :bytes, 2 + optional :trace_state, :string, 3 + optional :parent_span_id, :bytes, 4 + optional :name, :string, 5 + optional :kind, :enum, 6, "opentelemetry.proto.trace.v1.Span.SpanKind" + optional :start_time_unix_nano, :fixed64, 7 + optional :end_time_unix_nano, :fixed64, 8 + repeated :attributes, :message, 9, "opentelemetry.proto.common.v1.KeyValue" + optional :dropped_attributes_count, :uint32, 10 + repeated :events, :message, 11, "opentelemetry.proto.trace.v1.Span.Event" + optional :dropped_events_count, :uint32, 12 + repeated :links, :message, 13, "opentelemetry.proto.trace.v1.Span.Link" + optional :dropped_links_count, :uint32, 14 + optional :status, :message, 15, "opentelemetry.proto.trace.v1.Status" + end + add_message "opentelemetry.proto.trace.v1.Span.Event" do + optional :time_unix_nano, :fixed64, 1 + optional :name, :string, 2 + repeated :attributes, :message, 3, "opentelemetry.proto.common.v1.KeyValue" + optional :dropped_attributes_count, :uint32, 4 + end + add_message "opentelemetry.proto.trace.v1.Span.Link" do + optional :trace_id, :bytes, 1 + optional :span_id, :bytes, 2 + optional :trace_state, :string, 3 + repeated :attributes, :message, 4, "opentelemetry.proto.common.v1.KeyValue" + optional :dropped_attributes_count, :uint32, 5 + end + add_enum "opentelemetry.proto.trace.v1.Span.SpanKind" do + value :SPAN_KIND_UNSPECIFIED, 0 + value :INTERNAL, 1 + value :SERVER, 2 + value :CLIENT, 3 + value :PRODUCER, 4 + value :CONSUMER, 5 + end + add_message "opentelemetry.proto.trace.v1.Status" do + optional :code, :enum, 1, "opentelemetry.proto.trace.v1.Status.StatusCode" + optional :message, :string, 2 + end + add_enum "opentelemetry.proto.trace.v1.Status.StatusCode" do + value :Ok, 0 + value :Cancelled, 1 + value :UnknownError, 2 + value :InvalidArgument, 3 + value :DeadlineExceeded, 4 + value :NotFound, 5 + value :AlreadyExists, 6 + value :PermissionDenied, 7 + value :ResourceExhausted, 8 + value :FailedPrecondition, 9 + value :Aborted, 10 + value :OutOfRange, 11 + value :Unimplemented, 12 + value :InternalError, 13 + value :Unavailable, 14 + value :DataLoss, 15 + value :Unauthenticated, 16 + end + end +end + +module Opentelemetry + module Proto + module Trace + module V1 + ResourceSpans = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.ResourceSpans").msgclass + InstrumentationLibrarySpans = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.InstrumentationLibrarySpans").msgclass + Span = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.Span").msgclass + Span::Event = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.Span.Event").msgclass + Span::Link = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.Span.Link").msgclass + Span::SpanKind = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.Span.SpanKind").enummodule + Status = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.Status").msgclass + Status::StatusCode = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("opentelemetry.proto.trace.v1.Status.StatusCode").enummodule + end + end + end +end diff --git a/exporters/otlp/opentelemetry-exporters-jaeger.gemspec b/exporters/otlp/opentelemetry-exporters-otlp.gemspec similarity index 92% rename from exporters/otlp/opentelemetry-exporters-jaeger.gemspec rename to exporters/otlp/opentelemetry-exporters-otlp.gemspec index 25878530d5..3f38943eac 100644 --- a/exporters/otlp/opentelemetry-exporters-jaeger.gemspec +++ b/exporters/otlp/opentelemetry-exporters-otlp.gemspec @@ -25,7 +25,8 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.5.0' - spec.add_dependency 'opentelemetry-api', '~> 0.4.0' + spec.add_dependency 'opentelemetry-api', '~> 0.5.0' + spec.add_dependency 'google-protobuf', '>= 3.4.1.1', '< 4' spec.add_development_dependency 'bundler', '>= 1.17' spec.add_development_dependency 'faraday', '~> 0.13' diff --git a/exporters/otlp/test/.rubocop.yml b/exporters/otlp/test/.rubocop.yml new file mode 100644 index 0000000000..4c8c0d91ed --- /dev/null +++ b/exporters/otlp/test/.rubocop.yml @@ -0,0 +1,6 @@ +inherit_from: ../.rubocop.yml + +Metrics/BlockLength: + Enabled: false +Metrics/LineLength: + Enabled: false diff --git a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb new file mode 100644 index 0000000000..673d80fc93 --- /dev/null +++ b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +# Copyright 2019 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 +require 'test_helper' + +describe OpenTelemetry::Exporters::OTLP::Exporter do + SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS + FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE + + describe '#initialize' do + it 'initializes' do + exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 56781) + _(exporter).wont_be_nil + end + end + + describe '#export' do + let(:exporter) { OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 56781) } + + before do + OpenTelemetry.tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new + end + + it 'returns FAILURE when shutdown' do + exporter.shutdown + result = exporter.export(nil) + _(result).must_equal(FAILURE) + end + + it 'exports a span_data' do + skip 'WIP' + socket = UDPSocket.new + socket.bind('127.0.0.1', 0) + exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: socket.addr[1]) + span_data = create_span_data + result = exporter.export([span_data]) + packet = socket.recvfrom(65_000) + socket.close + _(result).must_equal(SUCCESS) + _(packet).wont_be_nil + end + + it 'exports a span from a tracer' do + skip 'WIP' + socket = UDPSocket.new + socket.bind('127.0.0.1', 0) + exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: socket.addr[1]) + processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter: exporter, max_queue_size: 1, max_export_batch_size: 1) + OpenTelemetry.tracer_provider.add_span_processor(processor) + OpenTelemetry.tracer_provider.tracer.start_root_span('foo').finish + OpenTelemetry.tracer_provider.shutdown + packet = socket.recvfrom(65_000) + socket.close + _(packet).wont_be_nil + end + end + + def create_span_data(name: '', kind: nil, status: nil, parent_span_id: OpenTelemetry::Trace::INVALID_SPAN_ID, child_count: 0, + total_recorded_attributes: 0, total_recorded_events: 0, total_recorded_links: 0, start_timestamp: Time.now, + end_timestamp: Time.now, attributes: nil, links: nil, events: nil, library_resource: nil, + instrumentation_library: OpenTelemetry::SDK::InstrumentationLibrary.new('', 'v0.0.1'), + span_id: OpenTelemetry::Trace.generate_span_id, trace_id: OpenTelemetry::Trace.generate_trace_id, + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, tracestate: nil) + OpenTelemetry::SDK::Trace::SpanData.new(name, kind, status, parent_span_id, child_count, total_recorded_attributes, + total_recorded_events, total_recorded_links, start_timestamp, end_timestamp, + attributes, links, events, library_resource, instrumentation_library, span_id, trace_id, trace_flags, tracestate) + end +end diff --git a/exporters/otlp/test/test_helper.rb b/exporters/otlp/test/test_helper.rb new file mode 100644 index 0000000000..c5836d1418 --- /dev/null +++ b/exporters/otlp/test/test_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Copyright 2019 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'simplecov' +SimpleCov.start + +require 'opentelemetry/exporters/otlp' +require 'minitest/autorun' + +OpenTelemetry.logger = Logger.new('/dev/null') From 5684db7d00f338f4ccaa2af61254ff4bb8c3ab1c Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 29 Jul 2020 17:53:25 -0400 Subject: [PATCH 04/10] Fix tests + path --- .../opentelemetry/exporters/otlp/exporter.rb | 2 +- .../otlp/opentelemetry-exporters-otlp.gemspec | 1 + .../exporters/otlp/exporter_test.rb | 23 ++++++------------- exporters/otlp/test/test_helper.rb | 1 + 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb index ab1337b685..d81c2fc49d 100644 --- a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb +++ b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb @@ -26,7 +26,7 @@ class Exporter OPEN_TIMEOUT = 5 READ_TIMEOUT = 5 RETRY_COUNT = 5 - PATH = '/v1/traces' + PATH = '/v1/trace' private_constant(:KEEP_ALIVE_TIMEOUT, :OPEN_TIMEOUT, :READ_TIMEOUT, :RETRY_COUNT, :PATH) def initialize(host:, diff --git a/exporters/otlp/opentelemetry-exporters-otlp.gemspec b/exporters/otlp/opentelemetry-exporters-otlp.gemspec index 3f38943eac..f55807ea22 100644 --- a/exporters/otlp/opentelemetry-exporters-otlp.gemspec +++ b/exporters/otlp/opentelemetry-exporters-otlp.gemspec @@ -34,6 +34,7 @@ Gem::Specification.new do |spec| 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 'webmock', '~> 3.7.6' spec.add_development_dependency 'yard', '~> 0.9' spec.add_development_dependency 'yard-doctest', '~> 0.1.6' end diff --git a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb index 673d80fc93..1ea00d7ba8 100644 --- a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb +++ b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb @@ -11,13 +11,13 @@ describe '#initialize' do it 'initializes' do - exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 56781) + exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) _(exporter).wont_be_nil end end describe '#export' do - let(:exporter) { OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 56781) } + let(:exporter) { OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) } before do OpenTelemetry.tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new @@ -30,30 +30,21 @@ end it 'exports a span_data' do - skip 'WIP' - socket = UDPSocket.new - socket.bind('127.0.0.1', 0) - exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: socket.addr[1]) + stub_request(:post, 'http://127.0.0.1:55681/v1/trace').to_return(status: 200) + exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) span_data = create_span_data result = exporter.export([span_data]) - packet = socket.recvfrom(65_000) - socket.close _(result).must_equal(SUCCESS) - _(packet).wont_be_nil end it 'exports a span from a tracer' do - skip 'WIP' - socket = UDPSocket.new - socket.bind('127.0.0.1', 0) - exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: socket.addr[1]) + stub_post = stub_request(:post, 'http://127.0.0.1:55681/v1/trace').to_return(status: 200) + exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter: exporter, max_queue_size: 1, max_export_batch_size: 1) OpenTelemetry.tracer_provider.add_span_processor(processor) OpenTelemetry.tracer_provider.tracer.start_root_span('foo').finish OpenTelemetry.tracer_provider.shutdown - packet = socket.recvfrom(65_000) - socket.close - _(packet).wont_be_nil + assert_requested(stub_post) end end diff --git a/exporters/otlp/test/test_helper.rb b/exporters/otlp/test/test_helper.rb index c5836d1418..1585ea1ad4 100644 --- a/exporters/otlp/test/test_helper.rb +++ b/exporters/otlp/test/test_helper.rb @@ -9,5 +9,6 @@ require 'opentelemetry/exporters/otlp' require 'minitest/autorun' +require 'webmock/minitest' OpenTelemetry.logger = Logger.new('/dev/null') From d0c549a046973969e400900d46354d132eb45bab Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 29 Jul 2020 20:17:13 -0400 Subject: [PATCH 05/10] Enhance tests --- .gitignore | 1 + api/lib/opentelemetry/trace/span.rb | 2 +- .../otlp/opentelemetry-exporters-otlp.gemspec | 2 + .../exporters/otlp/exporter_test.rb | 68 ++++++++++++++++++- exporters/otlp/test/test_helper.rb | 1 + 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f2086c0a3f..d7bb4694b1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ **/*.gem **/.bundle **/.DS_Store +**/.byebug_history # Appraisals instrumentation/**/*.gemfile.lock diff --git a/api/lib/opentelemetry/trace/span.rb b/api/lib/opentelemetry/trace/span.rb index 0a2845110c..f06116ee56 100644 --- a/api/lib/opentelemetry/trace/span.rb +++ b/api/lib/opentelemetry/trace/span.rb @@ -56,7 +56,7 @@ def recording? def set_attribute(key, value) self end - alias []= set_attribute + alias_method :[]=, :set_attribute # Add an Event to a {Span}. This can be accomplished eagerly or lazily. # Lazy evaluation is useful when the event attributes are expensive to diff --git a/exporters/otlp/opentelemetry-exporters-otlp.gemspec b/exporters/otlp/opentelemetry-exporters-otlp.gemspec index f55807ea22..e7c99b43ce 100644 --- a/exporters/otlp/opentelemetry-exporters-otlp.gemspec +++ b/exporters/otlp/opentelemetry-exporters-otlp.gemspec @@ -29,8 +29,10 @@ Gem::Specification.new do |spec| spec.add_dependency 'google-protobuf', '>= 3.4.1.1', '< 4' spec.add_development_dependency 'bundler', '>= 1.17' + spec.add_development_dependency 'byebug', '~> 9.0.6' spec.add_development_dependency 'faraday', '~> 0.13' spec.add_development_dependency 'minitest', '~> 5.0' + spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'rake', '~> 12.0' spec.add_development_dependency 'rubocop', '~> 0.73.0' spec.add_development_dependency 'simplecov', '~> 0.17' diff --git a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb index 1ea00d7ba8..6af5e916e8 100644 --- a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb +++ b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb @@ -20,7 +20,7 @@ let(:exporter) { OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) } before do - OpenTelemetry.tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new + OpenTelemetry.tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new(OpenTelemetry::SDK::Resources::Resource.telemetry_sdk) end it 'returns FAILURE when shutdown' do @@ -46,6 +46,72 @@ OpenTelemetry.tracer_provider.shutdown assert_requested(stub_post) end + + it 'translates all the things' do + stub_request(:post, 'http://127.0.0.1:55681/v1/trace').to_return(status: 200) + exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) + processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter: exporter) + tracer = OpenTelemetry.tracer_provider.tracer('tracer', 'v0.0.1') + other_tracer = OpenTelemetry.tracer_provider.tracer('other_tracer') + trace_id = OpenTelemetry::Trace.generate_trace_id + OpenTelemetry.tracer_provider.add_span_processor(processor) + OpenTelemetry::Trace.stub(:generate_trace_id, trace_id) do + root = tracer.start_root_span('root', kind: :internal) + tracer.in_span('child', with_parent: root, kind: :producer, links: [OpenTelemetry::Trace::Link.new(root.context, { 'attr' => 4 })]) do |span| + span['b'] = true + span['f'] = 1.1 + span['i'] = 2 + span['s'] = 'val' + span.status = OpenTelemetry::Trace::Status.new(OpenTelemetry::Trace::Status::UNKNOWN_ERROR) + tracer.in_span('client', kind: :client) do + other_tracer.in_span('server', kind: :server) do + span.add_event(name: 'event', attributes: { 'attr' => 42 }) + end + end + tracer.in_span('consumer', kind: :consumer) do + end + end + root.finish + end + OpenTelemetry.tracer_provider.shutdown + + encoded_etsr = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode( + Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new( + resource_spans: [ + Opentelemetry::Proto::Trace::V1::ResourceSpans.new( + resource: Opentelemetry::Proto::Resource::V1::Resource.new( + attributes: [ + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'telemetry.sdk.name', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: 'opentelemetry')), + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'telemetry.sdk.language', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: 'ruby')), + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'telemetry.sdk.version', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: "semver:#{OpenTelemetry::SDK::VERSION}")), + ], + ), + instrumentation_library_spans: [ + Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( + instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( + name: 'tracer', + version: 'v0.0.1', + ), + spans: [], + ), + Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( + instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( + name: 'other_tracer', + ), + spans: [], + ) + ] + ) + ] + ) + ) + + assert_requested(:post, "http://127.0.0.1:55681/v1/trace") + # assert_requested(:post, "http://127.0.0.1:55681/v1/trace") do |req| + # byebug + # req.body == encoded_etsr + # end + end end def create_span_data(name: '', kind: nil, status: nil, parent_span_id: OpenTelemetry::Trace::INVALID_SPAN_ID, child_count: 0, diff --git a/exporters/otlp/test/test_helper.rb b/exporters/otlp/test/test_helper.rb index 1585ea1ad4..06a26322cf 100644 --- a/exporters/otlp/test/test_helper.rb +++ b/exporters/otlp/test/test_helper.rb @@ -10,5 +10,6 @@ require 'opentelemetry/exporters/otlp' require 'minitest/autorun' require 'webmock/minitest' +require 'byebug' OpenTelemetry.logger = Logger.new('/dev/null') From 3d6c0a70b509e19109b6a91158475ae06fd90e74 Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 29 Jul 2020 21:51:33 -0400 Subject: [PATCH 06/10] Fix translation test --- api/lib/opentelemetry/trace/span.rb | 2 +- .../opentelemetry/exporters/otlp/exporter.rb | 2 +- .../exporters/otlp/exporter_test.rb | 135 ++++++++++++++---- sdk/lib/opentelemetry/sdk/trace/span.rb | 3 +- 4 files changed, 113 insertions(+), 29 deletions(-) diff --git a/api/lib/opentelemetry/trace/span.rb b/api/lib/opentelemetry/trace/span.rb index f06116ee56..0a2845110c 100644 --- a/api/lib/opentelemetry/trace/span.rb +++ b/api/lib/opentelemetry/trace/span.rb @@ -56,7 +56,7 @@ def recording? def set_attribute(key, value) self end - alias_method :[]=, :set_attribute + alias []= set_attribute # Add an Event to a {Span}. This can be accomplished eagerly or lazily. # Lazy evaluation is useful when the event attributes are expensive to diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb index d81c2fc49d..ead2a546fa 100644 --- a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb +++ b/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb @@ -208,7 +208,7 @@ def as_otlp_span(span_data) dropped_links_count: span_data.total_recorded_links - span_data.links&.size.to_i, status: span_data.status&.yield_self do |status| Opentelemetry::Proto::Trace::V1::Status.new( - code: status.canonical_code, # TODO: verify that the Integer canonical code can be used here instead of the enum. + code: status.canonical_code, message: status.description, ) end, diff --git a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb index 6af5e916e8..1fd606dcf3 100644 --- a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb +++ b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb @@ -53,26 +53,29 @@ processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter: exporter) tracer = OpenTelemetry.tracer_provider.tracer('tracer', 'v0.0.1') other_tracer = OpenTelemetry.tracer_provider.tracer('other_tracer') + trace_id = OpenTelemetry::Trace.generate_trace_id + root_span_id = OpenTelemetry::Trace.generate_span_id + child_span_id = OpenTelemetry::Trace.generate_span_id + client_span_id = OpenTelemetry::Trace.generate_span_id + server_span_id = OpenTelemetry::Trace.generate_span_id + consumer_span_id = OpenTelemetry::Trace.generate_span_id + start_timestamp = Time.now + end_timestamp = start_timestamp + 6 + OpenTelemetry.tracer_provider.add_span_processor(processor) - OpenTelemetry::Trace.stub(:generate_trace_id, trace_id) do - root = tracer.start_root_span('root', kind: :internal) - tracer.in_span('child', with_parent: root, kind: :producer, links: [OpenTelemetry::Trace::Link.new(root.context, { 'attr' => 4 })]) do |span| - span['b'] = true - span['f'] = 1.1 - span['i'] = 2 - span['s'] = 'val' - span.status = OpenTelemetry::Trace::Status.new(OpenTelemetry::Trace::Status::UNKNOWN_ERROR) - tracer.in_span('client', kind: :client) do - other_tracer.in_span('server', kind: :server) do - span.add_event(name: 'event', attributes: { 'attr' => 42 }) - end - end - tracer.in_span('consumer', kind: :consumer) do - end - end - root.finish - end + root = with_ids(trace_id, root_span_id) { tracer.start_root_span('root', kind: :internal, start_timestamp: start_timestamp).finish(end_timestamp: end_timestamp) } + span = with_ids(trace_id, child_span_id) { tracer.start_span('child', with_parent: root, kind: :producer, start_timestamp: start_timestamp + 1, links: [OpenTelemetry::Trace::Link.new(root.context, { 'attr' => 4 })]) } + span['b'] = true + span['f'] = 1.1 + span['i'] = 2 + span['s'] = 'val' + span.status = OpenTelemetry::Trace::Status.new(OpenTelemetry::Trace::Status::UNKNOWN_ERROR) + client = with_ids(trace_id, client_span_id) { tracer.start_span('client', with_parent: span, kind: :client, start_timestamp: start_timestamp + 2).finish(end_timestamp: end_timestamp) } + with_ids(trace_id, server_span_id) { other_tracer.start_span('server', with_parent: client, kind: :server, start_timestamp: start_timestamp + 3).finish(end_timestamp: end_timestamp) } + span.add_event(name: 'event', attributes: { 'attr' => 42 }, timestamp: start_timestamp + 4) + with_ids(trace_id, consumer_span_id) { tracer.start_span('consumer', with_parent: span, kind: :consumer, start_timestamp: start_timestamp + 5).finish(end_timestamp: end_timestamp) } + span.finish(end_timestamp: end_timestamp) OpenTelemetry.tracer_provider.shutdown encoded_etsr = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode( @@ -92,25 +95,105 @@ name: 'tracer', version: 'v0.0.1', ), - spans: [], + spans: [ + Opentelemetry::Proto::Trace::V1::Span.new( + trace_id: trace_id, + span_id: root_span_id, + parent_span_id: OpenTelemetry::Trace::INVALID_SPAN_ID, + name: 'root', + kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::INTERNAL, + start_time_unix_nano: (start_timestamp.to_r * 1_000_000_000).to_i, + end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + ), + Opentelemetry::Proto::Trace::V1::Span.new( + trace_id: trace_id, + span_id: client_span_id, + parent_span_id: child_span_id, + name: 'client', + kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::CLIENT, + start_time_unix_nano: ((start_timestamp + 2).to_r * 1_000_000_000).to_i, + end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + ), + Opentelemetry::Proto::Trace::V1::Span.new( + trace_id: trace_id, + span_id: consumer_span_id, + parent_span_id: child_span_id, + name: 'consumer', + kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::CONSUMER, + start_time_unix_nano: ((start_timestamp + 5).to_r * 1_000_000_000).to_i, + end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + ), + Opentelemetry::Proto::Trace::V1::Span.new( + trace_id: trace_id, + span_id: child_span_id, + parent_span_id: root_span_id, + name: 'child', + kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::PRODUCER, + start_time_unix_nano: ((start_timestamp + 1).to_r * 1_000_000_000).to_i, + end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i, + attributes: [ + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'b', value: Opentelemetry::Proto::Common::V1::AnyValue.new(bool_value: true)), + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'f', value: Opentelemetry::Proto::Common::V1::AnyValue.new(double_value: 1.1)), + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'i', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 2)), + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 's', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: 'val')), + ], + events: [ + Opentelemetry::Proto::Trace::V1::Span::Event.new( + time_unix_nano: ((start_timestamp + 4).to_r * 1_000_000_000).to_i, + name: 'event', + attributes: [ + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 42)), + ], + ), + ], + links: [ + Opentelemetry::Proto::Trace::V1::Span::Link.new( + trace_id: trace_id, + span_id: root_span_id, + attributes: [ + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 4)), + ], + ) + ], + status: Opentelemetry::Proto::Trace::V1::Status.new( + code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::UnknownError, + ), + ), + ], ), Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( name: 'other_tracer', ), - spans: [], - ) + spans: [ + Opentelemetry::Proto::Trace::V1::Span.new( + trace_id: trace_id, + span_id: server_span_id, + parent_span_id: client_span_id, + name: 'server', + kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::SERVER, + start_time_unix_nano: ((start_timestamp + 3).to_r * 1_000_000_000).to_i, + end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + ) + ], + ), ] ) ] ) ) - assert_requested(:post, "http://127.0.0.1:55681/v1/trace") - # assert_requested(:post, "http://127.0.0.1:55681/v1/trace") do |req| - # byebug - # req.body == encoded_etsr - # end + assert_requested(:post, "http://127.0.0.1:55681/v1/trace") do |req| + req.body == encoded_etsr + end + end + end + + def with_ids(trace_id, span_id) + OpenTelemetry::Trace.stub(:generate_trace_id, trace_id) do + OpenTelemetry::Trace.stub(:generate_span_id, span_id) do + yield + end end end diff --git a/sdk/lib/opentelemetry/sdk/trace/span.rb b/sdk/lib/opentelemetry/sdk/trace/span.rb index 6b65593d92..99a6a10648 100644 --- a/sdk/lib/opentelemetry/sdk/trace/span.rb +++ b/sdk/lib/opentelemetry/sdk/trace/span.rb @@ -21,7 +21,7 @@ class Span < OpenTelemetry::Trace::Span attr_reader :name, :status, :kind, :parent_span_id, :start_timestamp, :end_timestamp, :links, :library_resource, :instrumentation_library # Return a frozen copy of the current attributes. This is intended for - # use of SpanProcesses and should not be considered part of the public + # use of SpanProcessors and should not be considered part of the public # interface for instrumentation. # # @return [Hash{String => String, Numeric, Boolean, Array}] may be nil. @@ -76,6 +76,7 @@ def set_attribute(key, value) end self end + alias []= set_attribute # Add an Event to a {Span}. This can be accomplished eagerly or lazily. # Lazy evaluation is useful when the event attributes are expensive to From 875abed608bd33ef8ba52d765d643c8d2c12c2c9 Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 29 Jul 2020 22:08:03 -0400 Subject: [PATCH 07/10] Add integration test --- .../test/opentelemetry/exporters/otlp/exporter_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb index 1fd606dcf3..7feca6b08e 100644 --- a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb +++ b/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb @@ -23,6 +23,14 @@ OpenTelemetry.tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new(OpenTelemetry::SDK::Resources::Resource.telemetry_sdk) end + it 'integrates with collector' do + skip unless ENV['TRACING_INTEGRATION_TEST'] + WebMock.disable_net_connect!(allow: '127.0.0.1') + span_data = create_span_data + result = exporter.export([span_data]) + _(result).must_equal(SUCCESS) + end + it 'returns FAILURE when shutdown' do exporter.shutdown result = exporter.export(nil) From e8ac9af0a4f5fdb48340ae45876e586fafcf926a Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Wed, 2 Sep 2020 20:59:04 -0400 Subject: [PATCH 08/10] Merge branch 'master' into otlp-exporter --- .circleci/config.yml | 6 + Rakefile | 6 +- ci/rake_test.sh | 8 + exporter/otlp/.rubocop.yml | 21 +++ exporter/otlp/CHANGELOG.md | 1 + {exporters => exporter}/otlp/Gemfile | 0 {exporters => exporter}/otlp/LICENSE | 0 {exporters => exporter}/otlp/README.md | 22 ++- {exporters => exporter}/otlp/Rakefile | 4 +- .../otlp/lib/opentelemetry-exporter-otlp.rb | 2 +- .../otlp/lib/opentelemetry/exporter}/otlp.rb | 4 +- .../opentelemetry/exporter}/otlp/exporter.rb | 162 ++++++++++------- .../opentelemetry/exporter}/otlp/version.rb | 2 +- .../collector/trace/v1/trace_service_pb.rb | 0 .../proto/common/v1/common_pb.rb | 0 .../proto/resource/v1/resource_pb.rb | 0 .../opentelemetry/proto/trace/v1/trace_pb.rb | 0 .../otlp/opentelemetry-exporter-otlp.gemspec | 10 +- .../otlp/test/.rubocop.yml | 0 .../exporter}/otlp/exporter_test.rb | 168 ++++++++++++++---- exporter/otlp/test/test_helper.rb | 24 +++ exporters/otlp/CHANGELOG.md | 1 - exporters/otlp/test/test_helper.rb | 15 -- 23 files changed, 318 insertions(+), 138 deletions(-) create mode 100644 exporter/otlp/.rubocop.yml create mode 100644 exporter/otlp/CHANGELOG.md rename {exporters => exporter}/otlp/Gemfile (100%) rename {exporters => exporter}/otlp/LICENSE (100%) rename {exporters => exporter}/otlp/README.md (68%) rename {exporters => exporter}/otlp/Rakefile (94%) rename exporters/otlp/lib/opentelemetry-exporters-otlp.rb => exporter/otlp/lib/opentelemetry-exporter-otlp.rb (73%) rename {exporters/otlp/lib/opentelemetry/exporters => exporter/otlp/lib/opentelemetry/exporter}/otlp.rb (83%) rename {exporters/otlp/lib/opentelemetry/exporters => exporter/otlp/lib/opentelemetry/exporter}/otlp/exporter.rb (59%) rename {exporters/otlp/lib/opentelemetry/exporters => exporter/otlp/lib/opentelemetry/exporter}/otlp/version.rb (92%) rename {exporters => exporter}/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb (100%) rename {exporters => exporter}/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb (100%) rename {exporters => exporter}/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb (100%) rename {exporters => exporter}/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb (100%) rename exporters/otlp/opentelemetry-exporters-otlp.gemspec => exporter/otlp/opentelemetry-exporter-otlp.gemspec (84%) rename {exporters => exporter}/otlp/test/.rubocop.yml (100%) rename {exporters/otlp/test/opentelemetry/exporters => exporter/otlp/test/opentelemetry/exporter}/otlp/exporter_test.rb (61%) create mode 100644 exporter/otlp/test/test_helper.rb delete mode 100644 exporters/otlp/CHANGELOG.md delete mode 100644 exporters/otlp/test/test_helper.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index af37858778..c1af0807c2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,6 +58,9 @@ commands: - run: name: Bundle (Jaeger) command: "cd exporter/jaeger && gem install --no-document bundler && bundle install --jobs=3 --retry=3" + - run: + name: Bundle (OTLP) + command: "cd exporter/otlp && gem install --no-document bundler && bundle install --jobs=3 --retry=3" - run: name: CI (API) command: "cd api && bundle exec rake" @@ -67,6 +70,9 @@ commands: - run: name: CI (Jaeger) command: "cd exporter/jaeger && bundle exec rake" + - run: + name: CI (OTLP) + command: "cd exporter/otlp && bundle exec rake" rake-test-appraisal: steps: - run: diff --git a/Rakefile b/Rakefile index a27b9ee283..536d41c567 100644 --- a/Rakefile +++ b/Rakefile @@ -57,10 +57,10 @@ GEM_INFO = { OpenTelemetry::Exporter::Jaeger::VERSION } }, - "opentelemetry-exporters-otlp" => { + "opentelemetry-exporter-otlp" => { version_getter: ->() { - require './lib/opentelemetry/exporters/otlp/version.rb' - OpenTelemetry::Exporters::OTLP::VERSION + require './lib/opentelemetry/exporter/otlp/version.rb' + OpenTelemetry::Exporter::OTLP::VERSION } }, "opentelemetry-resource_detectors" => { diff --git a/ci/rake_test.sh b/ci/rake_test.sh index 47f2659b53..5c16b210fa 100755 --- a/ci/rake_test.sh +++ b/ci/rake_test.sh @@ -16,6 +16,10 @@ printf "\nname: Bundle (Jaeger) \n" cd exporter/jaeger && gem install --no-document bundler && bundle install --jobs=3 --retry=3 cd $root +printf "\nname: Bundle (OTLP) \n" +cd exporter/otlp && gem install --no-document bundler && bundle install --jobs=3 --retry=3 +cd $root + printf "\nname: CI (API) \n" cd api && bundle exec rake cd $root @@ -27,3 +31,7 @@ cd $root printf "\nname: CI (Jaeger) \n" cd exporter/jaeger && bundle exec rake cd $root + +printf "\nname: CI (OTLP) \n" +cd exporter/otlp && bundle exec rake +cd $root diff --git a/exporter/otlp/.rubocop.yml b/exporter/otlp/.rubocop.yml new file mode 100644 index 0000000000..0441b05385 --- /dev/null +++ b/exporter/otlp/.rubocop.yml @@ -0,0 +1,21 @@ +AllCops: + TargetRubyVersion: "2.5.0" + Exclude: + - "lib/opentelemetry/proto/**/*" + - "vendor/**/*" + +Lint/UnusedMethodArgument: + Enabled: false +Metrics/AbcSize: + Max: 18 +Metrics/LineLength: + Enabled: false +Metrics/MethodLength: + Max: 21 +Metrics/ParameterLists: + Enabled: false +Naming/FileName: + Exclude: + - "lib/opentelemetry-exporter-otlp.rb" +Style/ModuleFunction: + Enabled: false diff --git a/exporter/otlp/CHANGELOG.md b/exporter/otlp/CHANGELOG.md new file mode 100644 index 0000000000..5768479c8c --- /dev/null +++ b/exporter/otlp/CHANGELOG.md @@ -0,0 +1 @@ +# Release History: opentelemetry-exporter-otlp diff --git a/exporters/otlp/Gemfile b/exporter/otlp/Gemfile similarity index 100% rename from exporters/otlp/Gemfile rename to exporter/otlp/Gemfile diff --git a/exporters/otlp/LICENSE b/exporter/otlp/LICENSE similarity index 100% rename from exporters/otlp/LICENSE rename to exporter/otlp/LICENSE diff --git a/exporters/otlp/README.md b/exporter/otlp/README.md similarity index 68% rename from exporters/otlp/README.md rename to exporter/otlp/README.md index 39d1b71651..7ac2ab4108 100644 --- a/exporters/otlp/README.md +++ b/exporter/otlp/README.md @@ -1,6 +1,6 @@ -# opentelemetry-exporters-otlp +# opentelemetry-exporter-otlp -The `opentelemetry-exporters-otlp` gem provides an OTLP exporter for OpenTelemetry for Ruby. Using `opentelemetry-exporters-otlp`, an application can configure OpenTelemetry to export collected tracing data to [the OpenTelemetry Collector][opentelemetry-collector-home]. +The `opentelemetry-exporter-otlp` gem provides an [OTLP](https://github.com/open-telemetry/opentelemetry-proto) exporter for OpenTelemetry for Ruby. Using `opentelemetry-exporter-otlp`, an application can configure OpenTelemetry to export collected tracing data to [the OpenTelemetry Collector][opentelemetry-collector-home]. ## What is OpenTelemetry? @@ -10,9 +10,13 @@ OpenTelemetry provides a single set of APIs, libraries, agents, and collector se ## How does this gem fit in? -The `opentelemetry-exporters-otlp` gem is a plugin that provides OTLP export. To export to the OpenTelemetry Collector, an application can include this gem along with `opentelemetry-sdk`, and configure the `SDK` to use the provided OTLP exporter as a span processor. +The `opentelemetry-exporter-otlp` gem is a plugin that provides OTLP export. To export to the OpenTelemetry Collector, an application can include this gem along with `opentelemetry-sdk`, and configure the `SDK` to use the provided OTLP exporter as a span processor. -Generally, *libraries* that produce telemetry data should avoid depending directly on specific exporters, deferring that choice to the application developer. +Generally, *libraries* that produce telemetry data should avoid depending directly on specific exporter, deferring that choice to the application developer. + +### Supported protocol version + +This gem supports the [v0.4.0 release](https://github.com/open-telemetry/opentelemetry-proto/releases/tag/v0.4.0) of OTLP. ## How do I get started? @@ -20,7 +24,7 @@ Install the gem using: ``` gem install opentelemetry-sdk -gem install opentelemetry-exporters-otlp +gem install opentelemetry-exporter-otlp ``` Or, if you use [bundler][bundler-home], include `opentelemetry-sdk` in your `Gemfile`. @@ -29,13 +33,13 @@ Then, configure the SDK to use the OTLP exporter as a span processor, and use th ```ruby require 'opentelemetry/sdk' -require 'opentelemetry/exporters/otlp' +require 'opentelemetry/exporter/otlp' # Configure the sdk with custom export OpenTelemetry::SDK.configure do |c| c.add_span_processor( OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new( - OpenTelemetry::Exporters::OTLP::Exporter.new( + OpenTelemetry::Exporter::OTLP::Exporter.new( host: 'localhost', port: 55680 ) ) @@ -63,13 +67,13 @@ For additional examples, see the [examples on github][examples-github]. ## How can I get involved? -The `opentelemetry-exporters-otlp` gem source is [on github][repo-github], along with related gems including `opentelemetry-sdk`. +The `opentelemetry-exporter-otlp` gem source is [on github][repo-github], along with related gems including `opentelemetry-sdk`. 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-exporters-otlp` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. +The `opentelemetry-exporter-otlp` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. [opentelemetry-collector-home]: https://opentelemetry.io/docs/collector/about/ diff --git a/exporters/otlp/Rakefile b/exporter/otlp/Rakefile similarity index 94% rename from exporters/otlp/Rakefile rename to exporter/otlp/Rakefile index 2e6dcbbd83..511f7665d9 100644 --- a/exporters/otlp/Rakefile +++ b/exporter/otlp/Rakefile @@ -33,8 +33,8 @@ PROTOBUF_FILES = [ 'common/v1/common.proto', 'resource/v1/resource.proto', 'trace/v1/trace.proto', - 'collector/trace/v1/trace_service.proto', -] + 'collector/trace/v1/trace_service.proto' +].freeze task :update_protobuf do system('git clone https://github.com/open-telemetry/opentelemetry-proto') diff --git a/exporters/otlp/lib/opentelemetry-exporters-otlp.rb b/exporter/otlp/lib/opentelemetry-exporter-otlp.rb similarity index 73% rename from exporters/otlp/lib/opentelemetry-exporters-otlp.rb rename to exporter/otlp/lib/opentelemetry-exporter-otlp.rb index 52434496ad..5c97153c35 100644 --- a/exporters/otlp/lib/opentelemetry-exporters-otlp.rb +++ b/exporter/otlp/lib/opentelemetry-exporter-otlp.rb @@ -4,4 +4,4 @@ # # SPDX-License-Identifier: Apache-2.0 -require 'opentelemetry/exporters/otlp' +require 'opentelemetry/exporter/otlp' diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp.rb b/exporter/otlp/lib/opentelemetry/exporter/otlp.rb similarity index 83% rename from exporters/otlp/lib/opentelemetry/exporters/otlp.rb rename to exporter/otlp/lib/opentelemetry/exporter/otlp.rb index fb23165f1f..58f4864621 100644 --- a/exporters/otlp/lib/opentelemetry/exporters/otlp.rb +++ b/exporter/otlp/lib/opentelemetry/exporter/otlp.rb @@ -4,8 +4,8 @@ # # SPDX-License-Identifier: Apache-2.0 -require 'opentelemetry/exporters/otlp/exporter' -require 'opentelemetry/exporters/otlp/version' +require 'opentelemetry/exporter/otlp/exporter' +require 'opentelemetry/exporter/otlp/version' # OpenTelemetry is an open source observability framework, providing a # general-purpose API, SDK, and related tools required for the instrumentation diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb b/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb similarity index 59% rename from exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb rename to exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb index ead2a546fa..c1c78b57f8 100644 --- a/exporters/otlp/lib/opentelemetry/exporters/otlp/exporter.rb +++ b/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb @@ -6,6 +6,7 @@ require 'opentelemetry/sdk' require 'net/http' +require 'csv' require 'opentelemetry/proto/common/v1/common_pb' require 'opentelemetry/proto/resource/v1/resource_pb' @@ -13,10 +14,10 @@ require 'opentelemetry/proto/collector/trace/v1/trace_service_pb' module OpenTelemetry - module Exporters + module Exporter module OTLP # An OpenTelemetry trace exporter that sends spans over HTTP as Protobuf encoded OTLP ExportTraceServiceRequests. - class Exporter + class Exporter # rubocop:disable Metrics/ClassLength SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE private_constant(:SUCCESS, :FAILURE) @@ -26,25 +27,32 @@ class Exporter OPEN_TIMEOUT = 5 READ_TIMEOUT = 5 RETRY_COUNT = 5 - PATH = '/v1/trace' - private_constant(:KEEP_ALIVE_TIMEOUT, :OPEN_TIMEOUT, :READ_TIMEOUT, :RETRY_COUNT, :PATH) - - def initialize(host:, - port:, - path: PATH, - use_ssl: false, - keep_alive_timeout: KEEP_ALIVE_TIMEOUT, - open_timeout: OPEN_TIMEOUT, - read_timeout: READ_TIMEOUT, - retry_count: RETRY_COUNT) - @http = Net::HTTP.new(host, port) - @http.use_ssl = use_ssl - @http.keep_alive_timeout = keep_alive_timeout - @http.open_timeout = open_timeout - @http.read_timeout = read_timeout - - @path = path - @max_retry_count = retry_count + private_constant(:KEEP_ALIVE_TIMEOUT, :OPEN_TIMEOUT, :READ_TIMEOUT, :RETRY_COUNT) + + def initialize(endpoint: config_opt('OTEL_EXPORTER_OTLP_SPAN_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'localhost:55681/v1/trace'), # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity + insecure: config_opt('OTEL_EXPORTER_OTLP_SPAN_INSECURE', 'OTEL_EXPORTER_OTLP_INSECURE', default: false), + certificate_file: config_opt('OTEL_EXPORTER_OTLP_SPAN_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'), + headers: config_opt('OTEL_EXPORTER_OTLP_SPAN_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS'), # TODO: what format is expected here? + compression: config_opt('OTEL_EXPORTER_OTLP_SPAN_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION'), + timeout: config_opt('OTEL_EXPORTER_OTLP_SPAN_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10)) + raise ArgumentError, "invalid url for OTLP::Exporter #{endpoint}" if invalid_url?("http://#{endpoint}") + raise ArgumentError, "unsupported compression key #{compression}" unless compression.nil? + raise ArgumentError, 'headers must be comma-separated k:v pairs or a Hash' unless valid_headers?(headers) + + uri = URI "http://#{endpoint}" + @http = Net::HTTP.new(uri.host, uri.port) + @http.use_ssl = insecure.to_s.downcase == 'false' + @http.ca_file = certificate_file unless certificate_file.nil? + @http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT + @http.open_timeout = OPEN_TIMEOUT + @http.read_timeout = READ_TIMEOUT + + @path = uri.path + @headers = case headers + when String then CSV.parse(headers, col_sep: ':', row_sep: ',').to_h + when Hash then headers + end + @timeout = timeout.to_f # TODO: use this as a default timeout when we implement timeouts in https://github.com/open-telemetry/opentelemetry-ruby/pull/341 @tracer = OpenTelemetry.tracer_provider.tracer @shutdown = false @@ -72,15 +80,43 @@ def shutdown private - def send_bytes(bytes) + def config_opt(*env_vars, default: nil) + env_vars.each do |env_var| + val = ENV[env_var] + return val unless val.nil? + end + default + end + + def valid_headers?(headers) + return true if headers.nil? || headers.is_a?(Hash) + return false unless headers.is_a?(String) + + CSV.parse(headers, col_sep: ':', row_sep: ',').to_h + true + rescue ArgumentError + false + end + + def invalid_url?(url) + return true if url.nil? || url.strip.empty? + + uri = URI(url) + uri.path.nil? || uri.path.empty? + rescue URI::InvalidURIError + true + end + + def send_bytes(bytes) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity retry_count = 0 - untraced do + untraced do # rubocop:disable Metrics/BlockLength request = Net::HTTP::Post.new(@path) request.body = bytes request.add_field('Content-Type', 'application/x-protobuf') + @headers&.each { |key, value| request.add_field(key, value) } # TODO: enable gzip when https://github.com/open-telemetry/opentelemetry-collector/issues/1344 is fixed. # request.add_field('Content-Encoding', 'gzip') - + @http.start unless @http.started? response = @http.request(request) @@ -116,9 +152,6 @@ def send_bytes(bytes) def handle_redirect(location) # TODO: figure out destination and reinitialize @http and @path - location = response['location'] - location = URI(location).relative? ? (@uri + location).to_s : location - initialize_http(location) # Assume this is our new destination from now on. end def untraced @@ -126,7 +159,7 @@ def untraced end def backoff?(retry_after: nil, retry_count:, reason:) - return false if retry_count > @max_retry_count + return false if retry_count > RETRY_COUNT sleep_interval = nil unless retry_after.nil? @@ -139,7 +172,7 @@ def backoff?(retry_after: nil, retry_count:, reason:) sleep_interval ||= begin Time.httpdate(retry_after) - Time.now - rescue + rescue # rubocop:disable Style/RescueStandardError nil end sleep_interval = nil unless sleep_interval&.positive? @@ -150,32 +183,34 @@ def backoff?(retry_after: nil, retry_count:, reason:) true end - def encode(span_data) + def encode(span_data) # rubocop:disable Metrics/MethodLength Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.encode( Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.new( - resource_spans: [ - Opentelemetry::Proto::Trace::V1::ResourceSpans.new( - resource: Opentelemetry::Proto::Resource::V1::Resource.new( - attributes: OpenTelemetry.tracer_provider.resource.label_enumerator.map { |key, value| as_otlp_key_value(key, value) }, - ), - instrumentation_library_spans: span_data - .group_by { |sd| sd.instrumentation_library } - .map do |il, sds| - Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( - instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( - name: il.name, - version: il.version, - ), - spans: sds.map { |sd| as_otlp_span(sd) }, - ) - end, - ) - ] + resource_spans: span_data + .group_by(&:resource) + .map do |resource, span_datas| + Opentelemetry::Proto::Trace::V1::ResourceSpans.new( + resource: Opentelemetry::Proto::Resource::V1::Resource.new( + attributes: resource.label_enumerator.map { |key, value| as_otlp_key_value(key, value) } + ), + instrumentation_library_spans: span_datas + .group_by(&:instrumentation_library) + .map do |il, sds| + Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( + instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( + name: il.name, + version: il.version + ), + spans: sds.map { |sd| as_otlp_span(sd) } + ) + end + ) + end ) ) end - def as_otlp_span(span_data) + def as_otlp_span(span_data) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength Opentelemetry::Proto::Trace::V1::Span.new( trace_id: span_data.trace_id, span_id: span_data.span_id, @@ -191,7 +226,7 @@ def as_otlp_span(span_data) Opentelemetry::Proto::Trace::V1::Span::Event.new( time_unix_nano: as_otlp_timestamp(event.timestamp), name: event.name, - attributes: event.attributes&.map { |k, v| as_otlp_key_value(k, v) }, + attributes: event.attributes&.map { |k, v| as_otlp_key_value(k, v) } # TODO: track dropped_attributes_count in Span#append_event ) end, @@ -201,7 +236,7 @@ def as_otlp_span(span_data) trace_id: link.context.trace_id, span_id: link.context.span_id, trace_state: link.context.tracestate, - attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) }, + attributes: link.attributes&.map { |k, v| as_otlp_key_value(k, v) } # TODO: track dropped_attributes_count in Span#trim_links ) end, @@ -209,9 +244,9 @@ def as_otlp_span(span_data) status: span_data.status&.yield_self do |status| Opentelemetry::Proto::Trace::V1::Status.new( code: status.canonical_code, - message: status.description, + message: status.description ) - end, + end ) end @@ -231,20 +266,25 @@ def as_otlp_span_kind(kind) end def as_otlp_key_value(key, value) - kv = Opentelemetry::Proto::Common::V1::KeyValue.new(key: key) - kv.value = Opentelemetry::Proto::Common::V1::AnyValue.new + Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value(value)) + end + + def as_otlp_any_value(value) + result = Opentelemetry::Proto::Common::V1::AnyValue.new case value when String - kv.value.string_value = value + result.string_value = value when Integer - kv.value.int_value = value + result.int_value = value when Float - kv.value.double_value = value + result.double_value = value when true, false - kv.value.bool_value = value - # TODO: when Array + result.bool_value = value + when Array + values = value.map { |element| as_otlp_any_value(element) } + result.array_value = Opentelemetry::Proto::Common::V1::ArrayValue.new(values: values) end - kv + result end end end diff --git a/exporters/otlp/lib/opentelemetry/exporters/otlp/version.rb b/exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb similarity index 92% rename from exporters/otlp/lib/opentelemetry/exporters/otlp/version.rb rename to exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb index ca725aed74..286f6e2c40 100644 --- a/exporters/otlp/lib/opentelemetry/exporters/otlp/version.rb +++ b/exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb @@ -5,7 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 module OpenTelemetry - module Exporters + module Exporter module OTLP ## Current OpenTelemetry OTLP exporter version VERSION = '0.5.0' diff --git a/exporters/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb b/exporter/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb similarity index 100% rename from exporters/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb rename to exporter/otlp/lib/opentelemetry/proto/collector/trace/v1/trace_service_pb.rb diff --git a/exporters/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb b/exporter/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb similarity index 100% rename from exporters/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb rename to exporter/otlp/lib/opentelemetry/proto/common/v1/common_pb.rb diff --git a/exporters/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb b/exporter/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb similarity index 100% rename from exporters/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb rename to exporter/otlp/lib/opentelemetry/proto/resource/v1/resource_pb.rb diff --git a/exporters/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb b/exporter/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb similarity index 100% rename from exporters/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb rename to exporter/otlp/lib/opentelemetry/proto/trace/v1/trace_pb.rb diff --git a/exporters/otlp/opentelemetry-exporters-otlp.gemspec b/exporter/otlp/opentelemetry-exporter-otlp.gemspec similarity index 84% rename from exporters/otlp/opentelemetry-exporters-otlp.gemspec rename to exporter/otlp/opentelemetry-exporter-otlp.gemspec index e7c99b43ce..b0a26fa98c 100644 --- a/exporters/otlp/opentelemetry-exporters-otlp.gemspec +++ b/exporter/otlp/opentelemetry-exporter-otlp.gemspec @@ -6,11 +6,11 @@ lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'opentelemetry/exporters/otlp/version' +require 'opentelemetry/exporter/otlp/version' Gem::Specification.new do |spec| - spec.name = 'opentelemetry-exporters-otlp' - spec.version = OpenTelemetry::Exporters::OTLP::VERSION + spec.name = 'opentelemetry-exporter-otlp' + spec.version = OpenTelemetry::Exporter::OTLP::VERSION spec.authors = ['OpenTelemetry Authors'] spec.email = ['cncf-opentelemetry-contributors@lists.cncf.io'] @@ -25,14 +25,12 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.5.0' - spec.add_dependency 'opentelemetry-api', '~> 0.5.0' spec.add_dependency 'google-protobuf', '>= 3.4.1.1', '< 4' + spec.add_dependency 'opentelemetry-api', '~> 0.5.0' spec.add_development_dependency 'bundler', '>= 1.17' - spec.add_development_dependency 'byebug', '~> 9.0.6' spec.add_development_dependency 'faraday', '~> 0.13' spec.add_development_dependency 'minitest', '~> 5.0' - spec.add_development_dependency 'pry-byebug' spec.add_development_dependency 'rake', '~> 12.0' spec.add_development_dependency 'rubocop', '~> 0.73.0' spec.add_development_dependency 'simplecov', '~> 0.17' diff --git a/exporters/otlp/test/.rubocop.yml b/exporter/otlp/test/.rubocop.yml similarity index 100% rename from exporters/otlp/test/.rubocop.yml rename to exporter/otlp/test/.rubocop.yml diff --git a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb b/exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb similarity index 61% rename from exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb rename to exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb index 7feca6b08e..38c4124370 100644 --- a/exporters/otlp/test/opentelemetry/exporters/otlp/exporter_test.rb +++ b/exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb @@ -5,19 +5,85 @@ # SPDX-License-Identifier: Apache-2.0 require 'test_helper' -describe OpenTelemetry::Exporters::OTLP::Exporter do +describe OpenTelemetry::Exporter::OTLP::Exporter do SUCCESS = OpenTelemetry::SDK::Trace::Export::SUCCESS FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE describe '#initialize' do - it 'initializes' do - exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) - _(exporter).wont_be_nil + it 'initializes with defaults' do + exp = OpenTelemetry::Exporter::OTLP::Exporter.new + _(exp).wont_be_nil + _(exp.instance_variable_get(:@headers)).must_be_nil + _(exp.instance_variable_get(:@timeout)).must_equal 10.0 + _(exp.instance_variable_get(:@path)).must_equal '/v1/trace' + http = exp.instance_variable_get(:@http) + _(http.ca_file).must_be_nil + _(http.use_ssl?).must_equal true + _(http.address).must_equal 'localhost' + _(http.port).must_equal 55_681 + end + + it 'refuses invalid headers' do + assert_raises ArgumentError do + OpenTelemetry::Exporter::OTLP::Exporter.new(headers: 'a:b,c') + end + end + + it 'refuses invalid endpoint' do + assert_raises ArgumentError do + OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'not a url') + end + end + + it 'refuses compression' do + assert_raises ArgumentError do + OpenTelemetry::Exporter::OTLP::Exporter.new(compression: 'gzip') + end + end + + it 'sets parameters from the environment' do + exp = with_env('OTEL_EXPORTER_OTLP_ENDPOINT' => 'localhost:1234/v2/trace', + 'OTEL_EXPORTER_OTLP_INSECURE' => 'true', + 'OTEL_EXPORTER_OTLP_CERTIFICATE' => '/foo/bar', + 'OTEL_EXPORTER_OTLP_HEADERS' => 'a:b,c:d', + 'OTEL_EXPORTER_OTLP_TIMEOUT' => '11') do + OpenTelemetry::Exporter::OTLP::Exporter.new + end + _(exp.instance_variable_get(:@headers)).must_equal('a' => 'b', 'c' => 'd') + _(exp.instance_variable_get(:@timeout)).must_equal 11.0 + _(exp.instance_variable_get(:@path)).must_equal '/v2/trace' + http = exp.instance_variable_get(:@http) + _(http.ca_file).must_equal '/foo/bar' + _(http.use_ssl?).must_equal false + _(http.address).must_equal 'localhost' + _(http.port).must_equal 1234 + end + + it 'prefers explicit parameters rather than the environment' do + exp = with_env('OTEL_EXPORTER_OTLP_ENDPOINT' => 'localhost:1234/v2/trace', + 'OTEL_EXPORTER_OTLP_INSECURE' => 'true', + 'OTEL_EXPORTER_OTLP_CERTIFICATE' => '/foo/bar', + 'OTEL_EXPORTER_OTLP_HEADERS' => 'a:b,c:d', + 'OTEL_EXPORTER_OTLP_TIMEOUT' => '11') do + OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'localhost:4321/v3/trace', + insecure: 'false', + certificate_file: '/baz', + headers: { 'x' => 'y' }, + timeout: 12) + end + _(exp.instance_variable_get(:@headers)).must_equal('x' => 'y') + _(exp.instance_variable_get(:@timeout)).must_equal 12.0 + _(exp.instance_variable_get(:@path)).must_equal '/v3/trace' + http = exp.instance_variable_get(:@http) + _(http.ca_file).must_equal '/baz' + _(http.use_ssl?).must_equal true + _(http.address).must_equal 'localhost' + _(http.port).must_equal 4321 end end describe '#export' do - let(:exporter) { OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) } + let(:exporter) { OpenTelemetry::Exporter::OTLP::Exporter.new } before do OpenTelemetry.tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new(OpenTelemetry::SDK::Resources::Resource.telemetry_sdk) @@ -25,8 +91,9 @@ it 'integrates with collector' do skip unless ENV['TRACING_INTEGRATION_TEST'] - WebMock.disable_net_connect!(allow: '127.0.0.1') + WebMock.disable_net_connect!(allow: 'localhost') span_data = create_span_data + exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(insecure: true) result = exporter.export([span_data]) _(result).must_equal(SUCCESS) end @@ -38,16 +105,14 @@ end it 'exports a span_data' do - stub_request(:post, 'http://127.0.0.1:55681/v1/trace').to_return(status: 200) - exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) + stub_request(:post, 'https://localhost:55681/v1/trace').to_return(status: 200) span_data = create_span_data result = exporter.export([span_data]) _(result).must_equal(SUCCESS) end it 'exports a span from a tracer' do - stub_post = stub_request(:post, 'http://127.0.0.1:55681/v1/trace').to_return(status: 200) - exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) + stub_post = stub_request(:post, 'https://localhost:55681/v1/trace').to_return(status: 200) processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter: exporter, max_queue_size: 1, max_export_batch_size: 1) OpenTelemetry.tracer_provider.add_span_processor(processor) OpenTelemetry.tracer_provider.tracer.start_root_span('foo').finish @@ -55,9 +120,25 @@ assert_requested(stub_post) end + it 'batches per resource' do + etsr = nil + stub_post = stub_request(:post, 'https://localhost:55681/v1/trace').to_return do |request| + etsr = Opentelemetry::Proto::Collector::Trace::V1::ExportTraceServiceRequest.decode(request.body) + { status: 200 } + end + + span_data1 = create_span_data(resource: OpenTelemetry::SDK::Resources::Resource.create('k1' => 'v1')) + span_data2 = create_span_data(resource: OpenTelemetry::SDK::Resources::Resource.create('k2' => 'v2')) + + result = exporter.export([span_data1, span_data2]) + + _(result).must_equal(SUCCESS) + assert_requested(stub_post) + _(etsr.resource_spans.length).must_equal(2) + end + it 'translates all the things' do - stub_request(:post, 'http://127.0.0.1:55681/v1/trace').to_return(status: 200) - exporter = OpenTelemetry::Exporters::OTLP::Exporter.new(host: '127.0.0.1', port: 55681) + stub_request(:post, 'https://localhost:55681/v1/trace').to_return(status: 200) processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter: exporter) tracer = OpenTelemetry.tracer_provider.tracer('tracer', 'v0.0.1') other_tracer = OpenTelemetry.tracer_provider.tracer('other_tracer') @@ -73,15 +154,16 @@ OpenTelemetry.tracer_provider.add_span_processor(processor) root = with_ids(trace_id, root_span_id) { tracer.start_root_span('root', kind: :internal, start_timestamp: start_timestamp).finish(end_timestamp: end_timestamp) } - span = with_ids(trace_id, child_span_id) { tracer.start_span('child', with_parent: root, kind: :producer, start_timestamp: start_timestamp + 1, links: [OpenTelemetry::Trace::Link.new(root.context, { 'attr' => 4 })]) } + span = with_ids(trace_id, child_span_id) { tracer.start_span('child', with_parent: root, kind: :producer, start_timestamp: start_timestamp + 1, links: [OpenTelemetry::Trace::Link.new(root.context, 'attr' => 4)]) } span['b'] = true span['f'] = 1.1 span['i'] = 2 span['s'] = 'val' + span['a'] = [3, 4] span.status = OpenTelemetry::Trace::Status.new(OpenTelemetry::Trace::Status::UNKNOWN_ERROR) client = with_ids(trace_id, client_span_id) { tracer.start_span('client', with_parent: span, kind: :client, start_timestamp: start_timestamp + 2).finish(end_timestamp: end_timestamp) } with_ids(trace_id, server_span_id) { other_tracer.start_span('server', with_parent: client, kind: :server, start_timestamp: start_timestamp + 3).finish(end_timestamp: end_timestamp) } - span.add_event(name: 'event', attributes: { 'attr' => 42 }, timestamp: start_timestamp + 4) + span.add_event('event', attributes: { 'attr' => 42 }, timestamp: start_timestamp + 4) with_ids(trace_id, consumer_span_id) { tracer.start_span('consumer', with_parent: span, kind: :consumer, start_timestamp: start_timestamp + 5).finish(end_timestamp: end_timestamp) } span.finish(end_timestamp: end_timestamp) OpenTelemetry.tracer_provider.shutdown @@ -94,14 +176,14 @@ attributes: [ Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'telemetry.sdk.name', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: 'opentelemetry')), Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'telemetry.sdk.language', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: 'ruby')), - Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'telemetry.sdk.version', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: "semver:#{OpenTelemetry::SDK::VERSION}")), - ], + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'telemetry.sdk.version', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: OpenTelemetry::SDK::VERSION)) + ] ), instrumentation_library_spans: [ Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( name: 'tracer', - version: 'v0.0.1', + version: 'v0.0.1' ), spans: [ Opentelemetry::Proto::Trace::V1::Span.new( @@ -111,7 +193,7 @@ name: 'root', kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::INTERNAL, start_time_unix_nano: (start_timestamp.to_r * 1_000_000_000).to_i, - end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i ), Opentelemetry::Proto::Trace::V1::Span.new( trace_id: trace_id, @@ -120,7 +202,7 @@ name: 'client', kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::CLIENT, start_time_unix_nano: ((start_timestamp + 2).to_r * 1_000_000_000).to_i, - end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i ), Opentelemetry::Proto::Trace::V1::Span.new( trace_id: trace_id, @@ -129,7 +211,7 @@ name: 'consumer', kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::CONSUMER, start_time_unix_nano: ((start_timestamp + 5).to_r * 1_000_000_000).to_i, - end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i ), Opentelemetry::Proto::Trace::V1::Span.new( trace_id: trace_id, @@ -138,40 +220,51 @@ name: 'child', kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::PRODUCER, start_time_unix_nano: ((start_timestamp + 1).to_r * 1_000_000_000).to_i, - end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i, + end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i, attributes: [ Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'b', value: Opentelemetry::Proto::Common::V1::AnyValue.new(bool_value: true)), Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'f', value: Opentelemetry::Proto::Common::V1::AnyValue.new(double_value: 1.1)), Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'i', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 2)), Opentelemetry::Proto::Common::V1::KeyValue.new(key: 's', value: Opentelemetry::Proto::Common::V1::AnyValue.new(string_value: 'val')), + Opentelemetry::Proto::Common::V1::KeyValue.new( + key: 'a', + value: Opentelemetry::Proto::Common::V1::AnyValue.new( + array_value: Opentelemetry::Proto::Common::V1::ArrayValue.new( + values: [ + Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 3), + Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 4) + ] + ) + ) + ) ], events: [ Opentelemetry::Proto::Trace::V1::Span::Event.new( time_unix_nano: ((start_timestamp + 4).to_r * 1_000_000_000).to_i, name: 'event', attributes: [ - Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 42)), - ], - ), + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 42)) + ] + ) ], links: [ Opentelemetry::Proto::Trace::V1::Span::Link.new( trace_id: trace_id, span_id: root_span_id, attributes: [ - Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 4)), - ], + Opentelemetry::Proto::Common::V1::KeyValue.new(key: 'attr', value: Opentelemetry::Proto::Common::V1::AnyValue.new(int_value: 4)) + ] ) ], status: Opentelemetry::Proto::Trace::V1::Status.new( - code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::UnknownError, - ), - ), - ], + code: Opentelemetry::Proto::Trace::V1::Status::StatusCode::UnknownError + ) + ) + ] ), Opentelemetry::Proto::Trace::V1::InstrumentationLibrarySpans.new( instrumentation_library: Opentelemetry::Proto::Common::V1::InstrumentationLibrary.new( - name: 'other_tracer', + name: 'other_tracer' ), spans: [ Opentelemetry::Proto::Trace::V1::Span.new( @@ -181,17 +274,17 @@ name: 'server', kind: Opentelemetry::Proto::Trace::V1::Span::SpanKind::SERVER, start_time_unix_nano: ((start_timestamp + 3).to_r * 1_000_000_000).to_i, - end_time_unix_nano: ((end_timestamp).to_r * 1_000_000_000).to_i + end_time_unix_nano: (end_timestamp.to_r * 1_000_000_000).to_i ) - ], - ), + ] + ) ] ) ] ) ) - assert_requested(:post, "http://127.0.0.1:55681/v1/trace") do |req| + assert_requested(:post, 'https://localhost:55681/v1/trace') do |req| req.body == encoded_etsr end end @@ -207,12 +300,13 @@ def with_ids(trace_id, span_id) def create_span_data(name: '', kind: nil, status: nil, parent_span_id: OpenTelemetry::Trace::INVALID_SPAN_ID, child_count: 0, total_recorded_attributes: 0, total_recorded_events: 0, total_recorded_links: 0, start_timestamp: Time.now, - end_timestamp: Time.now, attributes: nil, links: nil, events: nil, library_resource: nil, + end_timestamp: Time.now, attributes: nil, links: nil, events: nil, resource: nil, instrumentation_library: OpenTelemetry::SDK::InstrumentationLibrary.new('', 'v0.0.1'), span_id: OpenTelemetry::Trace.generate_span_id, trace_id: OpenTelemetry::Trace.generate_trace_id, trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, tracestate: nil) + resource ||= OpenTelemetry::SDK::Resources::Resource.telemetry_sdk OpenTelemetry::SDK::Trace::SpanData.new(name, kind, status, parent_span_id, child_count, total_recorded_attributes, total_recorded_events, total_recorded_links, start_timestamp, end_timestamp, - attributes, links, events, library_resource, instrumentation_library, span_id, trace_id, trace_flags, tracestate) + attributes, links, events, resource, instrumentation_library, span_id, trace_id, trace_flags, tracestate) end end diff --git a/exporter/otlp/test/test_helper.rb b/exporter/otlp/test/test_helper.rb new file mode 100644 index 0000000000..bab57ae734 --- /dev/null +++ b/exporter/otlp/test/test_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Copyright 2019 OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'simplecov' +SimpleCov.start + +require 'opentelemetry/exporter/otlp' +require 'minitest/autorun' +require 'webmock/minitest' + +OpenTelemetry.logger = Logger.new('/dev/null') + +def with_env(new_env) + env_to_reset = ENV.select { |k, _| new_env.key?(k) } + keys_to_delete = new_env.keys - ENV.keys + new_env.each_pair { |k, v| ENV[k] = v } + yield +ensure + env_to_reset.each_pair { |k, v| ENV[k] = v } + keys_to_delete.each { |k| ENV.delete(k) } +end diff --git a/exporters/otlp/CHANGELOG.md b/exporters/otlp/CHANGELOG.md deleted file mode 100644 index bdf76cc5b3..0000000000 --- a/exporters/otlp/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -# Release History: opentelemetry-exporters-otlp diff --git a/exporters/otlp/test/test_helper.rb b/exporters/otlp/test/test_helper.rb deleted file mode 100644 index 06a26322cf..0000000000 --- a/exporters/otlp/test/test_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# Copyright 2019 OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'simplecov' -SimpleCov.start - -require 'opentelemetry/exporters/otlp' -require 'minitest/autorun' -require 'webmock/minitest' -require 'byebug' - -OpenTelemetry.logger = Logger.new('/dev/null') From 40f2bc2e7469dacf1d318e0de4b63de3a9bf56a0 Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Tue, 8 Sep 2020 20:37:32 -0400 Subject: [PATCH 09/10] Add OTLP exporter to releases --- .toys/.data/releases.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.toys/.data/releases.yml b/.toys/.data/releases.yml index 926285da50..1e0883c58e 100644 --- a/.toys/.data/releases.yml +++ b/.toys/.data/releases.yml @@ -16,6 +16,10 @@ gems: directory: exporter/jaeger version_constant: [OpenTelemetry, Exporter, Jaeger, VERSION] + - name: opentelemetry-exporter-otlp + directory: exporter/otlp + version_constant: [OpenTelemetry, Exporter, OTLP, VERSION] + - name: opentelemetry-instrumentation-all directory: instrumentation/all version_constant: [OpenTelemetry, Instrumentation, All, VERSION] From 8305b28acb9e1413b4ca5e89d64c73564c27cd2a Mon Sep 17 00:00:00 2001 From: Francis Bogsanyi Date: Tue, 8 Sep 2020 20:41:25 -0400 Subject: [PATCH 10/10] Exclude JRuby from CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a6cc246c9..e77afb9399 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: flags: --include-simple --include-appraisal --check-rubocop --check-yard - name: JRuby container: circleci/jruby:latest - flags: --include-simple --exclude opentelemetry-resource_detectors + flags: --include-simple --exclude opentelemetry-resource_detectors --exclude opentelemetry-exporter-otlp fail-fast: false name: Test ${{ matrix.name }} (${{ matrix.flags }}) runs-on: ubuntu-latest