From a217e9aec8ce9df2baa92ecff224bf67098268cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:52:18 -0700 Subject: [PATCH 01/14] release: Release 4 gems (#1692) * opentelemetry-api 1.4.0 (was 1.3.0) * opentelemetry-exporter-otlp 0.29.0 (was 0.28.1) * opentelemetry-exporter-otlp-metrics 0.1.0 (initial release) * opentelemetry-metrics-sdk 0.2.0 (was 0.1.0) Co-authored-by: Daniel Azuma --- api/CHANGELOG.md | 4 ++++ api/lib/opentelemetry/version.rb | 2 +- exporter/otlp-metrics/CHANGELOG.md | 4 ++++ exporter/otlp/CHANGELOG.md | 4 ++++ exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb | 2 +- metrics_sdk/CHANGELOG.md | 4 ++++ metrics_sdk/lib/opentelemetry/sdk/metrics/version.rb | 2 +- 7 files changed, 19 insertions(+), 3 deletions(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 35c3e87ad9..1cb9acd401 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History: opentelemetry-api +### v1.4.0 / 2024-08-27 + +* ADDED: Include backtrace first line for better debug info + ### v1.3.0 / 2024-07-24 * ADDED: Add add_link to span api/sdk diff --git a/api/lib/opentelemetry/version.rb b/api/lib/opentelemetry/version.rb index 009daf16ba..86295b3a96 100644 --- a/api/lib/opentelemetry/version.rb +++ b/api/lib/opentelemetry/version.rb @@ -6,5 +6,5 @@ module OpenTelemetry ## Current OpenTelemetry version - VERSION = '1.3.0' + VERSION = '1.4.0' end diff --git a/exporter/otlp-metrics/CHANGELOG.md b/exporter/otlp-metrics/CHANGELOG.md index 94a2c19729..b42812d565 100644 --- a/exporter/otlp-metrics/CHANGELOG.md +++ b/exporter/otlp-metrics/CHANGELOG.md @@ -1 +1,5 @@ # Release History: opentelemetry-exporter-otlp-metrics + +### v0.1.0 / 2024-08-27 + +Initial release. diff --git a/exporter/otlp/CHANGELOG.md b/exporter/otlp/CHANGELOG.md index 88ee03a7dc..bd86c03135 100644 --- a/exporter/otlp/CHANGELOG.md +++ b/exporter/otlp/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History: opentelemetry-exporter-otlp +### v0.29.0 / 2024-08-27 + +* ADDED: Add support for mutual TLS. + ### v0.28.1 / 2024-07-24 * ADDED: Improve SSL error logging. diff --git a/exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb b/exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb index b12e7e7de2..fd37180664 100644 --- a/exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb +++ b/exporter/otlp/lib/opentelemetry/exporter/otlp/version.rb @@ -8,7 +8,7 @@ module OpenTelemetry module Exporter module OTLP ## Current OpenTelemetry OTLP exporter version - VERSION = '0.28.1' + VERSION = '0.29.0' end end end diff --git a/metrics_sdk/CHANGELOG.md b/metrics_sdk/CHANGELOG.md index a1385c5c6b..1328411e29 100644 --- a/metrics_sdk/CHANGELOG.md +++ b/metrics_sdk/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History: opentelemetry-metrics-sdk +### v0.2.0 / 2024-08-27 + +* ADDED: Add basic periodic exporting metric_reader + ### v0.1.0 / 2024-07-31 Initial release. diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/version.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/version.rb index 41bd724c7a..4016895fb7 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/version.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/version.rb @@ -8,7 +8,7 @@ module OpenTelemetry module SDK module Metrics # Current OpenTelemetry metrics sdk version - VERSION = '0.1.0' + VERSION = '0.2.0' end end end From 3f96eba2f431d58d13aed723e06b1cd861cd1245 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:05:30 -0700 Subject: [PATCH 02/14] chore: Loosen metrics gems versions (#1698) The OTLP metrics exporter will fail to install for tests because the latest metrics SDK is newer than the previous version constraint. The rest of the gems are installed based on minor versions. Now metrics_sdk/_api are in sync. --- .../otlp-metrics/opentelemetry-exporter-otlp-metrics.gemspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/otlp-metrics/opentelemetry-exporter-otlp-metrics.gemspec b/exporter/otlp-metrics/opentelemetry-exporter-otlp-metrics.gemspec index 571b3b203e..0729f3e377 100644 --- a/exporter/otlp-metrics/opentelemetry-exporter-otlp-metrics.gemspec +++ b/exporter/otlp-metrics/opentelemetry-exporter-otlp-metrics.gemspec @@ -29,8 +29,8 @@ Gem::Specification.new do |spec| spec.add_dependency 'google-protobuf', '>= 3.18', '< 5.0' spec.add_dependency 'opentelemetry-api', '~> 1.1' spec.add_dependency 'opentelemetry-common', '~> 0.20' - spec.add_dependency 'opentelemetry-metrics-api', '~> 0.1.0' - spec.add_dependency 'opentelemetry-metrics-sdk', '~> 0.1.0' + spec.add_dependency 'opentelemetry-metrics-api', '~> 0.1' + spec.add_dependency 'opentelemetry-metrics-sdk', '~> 0.2' spec.add_dependency 'opentelemetry-sdk', '~> 1.2' spec.add_dependency 'opentelemetry-semantic_conventions' From aad3cf7f508795b0ef2c6fb31bab59a94898c714 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:57:34 -0700 Subject: [PATCH 03/14] feat: Add console and in-memory log exporters (#1687) The ConsoleLogRecordExporter will output logs to the console. The InMemoryLogRecordExporter is useful for testing OpenTelemetry integration. Co-authored-by: Matthew Wear --- logs_sdk/lib/opentelemetry/sdk/logs/export.rb | 2 + .../export/console_log_record_exporter.rb | 39 +++++++ .../export/in_memory_log_record_exporter.rb | 104 ++++++++++++++++++ .../console_log_record_exporter_test.rb | 56 ++++++++++ .../in_memory_log_record_exporter_test.rb | 93 ++++++++++++++++ 5 files changed, 294 insertions(+) create mode 100644 logs_sdk/lib/opentelemetry/sdk/logs/export/console_log_record_exporter.rb create mode 100644 logs_sdk/lib/opentelemetry/sdk/logs/export/in_memory_log_record_exporter.rb create mode 100644 logs_sdk/test/opentelemetry/sdk/logs/export/console_log_record_exporter_test.rb create mode 100644 logs_sdk/test/opentelemetry/sdk/logs/export/in_memory_log_record_exporter_test.rb diff --git a/logs_sdk/lib/opentelemetry/sdk/logs/export.rb b/logs_sdk/lib/opentelemetry/sdk/logs/export.rb index 2565dbf85f..414670e86c 100644 --- a/logs_sdk/lib/opentelemetry/sdk/logs/export.rb +++ b/logs_sdk/lib/opentelemetry/sdk/logs/export.rb @@ -24,6 +24,8 @@ module Export end end +require_relative 'export/console_log_record_exporter' +require_relative 'export/in_memory_log_record_exporter' require_relative 'export/log_record_exporter' require_relative 'export/simple_log_record_processor' require_relative 'export/batch_log_record_processor' diff --git a/logs_sdk/lib/opentelemetry/sdk/logs/export/console_log_record_exporter.rb b/logs_sdk/lib/opentelemetry/sdk/logs/export/console_log_record_exporter.rb new file mode 100644 index 0000000000..9f1af854d1 --- /dev/null +++ b/logs_sdk/lib/opentelemetry/sdk/logs/export/console_log_record_exporter.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module SDK + module Logs + module Export + # Outputs {LogRecordData} to the console. + # + # Potentially useful for exploratory purposes. + class ConsoleLogRecordExporter + def initialize + @stopped = false + end + + def export(log_records, timeout: nil) + return FAILURE if @stopped + + Array(log_records).each { |s| pp s } + + SUCCESS + end + + def force_flush(timeout: nil) + SUCCESS + end + + def shutdown(timeout: nil) + @stopped = true + SUCCESS + end + end + end + end + end +end diff --git a/logs_sdk/lib/opentelemetry/sdk/logs/export/in_memory_log_record_exporter.rb b/logs_sdk/lib/opentelemetry/sdk/logs/export/in_memory_log_record_exporter.rb new file mode 100644 index 0000000000..10c181e122 --- /dev/null +++ b/logs_sdk/lib/opentelemetry/sdk/logs/export/in_memory_log_record_exporter.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module SDK + module Logs + module Export + # A LogRecordExporter implementation that can be used to test OpenTelemetry integration. + # + # @example Usage in a test suite: + # class MyClassTest + # def setup + # @logger_provider = LoggerProvider.new + # @exporter = InMemoryLogRecordExporter.new + # @logger_provider.add_log_record_processor(SimpleLogRecordProcessor.new(@exporter)) + # end + # + # def test_emitted_log_records + # log_record = OpenTelemetry::SDK::Logs::LogRecord.new(body: 'log') + # @logger_provider.logger.on_emit(log_record, context) + # + # log_records = @exporter.emitted_log_records + + # refute_nil(log_records) + # assert_equal(1, log_records.size) + # assert_equal(log_records[0].body, 'log') + # end + # end + class InMemoryLogRecordExporter + # Returns a new instance of the {InMemoryLogRecordExporter}. + # + # @return a new instance of the {InMemoryLogRecordExporter}. + def initialize + @emitted_log_records = [] + @stopped = false + @mutex = Mutex.new + end + + # Returns a frozen array of the emitted {LogRecordData}s, represented by + # {io.opentelemetry.proto.trace.v1.LogRecord}. + # + # @return [Array] a frozen array of the emitted {LogRecordData}s. + def emitted_log_records + @mutex.synchronize do + @emitted_log_records.clone.freeze + end + end + + # Clears the internal collection of emitted {LogRecord}s. + # + # Does not reset the state of this exporter if already shutdown. + def reset + @mutex.synchronize do + @emitted_log_records.clear + end + end + + # Called to export {LogRecordData}s. + # + # @param [Enumerable] log_record_datas the list of {LogRecordData}s to be + # exported. + # @param [optional Numeric] timeout An optional timeout in seconds. + # @return [Integer] the result of the export, SUCCESS or + # FAILURE + def export(log_record_datas, timeout: nil) + @mutex.synchronize do + return FAILURE if @stopped + + @emitted_log_records.concat(log_record_datas.to_a) + end + SUCCESS + end + + # Called when {LoggerProvider#force_flush} is called, if this exporter is + # registered to a {LoggerProvider} object. + # + # @param [optional Numeric] timeout An optional timeout in seconds. + # @return [Integer] SUCCESS if no error occurred, FAILURE if a + # non-specific failure occurred, TIMEOUT if a timeout occurred. + def force_flush(timeout: nil) + SUCCESS + end + + # Called when {LoggerProvider#shutdown} is called, if this exporter is + # registered to a {LoggerProvider} object. + # + # @param [optional Numeric] timeout An optional timeout in seconds. + # @return [Integer] SUCCESS if no error occurred, FAILURE if a + # non-specific failure occurred, TIMEOUT if a timeout occurred. + def shutdown(timeout: nil) + @mutex.synchronize do + @emitted_log_records.clear + @stopped = true + end + SUCCESS + end + end + end + end + end +end diff --git a/logs_sdk/test/opentelemetry/sdk/logs/export/console_log_record_exporter_test.rb b/logs_sdk/test/opentelemetry/sdk/logs/export/console_log_record_exporter_test.rb new file mode 100644 index 0000000000..b549bc944a --- /dev/null +++ b/logs_sdk/test/opentelemetry/sdk/logs/export/console_log_record_exporter_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::SDK::Logs::Export::ConsoleLogRecordExporter do + export = OpenTelemetry::SDK::Logs::Export + + let(:captured_stdout) { StringIO.new } + let(:log_record_data1) { Logs::LogRecordData.new } + let(:log_record_data2) { Logs::LogRecordData.new } + let(:log_records) { [log_record_data1, log_record_data2] } + let(:exporter) { export::ConsoleLogRecordExporter.new } + + before do + @original_stdout = $stdout + $stdout = captured_stdout + end + + after do + $stdout = @original_stdout + end + + it 'accepts an Array of LogRecordData as arg to #export and succeeds' do + assert_equal(export::SUCCESS, exporter.export(log_records)) + end + + it 'accepts an Enumerable of LogRecordData as arg to #export and succeeds' do + enumerable = Struct.new(:log_record0, :log_record1).new(log_records[0], log_records[1]) + + assert_equal(export::SUCCESS, exporter.export(enumerable)) + end + + it 'outputs to console (stdout)' do + exporter.export(log_records) + + assert_match(/# Date: Wed, 28 Aug 2024 18:19:24 -0400 Subject: [PATCH 04/14] fix: remove WRITE_TIMEOUT_SUPPORTED (#1673) Co-authored-by: Matthew Wear --- .../lib/opentelemetry/exporter/otlp/http/trace_exporter.rb | 7 +++---- exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb | 7 +++---- .../zipkin/lib/opentelemetry/exporter/zipkin/exporter.rb | 5 ++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb b/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb index e71c1a9457..f6f0ecd98e 100644 --- a/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb +++ b/exporter/otlp-http/lib/opentelemetry/exporter/otlp/http/trace_exporter.rb @@ -25,8 +25,7 @@ class TraceExporter # rubocop:disable Metrics/ClassLength # Default timeouts in seconds. KEEP_ALIVE_TIMEOUT = 30 RETRY_COUNT = 5 - WRITE_TIMEOUT_SUPPORTED = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') - private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT, :WRITE_TIMEOUT_SUPPORTED) + private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT) ERROR_MESSAGE_INVALID_HEADERS = 'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash' private_constant(:ERROR_MESSAGE_INVALID_HEADERS) @@ -153,7 +152,7 @@ def send_bytes(bytes, timeout:) # rubocop:disable Metrics/MethodLength @http.open_timeout = remaining_timeout @http.read_timeout = remaining_timeout - @http.write_timeout = remaining_timeout if WRITE_TIMEOUT_SUPPORTED + @http.write_timeout = remaining_timeout @http.start unless @http.started? response = measure_request_duration { @http.request(request) } @@ -209,7 +208,7 @@ def send_bytes(bytes, timeout:) # rubocop:disable Metrics/MethodLength # Reset timeouts to defaults for the next call. @http.open_timeout = @timeout @http.read_timeout = @timeout - @http.write_timeout = @timeout if WRITE_TIMEOUT_SUPPORTED + @http.write_timeout = @timeout end def handle_redirect(location) diff --git a/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb b/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb index 6083692ffc..35754f1e91 100644 --- a/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb +++ b/exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb @@ -28,8 +28,7 @@ class Exporter # rubocop:disable Metrics/ClassLength # Default timeouts in seconds. KEEP_ALIVE_TIMEOUT = 30 RETRY_COUNT = 5 - WRITE_TIMEOUT_SUPPORTED = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') - private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT, :WRITE_TIMEOUT_SUPPORTED) + private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT) ERROR_MESSAGE_INVALID_HEADERS = 'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash' private_constant(:ERROR_MESSAGE_INVALID_HEADERS) @@ -153,7 +152,7 @@ def send_bytes(bytes, timeout:) # rubocop:disable Metrics/CyclomaticComplexity, @http.open_timeout = remaining_timeout @http.read_timeout = remaining_timeout - @http.write_timeout = remaining_timeout if WRITE_TIMEOUT_SUPPORTED + @http.write_timeout = remaining_timeout @http.start unless @http.started? response = measure_request_duration { @http.request(request) } @@ -213,7 +212,7 @@ def send_bytes(bytes, timeout:) # rubocop:disable Metrics/CyclomaticComplexity, # Reset timeouts to defaults for the next call. @http.open_timeout = @timeout @http.read_timeout = @timeout - @http.write_timeout = @timeout if WRITE_TIMEOUT_SUPPORTED + @http.write_timeout = @timeout end def handle_redirect(location) diff --git a/exporter/zipkin/lib/opentelemetry/exporter/zipkin/exporter.rb b/exporter/zipkin/lib/opentelemetry/exporter/zipkin/exporter.rb index 43efbee2cb..779df0022d 100644 --- a/exporter/zipkin/lib/opentelemetry/exporter/zipkin/exporter.rb +++ b/exporter/zipkin/lib/opentelemetry/exporter/zipkin/exporter.rb @@ -24,8 +24,7 @@ class Exporter # rubocop:disable Metrics/ClassLength # Default timeouts in seconds. KEEP_ALIVE_TIMEOUT = 30 RETRY_COUNT = 5 - WRITE_TIMEOUT_SUPPORTED = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') - private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT, :WRITE_TIMEOUT_SUPPORTED) + private_constant(:KEEP_ALIVE_TIMEOUT, :RETRY_COUNT) def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_ZIPKIN_ENDPOINT', default: 'http://localhost:9411/api/v2/spans'), headers: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_ZIPKIN_TRACES_HEADERS', 'OTEL_EXPORTER_ZIPKIN_HEADERS'), @@ -130,7 +129,7 @@ def send_spans(zipkin_spans, timeout: nil) # rubocop:disable Metrics/MethodLengt @http.open_timeout = remaining_timeout @http.read_timeout = remaining_timeout - @http.write_timeout = remaining_timeout if WRITE_TIMEOUT_SUPPORTED + @http.write_timeout = remaining_timeout @http.start unless @http.started? response = measure_request_duration { @http.request(request) } From 75a48853b3525231a06aa17b0f76a247f4782060 Mon Sep 17 00:00:00 2001 From: Xuan <112967240+xuan-cao-swi@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:23:21 -0400 Subject: [PATCH 05/14] Rubocop on metrics (#1683) * fix: use empty hash as default value for metric instrument attributes * fix: update otlp metrics rubocop yml * lint --------- Co-authored-by: Matthew Wear --- exporter/otlp-metrics/.rubocop.yml | 29 +- .../exporter/otlp/metrics/metrics_exporter.rb | 8 +- .../exporter/otlp/metrics/util.rb | 2 +- .../exporter/otlp/metrics_exporter.rb | 309 ++++++++++++++++++ .../lib/opentelemetry/exporter/otlp/util.rb | 139 ++++++++ .../metrics/instrument/counter.rb | 2 +- .../metrics/instrument/histogram.rb | 2 +- .../metrics/instrument/up_down_counter.rb | 2 +- .../sdk/metrics/instrument/histogram.rb | 2 +- .../sdk/metrics/instrument/up_down_counter.rb | 2 +- 10 files changed, 467 insertions(+), 30 deletions(-) create mode 100644 exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb create mode 100644 exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb diff --git a/exporter/otlp-metrics/.rubocop.yml b/exporter/otlp-metrics/.rubocop.yml index 684add8d91..52c76f71be 100644 --- a/exporter/otlp-metrics/.rubocop.yml +++ b/exporter/otlp-metrics/.rubocop.yml @@ -1,32 +1,21 @@ +inherit_from: ../../contrib/rubocop.yml + AllCops: - TargetRubyVersion: "3.0" - NewCops: disable - SuggestExtensions: false Exclude: - "lib/opentelemetry/proto/**/*" - "vendor/**/*" -Bundler/OrderedGems: - Exclude: - - gemfiles/**/* -Lint/UnusedMethodArgument: - Enabled: false -Lint/MissingSuper: - Enabled: false -Lint/ConstantDefinitionInBlock: - Exclude: - - "test/**/*" -Style/StringConcatenation: - Exclude: - - "test/**/*" -Metrics/AbcSize: +Metrics/CyclomaticComplexity: Enabled: false -Layout/LineLength: +Metrics/PerceivedComplexity: Enabled: false Metrics/MethodLength: - Max: 20 -Metrics/ParameterLists: Enabled: false +Metrics/ClassLength: + Enabled: false +Bundler/OrderedGems: + Exclude: + - gemfiles/**/* Style/FrozenStringLiteralComment: Exclude: - gemfiles/**/* diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb index be21d5dde2..a1b0bf415f 100644 --- a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb +++ b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb @@ -26,7 +26,7 @@ module Exporter module OTLP module Metrics # An OpenTelemetry metrics exporter that sends metrics over HTTP as Protobuf encoded OTLP ExportMetricsServiceRequest. - class MetricsExporter < ::OpenTelemetry::SDK::Metrics::Export::MetricReader # rubocop:disable Metrics/ClassLength + class MetricsExporter < ::OpenTelemetry::SDK::Metrics::Export::MetricReader include Util attr_reader :metric_snapshots @@ -87,7 +87,7 @@ def export(metrics, timeout: nil) end end - def send_bytes(bytes, timeout:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def send_bytes(bytes, timeout:) return FAILURE if bytes.nil? request = Net::HTTP::Post.new(@path) @@ -181,7 +181,7 @@ def send_bytes(bytes, timeout:) # rubocop:disable Metrics/CyclomaticComplexity, @http.write_timeout = @timeout end - def encode(metrics_data) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity + def encode(metrics_data) Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.encode( Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.new( resource_metrics: metrics_data @@ -215,7 +215,7 @@ def encode(metrics_data) # rubocop:disable Metrics/MethodLength, Metrics/Cycloma # current metric sdk only implements instrument: :counter -> :sum, :histogram -> :histogram # # metrics [MetricData] - def as_otlp_metrics(metrics) # rubocop:disable Metrics/MethodLength + def as_otlp_metrics(metrics) case metrics.instrument_kind when :observable_gauge Opentelemetry::Proto::Metrics::V1::Metric.new( diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb index 568c9a0e48..264b1e19bf 100644 --- a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb +++ b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb @@ -97,7 +97,7 @@ def parse_headers(raw) end end - def backoff?(retry_count:, reason:, retry_after: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def backoff?(retry_count:, reason:, retry_after: nil) return false if retry_count > RETRY_COUNT sleep_interval = nil diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb new file mode 100644 index 0000000000..3d86f9e979 --- /dev/null +++ b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb @@ -0,0 +1,309 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry/common' +require 'opentelemetry/sdk' +require 'net/http' +require 'csv' +require 'zlib' + +require 'google/rpc/status_pb' + +require 'opentelemetry/proto/common/v1/common_pb' +require 'opentelemetry/proto/resource/v1/resource_pb' +require 'opentelemetry/proto/metrics/v1/metrics_pb' +require 'opentelemetry/proto/collector/metrics/v1/metrics_service_pb' + +require 'opentelemetry/metrics' +require 'opentelemetry/sdk/metrics' + +require_relative './util' + +module OpenTelemetry + module Exporter + module OTLP + # An OpenTelemetry metrics exporter that sends metrics over HTTP as Protobuf encoded OTLP ExportMetricsServiceRequest. + class MetricsExporter < ::OpenTelemetry::SDK::Metrics::Export::MetricReader + include Util + + attr_reader :metric_snapshots + + SUCCESS = OpenTelemetry::SDK::Metrics::Export::SUCCESS + FAILURE = OpenTelemetry::SDK::Metrics::Export::FAILURE + private_constant(:SUCCESS, :FAILURE) + + WRITE_TIMEOUT_SUPPORTED = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') + private_constant(:WRITE_TIMEOUT_SUPPORTED) + + def self.ssl_verify_mode + if ENV.key?('OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER') + OpenSSL::SSL::VERIFY_PEER + elsif ENV.key?('OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE') + OpenSSL::SSL::VERIFY_NONE + else + OpenSSL::SSL::VERIFY_PEER + end + end + + def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'http://localhost:4318/v1/metrics'), + certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'), + ssl_verify_mode: MetricsExporter.ssl_verify_mode, + headers: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS', default: {}), + compression: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION', default: 'gzip'), + timeout: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10)) + raise ArgumentError, "invalid url for OTLP::MetricsExporter #{endpoint}" unless OpenTelemetry::Common::Utilities.valid_url?(endpoint) + raise ArgumentError, "unsupported compression key #{compression}" unless compression.nil? || %w[gzip none].include?(compression) + + # create the MetricStore object + super() + + @uri = if endpoint == ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] + URI.join(endpoint, 'v1/metrics') + else + URI(endpoint) + end + + @http = http_connection(@uri, ssl_verify_mode, certificate_file) + + @path = @uri.path + @headers = prepare_headers(headers) + @timeout = timeout.to_f + @compression = compression + @mutex = Mutex.new + @shutdown = false + end + + # consolidate the metrics data into the form of MetricData + # + # return MetricData + def pull + export(collect) + end + + # metrics Array[MetricData] + def export(metrics, timeout: nil) + @mutex.synchronize do + send_bytes(encode(metrics), timeout: timeout) + end + end + + def send_bytes(bytes, timeout:) + return FAILURE if bytes.nil? + + request = Net::HTTP::Post.new(@path) + + if @compression == 'gzip' + request.add_field('Content-Encoding', 'gzip') + body = Zlib.gzip(bytes) + else + body = bytes + end + + request.body = body + request.add_field('Content-Type', 'application/x-protobuf') + @headers.each { |key, value| request.add_field(key, value) } + + retry_count = 0 + timeout ||= @timeout + start_time = OpenTelemetry::Common::Utilities.timeout_timestamp + + around_request do + remaining_timeout = OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time) + return FAILURE if remaining_timeout.zero? + + @http.open_timeout = remaining_timeout + @http.read_timeout = remaining_timeout + @http.write_timeout = remaining_timeout if WRITE_TIMEOUT_SUPPORTED + @http.start unless @http.started? + response = measure_request_duration { @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, reason: response.code) + OpenTelemetry.logger.warn('Net::HTTPServiceUnavailable/Net::HTTPTooManyRequests in MetricsExporter#send_bytes') + FAILURE + when Net::HTTPRequestTimeOut, Net::HTTPGatewayTimeOut, Net::HTTPBadGateway + response.body # Read and discard body + redo if backoff?(retry_count: retry_count += 1, reason: response.code) + OpenTelemetry.logger.warn('Net::HTTPRequestTimeOut/Net::HTTPGatewayTimeOut/Net::HTTPBadGateway in MetricsExporter#send_bytes') + FAILURE + when Net::HTTPNotFound + OpenTelemetry.handle_error(message: "OTLP metrics_exporter received http.code=404 for uri: '#{@path}'") + FAILURE + when Net::HTTPBadRequest, Net::HTTPClientError, Net::HTTPServerError + log_status(response.body) + OpenTelemetry.logger.warn('Net::HTTPBadRequest/Net::HTTPClientError/Net::HTTPServerError in MetricsExporter#send_bytes') + FAILURE + when Net::HTTPRedirection + @http.finish + handle_redirect(response['location']) + redo if backoff?(retry_after: 0, retry_count: retry_count += 1, reason: response.code) + else + @http.finish + OpenTelemetry.logger.warn("Unexpected error in OTLP::MetricsExporter#send_bytes - #{response.message}") + FAILURE + end + rescue Net::OpenTimeout, Net::ReadTimeout + retry if backoff?(retry_count: retry_count += 1, reason: 'timeout') + OpenTelemetry.logger.warn('Net::OpenTimeout/Net::ReadTimeout in MetricsExporter#send_bytes') + return FAILURE + rescue OpenSSL::SSL::SSLError + retry if backoff?(retry_count: retry_count += 1, reason: 'openssl_error') + OpenTelemetry.logger.warn('OpenSSL::SSL::SSLError in MetricsExporter#send_bytes') + return FAILURE + rescue SocketError + retry if backoff?(retry_count: retry_count += 1, reason: 'socket_error') + OpenTelemetry.logger.warn('SocketError in MetricsExporter#send_bytes') + return FAILURE + rescue SystemCallError => e + retry if backoff?(retry_count: retry_count += 1, reason: e.class.name) + OpenTelemetry.logger.warn('SystemCallError in MetricsExporter#send_bytes') + return FAILURE + rescue EOFError + retry if backoff?(retry_count: retry_count += 1, reason: 'eof_error') + OpenTelemetry.logger.warn('EOFError in MetricsExporter#send_bytes') + return FAILURE + rescue Zlib::DataError + retry if backoff?(retry_count: retry_count += 1, reason: 'zlib_error') + OpenTelemetry.logger.warn('Zlib::DataError in MetricsExporter#send_bytes') + return FAILURE + rescue StandardError => e + OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::MetricsExporter#send_bytes') + return FAILURE + end + ensure + # Reset timeouts to defaults for the next call. + @http.open_timeout = @timeout + @http.read_timeout = @timeout + @http.write_timeout = @timeout if WRITE_TIMEOUT_SUPPORTED + end + + def encode(metrics_data) + Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.encode( + Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.new( + resource_metrics: metrics_data + .group_by(&:resource) + .map do |resource, scope_metrics| + Opentelemetry::Proto::Metrics::V1::ResourceMetrics.new( + resource: Opentelemetry::Proto::Resource::V1::Resource.new( + attributes: resource.attribute_enumerator.map { |key, value| as_otlp_key_value(key, value) } + ), + scope_metrics: scope_metrics + .group_by(&:instrumentation_scope) + .map do |instrumentation_scope, metrics| + Opentelemetry::Proto::Metrics::V1::ScopeMetrics.new( + scope: Opentelemetry::Proto::Common::V1::InstrumentationScope.new( + name: instrumentation_scope.name, + version: instrumentation_scope.version + ), + metrics: metrics.map { |sd| as_otlp_metrics(sd) } + ) + end + ) + end + ) + ) + rescue StandardError => e + OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::MetricsExporter#encode') + nil + end + + # metrics_pb has following type of data: :gauge, :sum, :histogram, :exponential_histogram, :summary + # current metric sdk only implements instrument: :counter -> :sum, :histogram -> :histogram + # + # metrics [MetricData] + def as_otlp_metrics(metrics) + case metrics.instrument_kind + when :observable_gauge + Opentelemetry::Proto::Metrics::V1::Metric.new( + name: metrics.name, + description: metrics.description, + unit: metrics.unit, + gauge: Opentelemetry::Proto::Metrics::V1::Gauge.new( + aggregation_temporality: as_otlp_aggregation_temporality(metrics.aggregation_temporality), + data_points: metrics.data_points.map do |ndp| + number_data_point(ndp) + end + ) + ) + + when :counter, :up_down_counter + Opentelemetry::Proto::Metrics::V1::Metric.new( + name: metrics.name, + description: metrics.description, + unit: metrics.unit, + sum: Opentelemetry::Proto::Metrics::V1::Sum.new( + aggregation_temporality: as_otlp_aggregation_temporality(metrics.aggregation_temporality), + data_points: metrics.data_points.map do |ndp| + number_data_point(ndp) + end + ) + ) + + when :histogram + Opentelemetry::Proto::Metrics::V1::Metric.new( + name: metrics.name, + description: metrics.description, + unit: metrics.unit, + histogram: Opentelemetry::Proto::Metrics::V1::Histogram.new( + aggregation_temporality: as_otlp_aggregation_temporality(metrics.aggregation_temporality), + data_points: metrics.data_points.map do |hdp| + histogram_data_point(hdp) + end + ) + ) + end + end + + def as_otlp_aggregation_temporality(type) + case type + when :delta then Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_DELTA + when :cumulative then Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_CUMULATIVE + else Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_UNSPECIFIED + end + end + + def histogram_data_point(hdp) + Opentelemetry::Proto::Metrics::V1::HistogramDataPoint.new( + attributes: hdp.attributes.map { |k, v| as_otlp_key_value(k, v) }, + start_time_unix_nano: hdp.start_time_unix_nano, + time_unix_nano: hdp.time_unix_nano, + count: hdp.count, + sum: hdp.sum, + bucket_counts: hdp.bucket_counts, + explicit_bounds: hdp.explicit_bounds, + exemplars: hdp.exemplars, + min: hdp.min, + max: hdp.max + ) + end + + def number_data_point(ndp) + Opentelemetry::Proto::Metrics::V1::NumberDataPoint.new( + attributes: ndp.attributes.map { |k, v| as_otlp_key_value(k, v) }, + as_int: ndp.value, + start_time_unix_nano: ndp.start_time_unix_nano, + time_unix_nano: ndp.time_unix_nano, + exemplars: ndp.exemplars # exemplars not implemented yet from metrics sdk + ) + end + + # may not need this + def reset + SUCCESS + end + + def shutdown(timeout: nil) + @shutdown = true + SUCCESS + end + end + end + end +end diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb new file mode 100644 index 0000000000..85070a6f43 --- /dev/null +++ b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +module OpenTelemetry + module Exporter + module OTLP + # Util module provide essential functionality for exporter + module Util # rubocop:disable Metrics/ModuleLength + KEEP_ALIVE_TIMEOUT = 30 + RETRY_COUNT = 5 + ERROR_MESSAGE_INVALID_HEADERS = 'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash' + DEFAULT_USER_AGENT = "OTel-OTLP-MetricsExporter-Ruby/#{OpenTelemetry::Exporter::OTLP::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}; #{RUBY_ENGINE}/#{RUBY_ENGINE_VERSION})".freeze + + def http_connection(uri, ssl_verify_mode, certificate_file) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = uri.scheme == 'https' + http.verify_mode = ssl_verify_mode + http.ca_file = certificate_file unless certificate_file.nil? + http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT + http + end + + def around_request + OpenTelemetry::Common::Utilities.untraced { yield } # rubocop:disable Style/ExplicitBlockArgument + end + + def as_otlp_key_value(key, value) + Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value(value)) + rescue Encoding::UndefinedConversionError => e + encoded_value = value.encode('UTF-8', invalid: :replace, undef: :replace, replace: '�') + OpenTelemetry.handle_error(exception: e, message: "encoding error for key #{key} and value #{encoded_value}") + Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value('Encoding Error')) + end + + def as_otlp_any_value(value) + result = Opentelemetry::Proto::Common::V1::AnyValue.new + case value + when String + result.string_value = value + when Integer + result.int_value = value + when Float + result.double_value = value + when true, false + 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 + result + end + + def prepare_headers(config_headers) + headers = case config_headers + when String then parse_headers(config_headers) + when Hash then config_headers.dup + else + raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS + end + + headers['User-Agent'] = "#{headers.fetch('User-Agent', '')} #{DEFAULT_USER_AGENT}".strip + + headers + end + + def measure_request_duration + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + begin + yield + ensure + stop = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 1000.0 * (stop - start) + end + end + + def parse_headers(raw) + entries = raw.split(',') + raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if entries.empty? + + entries.each_with_object({}) do |entry, headers| + k, v = entry.split('=', 2).map(&CGI.method(:unescape)) + begin + k = k.to_s.strip + v = v.to_s.strip + rescue Encoding::CompatibilityError + raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS + rescue ArgumentError => e + raise e, ERROR_MESSAGE_INVALID_HEADERS + end + raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if k.empty? || v.empty? + + headers[k] = v + end + end + + def backoff?(retry_count:, reason:, retry_after: nil) + return false if retry_count > 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 # rubocop:disable Style/RescueStandardError + nil + end + sleep_interval = nil unless sleep_interval&.positive? + end + sleep_interval ||= rand(2**retry_count) + + sleep(sleep_interval) + true + end + + def log_status(body) + status = Google::Rpc::Status.decode(body) + details = status.details.map do |detail| + klass_or_nil = ::Google::Protobuf::DescriptorPool.generated_pool.lookup(detail.type_name).msgclass + detail.unpack(klass_or_nil) if klass_or_nil + end.compact + OpenTelemetry.handle_error(message: "OTLP metrics_exporter received rpc.Status{message=#{status.message}, details=#{details}}") + rescue StandardError => e + OpenTelemetry.handle_error(exception: e, message: 'unexpected error decoding rpc.Status in OTLP::MetricsExporter#log_status') + end + + def handle_redirect(location); end + end + end + end +end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb index 870364ec89..f9e7225f0a 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/counter.rb @@ -16,7 +16,7 @@ class Counter # Values must be non-nil and (array of) string, boolean or numeric type. # Array values must not contain nil elements and all elements must be of # the same basic type (string, numeric, boolean). - def add(increment, attributes: nil); end + def add(increment, attributes: {}); end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb b/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb index 1de448baab..3672a08817 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/histogram.rb @@ -16,7 +16,7 @@ class Histogram # Values must be non-nil and (array of) string, boolean or numeric type. # Array values must not contain nil elements and all elements must be of # the same basic type (string, numeric, boolean). - def record(amount, attributes: nil); end + def record(amount, attributes: {}); end end end end diff --git a/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb b/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb index f9793d1508..b9a943db91 100644 --- a/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb +++ b/metrics_api/lib/opentelemetry/metrics/instrument/up_down_counter.rb @@ -16,7 +16,7 @@ class UpDownCounter # Values must be non-nil and (array of) string, boolean or numeric type. # Array values must not contain nil elements and all elements must be of # the same basic type (string, numeric, boolean). - def add(amount, attributes: nil); end + def add(amount, attributes: {}); end end end end diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb index 9cdcf60d80..5c8e00f157 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/histogram.rb @@ -24,7 +24,7 @@ def instrument_kind # Values must be non-nil and (array of) string, boolean or numeric type. # Array values must not contain nil elements and all elements must be of # the same basic type (string, numeric, boolean). - def record(amount, attributes: nil) + def record(amount, attributes: {}) update(amount, attributes) nil rescue StandardError => e diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb index cf2dc0d8b4..bba734bb0c 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/instrument/up_down_counter.rb @@ -24,7 +24,7 @@ def instrument_kind # Values must be non-nil and (array of) string, boolean or numeric type. # Array values must not contain nil elements and all elements must be of # the same basic type (string, numeric, boolean). - def add(amount, attributes: nil) + def add(amount, attributes: {}) update(amount, attributes) nil rescue StandardError => e From 0f5cbfc6f607bb877e2cc463041ecfc57494df9d Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:40:22 -0700 Subject: [PATCH 06/14] fix: Remove Metrics OTLP exporter `Util#measure_request_duration` and duplicate files (#1717) * fix: Remove measure_request_duration The OTLP metrics exporter does not use the metrics reporter present in the traces OTLP exporter. The measure_request_duration method is used in the traces exporter to provide a value for the metrics reporter. Since the metrics OTLP exporter does not use this metrics reporter, the method is not needed. Also, with the current code, the duration was being calculated in a void context. The value of the response variable has been and should be the result of @http.request(request). * fix: Remove old files outside nesting structure There were some duplicate files at the path exporter/otlp/* that were no longer used, now that the files are nested under exporter/otlp/metrics/* The test file location was updated to reflect the new location. * fix: Remove measure_request_duration from Util * style: Rubocop * style: Rubocop --- .../exporter/otlp/metrics/metrics_exporter.rb | 2 +- .../exporter/otlp/metrics/util.rb | 12 +- .../exporter/otlp/metrics_exporter.rb | 309 ------------------ .../lib/opentelemetry/exporter/otlp/util.rb | 139 -------- .../{ => metrics}/metrics_exporter_test.rb | 0 5 files changed, 2 insertions(+), 460 deletions(-) delete mode 100644 exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb delete mode 100644 exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb rename exporter/otlp-metrics/test/opentelemetry/exporter/otlp/{ => metrics}/metrics_exporter_test.rb (100%) diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb index a1b0bf415f..44302e0e92 100644 --- a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb +++ b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/metrics_exporter.rb @@ -115,7 +115,7 @@ def send_bytes(bytes, timeout:) @http.read_timeout = remaining_timeout @http.write_timeout = remaining_timeout @http.start unless @http.started? - response = measure_request_duration { @http.request(request) } + response = @http.request(request) case response when Net::HTTPOK response.body # Read and discard body diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb index 264b1e19bf..37f1896da0 100644 --- a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb +++ b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics/util.rb @@ -9,7 +9,7 @@ module Exporter module OTLP module Metrics # Util module provide essential functionality for exporter - module Util # rubocop:disable Metrics/ModuleLength + module Util KEEP_ALIVE_TIMEOUT = 30 RETRY_COUNT = 5 ERROR_MESSAGE_INVALID_HEADERS = 'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash' @@ -67,16 +67,6 @@ def prepare_headers(config_headers) headers end - def measure_request_duration - start = Process.clock_gettime(Process::CLOCK_MONOTONIC) - begin - yield - ensure - stop = Process.clock_gettime(Process::CLOCK_MONOTONIC) - 1000.0 * (stop - start) - end - end - def parse_headers(raw) entries = raw.split(',') raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if entries.empty? diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb deleted file mode 100644 index 3d86f9e979..0000000000 --- a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/metrics_exporter.rb +++ /dev/null @@ -1,309 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'opentelemetry/common' -require 'opentelemetry/sdk' -require 'net/http' -require 'csv' -require 'zlib' - -require 'google/rpc/status_pb' - -require 'opentelemetry/proto/common/v1/common_pb' -require 'opentelemetry/proto/resource/v1/resource_pb' -require 'opentelemetry/proto/metrics/v1/metrics_pb' -require 'opentelemetry/proto/collector/metrics/v1/metrics_service_pb' - -require 'opentelemetry/metrics' -require 'opentelemetry/sdk/metrics' - -require_relative './util' - -module OpenTelemetry - module Exporter - module OTLP - # An OpenTelemetry metrics exporter that sends metrics over HTTP as Protobuf encoded OTLP ExportMetricsServiceRequest. - class MetricsExporter < ::OpenTelemetry::SDK::Metrics::Export::MetricReader - include Util - - attr_reader :metric_snapshots - - SUCCESS = OpenTelemetry::SDK::Metrics::Export::SUCCESS - FAILURE = OpenTelemetry::SDK::Metrics::Export::FAILURE - private_constant(:SUCCESS, :FAILURE) - - WRITE_TIMEOUT_SUPPORTED = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6') - private_constant(:WRITE_TIMEOUT_SUPPORTED) - - def self.ssl_verify_mode - if ENV.key?('OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER') - OpenSSL::SSL::VERIFY_PEER - elsif ENV.key?('OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE') - OpenSSL::SSL::VERIFY_NONE - else - OpenSSL::SSL::VERIFY_PEER - end - end - - def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'http://localhost:4318/v1/metrics'), - certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'), - ssl_verify_mode: MetricsExporter.ssl_verify_mode, - headers: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS', default: {}), - compression: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION', default: 'gzip'), - timeout: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_METRICS_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10)) - raise ArgumentError, "invalid url for OTLP::MetricsExporter #{endpoint}" unless OpenTelemetry::Common::Utilities.valid_url?(endpoint) - raise ArgumentError, "unsupported compression key #{compression}" unless compression.nil? || %w[gzip none].include?(compression) - - # create the MetricStore object - super() - - @uri = if endpoint == ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] - URI.join(endpoint, 'v1/metrics') - else - URI(endpoint) - end - - @http = http_connection(@uri, ssl_verify_mode, certificate_file) - - @path = @uri.path - @headers = prepare_headers(headers) - @timeout = timeout.to_f - @compression = compression - @mutex = Mutex.new - @shutdown = false - end - - # consolidate the metrics data into the form of MetricData - # - # return MetricData - def pull - export(collect) - end - - # metrics Array[MetricData] - def export(metrics, timeout: nil) - @mutex.synchronize do - send_bytes(encode(metrics), timeout: timeout) - end - end - - def send_bytes(bytes, timeout:) - return FAILURE if bytes.nil? - - request = Net::HTTP::Post.new(@path) - - if @compression == 'gzip' - request.add_field('Content-Encoding', 'gzip') - body = Zlib.gzip(bytes) - else - body = bytes - end - - request.body = body - request.add_field('Content-Type', 'application/x-protobuf') - @headers.each { |key, value| request.add_field(key, value) } - - retry_count = 0 - timeout ||= @timeout - start_time = OpenTelemetry::Common::Utilities.timeout_timestamp - - around_request do - remaining_timeout = OpenTelemetry::Common::Utilities.maybe_timeout(timeout, start_time) - return FAILURE if remaining_timeout.zero? - - @http.open_timeout = remaining_timeout - @http.read_timeout = remaining_timeout - @http.write_timeout = remaining_timeout if WRITE_TIMEOUT_SUPPORTED - @http.start unless @http.started? - response = measure_request_duration { @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, reason: response.code) - OpenTelemetry.logger.warn('Net::HTTPServiceUnavailable/Net::HTTPTooManyRequests in MetricsExporter#send_bytes') - FAILURE - when Net::HTTPRequestTimeOut, Net::HTTPGatewayTimeOut, Net::HTTPBadGateway - response.body # Read and discard body - redo if backoff?(retry_count: retry_count += 1, reason: response.code) - OpenTelemetry.logger.warn('Net::HTTPRequestTimeOut/Net::HTTPGatewayTimeOut/Net::HTTPBadGateway in MetricsExporter#send_bytes') - FAILURE - when Net::HTTPNotFound - OpenTelemetry.handle_error(message: "OTLP metrics_exporter received http.code=404 for uri: '#{@path}'") - FAILURE - when Net::HTTPBadRequest, Net::HTTPClientError, Net::HTTPServerError - log_status(response.body) - OpenTelemetry.logger.warn('Net::HTTPBadRequest/Net::HTTPClientError/Net::HTTPServerError in MetricsExporter#send_bytes') - FAILURE - when Net::HTTPRedirection - @http.finish - handle_redirect(response['location']) - redo if backoff?(retry_after: 0, retry_count: retry_count += 1, reason: response.code) - else - @http.finish - OpenTelemetry.logger.warn("Unexpected error in OTLP::MetricsExporter#send_bytes - #{response.message}") - FAILURE - end - rescue Net::OpenTimeout, Net::ReadTimeout - retry if backoff?(retry_count: retry_count += 1, reason: 'timeout') - OpenTelemetry.logger.warn('Net::OpenTimeout/Net::ReadTimeout in MetricsExporter#send_bytes') - return FAILURE - rescue OpenSSL::SSL::SSLError - retry if backoff?(retry_count: retry_count += 1, reason: 'openssl_error') - OpenTelemetry.logger.warn('OpenSSL::SSL::SSLError in MetricsExporter#send_bytes') - return FAILURE - rescue SocketError - retry if backoff?(retry_count: retry_count += 1, reason: 'socket_error') - OpenTelemetry.logger.warn('SocketError in MetricsExporter#send_bytes') - return FAILURE - rescue SystemCallError => e - retry if backoff?(retry_count: retry_count += 1, reason: e.class.name) - OpenTelemetry.logger.warn('SystemCallError in MetricsExporter#send_bytes') - return FAILURE - rescue EOFError - retry if backoff?(retry_count: retry_count += 1, reason: 'eof_error') - OpenTelemetry.logger.warn('EOFError in MetricsExporter#send_bytes') - return FAILURE - rescue Zlib::DataError - retry if backoff?(retry_count: retry_count += 1, reason: 'zlib_error') - OpenTelemetry.logger.warn('Zlib::DataError in MetricsExporter#send_bytes') - return FAILURE - rescue StandardError => e - OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::MetricsExporter#send_bytes') - return FAILURE - end - ensure - # Reset timeouts to defaults for the next call. - @http.open_timeout = @timeout - @http.read_timeout = @timeout - @http.write_timeout = @timeout if WRITE_TIMEOUT_SUPPORTED - end - - def encode(metrics_data) - Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.encode( - Opentelemetry::Proto::Collector::Metrics::V1::ExportMetricsServiceRequest.new( - resource_metrics: metrics_data - .group_by(&:resource) - .map do |resource, scope_metrics| - Opentelemetry::Proto::Metrics::V1::ResourceMetrics.new( - resource: Opentelemetry::Proto::Resource::V1::Resource.new( - attributes: resource.attribute_enumerator.map { |key, value| as_otlp_key_value(key, value) } - ), - scope_metrics: scope_metrics - .group_by(&:instrumentation_scope) - .map do |instrumentation_scope, metrics| - Opentelemetry::Proto::Metrics::V1::ScopeMetrics.new( - scope: Opentelemetry::Proto::Common::V1::InstrumentationScope.new( - name: instrumentation_scope.name, - version: instrumentation_scope.version - ), - metrics: metrics.map { |sd| as_otlp_metrics(sd) } - ) - end - ) - end - ) - ) - rescue StandardError => e - OpenTelemetry.handle_error(exception: e, message: 'unexpected error in OTLP::MetricsExporter#encode') - nil - end - - # metrics_pb has following type of data: :gauge, :sum, :histogram, :exponential_histogram, :summary - # current metric sdk only implements instrument: :counter -> :sum, :histogram -> :histogram - # - # metrics [MetricData] - def as_otlp_metrics(metrics) - case metrics.instrument_kind - when :observable_gauge - Opentelemetry::Proto::Metrics::V1::Metric.new( - name: metrics.name, - description: metrics.description, - unit: metrics.unit, - gauge: Opentelemetry::Proto::Metrics::V1::Gauge.new( - aggregation_temporality: as_otlp_aggregation_temporality(metrics.aggregation_temporality), - data_points: metrics.data_points.map do |ndp| - number_data_point(ndp) - end - ) - ) - - when :counter, :up_down_counter - Opentelemetry::Proto::Metrics::V1::Metric.new( - name: metrics.name, - description: metrics.description, - unit: metrics.unit, - sum: Opentelemetry::Proto::Metrics::V1::Sum.new( - aggregation_temporality: as_otlp_aggregation_temporality(metrics.aggregation_temporality), - data_points: metrics.data_points.map do |ndp| - number_data_point(ndp) - end - ) - ) - - when :histogram - Opentelemetry::Proto::Metrics::V1::Metric.new( - name: metrics.name, - description: metrics.description, - unit: metrics.unit, - histogram: Opentelemetry::Proto::Metrics::V1::Histogram.new( - aggregation_temporality: as_otlp_aggregation_temporality(metrics.aggregation_temporality), - data_points: metrics.data_points.map do |hdp| - histogram_data_point(hdp) - end - ) - ) - end - end - - def as_otlp_aggregation_temporality(type) - case type - when :delta then Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_DELTA - when :cumulative then Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_CUMULATIVE - else Opentelemetry::Proto::Metrics::V1::AggregationTemporality::AGGREGATION_TEMPORALITY_UNSPECIFIED - end - end - - def histogram_data_point(hdp) - Opentelemetry::Proto::Metrics::V1::HistogramDataPoint.new( - attributes: hdp.attributes.map { |k, v| as_otlp_key_value(k, v) }, - start_time_unix_nano: hdp.start_time_unix_nano, - time_unix_nano: hdp.time_unix_nano, - count: hdp.count, - sum: hdp.sum, - bucket_counts: hdp.bucket_counts, - explicit_bounds: hdp.explicit_bounds, - exemplars: hdp.exemplars, - min: hdp.min, - max: hdp.max - ) - end - - def number_data_point(ndp) - Opentelemetry::Proto::Metrics::V1::NumberDataPoint.new( - attributes: ndp.attributes.map { |k, v| as_otlp_key_value(k, v) }, - as_int: ndp.value, - start_time_unix_nano: ndp.start_time_unix_nano, - time_unix_nano: ndp.time_unix_nano, - exemplars: ndp.exemplars # exemplars not implemented yet from metrics sdk - ) - end - - # may not need this - def reset - SUCCESS - end - - def shutdown(timeout: nil) - @shutdown = true - SUCCESS - end - end - end - end -end diff --git a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb b/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb deleted file mode 100644 index 85070a6f43..0000000000 --- a/exporter/otlp-metrics/lib/opentelemetry/exporter/otlp/util.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -module OpenTelemetry - module Exporter - module OTLP - # Util module provide essential functionality for exporter - module Util # rubocop:disable Metrics/ModuleLength - KEEP_ALIVE_TIMEOUT = 30 - RETRY_COUNT = 5 - ERROR_MESSAGE_INVALID_HEADERS = 'headers must be a String with comma-separated URL Encoded UTF-8 k=v pairs or a Hash' - DEFAULT_USER_AGENT = "OTel-OTLP-MetricsExporter-Ruby/#{OpenTelemetry::Exporter::OTLP::VERSION} Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM}; #{RUBY_ENGINE}/#{RUBY_ENGINE_VERSION})".freeze - - def http_connection(uri, ssl_verify_mode, certificate_file) - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = uri.scheme == 'https' - http.verify_mode = ssl_verify_mode - http.ca_file = certificate_file unless certificate_file.nil? - http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT - http - end - - def around_request - OpenTelemetry::Common::Utilities.untraced { yield } # rubocop:disable Style/ExplicitBlockArgument - end - - def as_otlp_key_value(key, value) - Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value(value)) - rescue Encoding::UndefinedConversionError => e - encoded_value = value.encode('UTF-8', invalid: :replace, undef: :replace, replace: '�') - OpenTelemetry.handle_error(exception: e, message: "encoding error for key #{key} and value #{encoded_value}") - Opentelemetry::Proto::Common::V1::KeyValue.new(key: key, value: as_otlp_any_value('Encoding Error')) - end - - def as_otlp_any_value(value) - result = Opentelemetry::Proto::Common::V1::AnyValue.new - case value - when String - result.string_value = value - when Integer - result.int_value = value - when Float - result.double_value = value - when true, false - 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 - result - end - - def prepare_headers(config_headers) - headers = case config_headers - when String then parse_headers(config_headers) - when Hash then config_headers.dup - else - raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS - end - - headers['User-Agent'] = "#{headers.fetch('User-Agent', '')} #{DEFAULT_USER_AGENT}".strip - - headers - end - - def measure_request_duration - start = Process.clock_gettime(Process::CLOCK_MONOTONIC) - begin - yield - ensure - stop = Process.clock_gettime(Process::CLOCK_MONOTONIC) - 1000.0 * (stop - start) - end - end - - def parse_headers(raw) - entries = raw.split(',') - raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if entries.empty? - - entries.each_with_object({}) do |entry, headers| - k, v = entry.split('=', 2).map(&CGI.method(:unescape)) - begin - k = k.to_s.strip - v = v.to_s.strip - rescue Encoding::CompatibilityError - raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS - rescue ArgumentError => e - raise e, ERROR_MESSAGE_INVALID_HEADERS - end - raise ArgumentError, ERROR_MESSAGE_INVALID_HEADERS if k.empty? || v.empty? - - headers[k] = v - end - end - - def backoff?(retry_count:, reason:, retry_after: nil) - return false if retry_count > 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 # rubocop:disable Style/RescueStandardError - nil - end - sleep_interval = nil unless sleep_interval&.positive? - end - sleep_interval ||= rand(2**retry_count) - - sleep(sleep_interval) - true - end - - def log_status(body) - status = Google::Rpc::Status.decode(body) - details = status.details.map do |detail| - klass_or_nil = ::Google::Protobuf::DescriptorPool.generated_pool.lookup(detail.type_name).msgclass - detail.unpack(klass_or_nil) if klass_or_nil - end.compact - OpenTelemetry.handle_error(message: "OTLP metrics_exporter received rpc.Status{message=#{status.message}, details=#{details}}") - rescue StandardError => e - OpenTelemetry.handle_error(exception: e, message: 'unexpected error decoding rpc.Status in OTLP::MetricsExporter#log_status') - end - - def handle_redirect(location); end - end - end - end -end diff --git a/exporter/otlp-metrics/test/opentelemetry/exporter/otlp/metrics_exporter_test.rb b/exporter/otlp-metrics/test/opentelemetry/exporter/otlp/metrics/metrics_exporter_test.rb similarity index 100% rename from exporter/otlp-metrics/test/opentelemetry/exporter/otlp/metrics_exporter_test.rb rename to exporter/otlp-metrics/test/opentelemetry/exporter/otlp/metrics/metrics_exporter_test.rb From 3ff81bdbc3112a69c41275f68153619b98e32209 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:51:58 -0700 Subject: [PATCH 07/14] test: Misc logs fixes (#1697) * test: Add LogRecordProcessor tests * test: Update observed_timestamp LogRecord test Tests for timestamp were updated to use Time objects, but observed_timestamp was not. Now, they both evaluate Time. They are turned into floats when converted to LogRecordData. * test: Exclude test directory from SimpleCov * style: Misc docs/spacing updates * test: Skip flaky JRuby test with link to fix issue --------- Co-authored-by: Matthew Wear --- .../test/opentelemetry/logs/logger_test.rb | 2 +- logs_api/test/test_helper.rb | 7 +++- .../opentelemetry/sdk/logs/log_record_data.rb | 2 +- .../sdk/logs/log_record_processor.rb | 4 +-- logs_sdk/lib/opentelemetry/sdk/logs/logger.rb | 2 ++ .../export/batch_log_record_processor_test.rb | 4 +++ .../sdk/logs/log_record_processor_test.rb | 33 +++++++++++++++++++ .../opentelemetry/sdk/logs/log_record_test.rb | 10 ++---- logs_sdk/test/test_helper.rb | 7 +++- 9 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 logs_sdk/test/opentelemetry/sdk/logs/log_record_processor_test.rb diff --git a/logs_api/test/opentelemetry/logs/logger_test.rb b/logs_api/test/opentelemetry/logs/logger_test.rb index 9fd35a651c..faf549f3d6 100644 --- a/logs_api/test/opentelemetry/logs/logger_test.rb +++ b/logs_api/test/opentelemetry/logs/logger_test.rb @@ -9,7 +9,7 @@ describe OpenTelemetry::Logs::Logger do let(:logger) { OpenTelemetry::Logs::Logger.new } - describe '#emit' do + describe '#on_emit' do it 'returns nil, as it is a no-op method' do assert_nil(logger.on_emit) end diff --git a/logs_api/test/test_helper.rb b/logs_api/test/test_helper.rb index 151c9213ec..a141bff9aa 100644 --- a/logs_api/test/test_helper.rb +++ b/logs_api/test/test_helper.rb @@ -5,7 +5,12 @@ # SPDX-License-Identifier: Apache-2.0 require 'simplecov' -SimpleCov.start { enable_coverage :branch } + +SimpleCov.start do + enable_coverage :branch + add_filter '/test/' +end + SimpleCov.minimum_coverage 85 require 'opentelemetry-logs-api' diff --git a/logs_sdk/lib/opentelemetry/sdk/logs/log_record_data.rb b/logs_sdk/lib/opentelemetry/sdk/logs/log_record_data.rb index 17d8b263fc..5a7e456d5c 100644 --- a/logs_sdk/lib/opentelemetry/sdk/logs/log_record_data.rb +++ b/logs_sdk/lib/opentelemetry/sdk/logs/log_record_data.rb @@ -13,7 +13,7 @@ module Logs :severity_text, # optional String :severity_number, # optional Integer :body, # optional String, Numeric, Boolean, Array, Hash{String => String, Numeric, Boolean, Array} - :attributes, # optional Hash{String => String, Numeric, Boolean, Array} + :attributes, # optional Hash{String => String, Numeric, Boolean, Array} :trace_id, # optional String (16-byte binary) :span_id, # optional String (8-byte binary) :trace_flags, # optional Integer (8-bit byte of bit flags) diff --git a/logs_sdk/lib/opentelemetry/sdk/logs/log_record_processor.rb b/logs_sdk/lib/opentelemetry/sdk/logs/log_record_processor.rb index 6b51b16915..6cca12ea14 100644 --- a/logs_sdk/lib/opentelemetry/sdk/logs/log_record_processor.rb +++ b/logs_sdk/lib/opentelemetry/sdk/logs/log_record_processor.rb @@ -26,7 +26,7 @@ def on_emit(log_record, context); end # the process after an invocation, but before the `Processor` exports # the completed spans. # - # @param [Numeric] timeout An optional timeout in seconds. + # @param [optional Numeric] timeout An optional timeout in seconds. # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred. def force_flush(timeout: nil) @@ -35,7 +35,7 @@ def force_flush(timeout: nil) # Called when {LoggerProvider#shutdown} is called. # - # @param [Numeric] timeout An optional timeout in seconds. + # @param [optional Numeric] timeout An optional timeout in seconds. # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred. def shutdown(timeout: nil) diff --git a/logs_sdk/lib/opentelemetry/sdk/logs/logger.rb b/logs_sdk/lib/opentelemetry/sdk/logs/logger.rb index 51f096c143..231999a33c 100644 --- a/logs_sdk/lib/opentelemetry/sdk/logs/logger.rb +++ b/logs_sdk/lib/opentelemetry/sdk/logs/logger.rb @@ -34,6 +34,8 @@ def initialize(name, version, logger_provider) # @param [optional OpenTelemetry::Trace::SpanContext] span_context The # OpenTelemetry::Trace::SpanContext to associate with the # {LogRecord}. + # @param [optional String] severity_text Original string representation of + # the severity as it is known at the source. Also known as log level. # @param severity_number [optional Integer] Numerical value of the # severity. Smaller numerical values correspond to less severe events # (such as debug events), larger numerical values correspond to more diff --git a/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb b/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb index 08aee76e85..64e81477a3 100644 --- a/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb +++ b/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb @@ -204,6 +204,10 @@ def to_log_record_data end it 'logs a warning if a log record was emitted after the buffer is full' do + # This will be fixed as part of Issue #1701 + # https://github.com/open-telemetry/opentelemetry-ruby/issues/1701 + skip if RUBY_ENGINE == 'jruby' + mock_otel_logger = Minitest::Mock.new mock_otel_logger.expect(:warn, nil, ['1 log record(s) dropped. Reason: buffer-full']) diff --git a/logs_sdk/test/opentelemetry/sdk/logs/log_record_processor_test.rb b/logs_sdk/test/opentelemetry/sdk/logs/log_record_processor_test.rb new file mode 100644 index 0000000000..9986b1915f --- /dev/null +++ b/logs_sdk/test/opentelemetry/sdk/logs/log_record_processor_test.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::SDK::Logs::LogRecordProcessor do + let(:processor) { OpenTelemetry::SDK::Logs::LogRecordProcessor.new } + let(:log_record) { nil } + let(:context) { nil } + + it 'implements #on_emit' do + processor.on_emit(log_record, context) + end + + it 'implements #force_flush' do + processor.force_flush + end + + it 'returns a success code when #force_flush is called' do + assert(OpenTelemetry::SDK::Logs::Export::SUCCESS, processor.force_flush) + end + + it 'implements #shutdown' do + processor.shutdown + end + + it 'returns a success code when #shutdown is called' do + assert(OpenTelemetry::SDK::Logs::Export::SUCCESS, processor.shutdown) + end +end diff --git a/logs_sdk/test/opentelemetry/sdk/logs/log_record_test.rb b/logs_sdk/test/opentelemetry/sdk/logs/log_record_test.rb index 7ba19048fe..ca38350e1f 100644 --- a/logs_sdk/test/opentelemetry/sdk/logs/log_record_test.rb +++ b/logs_sdk/test/opentelemetry/sdk/logs/log_record_test.rb @@ -15,7 +15,8 @@ describe '#initialize' do describe 'observed_timestamp' do describe 'when observed_timestamp is present' do - let(:observed_timestamp) { '1692661486.2841358' } + let(:current_time) { Time.now } + let(:observed_timestamp) { current_time + 1 } let(:args) { { observed_timestamp: observed_timestamp } } it 'is equal to observed_timestamp' do @@ -26,13 +27,8 @@ refute_equal(log_record.timestamp, log_record.observed_timestamp) end - # Process.clock_gettime is used to set the current time - # That method returns a Float. Since the stubbed value of - # observed_timestamp is a String, we can know the the - # observed_timestamp was not set to the value of Process.clock_gettime - # by making sure its value is not a Float. it 'is not equal to the current time' do - refute_instance_of(Float, log_record.observed_timestamp) + refute_equal(current_time, log_record.observed_timestamp) end end diff --git a/logs_sdk/test/test_helper.rb b/logs_sdk/test/test_helper.rb index 59d743987f..7f11ee9508 100644 --- a/logs_sdk/test/test_helper.rb +++ b/logs_sdk/test/test_helper.rb @@ -5,7 +5,12 @@ # SPDX-License-Identifier: Apache-2.0 require 'simplecov' -SimpleCov.start { enable_coverage :branch } + +SimpleCov.start do + enable_coverage :branch + add_filter '/test/' +end + SimpleCov.minimum_coverage 85 require 'opentelemetry-logs-api' From eed70608e24e0bcae13ee51b28ba9edb36e32f60 Mon Sep 17 00:00:00 2001 From: Andrew Widdersheim Date: Tue, 10 Sep 2024 13:09:58 -0400 Subject: [PATCH 08/14] Fix README reference (#1691) There is no `SimpleSpanExporter`, only a `SimpleSpanProcessor`. Co-authored-by: Matthew Wear --- sdk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/README.md b/sdk/README.md index 97da0a9884..84cb8a3acb 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -59,7 +59,7 @@ OpenTelemetry::SDK.configure # c.use 'OpenTelemetry::Instrumentation::Net::HTTP' # end # -# Note that the SimpleSpanExporter is not recommended for use in production. +# Note that the SimpleSpanProcessor is not recommended for use in production. # To start a trace you need to get a Tracer from the TracerProvider From d9b29727c3aade558e3b06abbf0c3a8d4fddf197 Mon Sep 17 00:00:00 2001 From: Xuan <112967240+xuan-cao-swi@users.noreply.github.com> Date: Fri, 13 Sep 2024 17:38:38 -0400 Subject: [PATCH 09/14] chore: add markdown link & lint ci (#1684) * feat: add markdwon link lint ci * update word --- .github/workflows/ci-markdown-link.yml | 17 +++++++++ .github/workflows/ci-markdownlint.yml | 20 +++++++++++ .markdown-link-check.json | 10 ++++++ .markdownlint.json | 13 +++++++ CONTRIBUTING.md | 49 +++++++++++++------------- README.md | 1 + api/README.md | 3 +- common/README.md | 2 +- examples/http/README.md | 4 ++- examples/metrics_sdk/README.md | 12 +++---- exporter/jaeger/README.md | 3 +- exporter/otlp-common/README.md | 3 -- exporter/otlp-grpc/README.md | 3 +- exporter/otlp-http/README.md | 3 +- exporter/otlp-metrics/README.md | 38 ++++++++++++++++++-- exporter/zipkin/README.md | 3 +- metrics_api/README.md | 2 +- metrics_sdk/README.md | 5 +-- propagator/b3/README.md | 2 +- propagator/jaeger/README.md | 2 +- registry/README.md | 2 +- sdk/README.md | 3 +- sdk_experimental/README.md | 1 - semantic_conventions/README.md | 4 +-- test_helpers/README.md | 5 +-- 25 files changed, 148 insertions(+), 62 deletions(-) create mode 100644 .github/workflows/ci-markdown-link.yml create mode 100644 .github/workflows/ci-markdownlint.yml create mode 100644 .markdown-link-check.json create mode 100644 .markdownlint.json diff --git a/.github/workflows/ci-markdown-link.yml b/.github/workflows/ci-markdown-link.yml new file mode 100644 index 0000000000..7b42be0c1e --- /dev/null +++ b/.github/workflows/ci-markdown-link.yml @@ -0,0 +1,17 @@ +name: Markdown Link Check + +on: + pull_request: + +jobs: + markdown-link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: "Markdown Link Check" + uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + config-file: '.markdown-link-check.json' + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' diff --git a/.github/workflows/ci-markdownlint.yml b/.github/workflows/ci-markdownlint.yml new file mode 100644 index 0000000000..f91609fdb9 --- /dev/null +++ b/.github/workflows/ci-markdownlint.yml @@ -0,0 +1,20 @@ +name: Markdown Lint Check + +on: + pull_request: + +jobs: + markdownlint-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # equivalent cli: markdownlint-cli2 "**/*.md" "#**/CHANGELOG.md" --config .markdownlint.json + - name: "Markdown Lint Check" + uses: DavidAnson/markdownlint-cli2-action@v16 + with: + fix: false + globs: | + **/*.md + !**/CHANGELOG.md + continue-on-error: true diff --git a/.markdown-link-check.json b/.markdown-link-check.json new file mode 100644 index 0000000000..12284d673f --- /dev/null +++ b/.markdown-link-check.json @@ -0,0 +1,10 @@ +{ + "ignorePatterns": [ + { + "pattern": "^http://localhost" + } + ], + "timeout": "5s", + "retryOn429": true, + "aliveStatusCodes": [200, 206, 429] +} diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000000..10b3203b8a --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,13 @@ +{ + "emphasis-style": false, + "line-length": false, + "link-fragments": false, + "list-marker-space": false, + "no-emphasis-as-heading": false, + "no-hard-tabs": false, + "no-inline-html": false, + "no-trailing-punctuation": false, + "no-trailing-spaces": true, + "custom-rules-below-this-point": false, + "trim-code-block-and-unindent": true +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e0bf8446a..496bd15304 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ conforms to the specification, but the interface and structure are flexible. It is preferable to have contributions follow the idioms of the language rather than conform to specific API names or argument patterns in the spec. -For a deeper discussion, see: https://github.com/open-telemetry/opentelemetry-specification/issues/165 +For a deeper discussion, see: ## Getting started @@ -40,6 +40,7 @@ git clone git@github.com:YOUR_GITHUB_NAME/opentelemetry-ruby.git ``` or + ```sh git clone https://github.com/YOUR_GITHUB_NAME/opentelemetry-ruby.git ``` @@ -66,11 +67,11 @@ _Setting up a running Ruby environment is outside the scope of this document._ This repository contains multiple Ruby gems: - * `opentelemetry-api` located in the `api` directory - * `opentelemetry-sdk` located in the `sdk` directory - * Various instrumentation gems located in subdirectories of `instrumentation` - * Various exporter gems located in subdirectories of `exporter` - * `opentelemetry-resource_detectors` located in the `resource_detectors` directory +* `opentelemetry-api` located in the `api` directory +* `opentelemetry-sdk` located in the `sdk` directory +* Various instrumentation gems located in subdirectories of `instrumentation` +* Various exporter gems located in subdirectories of `exporter` +* `opentelemetry-resource_detectors` located in the `resource_detectors` directory Each of these gems has its configuration and tests. @@ -89,10 +90,10 @@ configuration details. The services provided include: - * `app` - main container environment scoped to the `/app` directory. Used +* `app` - main container environment scoped to the `/app` directory. Used primarily to build and tag the `opentelemetry/opentelemetry-ruby:latest` image. - * `api` - convenience environment scoped to the `api` gem in the `/app/api` directory. - * `sdk` - convenience environment scoped to the `sdk` gem in the `/app/sdk` directory. +* `api` - convenience environment scoped to the `api` gem in the `/app/api` directory. +* `sdk` - convenience environment scoped to the `sdk` gem in the `/app/sdk` directory. To test using Docker: @@ -154,8 +155,8 @@ to ensure that your code complies before opening a pull request. We also use Yard to generate class documentation automatically. Among other things, this means: - * Methods and arguments should include the appropriate type annotations - * You can use markdown formatting in your documentation comments +* Methods and arguments should include the appropriate type annotations +* You can use markdown formatting in your documentation comments You can generate the docs locally to see the results, by running: @@ -249,7 +250,7 @@ Releases are normally performed using GitHub Actions. * For each gem, it will create a release tag and a GitHub release. * It will build and push the gems to rubygems. * It will build the docs and push them to - https://open-telemetry.github.io/opentelemetry-ruby + * If the releases succeed, the script will update the release pull request with the results and change its label to `release: complete`. If something went wrong, the script will, if possible, report the error @@ -269,15 +270,15 @@ review the release logs for the GitHub Actions workflows. There are four GitHub actions workflows related to releases. - * `Open release request` is the main release entrypoint, and is used to open +* `Open release request` is the main release entrypoint, and is used to open a release pull request. If something goes wrong with this process, the logs will appear in the workflow run. - * `Force release` is generally used only to restart a failed release. - * `[release hook] Update open releases` is run on pushes to the main branch, +* `Force release` is generally used only to restart a failed release. +* `[release hook] Update open releases` is run on pushes to the main branch, and pushes warnings to open release pull requests if you make modifications before triggering the release (i.e. because you might need to update the changelogs.) - * `[release hook] Process release` is the main release automation script and +* `[release hook] Process release` is the main release automation script and is run when a pull request is closed. If it determines that a release pull request was merged, it kicks off the release process for the affected gems. It also updates the label on a closed release pull request. Finally, it @@ -320,7 +321,7 @@ changed gems. To force-release, assuming the version and changelog are already modified: -``` +```sh toys release perform --rubygems-api-key=$API_KEY $GEM_NAME $GEM_VERSION ``` @@ -338,17 +339,17 @@ not correspond exactly to the gem name. For releases to succeed, new gems MUST include the following: - * The above configuration entry. - * The `*.gemspec` file, with the name matching the gem name. - * A `version.rb` file in the standard location, or in a location listed in +* The above configuration entry. +* The `*.gemspec` file, with the name matching the gem name. +* A `version.rb` file in the standard location, or in a location listed in the configuration. - * A `CHANGELOG.md` file. - * A `yard` rake task. +* A `CHANGELOG.md` file. +* A `yard` rake task. [cncf-cla]: https://identity.linuxfoundation.org/projects/cncf [github-draft]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ [kube-github-workflow-pr]: https://github.com/kubernetes/community/blob/master/contributors/guide/github-workflow.md#7-create-a-pull-request -[otel-contributor-guide]: https://github.com/open-telemetry/community/blob/master/CONTRIBUTING.md -[otel-github-workflow]: https://github.com/open-telemetry/community/blob/master/CONTRIBUTING.md#github-workflow +[otel-contributor-guide]: https://github.com/open-telemetry/community/blob/main/guides/contributor/README.md +[otel-github-workflow]: https://github.com/open-telemetry/community/blob/main/guides/contributor/processes.md#workflows [otel-lib-guidelines]: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/library-guidelines.md [otel-specification]: https://github.com/open-telemetry/opentelemetry-specification diff --git a/README.md b/README.md index 93c256a470..76c7b82084 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ the [Ruby language](https://www.ruby-lang.org/en/downloads/branches/). - For more information on OpenTelemetry, visit: - For help or feedback on this project, join us in [GitHub Discussions][discussions-url]. +- For more examples, check [SDK example][examples-github]. ## License diff --git a/api/README.md b/api/README.md index b44d1ff8a0..966295ed9e 100644 --- a/api/README.md +++ b/api/README.md @@ -21,7 +21,7 @@ data should depend only on `opentelemetry-api`, deferring the choice of concrete Install the gem using: -``` +```sh gem install opentelemetry-api ``` @@ -58,7 +58,6 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-api` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. - [opentelemetry-home]: https://opentelemetry.io [bundler-home]: https://bundler.io [repo-github]: https://github.com/open-telemetry/opentelemetry-ruby diff --git a/common/README.md b/common/README.md index 8e1535ea3d..ab0b0aeae1 100644 --- a/common/README.md +++ b/common/README.md @@ -16,7 +16,7 @@ The `opentelemetry-common` gem provides common helpers for semantic conventions, Install the gem using: -``` +```sh gem install opentelemetry-common ``` diff --git a/examples/http/README.md b/examples/http/README.md index ca69a2a146..0c79d228be 100644 --- a/examples/http/README.md +++ b/examples/http/README.md @@ -11,19 +11,21 @@ This is a simple example that demonstrates tracing an HTTP request from client t ### Running the example Install gems + ```sh bundle install ``` Start the server + ```sh ruby server.rb ``` In a separate terminal window, run the client to make a single request: + ```sh ruby client.rb ``` You should see console exporter output for both the client and server sessions. - diff --git a/examples/metrics_sdk/README.md b/examples/metrics_sdk/README.md index 46f7b94efa..3ba206d783 100644 --- a/examples/metrics_sdk/README.md +++ b/examples/metrics_sdk/README.md @@ -1,6 +1,6 @@ # OpenTelemetry Ruby Metrics SDK Example -### metrics_collect.rb +## metrics_collect.rb Run the script to see the metric data from console @@ -8,7 +8,7 @@ Run the script to see the metric data from console ruby metrics_collect.rb ``` -### metrics_collect_otlp.rb +## metrics_collect_otlp.rb **WARN: this example doesn't work on alpine aarch64 container due to grpc installation issues.** @@ -16,7 +16,7 @@ This example tests both the metrics sdk and the metrics otlp http exporter. You can view the metrics in your favored backend (e.g. jaeger). -#### 1. Set up the local opentelemetry-collector. +### 1. Set up the local opentelemetry-collector. Given you have a `config.yml` file in your current directory and Docker is installed on your machine, run the following commands to pull the collector image and run the collector. @@ -56,9 +56,9 @@ service: More information on how to setup the OTel collector can be found in the in [quick start docs](https://opentelemetry.io/docs/collector/quick-start/). -#### 2. Assign the endpoint value to your destination address +### 2. Assign the endpoint value to your destination address -``` +```sh # Using environment variable ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] = 'http://host.docker.internal:4318/v1/metrics' @@ -66,7 +66,7 @@ ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] = 'http://host.docker.internal:4318/v export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://host.docker.internal:4318/v1/metrics ``` -#### 3. Run the script to send metric data to OTLP collector +### 3. Run the script to send metric data to OTLP collector ```sh ruby metrics_collect_otlp.rb diff --git a/exporter/jaeger/README.md b/exporter/jaeger/README.md index 657d2f21f1..72a4a45f77 100644 --- a/exporter/jaeger/README.md +++ b/exporter/jaeger/README.md @@ -18,7 +18,7 @@ Generally, *libraries* that produce telemetry data should avoid depending direct Install the gem using: -``` +```sh gem install opentelemetry-sdk gem install opentelemetry-exporter-jaeger ``` @@ -104,7 +104,6 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-exporter-jaeger` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. - [jaeger-home]: https://www.jaegertracing.io [opentelemetry-home]: https://opentelemetry.io [bundler-home]: https://bundler.io diff --git a/exporter/otlp-common/README.md b/exporter/otlp-common/README.md index ec52cea0e6..48310702a1 100644 --- a/exporter/otlp-common/README.md +++ b/exporter/otlp-common/README.md @@ -26,12 +26,9 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-exporter-otlp-common` 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/main/LICENSE -[examples-github]: https://github.com/open-telemetry/opentelemetry-ruby/tree/main/examples [ruby-sig]: https://github.com/open-telemetry/community#ruby-sig [community-meetings]: https://github.com/open-telemetry/community#community-meetings [discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions diff --git a/exporter/otlp-grpc/README.md b/exporter/otlp-grpc/README.md index ddb20bd8dd..fd8a40e2bc 100644 --- a/exporter/otlp-grpc/README.md +++ b/exporter/otlp-grpc/README.md @@ -22,7 +22,7 @@ This gem supports the [v0.11.0 release](https://github.com/open-telemetry/opente Install the gem using: -``` +```sh gem install opentelemetry-sdk gem install opentelemetry-exporter-otlp-grpc ``` @@ -82,7 +82,6 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-exporter-otlp-grpc` 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 diff --git a/exporter/otlp-http/README.md b/exporter/otlp-http/README.md index 79514b946a..7b1597b9b4 100644 --- a/exporter/otlp-http/README.md +++ b/exporter/otlp-http/README.md @@ -22,7 +22,7 @@ This gem supports the [v0.11.0 release](https://github.com/open-telemetry/opente Install the gem using: -``` +```sh gem install opentelemetry-sdk gem install opentelemetry-exporter-otlp-http ``` @@ -92,7 +92,6 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-exporter-otlp-http` 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 diff --git a/exporter/otlp-metrics/README.md b/exporter/otlp-metrics/README.md index 3bbca793dd..4d8986546f 100644 --- a/exporter/otlp-metrics/README.md +++ b/exporter/otlp-metrics/README.md @@ -18,6 +18,41 @@ Generally, *libraries* that produce telemetry data should avoid depending direct This gem supports the [v0.20.0 release][otel-proto-release] of OTLP. +## Prerequisite + +The exporter-oltp-metrics depends on two gems that have not been officially released: opentelemetry-metrics-sdk and opentelemetry-metrics-api. + +Within the .gemspec file, these gems are not listed as dependencies. However, for users who need utilize this metrics exporter, they must first install and load these two gems before they can use the exporter. + +To facilitate this, there are couple recommended approaches: + +### 1. Download the source code + +1. Download the [opentelemetry-ruby](https://github.com/open-telemetry/opentelemetry-ruby). +2. Navigate to subfolder, then build the [metrics_sdk](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/metrics_sdk) and [metrics_api](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/metrics_api). +3. Execute `gem build *.gemspec`. +4. Lastly, install the built gem into the system. + +### 2. Using `path:` option in Gemfile with downloaded source code + +git clone [opentelemetry-ruby](https://github.com/open-telemetry/opentelemetry-ruby) first, then use Gemfile + +```ruby +# Gemfile +source 'https://rubygems.org' +gem 'opentelemetry-metrics-api', path: "opentelemetry-ruby/metrics_api" +gem 'opentelemetry-metrics-sdk', path: "opentelemetry-ruby/metrics_sdk" +``` + +### 3. Using `git:` option in Gemfile + +```ruby +# Gemfile +source 'https://rubygems.org' +gem 'opentelemetry-metrics-api', git: "https://github.com/open-telemetry/opentelemetry-ruby", glob: 'metrics_api/*.gemspec' +gem 'opentelemetry-metrics-sdk', git: "https://github.com/open-telemetry/opentelemetry-ruby", glob: 'metrics_sdk/*.gemspec' +``` + ## How do I get started? Install the gem using: @@ -66,7 +101,7 @@ The collector exporter can be configured explicitly in code, or via environment | Parameter | Environment variable | Default | | ------------------- | -------------------------------------------- | ----------------------------------- | | `endpoint:` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `"http://localhost:4318/v1/metrics"` | -| `certificate_file: `| `OTEL_EXPORTER_OTLP_CERTIFICATE` | | +| `certificate_file:`| `OTEL_EXPORTER_OTLP_CERTIFICATE` | | | `headers:` | `OTEL_EXPORTER_OTLP_HEADERS` | | | `compression:` | `OTEL_EXPORTER_OTLP_COMPRESSION` | `"gzip"` | | `timeout:` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10` | @@ -75,7 +110,6 @@ The collector exporter can be configured explicitly in code, or via environment `ssl_verify_mode:` parameter values should be flags for server certificate verification: `OpenSSL::SSL:VERIFY_PEER` and `OpenSSL::SSL:VERIFY_NONE` are acceptable. These values can also be set using the appropriately named environment variables as shown where `VERIFY_PEER` will take precedence over `VERIFY_NONE`. Please see [the Net::HTTP docs](https://ruby-doc.org/stdlib-2.7.6/libdoc/net/http/rdoc/Net/HTTP.html#verify_mode) for more information about these flags. - ## How can I get involved? The `opentelemetry-exporter-otlp-metrics` gem source is [on github][repo-github], along with related gems including `opentelemetry-metrics-sdk`. diff --git a/exporter/zipkin/README.md b/exporter/zipkin/README.md index fd7cdde0a8..b568ba7374 100644 --- a/exporter/zipkin/README.md +++ b/exporter/zipkin/README.md @@ -18,7 +18,7 @@ Generally, *libraries* that produce telemetry data should avoid depending direct Install the gem using: -``` +```sh gem install opentelemetry-sdk gem install opentelemetry-exporter-zipkin ``` @@ -80,7 +80,6 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-exporter-zipkin` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. - [zipkin-home]: https://zipkin.io/ [opentelemetry-home]: https://opentelemetry.io [bundler-home]: https://bundler.io diff --git a/metrics_api/README.md b/metrics_api/README.md index 8656711979..36bc4e39f8 100644 --- a/metrics_api/README.md +++ b/metrics_api/README.md @@ -24,7 +24,7 @@ This code is still under development and is not a complete implementation of the Install the gem using: -``` +```sh gem install opentelemetry-metrics-api ``` diff --git a/metrics_sdk/README.md b/metrics_sdk/README.md index feb8e89aa7..86a8e95b00 100644 --- a/metrics_sdk/README.md +++ b/metrics_sdk/README.md @@ -15,6 +15,7 @@ Metrics is one of the core signals in OpenTelemetry. This package allows you to This gem does not have a full implementation of the Metrics SDK specification. The work is in progress. At this time, you should be able to: + * Create synchronous: * counters * up down counters @@ -26,6 +27,7 @@ At this time, you should be able to: * Use delta aggregation temporality We do not yet have support for: + * Asynchronous instruments * Cumulative aggregation temporality * Metrics Views @@ -41,7 +43,7 @@ Until the Ruby implementation of OpenTelemetry Metrics becomes stable, the funct Install the gems using: -``` +```sh gem install opentelemetry-metrics-sdk gem install opentelemetry-sdk ``` @@ -97,7 +99,6 @@ During this experimental stage, we're looking for lots of community feedback abo The `opentelemetry-metrics-sdk` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. - [metrics-sdk]: https://opentelemetry.io/docs/specs/otel/metrics/sdk/ [opentelemetry-home]: https://opentelemetry.io [bundler-home]: https://bundler.io diff --git a/propagator/b3/README.md b/propagator/b3/README.md index 721899fae5..5f850bf68d 100644 --- a/propagator/b3/README.md +++ b/propagator/b3/README.md @@ -20,7 +20,7 @@ This gem can be used with any OpenTelemetry SDK implementation. This can be the Install the gem using: -``` +```sh gem install opentelemetry-propagator-b3 ``` diff --git a/propagator/jaeger/README.md b/propagator/jaeger/README.md index 66191bd177..b7ccf15f18 100644 --- a/propagator/jaeger/README.md +++ b/propagator/jaeger/README.md @@ -19,7 +19,7 @@ This gem can be used with any OpenTelemetry SDK implementation. This can be the Install the gem using: -``` +```sh gem install opentelemetry-propagator-jaeger ``` diff --git a/registry/README.md b/registry/README.md index f18dc59aba..90aa678b6a 100644 --- a/registry/README.md +++ b/registry/README.md @@ -4,7 +4,7 @@ The instrumentation Registry contains information about available instrumentatio The Registry allows for instrumentation to avoid depending directly on a specific SDK implementation. -The SDK depends on the Registry, the instrumentation Base class depends on the Registry, and auto instrumentation libraries extend the instrumentation Base class. +The SDK depends on the Registry, the instrumentation Base class depends on the Registry, and auto instrumentation libraries extend the instrumentation Base class. The motivation for decoupling the Registry (and by extension the instrumentation) from a specific SDK implementation means that anyone can implement their own OpenTelemetry API compatible SDK, and they could continue to use community made instrumentation. diff --git a/sdk/README.md b/sdk/README.md index 84cb8a3acb..4efbb220b5 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -24,7 +24,7 @@ produce telemetry data should generally depend only on Install the gem using: -``` +```sh gem install opentelemetry-sdk ``` @@ -91,7 +91,6 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-sdk` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. - [opentelemetry-home]: https://opentelemetry.io [bundler-home]: https://bundler.io [repo-github]: https://github.com/open-telemetry/opentelemetry-ruby diff --git a/sdk_experimental/README.md b/sdk_experimental/README.md index cd6c999f13..6dd7017193 100644 --- a/sdk_experimental/README.md +++ b/sdk_experimental/README.md @@ -5,4 +5,3 @@ here: * `traceresponse` propagator (in [w3c `trace-context` editor's draft](https://w3c.github.io/trace-context/)) * Consistent Probability Sampler (marked as `experimental` in [opentelemetry-specification)](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/tracestate-probability-sampling.md)) - diff --git a/semantic_conventions/README.md b/semantic_conventions/README.md index 72bad0149f..4344a61d28 100644 --- a/semantic_conventions/README.md +++ b/semantic_conventions/README.md @@ -16,7 +16,7 @@ The `opentelemetry-semantic_conventions` gem provides auto-generated constants t Install the gem using: -``` +```sh gem install opentelemetry-semantic_conventions ``` @@ -45,4 +45,4 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-semantic_conventions` gem is distributed under the Apache 2.0 license. See LICENSE for more information. [discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions -[semantic-conventions]: https://github.com/open-telemetry/opentelemetry-specification/tree/main/semantic_conventions +[semantic-conventions]: https://github.com/open-telemetry/opentelemetry-specification/tree/v1.20.0/semantic_conventions diff --git a/test_helpers/README.md b/test_helpers/README.md index 9b675e26cd..44d697f210 100644 --- a/test_helpers/README.md +++ b/test_helpers/README.md @@ -16,13 +16,12 @@ The `opentelemetry-test-helpers` gem is home to commonly used snippets of test c Install the gem using: -``` +```sh gem install opentelemetry-test-helpers ``` Or, if you use [bundler][bundler-home], include `opentelemetry-test-helpers` in your `Gemfile`. - ## How can I get involved? The `opentelemetry-test-helpers` gem source is [on github][repo-github], along with related gems including `opentelemetry-api`. @@ -33,12 +32,10 @@ The OpenTelemetry Ruby gems are maintained by the OpenTelemetry-Ruby special int The `opentelemetry-test-helpers` gem is distributed under the Apache 2.0 license. See [LICENSE][license-github] for more information. - [opentelemetry-home]: https://opentelemetry.io [bundler-home]: https://bundler.io [repo-github]: https://github.com/open-telemetry/opentelemetry-ruby [license-github]: https://github.com/open-telemetry/opentelemetry-ruby/blob/main/LICENSE -[examples-github]: https://github.com/open-telemetry/opentelemetry-ruby/tree/main/examples [ruby-sig]: https://github.com/open-telemetry/community#ruby-sig [community-meetings]: https://github.com/open-telemetry/community#community-meetings [discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions From e16803cf5f995eb53e9e715185d130d72b7bbbac Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:06:05 -0700 Subject: [PATCH 10/14] chore: Add dependabot.yml (#1680) * chore: Add dependabot Check for weekly updates to bundler on each gem and daily updates to GHA * chore: Move dependabot.yml to .github --------- Co-authored-by: Matthew Wear --- .github/dependabot.yml | 91 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..2e774171f0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,91 @@ +--- +version: 2 +updates: +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily +- package-ecosystem: bundler + directory: "/" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/api" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/common" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/exporter/jaeger" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/exporter/otlp" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/exporter/otlp-common" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/exporter/otlp-grpc" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/exporter/otlp-http" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/exporter/otlp-metrics" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/exporter/zipkin" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/logs_api" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/logs_sdk" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/metrics_api" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/metrics_sdk" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/propagator/b3" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/propagator/jaeger" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/registry" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/sdk" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/sdk_experimental" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/semantic_conventions" + schedule: + interval: weekly +- package-ecosystem: bundler + directory: "/" + schedule: + interval: weekly From cdce3cc1195a35cbaa48d00bea60e5fa64e19ab6 Mon Sep 17 00:00:00 2001 From: Gustavo Pantuza Date: Tue, 1 Oct 2024 14:08:59 -0300 Subject: [PATCH 11/14] Adds on_ending callback to allow processors to mutate spans before End operation (#1713) * feature: adds on_ending method as an optional callback for span processors Signed-off-by: Gustavo Pantuza * feature: calls every span processor that has on_ending implemented right after setting the end timestamp Signed-off-by: Gustavo Pantuza * test: adds tests for the new on_ending method from span processors Signed-off-by: Gustavo Pantuza * docs: Informs the on_ending callback is an experimental features from official spec Signed-off-by: Gustavo Pantuza * Update sdk/lib/opentelemetry/sdk/trace/span_processor.rb Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> * Update sdk/lib/opentelemetry/sdk/trace/span_processor.rb Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> * refactor: switch method name from on_ending to on_finish to comply with project name strategy Signed-off-by: Gustavo Pantuza --------- Signed-off-by: Gustavo Pantuza Co-authored-by: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> --- sdk/lib/opentelemetry/sdk/trace/span.rb | 3 +++ .../opentelemetry/sdk/trace/span_processor.rb | 18 ++++++++++++++++++ .../sdk/trace/span_processor_test.rb | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/sdk/lib/opentelemetry/sdk/trace/span.rb b/sdk/lib/opentelemetry/sdk/trace/span.rb index 274ac4c24d..c1f32657f8 100644 --- a/sdk/lib/opentelemetry/sdk/trace/span.rb +++ b/sdk/lib/opentelemetry/sdk/trace/span.rb @@ -271,6 +271,9 @@ def finish(end_timestamp: nil) return self end @end_timestamp = relative_timestamp(end_timestamp) + @span_processors.each do |processor| + processor.on_finishing(self) if processor.respond_to?(:on_finishing) + end @attributes = validated_attributes(@attributes).freeze @events.freeze @links.freeze diff --git a/sdk/lib/opentelemetry/sdk/trace/span_processor.rb b/sdk/lib/opentelemetry/sdk/trace/span_processor.rb index f1874f6504..2a60c3ba53 100644 --- a/sdk/lib/opentelemetry/sdk/trace/span_processor.rb +++ b/sdk/lib/opentelemetry/sdk/trace/span_processor.rb @@ -23,6 +23,24 @@ class SpanProcessor # started span. def on_start(span, parent_context); end + # The on_finishing method is an experimental feature and may have breaking changes. + # The OpenTelemetry specification defines it as "On Ending". As `end` is a reserved + # keyword in Ruby, we are using `on_finishing` instead. + # + # Called when a {Span} is ending, after the end timestamp has been set + # but before span becomes immutable. This allows for updating the span + # by setting attributes or adding links and events. + # + # This method is called synchronously and should not block the current + # thread nor throw exceptions. + # + # This method is optional on the Span Processor interface. It will only + # get called if it exists within the processor. + # + # @param [Span] span the {Span} that just is ending (still mutable). + # @return [void] + def on_finishing(span); end + # Called when a {Span} is ended, if the {Span#recording?} # returns true. # diff --git a/sdk/test/opentelemetry/sdk/trace/span_processor_test.rb b/sdk/test/opentelemetry/sdk/trace/span_processor_test.rb index 262ad53b92..2bfc1532ba 100644 --- a/sdk/test/opentelemetry/sdk/trace/span_processor_test.rb +++ b/sdk/test/opentelemetry/sdk/trace/span_processor_test.rb @@ -15,6 +15,10 @@ processor.on_start(span, context) end + it 'implements #on_finishing' do + processor.on_finishing(span) + end + it 'implements #on_finish' do processor.on_finish(span) end From 3c356fcc963f0014feedeff8c8cf93543a80c89f Mon Sep 17 00:00:00 2001 From: Kayla Reopelle <87386821+kaylareopelle@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:26:08 -0700 Subject: [PATCH 12/14] test: Fix intermittent test failures (#1730) Some tests in BatchLogRecordProcessor had timing issues due to the work method. Since we don't rely on that method for these tests, stub the work method to do nothing. Co-authored-by: Tanna McClure Co-authored-by: Matthew Wear --- .../export/batch_log_record_processor_test.rb | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb b/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb index 64e81477a3..a7ed0c3620 100644 --- a/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb +++ b/logs_sdk/test/opentelemetry/sdk/logs/export/batch_log_record_processor_test.rb @@ -188,19 +188,19 @@ def to_log_record_data it 'removes the older log records from the batch if full' do processor = BatchLogRecordProcessor.new(TestExporter.new, max_queue_size: 1, max_export_batch_size: 1) - older_log_record = TestLogRecord.new - newer_log_record = TestLogRecord.new - newest_log_record = TestLogRecord.new + # Don't actually try to export, we're looking at the log records array + processor.stub(:work, nil) do + older_log_record = TestLogRecord.new + newest_log_record = TestLogRecord.new - processor.on_emit(older_log_record, mock_context) - processor.on_emit(newer_log_record, mock_context) - processor.on_emit(newest_log_record, mock_context) + processor.on_emit(older_log_record, mock_context) + processor.on_emit(newest_log_record, mock_context) - records = processor.instance_variable_get(:@log_records) + records = processor.instance_variable_get(:@log_records) - assert_includes(records, newest_log_record) - refute_includes(records, newer_log_record) - refute_includes(records, older_log_record) + assert_includes(records, newest_log_record) + refute_includes(records, older_log_record) + end end it 'logs a warning if a log record was emitted after the buffer is full' do @@ -469,18 +469,21 @@ def shutdown(timeout: nil) let(:processor) { BatchLogRecordProcessor.new(exporter) } it 'reports export failures' do - mock_logger = Minitest::Mock.new - mock_logger.expect(:error, nil, [/Unable to export/]) - mock_logger.expect(:error, nil, [/Result code: 1/]) - mock_logger.expect(:error, nil, [/unexpected error in .*\#export_batch/]) - - OpenTelemetry.stub(:logger, mock_logger) do - log_records = [TestLogRecord.new, TestLogRecord.new, TestLogRecord.new, TestLogRecord.new] - log_records.each { |log_record| processor.on_emit(log_record, mock_context) } - processor.shutdown - end + # skip the work method's behavior, we rely on shutdown to get us to the failures + processor.stub(:work, nil) do + mock_logger = Minitest::Mock.new + mock_logger.expect(:error, nil, [/Unable to export/]) + mock_logger.expect(:error, nil, [/Result code: 1/]) + mock_logger.expect(:error, nil, [/unexpected error in .*\#export_batch/]) + + OpenTelemetry.stub(:logger, mock_logger) do + log_records = [TestLogRecord.new, TestLogRecord.new, TestLogRecord.new, TestLogRecord.new] + log_records.each { |log_record| processor.on_emit(log_record, mock_context) } + processor.shutdown + end - mock_logger.verify + mock_logger.verify + end end end From b0ecc1cb48b7bbdf86e337d2c9c064cfc935f7a4 Mon Sep 17 00:00:00 2001 From: Alex Boten <223565+codeboten@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:44:11 -0700 Subject: [PATCH 13/14] fix: update references to logging exporter (#1731) * fix: update references to logging exporter This exporter has been replaced by the debug exporter and will be removed soon Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> * update example to use v0.109.0 of the collector Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> --------- Signed-off-by: Alex Boten <223565+codeboten@users.noreply.github.com> Co-authored-by: Matthew Wear --- examples/metrics_sdk/README.md | 8 ++++---- examples/otel-collector/.env | 2 +- examples/otel-collector/otel-collector-config.yaml | 14 ++++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/examples/metrics_sdk/README.md b/examples/metrics_sdk/README.md index 3ba206d783..427a39fb24 100644 --- a/examples/metrics_sdk/README.md +++ b/examples/metrics_sdk/README.md @@ -36,8 +36,8 @@ receivers: # Default endpoints: 0.0.0.0:4317 for gRPC and 0.0.0.0:4318 for HTTP exporters: - logging: - loglevel: debug + debug: + verbosity: detailed processors: batch: @@ -47,11 +47,11 @@ service: traces: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] metrics: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] ``` More information on how to setup the OTel collector can be found in the in [quick start docs](https://opentelemetry.io/docs/collector/quick-start/). diff --git a/examples/otel-collector/.env b/examples/otel-collector/.env index d9c19232cd..2a845851e9 100644 --- a/examples/otel-collector/.env +++ b/examples/otel-collector/.env @@ -1,2 +1,2 @@ -OTELCOL_IMG=otel/opentelemetry-collector-contrib:0.35.0 +OTELCOL_IMG=otel/opentelemetry-collector-contrib:0.109.0 OTELCOL_ARGS= diff --git a/examples/otel-collector/otel-collector-config.yaml b/examples/otel-collector/otel-collector-config.yaml index 997fc86aa6..9484b84b91 100644 --- a/examples/otel-collector/otel-collector-config.yaml +++ b/examples/otel-collector/otel-collector-config.yaml @@ -3,17 +3,19 @@ receivers: otlp: protocols: http: + endpoint: 0.0.0.0:4318 exporters: - logging: + debug: zipkin: endpoint: "http://zipkin-all-in-one:9411/api/v2/spans" format: proto - jaeger: - endpoint: jaeger-all-in-one:14250 - insecure: true + otlp: + endpoint: jaeger-all-in-one:4317 + tls: + insecure: true processors: batch: @@ -23,8 +25,8 @@ service: traces: receivers: [otlp] processors: [batch] - exporters: [logging, zipkin, jaeger] + exporters: [debug, zipkin, otlp] metrics: receivers: [otlp] processors: [batch] - exporters: [logging] + exporters: [debug] From 6c9c771b4932d570773accb9b877d380e071586e Mon Sep 17 00:00:00 2001 From: Matthew Wear Date: Thu, 10 Oct 2024 13:12:37 -0700 Subject: [PATCH 14/14] chore: promote kaylareopelle to maintainer (#1745) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76c7b82084..68d9faf8ff 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,6 @@ Approvers ([@open-telemetry/ruby-approvers](https://github.com/orgs/open-telemet - [Andrew Hayworth](https://github.com/ahayworth), Shopify - [Sam Handler](https://github.com/plantfansam), Shopify - [Robb Kidd](https://github.com/robbkidd), Honeycomb -- [Kayla Reopelle](https://github.com/kaylareopelle), New Relic *Find more about the approver role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#approver).* @@ -43,6 +42,7 @@ Maintainers ([@open-telemetry/ruby-maintainers](https://github.com/orgs/open-tel - [Francis Bogsanyi](https://github.com/fbogsany), Shopify - [Matthew Wear](https://github.com/mwear), Lightstep - [Daniel Azuma](https://github.com/dazuma), Google +- [Kayla Reopelle](https://github.com/kaylareopelle), New Relic *Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer).*