diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index f360358a9e..08012d0593 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -6,6 +6,10 @@ cases for escaped identifiers. Optimize performance of parsing logic. ([#3627](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3627)) +* Sanitize the object name for SQL query text using the LOGIN or USER keywords and + remove from query summaries. + ([#3663](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3663)) + ## 1.14.0-beta.2 Released 2025-Nov-14 diff --git a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md index 83073163e5..068333d93d 100644 --- a/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.SqlClient/CHANGELOG.md @@ -11,6 +11,10 @@ cases for escaped identifiers. Optimize performance of parsing logic. ([#3627](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3627)) +* Sanitize the object name for SQL query text using the LOGIN or USER keywords and + remove from query summaries. + ([#3663](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3663)) + ## 1.14.0-beta.1 Released 2025-Nov-13 diff --git a/src/Shared/SqlProcessor.cs b/src/Shared/SqlProcessor.cs index 869e39925a..0f8e668c1f 100644 --- a/src/Shared/SqlProcessor.cs +++ b/src/Shared/SqlProcessor.cs @@ -92,6 +92,7 @@ private enum SqlKeyword Insert, Into, Join, + Login, NonClustered, Not, On, @@ -422,6 +423,7 @@ private static void ParseNextToken( } state.CaptureNextNonKeywordTokenAsIdentifier = SqlKeywordInfo.CaptureNextTokenInSummary(in state, potentialKeywordInfo.SqlKeyword); + state.SanitizeNextNonKeywordToken = SqlKeywordInfo.SanitizeNextToken(in state, potentialKeywordInfo.SqlKeyword); state.InFromClause = potentialKeywordInfo.SqlKeyword == SqlKeyword.From || (state.PreviousParsedKeyword?.SqlKeyword == SqlKeyword.From && state.CaptureNextNonKeywordTokenAsIdentifier); state.PreviousParsedKeyword = potentialKeywordInfo; state.ParsePosition += keywordLength; @@ -484,8 +486,15 @@ private static void ParseNextToken( } } - sql.Slice(start, length).CopyTo(buffer.Slice(state.SanitizedPosition)); - state.SanitizedPosition += length; + if (state.SanitizeNextNonKeywordToken) + { + buffer[state.SanitizedPosition++] = SanitizationPlaceholder; + } + else + { + sql.Slice(start, length).CopyTo(buffer.Slice(state.SanitizedPosition)); + state.SanitizedPosition += length; + } // Optionally copy to summary buffer. if (state.CaptureNextNonKeywordTokenAsIdentifier) @@ -500,6 +509,7 @@ private static void ParseNextToken( state.ParsePosition = i; state.CaptureNextNonKeywordTokenAsIdentifier = false; + state.SanitizeNextNonKeywordToken = false; state.PreviousTokenStartPosition = start; state.PreviousTokenEndPosition = i; } @@ -881,6 +891,8 @@ private ref struct ParseState public bool CaptureNextNonKeywordTokenAsIdentifier; // 1 byte + public bool SanitizeNextNonKeywordToken; // 1 byte + /// /// Used to track if we are in an escaped identifier (e.g., "[table]"). /// @@ -926,6 +938,7 @@ static SqlKeywordInfo() InsertKeyword = new("insert", SqlKeyword.Insert, Unknown); IntoKeyword = new("into", SqlKeyword.Into); JoinKeyword = new("join", SqlKeyword.Join); + LoginKeyword = new("login", SqlKeyword.Login, DdlKeywords); NotKeyword = new("not", SqlKeyword.Not); OnKeyword = new("on", SqlKeyword.On); ProcedureKeyword = new("procedure", SqlKeyword.Procedure, DdlKeywords); @@ -952,6 +965,7 @@ static SqlKeywordInfo() ProcedureKeyword, TriggerKeyword, DatabaseKeyword, + LoginKeyword, UserKeyword, RoleKeyword, SequenceKeyword, @@ -970,6 +984,7 @@ static SqlKeywordInfo() IndexKeyword.FollowedByKeywords = [OnKeyword, IfKeyword]; InsertKeyword.FollowedByKeywords = [IntoKeyword]; JoinKeyword.FollowedByKeywords = [OnKeyword]; + LoginKeyword.FollowedByKeywords = [IfKeyword]; NotKeyword.FollowedByKeywords = [ExistsKeyword]; OnKeyword.FollowedByKeywords = [JoinKeyword]; ProcedureKeyword.FollowedByKeywords = [IfKeyword]; @@ -1023,6 +1038,8 @@ private SqlKeywordInfo( public static SqlKeywordInfo JoinKeyword { get; } + public static SqlKeywordInfo LoginKeyword { get; } + public static SqlKeywordInfo NotKeyword { get; } public static SqlKeywordInfo OnKeyword { get; } @@ -1063,13 +1080,29 @@ private SqlKeywordInfo( public static bool CaptureNextTokenInSummary(in ParseState state, SqlKeyword currentKeyword) => currentKeyword switch { SqlKeyword.Exec => true, + SqlKeyword.Exists => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop && state.PreviousSummaryKeyword is not (SqlKeyword.Login or SqlKeyword.User), SqlKeyword.From => state.PreviousSummaryKeyword is SqlKeyword.Select or SqlKeyword.Distinct, SqlKeyword.Into => state.FirstSummaryKeyword is SqlKeyword.Insert, SqlKeyword.Join => state.FirstSummaryKeyword is SqlKeyword.Select or SqlKeyword.Join, - SqlKeyword.Database or SqlKeyword.Schema or SqlKeyword.Table or SqlKeyword.Index or SqlKeyword.View - or SqlKeyword.Procedure or SqlKeyword.Trigger or SqlKeyword.Function or SqlKeyword.User - or SqlKeyword.Role or SqlKeyword.Sequence => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop, - SqlKeyword.Exists => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop, + SqlKeyword.Login or SqlKeyword.User => false, + SqlKeyword.Database or + SqlKeyword.Function or + SqlKeyword.Index or + SqlKeyword.Procedure or + SqlKeyword.Role or + SqlKeyword.Schema or + SqlKeyword.Sequence or + SqlKeyword.Table or + SqlKeyword.Trigger or + SqlKeyword.View => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop, + _ => false, + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool SanitizeNextToken(in ParseState state, SqlKeyword currentKeyword) => currentKeyword switch + { + SqlKeyword.Login or SqlKeyword.User => state.FirstSummaryKeyword is SqlKeyword.Create or SqlKeyword.Alter or SqlKeyword.Drop, + SqlKeyword.Exists => state.PreviousSummaryKeyword is SqlKeyword.Login or SqlKeyword.User, _ => false, }; diff --git a/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json b/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json index 5e97615f8c..b227615849 100644 --- a/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json +++ b/test/OpenTelemetry.Contrib.Shared.Tests/SqlProcessorAdditionalTestCases.json @@ -480,9 +480,9 @@ }, "expected": { "db.query.text": [ - "CREATE USER johndoe WITH PASSWORD = ?;" + "CREATE USER ? WITH PASSWORD = ?;" ], - "db.query.summary": "CREATE USER johndoe" + "db.query.summary": "CREATE USER" } }, { @@ -493,9 +493,9 @@ }, "expected": { "db.query.text": [ - "ALTER USER johndoe WITH PASSWORD = ?;" + "ALTER USER ? WITH PASSWORD = ?;" ], - "db.query.summary": "ALTER USER johndoe" + "db.query.summary": "ALTER USER" } }, { @@ -506,9 +506,48 @@ }, "expected": { "db.query.text": [ - "DROP USER IF EXISTS johndoe;" + "DROP USER IF EXISTS ?;" ], - "db.query.summary": "DROP USER johndoe" + "db.query.summary": "DROP USER" + } + }, + { + "name": "create_login", + "input": { + "db.system.name": "other_sql", + "query": "CREATE LOGIN johndoe WITH PASSWORD = 'password';" + }, + "expected": { + "db.query.text": [ + "CREATE LOGIN ? WITH PASSWORD = ?;" + ], + "db.query.summary": "CREATE LOGIN" + } + }, + { + "name": "alter_login", + "input": { + "db.system.name": "other_sql", + "query": "ALTER LOGIN johndoe WITH PASSWORD = 'newpassword';" + }, + "expected": { + "db.query.text": [ + "ALTER LOGIN ? WITH PASSWORD = ?;" + ], + "db.query.summary": "ALTER LOGIN" + } + }, + { + "name": "drop_login", + "input": { + "db.system.name": "other_sql", + "query": "DROP LOGIN IF EXISTS johndoe;" + }, + "expected": { + "db.query.text": [ + "DROP LOGIN IF EXISTS ?;" + ], + "db.query.summary": "DROP LOGIN" } }, {