Skip to content

Instantly share code, notes, and snippets.

@bruceharrison1984
Last active March 11, 2021 19:26
Show Gist options
  • Save bruceharrison1984/8fff9633f3a7ea2c9f9b56361423e16e to your computer and use it in GitHub Desktop.
Save bruceharrison1984/8fff9633f3a7ea2c9f9b56361423e16e to your computer and use it in GitHub Desktop.
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