-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Add Mysql2 and Trilogy db.collection.name
attribute
#1109
base: main
Are you sure you want to change the base?
Changes from 3 commits
43d24ee
8bd5818
146bd71
f9c244e
6047052
cc43b33
b61e51e
bc7df18
a23b954
f099af3
8134ee3
8328668
8f42fa2
e73b23a
46616da
4af3e2c
d6ffcdf
d9f1975
b8d5a96
68f42ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,9 @@ module Mysql2 | |
module Patches | ||
# Module to prepend to Mysql2::Client for instrumentation | ||
module Client | ||
# Capture the first word (including letters, digits, underscores, & '.', ) that follows common table commands | ||
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 | ||
|
||
def query(sql, options = {}) | ||
tracer.in_span( | ||
_otel_span_name(sql), | ||
|
@@ -47,7 +50,7 @@ def _otel_span_name(sql) | |
end | ||
|
||
def _otel_span_attributes(sql) | ||
attributes = _otel_client_attributes | ||
attributes = _otel_client_attributes(sql) | ||
case config[:db_statement] | ||
when :include | ||
attributes[SemanticConventions::Trace::DB_STATEMENT] = sql | ||
|
@@ -68,7 +71,7 @@ def _otel_database_name | |
(query_options[:database] || query_options[:dbname] || query_options[:db])&.to_s | ||
end | ||
|
||
def _otel_client_attributes | ||
def _otel_client_attributes(sql) | ||
# 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 | ||
|
@@ -83,9 +86,17 @@ def _otel_client_attributes | |
|
||
attributes[SemanticConventions::Trace::DB_NAME] = _otel_database_name | ||
attributes[SemanticConventions::Trace::PEER_SERVICE] = config[:peer_service] | ||
attributes['db.collection.name'] = collection_name(sql) | ||
|
||
attributes | ||
end | ||
|
||
def collection_name(sql) | ||
sql.scan(TABLE_NAME).flatten[0] | ||
rescue StandardError | ||
hannahramadan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If an error occurs and the attribute is set to Please ensure that the attribute is not set in cases where the table name could not be extracted. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made some changes! Will log an error if trouble getting the table name and won't report. Is the error logging here okay? 4af3e2c |
||
end | ||
|
||
def tracer | ||
Mysql2::Instrumentation.instance.tracer | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
[ | ||
{ | ||
"name": "from", | ||
"sql": "SELECT * FROM test_table" | ||
}, | ||
{ | ||
"name": "select_count_from", | ||
"sql": "SELECT COUNT(*) FROM test_table WHERE condition" | ||
}, | ||
{ | ||
"name": "from_with_subquery", | ||
"sql": "SELECT * FROM (SELECT * FROM test_table) AS table_alias" | ||
}, | ||
{ | ||
"name": "insert_into", | ||
"sql": "INSERT INTO test_table (column1, column2) VALUES (value1, value2)" | ||
}, | ||
{ | ||
"name": "update", | ||
"sql": "UPDATE test_table SET column1 = value1 WHERE condition" | ||
}, | ||
{ | ||
"name": "delete_from", | ||
"sql": "DELETE FROM test_table WHERE condition" | ||
}, | ||
{ | ||
"name": "create_table", | ||
"sql": "CREATE TABLE test_table (column1 datatype, column2 datatype)" | ||
}, | ||
{ | ||
"name": "create_table_if_not_exists", | ||
"sql": "CREATE TABLE IF NOT EXISTS test_table (column1 datatype, column2 datatype)" | ||
}, | ||
{ | ||
"name": "alter_table", | ||
"sql": "ALTER TABLE test_table ADD column_name datatype" | ||
}, | ||
{ | ||
"name": "drop_table", | ||
"sql": "DROP TABLE test_table" | ||
}, | ||
{ | ||
"name": "drop_table_if_exists", | ||
"sql": "DROP TABLE IF EXISTS test_table" | ||
}, | ||
{ | ||
"name": "insert_into", | ||
"sql": "INSERT INTO test_table values('', 'a''b c',0, 1 , 'd''e f''s h')" | ||
}, | ||
{ | ||
"name": "from_with_join", | ||
"sql": "SELECT columns FROM test_table JOIN table2 ON test_table.column = table2.column" | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
[ | ||
{ | ||
"name": "from", | ||
"sql": "SELECT * FROM test_table" | ||
}, | ||
{ | ||
"name": "select_count_from", | ||
"sql": "SELECT COUNT(*) FROM test_table WHERE condition" | ||
}, | ||
{ | ||
"name": "from_with_subquery", | ||
"sql": "SELECT * FROM (SELECT * FROM test_table) AS table_alias" | ||
}, | ||
{ | ||
"name": "insert_into", | ||
"sql": "INSERT INTO test_table (column1, column2) VALUES (value1, value2)" | ||
}, | ||
{ | ||
"name": "update", | ||
"sql": "UPDATE test_table SET column1 = value1 WHERE condition" | ||
}, | ||
{ | ||
"name": "delete_from", | ||
"sql": "DELETE FROM test_table WHERE condition" | ||
}, | ||
{ | ||
"name": "create_table", | ||
"sql": "CREATE TABLE test_table (column1 datatype, column2 datatype)" | ||
}, | ||
{ | ||
"name": "create_table_if_not_exists", | ||
"sql": "CREATE TABLE IF NOT EXISTS test_table (column1 datatype, column2 datatype)" | ||
}, | ||
{ | ||
"name": "alter_table", | ||
"sql": "ALTER TABLE test_table ADD column_name datatype" | ||
}, | ||
{ | ||
"name": "drop_table", | ||
"sql": "DROP TABLE test_table" | ||
}, | ||
{ | ||
"name": "drop_table_if_exists", | ||
"sql": "DROP TABLE IF EXISTS test_table" | ||
}, | ||
{ | ||
"name": "insert_into", | ||
"sql": "INSERT INTO test_table values('', 'a''b c',0, 1 , 'd''e f''s h')" | ||
}, | ||
{ | ||
"name": "from_with_join", | ||
"sql": "SELECT columns FROM test_table JOIN table2 ON test_table.column = table2.column" | ||
} | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to avoid adding additional overhead to the instrumentation if possible.
In cases where the SQL statement is omitted or not sanitized, there isn't any regexp scan that occurs.
Is there a way to optimize it so that there isn't additional processing?
I would also like to consider that this
db.collection.name
matches newer versions of the OTel Schema and the instrumentations are still using pre-1.0 semantics.If I am not mistaken that would have been
db.table.name
. I think we should continue using pre-1.0 semantics until we haveOTEL_SEMCONV_STABILITY_OPT_IN
implementedhttps://opentelemetry.io/docs/specs/semconv/database/database-spans/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @arielvalentin! It makes sense to keep consistent with pre-1.0 semantics. In this case, the attribute is
db.sql.table
. I've made this update!Re additional processing: I see your point about putting every SQL statement through a regexp scan. Because the table/collection name isn't available on the client, I'm not sure how else we'd be able to get this information. We could make table name an opt-in/opt-out attribute, but is that level of control something we want to provide? What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean it's difficult to say. I feel like maybe this should only be done when the we also include the db.statement and I don't know if it would be possible to combine with the obfuscation code.
Maybe I'm making too much of it? I'm not sure.
Perhaps benchmarking will put me a bit at ease.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if only including the table name if we're recording the db.statement deviates from the convention (because as of now, the convention has it that recording the collection name isn't based on any condition, just availability).
The change to
Regexp.last_match(1)
and an updated regex string improved speed. In the following benchmark example, I used the MySQL obfuscation SQL code as a baseline, and found running the extradb.collection.name
sql was 1.19x slower.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arielvalentin We chatted about this in the SIG today and decided the reporting of table/collection names can be put behind a feature flag. What do you think? If that works, the remaining consideration is if the default on or off - do you have any preference on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arielvalentin - I made a suggestion for the config name
db_collection_name
and default valueinclude
, withomit
as the other option: 8328668There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @arielvalentin! Wanted to check in and see if you had thoughts on the above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your patience. I'm going to add this to my list to review by EoD tomorrow