Skip to content

Commit 43d24ee

Browse files
committed
Add Mysql2 and Trilogy collection_name attribute
1 parent f999e70 commit 43d24ee

File tree

7 files changed

+189
-5
lines changed

7 files changed

+189
-5
lines changed

instrumentation/mysql2/example/mysql2.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@
2121
client.query("SELECT * from information_schema.INNODB_TABLES; /**Dé**/").each do |row|
2222
puts row
2323
end
24+
25+
client.query('CREATE TABLE test_table (id SERIAL PRIMARY KEY, name VARCHAR(50), age INT)')
26+
client.query('DROP TABLE test_table')

instrumentation/mysql2/lib/opentelemetry/instrumentation/mysql2/patches/client.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ module Mysql2
1313
module Patches
1414
# Module to prepend to Mysql2::Client for instrumentation
1515
module Client
16+
17+
# Capture the first word (including letters, digits, underscores, & '.', ) that follows common table commands
18+
TABLE_NAME = /\b(?:FROM|INTO|UPDATE|CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?|DROP\s+TABLE(?:\s+IF\s+EXISTS)?|ALTER\s+TABLE(?:\s+IF\s+EXISTS)?)\s+([\w\.]+)/i
19+
1620
def query(sql, options = {})
1721
tracer.in_span(
1822
_otel_span_name(sql),
@@ -47,7 +51,7 @@ def _otel_span_name(sql)
4751
end
4852

4953
def _otel_span_attributes(sql)
50-
attributes = _otel_client_attributes
54+
attributes = _otel_client_attributes(sql)
5155
case config[:db_statement]
5256
when :include
5357
attributes[SemanticConventions::Trace::DB_STATEMENT] = sql
@@ -68,7 +72,7 @@ def _otel_database_name
6872
(query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s
6973
end
7074

71-
def _otel_client_attributes
75+
def _otel_client_attributes(sql)
7276
# The client specific attributes can be found via the query_options instance variable
7377
# exposed on the mysql2 Client
7478
# https://github.com/brianmario/mysql2/blob/ca08712c6c8ea672df658bb25b931fea22555f27/lib/mysql2/client.rb#L25-L26
@@ -83,9 +87,18 @@ def _otel_client_attributes
8387

8488
attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name
8589
attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service]
90+
attributes['db.collection.name'] = collection_name(sql)
91+
8692
attributes
8793
end
8894

95+
def collection_name(sql)
96+
sql.scan(TABLE_NAME).flatten[0]
97+
98+
rescue StandardError
99+
nil
100+
end
101+
89102
def tracer
90103
Mysql2::Instrumentation.instance.tracer
91104
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
[
2+
{
3+
"name": "from",
4+
"sql": "SELECT * FROM test_table"
5+
},
6+
{
7+
"name": "select_count_from",
8+
"sql": "SELECT COUNT(*) FROM test_table WHERE condition"
9+
},
10+
{
11+
"name": "from_with_subquery",
12+
"sql": "SELECT * FROM (SELECT * FROM test_table) AS table_alias"
13+
},
14+
{
15+
"name": "insert_into",
16+
"sql": "INSERT INTO test_table (column1, column2) VALUES (value1, value2)"
17+
},
18+
{
19+
"name": "update",
20+
"sql": "UPDATE test_table SET column1 = value1 WHERE condition"
21+
},
22+
{
23+
"name": "delete_from",
24+
"sql": "DELETE FROM test_table WHERE condition"
25+
},
26+
{
27+
"name": "create_table",
28+
"sql": "CREATE TABLE test_table (column1 datatype, column2 datatype)"
29+
},
30+
{
31+
"name": "create_table_if_not_exists",
32+
"sql": "CREATE TABLE IF NOT EXISTS test_table (column1 datatype, column2 datatype)"
33+
},
34+
{
35+
"name": "alter_table",
36+
"sql": "ALTER TABLE test_table ADD column_name datatype"
37+
},
38+
{
39+
"name": "drop_table",
40+
"sql": "DROP TABLE test_table"
41+
},
42+
{
43+
"name": "drop_table_if_exists",
44+
"sql": "DROP TABLE IF EXISTS test_table"
45+
},
46+
{
47+
"name": "insert_into",
48+
"sql": "INSERT INTO test_table values('', 'a''b c',0, 1 , 'd''e f''s h')"
49+
},
50+
{
51+
"name": "from_with_join",
52+
"sql": "SELECT columns FROM test_table JOIN table2 ON test_table.column = table2.column"
53+
}
54+
]

instrumentation/mysql2/test/opentelemetry/instrumentation/mysql2/instrumentation_test.rb

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
# 1. Build the opentelemetry/opentelemetry-ruby-contrib image
1616
# - docker-compose build
1717
# 2. Bundle install
18-
# - docker-compose run ex-instrumentation-mysql2-test bundle install
18+
# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal install
1919
# 3. Run test suite
20-
# - docker-compose run ex-instrumentation-mysql2-test bundle exec rake test
20+
# - docker-compose run ex-instrumentation-mysql2-test bundle exec appraisal rake test
2121
describe OpenTelemetry::Instrumentation::Mysql2::Instrumentation do
2222
let(:instrumentation) { OpenTelemetry::Instrumentation::Mysql2::Instrumentation.instance }
2323
let(:exporter) { EXPORTER }
@@ -473,6 +473,25 @@
473473
end
474474
end
475475
end
476+
477+
describe '#connection_name' do
478+
479+
def self.load_fixture
480+
data = File.read("#{Dir.pwd}/test/fixtures/sql_table_name.json")
481+
JSON.parse(data)
482+
end
483+
484+
load_fixture.each do |test_case|
485+
name = test_case['name']
486+
query = test_case['sql']
487+
488+
it "returns the table name for #{name}" do
489+
table_name = client.send(:collection_name, query)
490+
491+
expect(table_name).must_equal('test_table')
492+
end
493+
end
494+
end
476495
end
477496
end unless ENV['OMIT_SERVICES']
478497
end

instrumentation/trilogy/lib/opentelemetry/instrumentation/trilogy/patches/client.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ module Trilogy
1313
module Patches
1414
# Module to prepend to Trilogy for instrumentation
1515
module Client
16+
17+
# Capture the first word (including letters, digits, underscores, & '.', ) that follows common table commands
18+
TABLE_NAME = /\b(?:FROM|INTO|UPDATE|CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?|DROP\s+TABLE(?:\s+IF\s+EXISTS)?|ALTER\s+TABLE(?:\s+IF\s+EXISTS)?)\s+([\w\.]+)/i
19+
1620
def initialize(options = {})
1721
@connection_options = options # This is normally done by Trilogy#initialize
1822

@@ -76,6 +80,8 @@ def client_attributes(sql = nil)
7680
attributes['db.instance.id'] = @connected_host unless @connected_host.nil?
7781

7882
if sql
83+
attributes['db.collection.name'] = collection_name(sql)
84+
7985
case config[:db_statement]
8086
when :obfuscate
8187
attributes[::OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT] =
@@ -84,10 +90,17 @@ def client_attributes(sql = nil)
8490
attributes[::OpenTelemetry::SemanticConventions::Trace::DB_STATEMENT] = sql
8591
end
8692
end
87-
93+
attributes.compact!
8894
attributes
8995
end
9096

97+
def collection_name(sql)
98+
sql.scan(TABLE_NAME).flatten[0]
99+
100+
rescue StandardError
101+
nil
102+
end
103+
91104
def database_name
92105
connection_options[:database]
93106
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
[
2+
{
3+
"name": "from",
4+
"sql": "SELECT * FROM test_table"
5+
},
6+
{
7+
"name": "select_count_from",
8+
"sql": "SELECT COUNT(*) FROM test_table WHERE condition"
9+
},
10+
{
11+
"name": "from_with_subquery",
12+
"sql": "SELECT * FROM (SELECT * FROM test_table) AS table_alias"
13+
},
14+
{
15+
"name": "insert_into",
16+
"sql": "INSERT INTO test_table (column1, column2) VALUES (value1, value2)"
17+
},
18+
{
19+
"name": "update",
20+
"sql": "UPDATE test_table SET column1 = value1 WHERE condition"
21+
},
22+
{
23+
"name": "delete_from",
24+
"sql": "DELETE FROM test_table WHERE condition"
25+
},
26+
{
27+
"name": "create_table",
28+
"sql": "CREATE TABLE test_table (column1 datatype, column2 datatype)"
29+
},
30+
{
31+
"name": "create_table_if_not_exists",
32+
"sql": "CREATE TABLE IF NOT EXISTS test_table (column1 datatype, column2 datatype)"
33+
},
34+
{
35+
"name": "alter_table",
36+
"sql": "ALTER TABLE test_table ADD column_name datatype"
37+
},
38+
{
39+
"name": "drop_table",
40+
"sql": "DROP TABLE test_table"
41+
},
42+
{
43+
"name": "drop_table_if_exists",
44+
"sql": "DROP TABLE IF EXISTS test_table"
45+
},
46+
{
47+
"name": "insert_into",
48+
"sql": "INSERT INTO test_table values('', 'a''b c',0, 1 , 'd''e f''s h')"
49+
},
50+
{
51+
"name": "from_with_join",
52+
"sql": "SELECT columns FROM test_table JOIN table2 ON test_table.column = table2.column"
53+
}
54+
]

instrumentation/trilogy/test/opentelemetry/instrumentation/trilogy/instrumentation_test.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@
99
require_relative '../../../../lib/opentelemetry/instrumentation/trilogy'
1010
require_relative '../../../../lib/opentelemetry/instrumentation/trilogy/patches/client'
1111

12+
# This test suite requires a running mysql container and dedicated test container. We can use the same
13+
# docker-compose file as the mysql2 instrumentation tests. The test container should have the mysql client.
14+
# To run tests locally:
15+
# 1. Build the opentelemetry/opentelemetry-ruby-contrib image
16+
# - docker-compose build
17+
# 2. Open a bash shell in the test container and cd to the trilogy directory
18+
# - docker-compose run ex-instrumentation-mysql2-test bash -c 'cd ../trilogy && bash'
19+
# 3. Bundle install and run tests with the Appraisals gem
20+
# - bundle exec appraisal install && bundle exec appraisal rake test
21+
1222
describe OpenTelemetry::Instrumentation::Trilogy do
1323
let(:instrumentation) { OpenTelemetry::Instrumentation::Trilogy::Instrumentation.instance }
1424
let(:exporter) { EXPORTER }
@@ -626,5 +636,23 @@
626636
end
627637
end
628638
end
639+
640+
describe '#connection_name' do
641+
def self.load_fixture
642+
data = File.read("#{Dir.pwd}/test/fixtures/sql_table_name.json")
643+
JSON.parse(data)
644+
end
645+
646+
load_fixture.each do |test_case|
647+
name = test_case['name']
648+
query = test_case['sql']
649+
650+
it "returns the table name for #{name}" do
651+
table_name = client.send(:collection_name, query)
652+
653+
expect(table_name).must_equal('test_table')
654+
end
655+
end
656+
end
629657
end
630658
end

0 commit comments

Comments
 (0)