//----------------------------------------------------------------------------- // // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // A copy of the License is located at // // http://aws.amazon.com/apache2.0 // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // //----------------------------------------------------------------------------- using Amazon.XRay.Recorder.Core; using Amazon.XRay.Recorder.Core.Exceptions; using Amazon.XRay.Recorder.Core.Internal.Entities; #if NET45 using Amazon.XRay.Recorder.Core.Internal.Utils; #endif using System; using System.Data.Common; using System.Diagnostics.Tracing; using System.Globalization; using System.Text.RegularExpressions; namespace Amazon.XRay.Recorder.AutoInstrumentation.Utils { /// /// Utils for Sql request /// public static class SqlRequestUtil { private static readonly Regex _portNumberRegex = new Regex(@"[,|:]\d+$"); /// /// Removes the port number from data source. /// internal static string RemovePortNumberFromDataSource(string dataSource) { return _portNumberRegex.Replace(dataSource, string.Empty); } /// /// Begin subsegment and add name space. /// internal static void BeginSubsegment(DbCommand command) { AWSXRayRecorder.Instance.BeginSubsegment(BuildSubsegmentName(command)); AWSXRayRecorder.Instance.SetNamespace("remote"); } /// /// Process command. /// internal static void ProcessCommand(DbCommand command) { CollectSqlInformationDefault(command); } /// /// Process data from SqlEventListener /// internal static void ProcessEventData(EventWrittenEventArgs sqlEventData) { int id = Convert.ToInt32(sqlEventData.Payload[0], CultureInfo.InvariantCulture); string dataSource = Convert.ToString(sqlEventData.Payload[1], CultureInfo.InvariantCulture); string database = Convert.ToString(sqlEventData.Payload[2], CultureInfo.InvariantCulture); string commandText = Convert.ToString(sqlEventData.Payload[3], CultureInfo.InvariantCulture); if (string.IsNullOrEmpty(database) || string.IsNullOrEmpty(dataSource)) { return; } string subsegmentName = database + "@" + RemovePortNumberFromDataSource(dataSource); var recorder = AWSXRayRecorder.Instance; recorder.BeginSubsegment(subsegmentName); recorder.SetNamespace("remote"); recorder.AddSqlInformation("database_type", "sqlserver"); if (ShouldCollectSqlText()) { if (!string.IsNullOrEmpty(commandText)) { recorder.AddSqlInformation("sanitized_query", commandText); } } } /// /// Process exception. /// internal static void ProcessException(Exception exception) { AWSXRayRecorder.Instance.AddException(exception); } /// /// End subsegment. /// internal static void EndSubsegment() { AWSXRayRecorder.Instance.EndSubsegment(); } /// /// End subsegment and emit it to Daemon. /// internal static void EndSubsegment(Subsegment subsegment) { AWSXRayRecorder.Instance.SetEntity(subsegment); AWSXRayRecorder.Instance.EndSubsegment(); } /// /// Records the SQL information on the current subsegment. /// private static void CollectSqlInformationDefault(DbCommand command) { var recorder = AWSXRayRecorder.Instance; var databaseType = AgentUtil.GetDataBaseType(command); recorder.AddSqlInformation("database_type", databaseType); recorder.AddSqlInformation("database_version", command.Connection.ServerVersion); DbConnectionStringBuilder connectionStringBuilder = new DbConnectionStringBuilder { ConnectionString = command.Connection.ConnectionString }; // Remove sensitive information from connection string connectionStringBuilder.Remove("Password"); var userId = AgentUtil.GetUserId(connectionStringBuilder); // Do a pre-check for user ID since in the case of TrustedConnection, a user ID may not be available. if (userId != null) { recorder.AddSqlInformation("user", userId.ToString()); } recorder.AddSqlInformation("connection_string", connectionStringBuilder.ToString()); if (ShouldCollectSqlText()) { recorder.AddSqlInformation("sanitized_query", command.CommandText); } } /// /// Builds the name of the subsegment in the format database@datasource /// private static string BuildSubsegmentName(DbCommand command) => command.Connection.Database + "@" + RemovePortNumberFromDataSource(command.Connection.DataSource); /// /// Check if subsegment should collect Sql command text. /// #if !NET45 internal static bool ShouldCollectSqlText() => AWSXRayRecorder.Instance.XRayOptions.CollectSqlQueries; #else internal static bool ShouldCollectSqlText() => AppSettings.CollectSqlQueries; #endif /// /// Check if it's within an ef core subsegment, if so, skip it /// internal static bool IsTraceable() { try { var subsegment = AWSXRayRecorder.Instance.GetEntity() as Subsegment; if (subsegment == null || subsegment.Sql == null) { return true; } if (subsegment.IsInProgress && subsegment.Sql.Count > 0) { return false; } } catch (EntityNotAvailableException e) { AWSXRayRecorder.Instance.TraceContext.HandleEntityMissing(AWSXRayRecorder.Instance, e, "Failed to get entity since it is not available in trace context."); } return true; } } }