Last active
March 11, 2021 19:26
-
-
Save bruceharrison1984/8fff9633f3a7ea2c9f9b56361423e16e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Data.Common; | |
using System.Text.RegularExpressions; | |
using Amazon.Runtime.Internal.Util; | |
using Amazon.XRay.Recorder.Core; | |
using System.Configuration; | |
// copied from https://github.com/aws/aws-xray-sdk-dotnet/blob/d2b64125ecdd734af220795d5b102bd96b037981/sdk/src/Handlers/EntityFramework/EFUtil.cs#L58 | |
// Xray doesn't support EF5 OOTB right now, so this lets us hack it back in | |
// code is a little hacked up to get support working without having to drag in a bunch of internal deps | |
namespace MyProject.Data | |
{ | |
/// <summary> | |
/// Utilities for EFInterceptor | |
/// </summary> | |
internal static class EFUtil | |
{ | |
private static readonly string DefaultDbTypeEntityFramework = "entityframework"; | |
private static readonly string SqlServerCompact35 = "sqlservercompact35"; | |
private static readonly string SqlServerCompact40 = "sqlservercompact40"; | |
private static readonly string MicrosoftSqlClient = "microsoft.data.sqlclient"; | |
private static readonly string SystemSqlClient = "system.data.sqlclient"; | |
private static readonly string SqlServer = "sqlserver"; | |
private static readonly string[] UserIdFormatOptions = { "user id", "username", "user", "userid" }; // case insensitive | |
private static readonly string[] DatabaseTypes = { "sqlserver", "sqlite", "postgresql", "mysql", "firebirdsql", | |
"inmemory" , "cosmosdb" , "oracle" , "filecontextcore" , | |
"jet" , "teradata" , "openedge" , "ibm" , "mycat" , "vfp"}; | |
private static readonly Regex _portNumberRegex = new Regex(@"[,|:]\d+$"); | |
private static readonly AWSXRayRecorder _recorder = AWSXRayRecorder.Instance; | |
private static readonly Logger _logger = Logger.GetLogger(typeof(EFUtil)); | |
/// <summary> | |
/// Process command to begin subsegment. | |
/// </summary> | |
/// <param name="command">Instance of <see cref="DbCommand"/>.</param> | |
/// <param name="collectSqlQueriesOverride">Nullable to indicate whether to collect sql query text or not.</param> | |
internal static void ProcessBeginCommand(DbCommand command, bool? collectSqlQueriesOverride) | |
{ | |
_recorder.BeginSubsegment(BuildSubsegmentName(command)); | |
_recorder.SetNamespace("remote"); | |
CollectSqlInformation(command, collectSqlQueriesOverride); | |
} | |
/// <summary> | |
/// Process to end subsegment | |
/// </summary> | |
internal static void ProcessEndCommand() | |
{ | |
_recorder.EndSubsegment(); | |
} | |
/// <summary> | |
/// Process exception. | |
/// </summary> | |
/// <param name="exception">Instance of <see cref="Exception"/>.</param> | |
internal static void ProcessCommandError(Exception exception) | |
{ | |
_recorder.AddException(exception); | |
_recorder.EndSubsegment(); | |
} | |
/// <summary> | |
/// Builds the name of the subsegment in the format database@datasource | |
/// </summary> | |
/// <param name="command">Instance of <see cref="DbCommand"/>.</param> | |
/// <returns>Returns the formed subsegment name as a string.</returns> | |
private static string BuildSubsegmentName(DbCommand command) | |
=> command.Connection.Database + "@" + RemovePortNumberFromDataSource(command.Connection.DataSource); | |
/// <summary> | |
/// Records the SQL information on the current subsegment, | |
/// </summary> | |
private static void CollectSqlInformation(DbCommand command, bool? collectSqlQueriesOverride) | |
{ | |
// Get database type from DbCommand | |
string databaseType = GetDataBaseType(command); | |
_recorder.AddSqlInformation("database_type", databaseType); | |
DbConnectionStringBuilder connectionStringBuilder = new DbConnectionStringBuilder | |
{ | |
ConnectionString = command.Connection.ConnectionString | |
}; | |
// Remove sensitive information from connection string | |
connectionStringBuilder.Remove("Password"); | |
_recorder.AddSqlInformation("connection_string", connectionStringBuilder.ToString()); | |
// Do a pre-check for UserID since in the case of TrustedConnection, a UserID may not be available. | |
var user_id = GetUserId(connectionStringBuilder); | |
if (user_id != null) | |
{ | |
_recorder.AddSqlInformation("user", user_id.ToString()); | |
} | |
if (collectSqlQueriesOverride ?? GetSettingBool("CollectSqlQueries")) | |
{ | |
_recorder.AddSqlInformation("sanitized_query", command.CommandText); | |
} | |
_recorder.AddSqlInformation("database_version", command.Connection.ServerVersion); | |
} | |
/// <summary> | |
/// Extract database_type from <see cref="DbCommand"/>. | |
/// </summary> | |
/// <param name="command">Instance of <see cref="DbCommand"/>.</param> | |
/// <returns>Type of database.</returns> | |
internal static string GetDataBaseType(DbCommand command) | |
{ | |
var typeString = command?.Connection?.GetType()?.FullName?.ToLower(); | |
if (string.IsNullOrEmpty(typeString)) | |
{ | |
_logger.DebugFormat("Can't extract database type from connection, setting it as default: ({0})", DefaultDbTypeEntityFramework); | |
return DefaultDbTypeEntityFramework; | |
} | |
if (typeString.Contains(MicrosoftSqlClient) || typeString.Contains(SystemSqlClient)) | |
{ | |
return SqlServer; | |
} | |
if (typeString.Contains(SqlServerCompact35)) | |
{ | |
return SqlServerCompact35; | |
} | |
if (typeString.Contains(SqlServerCompact40)) | |
{ | |
return SqlServerCompact40; | |
} | |
foreach (var databaseType in DatabaseTypes) | |
{ | |
if (typeString.Contains(databaseType)) | |
{ | |
return databaseType; | |
} | |
} | |
return typeString; | |
} | |
/// <summary> | |
/// Extract user id from <see cref="DbConnectionStringBuilder"/>. | |
/// </summary> | |
/// <param name="builder">Instance of <see cref="DbConnectionStringBuilder"/>.</param> | |
/// <returns></returns> | |
internal static string GetUserId(DbConnectionStringBuilder builder) | |
{ | |
foreach (string key in UserIdFormatOptions) | |
{ | |
if (builder.TryGetValue(key, out object value)) | |
{ | |
return (string)value; | |
} | |
} | |
return null; | |
} | |
/// <summary> | |
/// Removes the port number from data source. | |
/// </summary> | |
/// <param name="dataSource">The data source.</param> | |
/// <returns>The data source string with port number removed.</returns> | |
private static string RemovePortNumberFromDataSource(string dataSource) | |
{ | |
return _portNumberRegex.Replace(dataSource, string.Empty); | |
} | |
private static string GetSetting(string key) | |
{ | |
var appSettings = ConfigurationManager.AppSettings; | |
if (appSettings == null) | |
{ | |
return null; | |
} | |
string value = appSettings[key]; | |
return value; | |
} | |
private static bool GetSettingBool(string key, bool defaultValue = false) | |
{ | |
string value = GetSetting(key); | |
bool result; | |
if (bool.TryParse(value, out result)) | |
{ | |
return result; | |
} | |
return defaultValue; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment