From 5d405628eeb1d32767bd7544c249e4aa74b1f140 Mon Sep 17 00:00:00 2001 From: Xuan <112967240+xuan-cao-swi@users.noreply.github.com> Date: Wed, 14 Feb 2024 18:30:05 -0500 Subject: [PATCH] feat: instrument mysql2 prepare statement (#862) * feat: instrument prepare for mysql2 lib * revision --- .../instrumentation/mysql2/patches/client.rb | 64 +++++++++++++------ .../mysql2/instrumentation_test.rb | 47 ++++++++++++++ 2 files changed, 90 insertions(+), 21 deletions(-) diff --git a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb index a4c78db4f..eca0e4d9f 100644 --- a/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb +++ b/instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb @@ -14,7 +14,40 @@ module Patches # Module to prepend to Mysql2::Client for instrumentation module Client def query(sql, options = {}) - attributes = client_attributes + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super(sql, options) + end + end + + def prepare(sql) + tracer.in_span( + _otel_span_name(sql), + attributes: _otel_span_attributes(sql), + kind: :client + ) do + super(sql) + 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 @@ -24,30 +57,18 @@ def query(sql, options = {}) sql, obfuscation_limit: config[:obfuscation_limit], adapter: :mysql ) end - tracer.in_span( - OpenTelemetry::Helpers::MySQL.database_span_name( - sql, - OpenTelemetry::Instrumentation::Mysql2.attributes[ - SemanticConventions::Trace::DB_OPERATION - ], - database_name, - config - ), - attributes: attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes), - kind: :client - ) do - super(sql, options) - end - end - private + attributes.merge!(OpenTelemetry::Instrumentation::Mysql2.attributes) + attributes.compact! + attributes + end - def database_name + def _otel_database_name # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L78 (query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s end - def client_attributes + def _otel_client_attributes # The client specific attributes can be found via the query_options instance variable # exposed on the mysql2 Client # https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26 @@ -59,8 +80,9 @@ def client_attributes SemanticConventions::Trace::NET_PEER_NAME => host, SemanticConventions::Trace::NET_PEER_PORT => port } - attributes[SemanticConventions::Trace::DB_NAME] = database_name if database_name - attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] if config[:peer_service] + + attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name + attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] attributes end diff --git a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb index 8a0b5a948..319e138d8 100644 --- a/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb +++ b/instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb @@ -88,6 +88,53 @@ end end + describe 'prepare statement' do + it 'after requests with prepare' do + client.prepare('SELECT 1') + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT 1' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + end + + it 'after requests with prepare select ?' do + client.prepare('SELECT ?') + + _(span.name).must_equal 'select' + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.attributes['db.statement']).must_equal 'SELECT ?' + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + end + + it 'query ? sequences for db.statement with prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.prepare(sql) + end.must_raise Mysql2::Error + + _(span.attributes['db.system']).must_equal 'mysql' + _(span.attributes['db.name']).must_equal 'mysql' + _(span.name).must_equal 'select' + _(span.attributes['db.statement']).must_equal sql + _(span.attributes['net.peer.name']).must_equal host.to_s + _(span.attributes['net.peer.port']).must_equal port.to_s + end + + it 'query invalid byte sequences for db.statement without prepare' do + sql = 'SELECT * from users where users.id = ? and users.email = ?' + expect do + client.query(sql) + end.must_raise Mysql2::Error + + _(span.events[0].attributes['exception.message'].slice(0, 37)).must_equal 'You have an error in your SQL syntax;' + end + end + it 'after requests' do client.query('SELECT 1')