From 5c8570cfc2e6218cd479424d5b87a7840b19ddb9 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Fri, 8 Aug 2025 18:30:54 -0700 Subject: [PATCH 01/12] feat: semantic convention opt it --- instrumentation/mysql2/Appraisals | 12 +- .../instrumentation/mysql2/instrumentation.rb | 42 +- .../instrumentation/mysql2/patches/client.rb | 100 ---- .../mysql2/patches/dup/client.rb | 111 ++++ .../mysql2/patches/old/client.rb | 102 ++++ .../mysql2/patches/stable/client.rb | 102 ++++ .../mysql2/dup/instrumentation_test.rb | 534 ++++++++++++++++++ .../mysql2/{ => old}/instrumentation_test.rb | 10 +- .../mysql2/stable/instrumentation_test.rb | 471 +++++++++++++++ 9 files changed, 1372 insertions(+), 112 deletions(-) delete mode 100644 instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb create mode 100644 instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb create mode 100644 instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb create mode 100644 instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb create mode 100644 instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb rename instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/{ => old}/instrumentation_test.rb (98%) create mode 100644 instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb diff --git a/instrumentation/mysql2/Appraisals b/instrumentation/mysql2/Appraisals index 71b5ab2255..bf45dbf52d 100644 --- a/instrumentation/mysql2/Appraisals +++ b/instrumentation/mysql2/Appraisals @@ -1,5 +1,13 @@ # frozen_string_literal: true -appraise 'mysql2-0.4.0' do - gem 'mysql2' +# To facilitate database semantic convention stability migration, we are using +# appraisal to test the different semantic convention modes along with different +# gem versions. For more information on the semantic convention modes, see: +# https://opentelemetry.io/docs/specs/semconv/non-normative/db-migration/ + +semconv_stability = %w[old stable dup] +semconv_stability.each do |stability| + appraise "mysql2-0.4.0-#{stability}" do + gem 'mysql2' + end end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb index 9a7b78ccbc..7f1ff815e3 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb @@ -11,8 +11,9 @@ module Mysql2 # instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base install do |_config| - require_dependencies - patch_client + patch_type = determine_semconv + send(:"require_dependencies_#{patch_type}") + send(:"patch_client_#{patch_type}") end present do @@ -26,12 +27,41 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base private - def require_dependencies - require_relative 'patches/client' + def determine_semconv + stability_opt_in = ENV.fetch('OTEL_SEMCONV_STABILITY_OPT_IN', '') + values = stability_opt_in.split(',').map(&:strip) + + if values.include?('database/dup') + 'dup' + elsif values.include?('database') + 'stable' + else + 'old' + end + end + + def require_dependencies_dup + require_relative 'patches/dup/client' + end + + def require_dependencies_old + require_relative 'patches/old/client' + end + + def require_dependencies_stable + require_relative 'patches/stable/client' + end + + def patch_client_dup + ::Mysql2::Client.prepend(Patches::Dup::Client) + end + + def patch_client_old + ::Mysql2::Client.prepend(Patches::Old::Client) end - def patch_client - ::Mysql2::Client.prepend(Patches::Client) + def patch_client_stable + ::Mysql2::Client.prepend(Patches::Stable::Client) end end end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb deleted file mode 100644 index c9dbdaddd9..0000000000 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -# Copyright The OpenTelemetry Authors -# -# SPDX-License-Identifier: Apache-2.0 - -require 'opentelemetry-helpers-mysql' -require 'opentelemetry-helpers-sql-obfuscation' - -module OpenTelemetry - module Instrumentation - module Mysql2 - module Patches - # Module to prepend to Mysql2::Client for instrumentation - module Client - def query(sql, options = {}) - tracer.in_span( - _otel_span_name(sql), - attributes: _otel_span_attributes(sql), - kind: :client - ) do - super - end - end - - def prepare(sql) - tracer.in_span( - _otel_span_name(sql), - attributes: _otel_span_attributes(sql), - kind: :client - ) do - super - end - end - - private - - def _otel_span_name(sql) - OpenTelemetry::Helpers::MySQL.database_span_name( - sql, - OpenTelemetry::Instrumentation::Mysql2.attributes[ - SemanticConventions::Trace::DB_OPERATION - ], - _otel_database_name, - config - ) - end - - def _otel_span_attributes(sql) - attributes = _otel_client_attributes - case config[:db_statement] - when :include - attributes[SemanticConventions::Trace::DB_STATEMENT] = sql - when :obfuscate - attributes[SemanticConventions::Trace::DB_STATEMENT] = - OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( - sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql - ) - end - - attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes) - attributes.compact! - attributes - end - - def _otel_database_name - # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L78 - (query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s - end - - def _otel_client_attributes - # The client specific attributes can be found via the query_options instance variable - # exposed on the mysql2 Client - # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 - host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port].to_s - - attributes = { - SemanticConventions::Trace::DB_SYSTEM => 'mysql', - SemanticConventions::Trace::NET_PEER_NAME => host, - SemanticConventions::Trace::NET_PEER_PORT => port - } - - attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name - attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] - attributes - end - - def tracer - Mysql2::Instrumentation.instance.tracer - end - - def config - Mysql2::Instrumentation.instance.config - end - end - end - end - end -end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb new file mode 100644 index 0000000000..68a63f4fb3 --- /dev/null +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry-helpers-mysql' +require 'opentelemetry-helpers-sql-obfuscation' + +module OpenTelemetry + module Instrumentation + module Mysql2 + module Patches + module Dup + # Module to prepend to Mysql2::Client for instrumentation + module Client + def query(sql, options = {}) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super + end + end + + def prepare(sql) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super + end + end + + private + + def _otel_span_name(sql) + OpenTelemetry::Helpers::MySQL.database_span_name( + sql, + OpenTelemetry::Instrumentation::Mysql2.attributes[ + 'db.operation.name' + ], + _otel_database_name, + config + ) + end + + def _otel_span_attributes(sql) + attributes = _otel_client_attributes + case config[:db_statement] + when :include + attributes[SemanticConventions::Trace::DB_STATEMENT] = sql + attributes['db.query.text'] = sql + when :obfuscate + attributes[SemanticConventions::Trace::DB_STATEMENT] = + OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql + ) + attributes['db.query.text'] = + OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql + ) + end + + attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes) + attributes.compact! + attributes + end + + def _otel_database_name + # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L78 + (query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s + end + + def _otel_client_attributes + # The client specific attributes can be found via the query_options instance variable + # exposed on the mysql2 Client + # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 + host = (query_options[:host] || query_options[:hostname]).to_s + port = query_options[:port].to_s + + attributes = { + SemanticConventions::Trace::DB_SYSTEM => 'mysql', + SemanticConventions::Trace::NET_PEER_NAME => host, + SemanticConventions::Trace::NET_PEER_PORT => port, + 'db.system.name' => 'mysql', + 'server.address' => host, + 'server.port' => port + } + + attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name + attributes['db.namespace'] = _otel_database_name + attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] + attributes + end + + def tracer + Mysql2::Instrumentation.instance.tracer + end + + def config + Mysql2::Instrumentation.instance.config + end + end + end + end + end + end +end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb new file mode 100644 index 0000000000..a8ced8a5f7 --- /dev/null +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry-helpers-mysql' +require 'opentelemetry-helpers-sql-obfuscation' + +module OpenTelemetry + module Instrumentation + module Mysql2 + module Patches + module Old + # Module to prepend to Mysql2::Client for instrumentation + module Client + def query(sql, options = {}) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super + end + end + + def prepare(sql) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super + end + end + + private + + def _otel_span_name(sql) + OpenTelemetry::Helpers::MySQL.database_span_name( + sql, + OpenTelemetry::Instrumentation::Mysql2.attributes[ + SemanticConventions::Trace::DB_OPERATION + ], + _otel_database_name, + config + ) + end + + def _otel_span_attributes(sql) + attributes = _otel_client_attributes + case config[:db_statement] + when :include + attributes[SemanticConventions::Trace::DB_STATEMENT] = sql + when :obfuscate + attributes[SemanticConventions::Trace::DB_STATEMENT] = + OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql + ) + end + + attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes) + attributes.compact! + attributes + end + + def _otel_database_name + # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L78 + (query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s + end + + def _otel_client_attributes + # The client specific attributes can be found via the query_options instance variable + # exposed on the mysql2 Client + # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 + host = (query_options[:host] || query_options[:hostname]).to_s + port = query_options[:port].to_s + + attributes = { + SemanticConventions::Trace::DB_SYSTEM => 'mysql', + SemanticConventions::Trace::NET_PEER_NAME => host, + SemanticConventions::Trace::NET_PEER_PORT => port + } + + attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name + attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] + attributes + end + + def tracer + Mysql2::Instrumentation.instance.tracer + end + + def config + Mysql2::Instrumentation.instance.config + end + end + end + end + end + end +end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb new file mode 100644 index 0000000000..893647dcf5 --- /dev/null +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'opentelemetry-helpers-mysql' +require 'opentelemetry-helpers-sql-obfuscation' + +module OpenTelemetry + module Instrumentation + module Mysql2 + module Patches + module Stable + # Module to prepend to Mysql2::Client for instrumentation + module Client + def query(sql, options = {}) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super + end + end + + def prepare(sql) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super + end + end + + private + + def _otel_span_name(sql) + OpenTelemetry::Helpers::MySQL.database_span_name( + sql, + OpenTelemetry::Instrumentation::Mysql2.attributes[ + 'db.operation.name' + ], + _otel_database_name, + config + ) + end + + def _otel_span_attributes(sql) + attributes = _otel_client_attributes + case config[:db_statement] + when :include + attributes['db.query.text'] = sql + when :obfuscate + attributes['db.query.text'] = + OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql + ) + end + + attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes) + attributes.compact! + attributes + end + + def _otel_database_name + # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L78 + (query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s + end + + def _otel_client_attributes + # The client specific attributes can be found via the query_options instance variable + # exposed on the mysql2 Client + # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 + host = (query_options[:host] || query_options[:hostname]).to_s + port = query_options[:port].to_s + + attributes = { + 'db.system.name' => 'mysql', + 'server.address' => host, + 'server.port' => port + } + + attributes['db.namespace'] = _otel_database_name + attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service] + attributes + end + + def tracer + Mysql2::Instrumentation.instance.tracer + end + + def config + Mysql2::Instrumentation.instance.config + end + end + end + end + end + end +end diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb new file mode 100644 index 0000000000..49a123c622 --- /dev/null +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb @@ -0,0 +1,534 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' +require 'mysql2' + +require_relative '../../../../../lib/opentelemetry/instrumentation/mysql2' +require_relative '../../../../../lib/opentelemetry/instrumentation/mysql2/patches/dup/client' + +# This test suite requires a running mysql container and dedicated test container +# To run tests: +# 1. Build the opentelemetry/opentelemetry-ruby-contrib image +# - docker-compose build +# 2. Bundle install +# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal install +# 3. Run test suite +# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal rake test +describe OpenTelemetry::Instrumentation::Mysql2::Instrumentation do + let(:instrumentation) { OpenTelemetry::Instrumentation::Mysql2::Instrumentation.instance } + let(:exporter) { EXPORTER } + let(:span) { exporter.finished_spans.first } + let(:config) { { db_statement: :include } } + + before do + skip unless ENV['BUNDLE_GEMFILE'].include?('dup') + + ENV['OTEL_SEMCONV_STABILITY_OPT_IN'] = 'database/dup' + + exporter.reset + end + + after do + # Force re-install of instrumentation + instrumentation.instance_variable_set(:@installed, false) + end + + describe 'tracing' do + let(:client) do + Mysql2::Client.new( + host: host, + port: port, + database: database, + username: username, + password: password + ) + end + + let(:host) { ENV.fetch('TEST_MYSQL_HOST', '127.0.0.1') } + let(:port) { ENV.fetch('TEST_MYSQL_PORT', '3306') } + let(:database) { ENV.fetch('TEST_MYSQL_DB', 'mysql') } + let(:username) { ENV.fetch('TEST_MYSQL_USER', 'root') } + let(:password) { ENV.fetch('TEST_MYSQL_PASSWORD', 'root') } + + before do + instrumentation.install(config) + end + + it 'before request' do + _(exporter.finished_spans.size).must_equal 0 + end + + it 'accepts peer service name from config' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'readonly:mysql') + client.query('SELECT 1') + + _(span.attributes['peer.service']).must_equal 'readonly:mysql' + end + + describe '.attributes' do + let(:attributes) { { 'db.statement' => 'foobar', 'db.query.text' => 'foobar' } } + + it 'returns an empty hash by default' do + _(OpenTelemetry::Instrumentation::Mysql2.attributes).must_equal({}) + end + + it 'returns the current attributes hash' do + OpenTelemetry::Instrumentation::Mysql2.with_attributes(attributes) do + _(OpenTelemetry::Instrumentation::Mysql2.attributes).must_equal(attributes) + end + end + + it 'sets span attributes according to with_attributes hash' do + OpenTelemetry::Instrumentation::Mysql2.with_attributes(attributes) do + client.query('SELECT 1') + end + + _(span.attributes['db.statement']).must_equal 'foobar' + _(span.attributes['db.query.text']).must_equal 'foobar' + end + end + + describe 'prepare statement' do + it 'after requests with prepare' do + client.prepare('SELECT 1') + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT 1' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT 1' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'after requests with prepare select ?' do + client.prepare('SELECT ?') + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT ?' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT ?' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'query ? sequences for db.statement with prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.prepare(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.statement']).must_equal sql + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.query.text']).must_equal sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'query invalid byte sequences for db.statement without prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.events[0].attributes['exception.message'].slice(0, 37)).must_equal 'You have an error in your SQL syntax;' + end + end + + it 'after requests' do + client.query('SELECT 1') + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT 1' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT 1' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'after error' do + expect do + client.query('SELECT INVALID') + end.must_raise Mysql2::Error + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT INVALID' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT INVALID' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + + _(span.status.code).must_equal( + OpenTelemetry::Trace::Status::ERROR + ) + _(span.events.first.name).must_equal 'exception' + _(span.events.first.attributes['exception.type']).must_equal 'Mysql2::Error' + assert(!span.events.first.attributes['exception.message'].nil?) + assert(!span.events.first.attributes['exception.stacktrace'].nil?) + end + + it 'extracts statement type that begins the query' do + base_sql = 'SELECT 1' + explain = 'EXPLAIN' + explain_sql = "#{explain} #{base_sql}" + client.query(explain_sql) + + _(span.name).must_equal 'explain' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal explain_sql + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal explain_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'uses component.name and instance.name as span.name fallbacks with invalid sql' do + expect do + client.query('DESELECT 1') + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'DESELECT 1' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'DESELECT 1' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + + _(span.status.code).must_equal( + OpenTelemetry::Trace::Status::ERROR + ) + _(span.events.first.name).must_equal 'exception' + _(span.events.first.attributes['exception.type']).must_equal 'Mysql2::Error' + assert(!span.events.first.attributes['exception.message'].nil?) + assert(!span.events.first.attributes['exception.stacktrace'].nil?) + end + + describe 'when db_statement set as obfuscate' do + let(:config) { { db_statement: :obfuscate } } + + it 'obfuscates SQL parameters in db.statement' do + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.statement']).must_equal obfuscated_sql + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.query.text']).must_equal obfuscated_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'encodes invalid byte sequences for db.statement' do + # \255 is off-limits https://en.wikipedia.org/wiki/UTF-8#Codepage_layout + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com\255'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'select' + _(span.attributes['db.statement']).must_equal obfuscated_sql + _(span.attributes['db.query.text']).must_equal obfuscated_sql + end + + describe 'with obfuscation_limit' do + let(:config) { { db_statement: :obfuscate, obfuscation_limit: 10 } } + + it 'returns a message when the limit is reached' do + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SQL not obfuscated, query exceeds 10 characters' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.statement']).must_equal obfuscated_sql + _(span.attributes['db.query.text']).must_equal obfuscated_sql + end + end + end + + describe 'when db_statement set as omit' do + let(:config) { { db_statement: :omit } } + + it 'omits db.statement attribute' do + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes).wont_include('db.statement') + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes).wont_include('db.query.text') + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + + describe 'when db_statement is configured via environment variable' do + describe 'when db_statement set as omit' do + it 'omits db.statement attribute' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'db_statement=omit;') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes).wont_include('db.statement') + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes).wont_include('db.query.text') + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + end + + describe 'when db_statement set as obfuscate' do + it 'obfuscates SQL parameters in db.statement' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'db_statement=obfuscate;') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.statement']).must_equal obfuscated_sql + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.query.text']).must_equal obfuscated_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + end + + describe 'when db_statement is set differently than local config' do + let(:config) { { db_statement: :omit } } + + it 'overrides local config and obfuscates SQL parameters in db.statement' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'db_statement=obfuscate') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.statement']).must_equal obfuscated_sql + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + _(span.attributes['db.query.text']).must_equal obfuscated_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + end + + describe 'when span_name is set as statement_type' do + it 'sets span name to statement type' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=statement_type') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'select' + end + end + + it 'sets span name to mysql when statement type is not recognized' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=statement_type') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = 'DESELECT 1' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + end + + describe 'when span_name is set as db_name' do + it 'sets span name to db name' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' # TODO: change the db name so we can distinguish it from the default + end + end + + describe 'when db name is nil' do + let(:database) { nil } + + it 'sets span name to mysql' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + end + end + + describe 'when span_name is set as db_operation_and_name' do + it 'sets span name to db operation and name' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_operation_and_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do + expect do + client.query(sql) + end.must_raise Mysql2::Error + end + + _(span.name).must_equal 'foo mysql' + end + end + + it 'sets span name to db name when db.operation is not set' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_operation_and_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + + describe 'when db name is nil' do + let(:database) { nil } + + it 'sets span name to db operation' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_operation_and_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do + expect do + client.query(sql) + end.must_raise Mysql2::Error + end + + _(span.name).must_equal 'foo' + end + end + + it 'sets span name to mysql when db.operation is not set' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + end + end + end + end unless ENV['OMIT_SERVICES'] +end diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/old/instrumentation_test.rb similarity index 98% rename from instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb rename to instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/old/instrumentation_test.rb index 405b714ee3..77190345f8 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/old/instrumentation_test.rb @@ -7,17 +7,17 @@ require 'test_helper' require 'mysql2' -require_relative '../../../../lib/opentelemetry/instrumentation/mysql2' -require_relative '../../../../lib/opentelemetry/instrumentation/mysql2/patches/client' +require_relative '../../../../../lib/opentelemetry/instrumentation/mysql2' +require_relative '../../../../../lib/opentelemetry/instrumentation/mysql2/patches/old/client' # This test suite requires a running mysql container and dedicated test container # To run tests: # 1. Build the opentelemetry/opentelemetry-ruby-contrib image # - docker-compose build # 2. Bundle install -# - docker-compose run ex-instrumentation-mysql2-test bundle install +# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal install # 3. Run test suite -# - docker-compose run ex-instrumentation-mysql2-test bundle exec rake test +# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal rake test describe OpenTelemetry::Instrumentation::Mysql2::Instrumentation do let(:instrumentation) { OpenTelemetry::Instrumentation::Mysql2::Instrumentation.instance } let(:exporter) { EXPORTER } @@ -25,6 +25,8 @@ let(:config) { { db_statement: :include } } before do + skip unless ENV['BUNDLE_GEMFILE'].include?('old') + exporter.reset end diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb new file mode 100644 index 0000000000..279ba8b686 --- /dev/null +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb @@ -0,0 +1,471 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' +require 'mysql2' + +require_relative '../../../../../lib/opentelemetry/instrumentation/mysql2' +require_relative '../../../../../lib/opentelemetry/instrumentation/mysql2/patches/stable/client' + +# This test suite requires a running mysql container and dedicated test container +# To run tests: +# 1. Build the opentelemetry/opentelemetry-ruby-contrib image +# - docker-compose build +# 2. Bundle install +# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal install +# 3. Run test suite +# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal rake test +describe OpenTelemetry::Instrumentation::Mysql2::Instrumentation do + let(:instrumentation) { OpenTelemetry::Instrumentation::Mysql2::Instrumentation.instance } + let(:exporter) { EXPORTER } + let(:span) { exporter.finished_spans.first } + let(:config) { { db_statement: :include } } + + before do + skip unless ENV['BUNDLE_GEMFILE'].include?('stable') + + ENV['OTEL_SEMCONV_STABILITY_OPT_IN'] = 'database' + + exporter.reset + end + + after do + # Force re-install of instrumentation + instrumentation.instance_variable_set(:@installed, false) + end + + describe 'tracing' do + let(:client) do + Mysql2::Client.new( + host: host, + port: port, + database: database, + username: username, + password: password + ) + end + + let(:host) { ENV.fetch('TEST_MYSQL_HOST', '127.0.0.1') } + let(:port) { ENV.fetch('TEST_MYSQL_PORT', '3306') } + let(:database) { ENV.fetch('TEST_MYSQL_DB', 'mysql') } + let(:username) { ENV.fetch('TEST_MYSQL_USER', 'root') } + let(:password) { ENV.fetch('TEST_MYSQL_PASSWORD', 'root') } + + before do + instrumentation.install(config) + end + + it 'before request' do + _(exporter.finished_spans.size).must_equal 0 + end + + it 'accepts peer service name from config' do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install(peer_service: 'readonly:mysql') + client.query('SELECT 1') + + _(span.attributes['peer.service']).must_equal 'readonly:mysql' + end + + describe '.attributes' do + let(:attributes) { { 'db.query.text' => 'foobar' } } + + it 'returns an empty hash by default' do + _(OpenTelemetry::Instrumentation::Mysql2.attributes).must_equal({}) + end + + it 'returns the current attributes hash' do + OpenTelemetry::Instrumentation::Mysql2.with_attributes(attributes) do + _(OpenTelemetry::Instrumentation::Mysql2.attributes).must_equal(attributes) + end + end + + it 'sets span attributes according to with_attributes hash' do + OpenTelemetry::Instrumentation::Mysql2.with_attributes(attributes) do + client.query('SELECT 1') + end + + _(span.attributes['db.query.text']).must_equal 'foobar' + end + end + + describe 'prepare statement' do + it 'after requests with prepare' do + client.prepare('SELECT 1') + + _(span.name).must_equal 'select' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT 1' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'after requests with prepare select ?' do + client.prepare('SELECT ?') + + _(span.name).must_equal 'select' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT ?' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'query ? sequences for db.query.text with prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.prepare(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.query.text']).must_equal sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'query invalid byte sequences for db.query.text without prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.events[0].attributes['exception.message'].slice(0, 37)).must_equal 'You have an error in your SQL syntax;' + end + end + + it 'after requests' do + client.query('SELECT 1') + + _(span.name).must_equal 'select' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT 1' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'after error' do + expect do + client.query('SELECT INVALID') + end.must_raise Mysql2::Error + + _(span.name).must_equal 'select' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'SELECT INVALID' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + + _(span.status.code).must_equal( + OpenTelemetry::Trace::Status::ERROR + ) + _(span.events.first.name).must_equal 'exception' + _(span.events.first.attributes['exception.type']).must_equal 'Mysql2::Error' + assert(!span.events.first.attributes['exception.message'].nil?) + assert(!span.events.first.attributes['exception.stacktrace'].nil?) + end + + it 'extracts statement type that begins the query' do + base_sql = 'SELECT 1' + explain = 'EXPLAIN' + explain_sql = "#{explain} #{base_sql}" + client.query(explain_sql) + + _(span.name).must_equal 'explain' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal explain_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'uses component.name and instance.name as span.name fallbacks with invalid sql' do + expect do + client.query('DESELECT 1') + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.attributes['db.query.text']).must_equal 'DESELECT 1' + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + + _(span.status.code).must_equal( + OpenTelemetry::Trace::Status::ERROR + ) + _(span.events.first.name).must_equal 'exception' + _(span.events.first.attributes['exception.type']).must_equal 'Mysql2::Error' + assert(!span.events.first.attributes['exception.message'].nil?) + assert(!span.events.first.attributes['exception.stacktrace'].nil?) + end + + describe 'when db_statement set as obfuscate' do + let(:config) { { db_statement: :obfuscate } } + + it 'obfuscates SQL parameters in db.query.text' do + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.query.text']).must_equal obfuscated_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + + it 'encodes invalid byte sequences for db.query.text' do + # \255 is off-limits https://en.wikipedia.org/wiki/UTF-8#Codepage_layout + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com\255'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'select' + _(span.attributes['db.query.text']).must_equal obfuscated_sql + end + + describe 'with obfuscation_limit' do + let(:config) { { db_statement: :obfuscate, obfuscation_limit: 10 } } + + it 'returns a message when the limit is reached' do + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SQL not obfuscated, query exceeds 10 characters' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.query.text']).must_equal obfuscated_sql + end + end + end + + describe 'when db_statement set as omit' do + let(:config) { { db_statement: :omit } } + + it 'omits db.query.text attribute' do + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes).wont_include('db.query.text') + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + + describe 'when db_statement is configured via environment variable' do + describe 'when db_statement set as omit' do + it 'omits db.query.text attribute' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'db_statement=omit;') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes).wont_include('db.query.text') + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + end + + describe 'when db_statement set as obfuscate' do + it 'obfuscates SQL parameters in db.query.text' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'db_statement=obfuscate;') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.query.text']).must_equal obfuscated_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + end + + describe 'when db_statement is set differently than local config' do + let(:config) { { db_statement: :omit } } + + it 'overrides local config and obfuscates SQL parameters in db.query.text' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'db_statement=obfuscate') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + obfuscated_sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system.name']).must_equal 'mysql' + _(span.attributes['db.namespace']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.query.text']).must_equal obfuscated_sql + _(span.attributes['server.address']).must_equal host.to_s + _(span.attributes['server.port']).must_equal port.to_s + end + end + end + + describe 'when span_name is set as statement_type' do + it 'sets span name to statement type' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=statement_type') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'select' + end + end + + it 'sets span name to mysql when statement type is not recognized' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=statement_type') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = 'DESELECT 1' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + end + + describe 'when span_name is set as db_name' do + it 'sets span name to db name' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' # TODO: change the db name so we can distinguish it from the default + end + end + + describe 'when db name is nil' do + let(:database) { nil } + + it 'sets span name to mysql' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + end + end + + describe 'when span_name is set as db_operation_and_name' do + it 'sets span name to db operation and name' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_operation_and_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do + expect do + client.query(sql) + end.must_raise Mysql2::Error + end + + _(span.name).must_equal 'foo mysql' + end + end + + it 'sets span name to db name when db.operation.name is not set' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_operation_and_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + + describe 'when db name is nil' do + let(:database) { nil } + + it 'sets span name to db operation' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_operation_and_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do + expect do + client.query(sql) + end.must_raise Mysql2::Error + end + + _(span.name).must_equal 'foo' + end + end + + it 'sets span name to mysql when db.operation.name is not set' do + OpenTelemetry::TestHelpers.with_env('OTEL_RUBY_INSTRUMENTATION_MYSQL2_CONFIG_OPTS' => 'span_name=db_name') do + instrumentation.instance_variable_set(:@installed, false) + instrumentation.install + + sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.name).must_equal 'mysql' + end + end + end + end + end + end unless ENV['OMIT_SERVICES'] +end From 1a9635246ba4d4d29dad73c2f9575028956a9768 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Fri, 8 Aug 2025 19:07:15 -0700 Subject: [PATCH 02/12] Update README --- instrumentation/mysql2/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/instrumentation/mysql2/README.md b/instrumentation/mysql2/README.md index 9c25a103c8..6a57e92f81 100644 --- a/instrumentation/mysql2/README.md +++ b/instrumentation/mysql2/README.md @@ -75,3 +75,19 @@ The `opentelemetry-instrumentation-mysql2` gem is distributed under the Apache 2 [community-meetings]: https://github.com/open-telemetry/community#community-meetings [slack-channel]: https://cloud-native.slack.com/archives/C01NWKKMKMY [discussions-url]: https://github.com/open-telemetry/opentelemetry-ruby/discussions + +## Database semantic convention stability + +In the OpenTelemetry ecosystem, database semantic conventions have now reached a stable state. However, the initial Mysql2 instrumentation was introduced before this stability was achieved, which resulted in database attributes being based on an older version of the semantic conventions. + +To facilitate the migration to stable semantic conventions, you can use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. This variable allows you to opt-in to the new stable conventions, ensuring compatibility and future-proofing your instrumentation. + +When setting the value for `OTEL_SEMCONV_STABILITY_OPT_IN`, you can specify which conventions you wish to adopt: + +- `database` - Emits the stable database and networking conventions and ceases emitting the old conventions previously emitted by the instrumentation. +- `database/dup` - Emits both the old and stable database and networking conventions, enabling a phased rollout of the stable semantic conventions. +- Default behavior (in the absence of either value) is to continue emitting the old database and networking conventions the instrumentation previously emitted. + +During the transition from old to stable conventions, Mysql2 instrumentation code comes in three patch versions: `dup`, `old`, and `stable`. These versions are identical except for the attributes they send. Any changes to Mysql2 instrumentation should consider all three patches. + +For additional information on migration, please refer to our [documentation](https://opentelemetry.io/docs/specs/semconv/non-normative/db-migration/). From a7159dc7b5015c42f69e70b4ba7aee2705ebb0b8 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Wed, 11 Feb 2026 12:53:50 -0800 Subject: [PATCH 03/12] obfuscation -> processor --- .../instrumentation/mysql2/patches/dup/client.rb | 6 +++--- .../instrumentation/mysql2/patches/old/client.rb | 4 ++-- .../instrumentation/mysql2/patches/stable/client.rb | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb index 68a63f4fb3..4b52a22f09 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -5,7 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 require 'opentelemetry-helpers-mysql' -require 'opentelemetry-helpers-sql-obfuscation' +require 'opentelemetry-helpers-sql-processor' module OpenTelemetry module Instrumentation @@ -55,11 +55,11 @@ def _otel_span_attributes(sql) attributes['db.query.text'] = sql when :obfuscate attributes[SemanticConventions::Trace::DB_STATEMENT] = - OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + OpenTelemetry::Helpers::SqlProcessor.obfuscate_sql( sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql ) attributes['db.query.text'] = - OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + OpenTelemetry::Helpers::SqlProcessor.obfuscate_sql( sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql ) end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb index a8ced8a5f7..7bdbb7968f 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb @@ -5,7 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 require 'opentelemetry-helpers-mysql' -require 'opentelemetry-helpers-sql-obfuscation' +require 'opentelemetry-helpers-sql-processor' module OpenTelemetry module Instrumentation @@ -54,7 +54,7 @@ def _otel_span_attributes(sql) attributes[SemanticConventions::Trace::DB_STATEMENT] = sql when :obfuscate attributes[SemanticConventions::Trace::DB_STATEMENT] = - OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + OpenTelemetry::Helpers::SqlProcessor.obfuscate_sql( sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql ) end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb index 893647dcf5..adc58a0e44 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb @@ -5,7 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 require 'opentelemetry-helpers-mysql' -require 'opentelemetry-helpers-sql-obfuscation' +require 'opentelemetry-helpers-sql-processor' module OpenTelemetry module Instrumentation @@ -54,7 +54,7 @@ def _otel_span_attributes(sql) attributes['db.query.text'] = sql when :obfuscate attributes['db.query.text'] = - OpenTelemetry::Helpers::SqlObfuscation.obfuscate_sql( + OpenTelemetry::Helpers::SqlProcessor.obfuscate_sql( sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql ) end From 1ee26d607275f506514ad15942d8ffbf012fe4e1 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 11:18:58 -0700 Subject: [PATCH 04/12] Add on failure attributes and port --- .../mysql2/patches/dup/client.rb | 53 +++++++++++++++---- .../mysql2/patches/stable/client.rb | 44 ++++++++++++--- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb index 4b52a22f09..5569674280 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -19,8 +19,20 @@ def query(sql, options = {}) _otel_span_name(sql), attributes: _otel_span_attributes(sql), kind: :client - ) do - super + ) do |span, context| + if propagator && sql.frozen? + sql = +sql + propagator.inject(sql, context: context) + sql.freeze + elsif propagator + propagator.inject(sql, context: context) + end + + super(sql, options) + rescue ::Mysql2::Error => e + span.set_attribute('error.type', e.class.name) + span.set_attribute('db.response.status_code', e.error_number.to_s) if e.respond_to?(:error_number) && e.error_number + raise end end @@ -29,8 +41,20 @@ def prepare(sql) _otel_span_name(sql), attributes: _otel_span_attributes(sql), kind: :client - ) do - super + ) do |span, context| + if propagator && sql.frozen? + sql = +sql + propagator.inject(sql, context: context) + sql.freeze + elsif propagator + propagator.inject(sql, context: context) + end + + super(sql) + rescue ::Mysql2::Error => e + span.set_attribute('error.type', e.class.name) + span.set_attribute('db.response.status_code', e.error_number.to_s) if e.respond_to?(:error_number) && e.error_number + raise end end @@ -39,9 +63,8 @@ def prepare(sql) def _otel_span_name(sql) OpenTelemetry::Helpers::MySQL.database_span_name( sql, - OpenTelemetry::Instrumentation::Mysql2.attributes[ - 'db.operation.name' - ], + OpenTelemetry::Instrumentation::Mysql2.attributes[SemanticConventions::Trace::DB_OPERATION] || + OpenTelemetry::Instrumentation::Mysql2.attributes['db.operation.name'], _otel_database_name, config ) @@ -79,17 +102,21 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port].to_s attributes = { SemanticConventions::Trace::DB_SYSTEM => 'mysql', SemanticConventions::Trace::NET_PEER_NAME => host, - SemanticConventions::Trace::NET_PEER_PORT => port, 'db.system.name' => 'mysql', - 'server.address' => host, - 'server.port' => port + 'server.address' => host } + # Add port only if explicitly provided + port = query_options[:port] + if port + attributes[SemanticConventions::Trace::NET_PEER_PORT] = port + attributes['server.port'] = port + end + attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name attributes['db.namespace'] = _otel_database_name attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] @@ -103,6 +130,10 @@ def tracer def config Mysql2::Instrumentation.instance.config end + + def propagator + Mysql2::Instrumentation.instance.propagator + end end end end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb index adc58a0e44..3091df7c7d 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb @@ -19,8 +19,20 @@ def query(sql, options = {}) _otel_span_name(sql), attributes: _otel_span_attributes(sql), kind: :client - ) do - super + ) do |span, context| + if propagator && sql.frozen? + sql = +sql + propagator.inject(sql, context: context) + sql.freeze + elsif propagator + propagator.inject(sql, context: context) + end + + super(sql, options) + rescue ::Mysql2::Error => e + span.set_attribute('error.type', e.class.name) + span.set_attribute('db.response.status_code', e.error_number.to_s) if e.respond_to?(:error_number) && e.error_number + raise end end @@ -29,8 +41,20 @@ def prepare(sql) _otel_span_name(sql), attributes: _otel_span_attributes(sql), kind: :client - ) do - super + ) do |span, context| + if propagator && sql.frozen? + sql = +sql + propagator.inject(sql, context: context) + sql.freeze + elsif propagator + propagator.inject(sql, context: context) + end + + super(sql) + rescue ::Mysql2::Error => e + span.set_attribute('error.type', e.class.name) + span.set_attribute('db.response.status_code', e.error_number.to_s) if e.respond_to?(:error_number) && e.error_number + raise end end @@ -74,14 +98,16 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port].to_s attributes = { 'db.system.name' => 'mysql', - 'server.address' => host, - 'server.port' => port + 'server.address' => host } + # Add server.port only if explicitly provided + port = query_options[:port] + attributes['server.port'] = port if port + attributes['db.namespace'] = _otel_database_name attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service] attributes @@ -94,6 +120,10 @@ def tracer def config Mysql2::Instrumentation.instance.config end + + def propagator + Mysql2::Instrumentation.instance.propagator + end end end end From b65b452c267b7c9df39d1d6de02f8ec3a4c13281 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 12:32:54 -0700 Subject: [PATCH 05/12] Update files --- .../mysql2/patches/dup/client.rb | 12 +++----- .../mysql2/patches/old/client.rb | 28 ++++++++++++++++--- .../mysql2/patches/stable/client.rb | 8 ++---- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb index 5569674280..f2fb423f1e 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -102,21 +102,17 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s + port = query_options[:port].to_s attributes = { SemanticConventions::Trace::DB_SYSTEM => 'mysql', SemanticConventions::Trace::NET_PEER_NAME => host, + SemanticConventions::Trace::NET_PEER_PORT => port, 'db.system.name' => 'mysql', - 'server.address' => host + 'server.address' => host, + 'server.port' => port } - # Add port only if explicitly provided - port = query_options[:port] - if port - attributes[SemanticConventions::Trace::NET_PEER_PORT] = port - attributes['server.port'] = port - end - attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name attributes['db.namespace'] = _otel_database_name attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb index 7bdbb7968f..0da389c861 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/old/client.rb @@ -19,8 +19,16 @@ def query(sql, options = {}) _otel_span_name(sql), attributes: _otel_span_attributes(sql), kind: :client - ) do - super + ) do |_span, context| + if propagator && sql.frozen? + sql = +sql + propagator.inject(sql, context: context) + sql.freeze + elsif propagator + propagator.inject(sql, context: context) + end + + super(sql, options) end end @@ -29,8 +37,16 @@ def prepare(sql) _otel_span_name(sql), attributes: _otel_span_attributes(sql), kind: :client - ) do - super + ) do |_span, context| + if propagator && sql.frozen? + sql = +sql + propagator.inject(sql, context: context) + sql.freeze + elsif propagator + propagator.inject(sql, context: context) + end + + super(sql) end end @@ -94,6 +110,10 @@ def tracer def config Mysql2::Instrumentation.instance.config end + + def propagator + Mysql2::Instrumentation.instance.propagator + end end end end diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb index 3091df7c7d..a52d90f428 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb @@ -98,16 +98,14 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s + port = query_options[:port].to_s attributes = { 'db.system.name' => 'mysql', - 'server.address' => host + 'server.address' => host, + 'server.port' => port } - # Add server.port only if explicitly provided - port = query_options[:port] - attributes['server.port'] = port if port - attributes['db.namespace'] = _otel_database_name attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service] attributes From 1e0e12c2bc522aeecb7a5963d6ce34822073ec0b Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 16:57:54 -0700 Subject: [PATCH 06/12] add server.port --- .../mysql2/patches/dup/client.rb | 8 ++- .../mysql2/patches/stable/client.rb | 8 ++- .../mysql2/dup/instrumentation_test.rb | 62 ++++++++++++------- .../mysql2/stable/instrumentation_test.rb | 62 ++++++++++++------- 4 files changed, 88 insertions(+), 52 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb index f2fb423f1e..8ed626f8b1 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -102,17 +102,19 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port].to_s + port = query_options[:port] attributes = { SemanticConventions::Trace::DB_SYSTEM => 'mysql', SemanticConventions::Trace::NET_PEER_NAME => host, SemanticConventions::Trace::NET_PEER_PORT => port, 'db.system.name' => 'mysql', - 'server.address' => host, - 'server.port' => port + 'server.address' => host } + # Add server.port only if non-default + attributes['server.port'] = port if port && port != 3306 + attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name attributes['db.namespace'] = _otel_database_name attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb index a52d90f428..0c80684226 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb @@ -98,14 +98,16 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port].to_s + port = query_options[:port] attributes = { 'db.system.name' => 'mysql', - 'server.address' => host, - 'server.port' => port + 'server.address' => host } + # Add server.port only if non-default + attributes['server.port'] = port if port && port != 3306 + attributes['db.namespace'] = _otel_database_name attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service] attributes diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb index 49a123c622..9939748fc9 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb @@ -70,6 +70,34 @@ _(span.attributes['peer.service']).must_equal 'readonly:mysql' end + describe 'server.port attribute' do + it 'does not include server.port when using default port (3306)' do + client.query('SELECT 1') + + _(span.attributes['server.port']).must_be_nil + end + + describe 'when using non-default port' do + let(:non_default_port) { 3307 } + + it 'includes server.port attribute as integer when port is not 3306' do + begin + Mysql2::Client.new( + host: host, + port: non_default_port, + database: database, + username: username, + password: password + ) + rescue Mysql2::Error + nil # Expected - connection fails but span is still recorded + end + + _(span.attributes['server.port']).must_equal(non_default_port) + end + end + end + describe '.attributes' do let(:attributes) { { 'db.statement' => 'foobar', 'db.query.text' => 'foobar' } } @@ -107,7 +135,6 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT 1' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'after requests with prepare select ?' do @@ -123,7 +150,6 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT ?' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'query ? sequences for db.statement with prepare' do @@ -141,7 +167,6 @@ _(span.attributes['net.peer.port']).must_equal port.to_s _(span.attributes['db.query.text']).must_equal sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'query invalid byte sequences for db.statement without prepare' do @@ -167,8 +192,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT 1' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end it 'after error' do expect do @@ -185,8 +209,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT INVALID' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -212,8 +235,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal explain_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end it 'uses component.name and instance.name as span.name fallbacks with invalid sql' do expect do @@ -230,8 +252,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'DESELECT 1' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -261,7 +282,6 @@ _(span.attributes['net.peer.port']).must_equal port.to_s _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'encodes invalid byte sequences for db.statement' do @@ -313,7 +333,6 @@ _(span.attributes['net.peer.port']).must_equal port.to_s _(span.attributes).wont_include('db.query.text') _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end end @@ -339,8 +358,7 @@ _(span.name).must_equal 'select' _(span.attributes).wont_include('db.query.text') _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end end end @@ -366,8 +384,7 @@ _(span.attributes['net.peer.port']).must_equal port.to_s _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end end end @@ -395,8 +412,7 @@ _(span.attributes['net.peer.port']).must_equal port.to_s _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end end end @@ -456,7 +472,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end @@ -474,7 +490,7 @@ OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error end _(span.name).must_equal 'foo mysql' @@ -508,7 +524,7 @@ expect do client.query(sql) end.must_raise Mysql2::Error - end + end _(span.name).must_equal 'foo' end @@ -522,7 +538,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb index 279ba8b686..869d20bbb7 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb @@ -70,6 +70,34 @@ _(span.attributes['peer.service']).must_equal 'readonly:mysql' end + describe 'server.port attribute' do + it 'does not include server.port when using default port (3306)' do + client.query('SELECT 1') + + _(span.attributes['server.port']).must_be_nil + end + + describe 'when using non-default port' do + let(:non_default_port) { 3307 } + + it 'includes server.port attribute as integer when port is not 3306' do + begin + Mysql2::Client.new( + host: host, + port: non_default_port, + database: database, + username: username, + password: password + ) + rescue Mysql2::Error + nil # Expected - connection fails but span is still recorded + end + + _(span.attributes['server.port']).must_equal(non_default_port) + end + end + end + describe '.attributes' do let(:attributes) { { 'db.query.text' => 'foobar' } } @@ -101,7 +129,6 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT 1' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'after requests with prepare select ?' do @@ -112,7 +139,6 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT ?' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'query ? sequences for db.query.text with prepare' do @@ -126,7 +152,6 @@ _(span.name).must_equal 'select' _(span.attributes['db.query.text']).must_equal sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'query invalid byte sequences for db.query.text without prepare' do @@ -147,8 +172,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT 1' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end it 'after error' do expect do @@ -160,8 +184,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT INVALID' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -182,8 +205,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal explain_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end it 'uses component.name and instance.name as span.name fallbacks with invalid sql' do expect do @@ -195,8 +217,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'DESELECT 1' _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -221,7 +242,6 @@ _(span.name).must_equal 'select' _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end it 'encodes invalid byte sequences for db.query.text' do @@ -266,7 +286,6 @@ _(span.name).must_equal 'select' _(span.attributes).wont_include('db.query.text') _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s end end @@ -286,8 +305,7 @@ _(span.name).must_equal 'select' _(span.attributes).wont_include('db.query.text') _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end end end @@ -308,8 +326,7 @@ _(span.name).must_equal 'select' _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end end end @@ -332,8 +349,7 @@ _(span.name).must_equal 'select' _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - _(span.attributes['server.port']).must_equal port.to_s - end + end end end @@ -393,7 +409,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end @@ -411,7 +427,7 @@ OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error end _(span.name).must_equal 'foo mysql' @@ -445,7 +461,7 @@ expect do client.query(sql) end.must_raise Mysql2::Error - end + end _(span.name).must_equal 'foo' end @@ -459,7 +475,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end From 68611010eb9730bee0e6ca3bfa55a25f5c58271d Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 17:03:19 -0700 Subject: [PATCH 07/12] server.port to_i --- .../opentelemetry/instrumentation/mysql2/patches/dup/client.rb | 2 +- .../instrumentation/mysql2/patches/stable/client.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb index 8ed626f8b1..daadd98327 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -102,7 +102,7 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port] + port = query_options[:port]&.to_i attributes = { SemanticConventions::Trace::DB_SYSTEM => 'mysql', diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb index 0c80684226..8e421ac903 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/stable/client.rb @@ -98,7 +98,7 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port] + port = query_options[:port]&.to_i attributes = { 'db.system.name' => 'mysql', From c55faec3135982d8f6a5fe6f66075e97d585abc1 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 20:15:35 -0700 Subject: [PATCH 08/12] try test again --- .../mysql2/dup/instrumentation_test.rb | 13 ++----------- .../mysql2/stable/instrumentation_test.rb | 13 ++----------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb index 9939748fc9..7ee9b16796 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb @@ -81,17 +81,8 @@ let(:non_default_port) { 3307 } it 'includes server.port attribute as integer when port is not 3306' do - begin - Mysql2::Client.new( - host: host, - port: non_default_port, - database: database, - username: username, - password: password - ) - rescue Mysql2::Error - nil # Expected - connection fails but span is still recorded - end + client.query_options[:port] = non_default_port + client.query('SELECT 1') _(span.attributes['server.port']).must_equal(non_default_port) end diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb index 869d20bbb7..38ef33f6ba 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb @@ -81,17 +81,8 @@ let(:non_default_port) { 3307 } it 'includes server.port attribute as integer when port is not 3306' do - begin - Mysql2::Client.new( - host: host, - port: non_default_port, - database: database, - username: username, - password: password - ) - rescue Mysql2::Error - nil # Expected - connection fails but span is still recorded - end + client.query_options[:port] = non_default_port + client.query('SELECT 1') _(span.attributes['server.port']).must_equal(non_default_port) end From 939fbc25b28fedf11575c0f3d4d13311a581d500 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 20:23:19 -0700 Subject: [PATCH 09/12] to_i for server. --- .../instrumentation/mysql2/patches/dup/client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb index daadd98327..5f28220809 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -102,12 +102,12 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port]&.to_i + port = query_options[:port] attributes = { SemanticConventions::Trace::DB_SYSTEM => 'mysql', SemanticConventions::Trace::NET_PEER_NAME => host, - SemanticConventions::Trace::NET_PEER_PORT => port, + SemanticConventions::Trace::NET_PEER_PORT => port&.to_s, 'db.system.name' => 'mysql', 'server.address' => host } From 730c267900cbb399e29628bc81aeb9ccc30bbee4 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 20:28:35 -0700 Subject: [PATCH 10/12] Update dup server.port --- .../opentelemetry/instrumentation/mysql2/patches/dup/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb index 5f28220809..ae6383633d 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/dup/client.rb @@ -102,7 +102,7 @@ def _otel_client_attributes # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 host = (query_options[:host] || query_options[:hostname]).to_s - port = query_options[:port] + port = query_options[:port]&.to_i attributes = { SemanticConventions::Trace::DB_SYSTEM => 'mysql', From e0c440f19178840585f64cd292eeca679767b2a0 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 20:31:48 -0700 Subject: [PATCH 11/12] Rubocop --- .../opentelemetry/instrumentation/mysql2/instrumentation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb index eecf3606fe..c3db06f402 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb @@ -11,11 +11,11 @@ module Mysql2 # instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - install do |_config| + install do |config| patch_type = determine_semconv send(:"require_dependencies_#{patch_type}") send(:"patch_client_#{patch_type}") - configure_propagator(_config) + configure_propagator(config) end present do From c5ea28b84fcd47ce2b158083e1aa6b0d5a7f8a84 Mon Sep 17 00:00:00 2001 From: Hannah Ramadan Date: Thu, 12 Mar 2026 20:46:01 -0700 Subject: [PATCH 12/12] autocorrect rubocop --- .../instrumentation/mysql2/instrumentation.rb | 1 - .../mysql2/dup/instrumentation_test.rb | 22 +++++++++---------- .../mysql2/stable/instrumentation_test.rb | 22 +++++++++---------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb index c3db06f402..0c45365262 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/instrumentation.rb @@ -10,7 +10,6 @@ module Mysql2 # The Instrumentation class contains logic to detect and install the Mysql2 # instrumentation class Instrumentation < OpenTelemetry::Instrumentation::Base - install do |config| patch_type = determine_semconv send(:"require_dependencies_#{patch_type}") diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb index 7ee9b16796..57291ce0d7 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/dup/instrumentation_test.rb @@ -183,7 +183,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT 1' _(span.attributes['server.address']).must_equal host.to_s - end + end it 'after error' do expect do @@ -200,7 +200,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT INVALID' _(span.attributes['server.address']).must_equal host.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -226,7 +226,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal explain_sql _(span.attributes['server.address']).must_equal host.to_s - end + end it 'uses component.name and instance.name as span.name fallbacks with invalid sql' do expect do @@ -243,7 +243,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'DESELECT 1' _(span.attributes['server.address']).must_equal host.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -349,7 +349,7 @@ _(span.name).must_equal 'select' _(span.attributes).wont_include('db.query.text') _(span.attributes['server.address']).must_equal host.to_s - end + end end end @@ -375,7 +375,7 @@ _(span.attributes['net.peer.port']).must_equal port.to_s _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - end + end end end @@ -403,7 +403,7 @@ _(span.attributes['net.peer.port']).must_equal port.to_s _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - end + end end end @@ -463,7 +463,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end @@ -481,7 +481,7 @@ OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error end _(span.name).must_equal 'foo mysql' @@ -515,7 +515,7 @@ expect do client.query(sql) end.must_raise Mysql2::Error - end + end _(span.name).must_equal 'foo' end @@ -529,7 +529,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb index 38ef33f6ba..763663c8a4 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/stable/instrumentation_test.rb @@ -163,7 +163,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT 1' _(span.attributes['server.address']).must_equal host.to_s - end + end it 'after error' do expect do @@ -175,7 +175,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'SELECT INVALID' _(span.attributes['server.address']).must_equal host.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -196,7 +196,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal explain_sql _(span.attributes['server.address']).must_equal host.to_s - end + end it 'uses component.name and instance.name as span.name fallbacks with invalid sql' do expect do @@ -208,7 +208,7 @@ _(span.attributes['db.namespace']).must_equal 'mysql' _(span.attributes['db.query.text']).must_equal 'DESELECT 1' _(span.attributes['server.address']).must_equal host.to_s - + _(span.status.code).must_equal( OpenTelemetry::Trace::Status::ERROR ) @@ -296,7 +296,7 @@ _(span.name).must_equal 'select' _(span.attributes).wont_include('db.query.text') _(span.attributes['server.address']).must_equal host.to_s - end + end end end @@ -317,7 +317,7 @@ _(span.name).must_equal 'select' _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - end + end end end @@ -340,7 +340,7 @@ _(span.name).must_equal 'select' _(span.attributes['db.query.text']).must_equal obfuscated_sql _(span.attributes['server.address']).must_equal host.to_s - end + end end end @@ -400,7 +400,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end @@ -418,7 +418,7 @@ OpenTelemetry::Instrumentation::Mysql2.with_attributes('db.operation.name' => 'foo') do expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error end _(span.name).must_equal 'foo mysql' @@ -452,7 +452,7 @@ expect do client.query(sql) end.must_raise Mysql2::Error - end + end _(span.name).must_equal 'foo' end @@ -466,7 +466,7 @@ sql = "SELECT * from users where users.id = 1 and users.email = 'test@test.com'" expect do client.query(sql) - end.must_raise Mysql2::Error + end.must_raise Mysql2::Error _(span.name).must_equal 'mysql' end