src/Elastic.Apm/Model/DbSpanCommon.cs (123 lines of code) (raw):

// Licensed to Elasticsearch B.V under one or more agreements. // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information using System; using System.Data; using System.Text; using Elastic.Apm.Api; using Elastic.Apm.Helpers; using Elastic.Apm.Logging; namespace Elastic.Apm.Model { internal class DbSpanCommon { private readonly DbConnectionStringParser _dbConnectionStringParser; internal DbSpanCommon(IApmLogger logger) => _dbConnectionStringParser = new DbConnectionStringParser(logger); internal static class DefaultPorts { internal const int MsSql = 1433; internal const int MySql = 3306; internal const int Oracle = 1521; internal const int PostgreSql = 5432; } internal ISpan StartSpan(IApmAgent agent, IDbCommand dbCommand, InstrumentationFlag instrumentationFlag, string subType = null, bool captureStackTraceOnStart = false ) { var spanName = GetDbSpanName(dbCommand); return ExecutionSegmentCommon.StartSpanOnCurrentExecutionSegment(agent, spanName, ApiConstants.TypeDb, subType, instrumentationFlag, captureStackTraceOnStart, true); } internal static string GetDbSpanName(IDbCommand dbCommand) { var signatureParser = new SignatureParser(new Scanner()); var name = new StringBuilder(); signatureParser.QuerySignature(dbCommand.CommandText.Replace(Environment.NewLine, " "), name, preparedStatement: dbCommand.Parameters.Count > 0); return name.ToString(); } internal void EndSpan(ISpan span, IDbCommand dbCommand, Outcome outcome, TimeSpan? duration = null) { if (span is Span capturedSpan) { if (duration.HasValue) capturedSpan.Duration = duration.Value.TotalMilliseconds; GetDefaultProperties(dbCommand.Connection.GetType().FullName, out var spanSubtype, out var defaultPort); capturedSpan.Subtype = spanSubtype; capturedSpan.Action = GetSpanAction(dbCommand.CommandType); if (capturedSpan.ShouldBeSentToApmServer) { capturedSpan.Context.Db = new Database { Statement = dbCommand.CommandText.Replace(Environment.NewLine, " "), Instance = dbCommand.Connection.Database, Type = Database.TypeSql }; capturedSpan.Context.Destination = GetDestination(dbCommand.Connection?.ConnectionString, defaultPort); } else { var type = !string.IsNullOrEmpty(capturedSpan.Subtype) ? capturedSpan.Subtype : capturedSpan.Type; var target = new Target(type, dbCommand.Connection.Database); capturedSpan.DroppedSpanStatCache = new Span.DroppedSpanStatCacheStruct(target, target.ToDestinationServiceResource()); } capturedSpan.Outcome = outcome; } span.End(); } private static void GetDefaultProperties(string dbConnectionClassName, out string spanSubtype, out int? defaultPort) { switch (dbConnectionClassName) { case { } str when str.ContainsOrdinalIgnoreCase("SQLite"): spanSubtype = ApiConstants.SubtypeSqLite; defaultPort = null; break; case { } str when str.ContainsOrdinalIgnoreCase("MySQL"): spanSubtype = ApiConstants.SubtypeMySql; defaultPort = DefaultPorts.MySql; break; case { } str when str.ContainsOrdinalIgnoreCase("Oracle"): spanSubtype = ApiConstants.SubtypeOracle; defaultPort = DefaultPorts.Oracle; break; case { } str when str.ContainsOrdinalIgnoreCase("Postgre"): spanSubtype = ApiConstants.SubtypePostgreSql; defaultPort = DefaultPorts.PostgreSql; break; case { } str when str.ContainsOrdinalIgnoreCase("NpgSQL"): spanSubtype = ApiConstants.SubtypePostgreSql; defaultPort = DefaultPorts.PostgreSql; break; case { } str when str.ContainsOrdinalIgnoreCase("Microsoft"): spanSubtype = ApiConstants.SubtypeMssql; defaultPort = DefaultPorts.MsSql; break; case { } str when str.ContainsOrdinalIgnoreCase("System.Data.SqlClient"): spanSubtype = ApiConstants.SubtypeMssql; defaultPort = DefaultPorts.MsSql; break; default: spanSubtype = dbConnectionClassName; //TODO, TBD: this is an unknown provider defaultPort = null; break; } } private static string GetSpanAction(CommandType dbCommandType) => dbCommandType switch { CommandType.Text => ApiConstants.ActionQuery, CommandType.StoredProcedure => ApiConstants.ActionExec, CommandType.TableDirect => "tabledirect", _ => dbCommandType.ToString() }; internal Destination GetDestination(string dbConnectionString, int? defaultPort) { if (dbConnectionString == null) return null; var destination = _dbConnectionStringParser.ExtractDestination(dbConnectionString); if (destination == null) return null; if (!destination.Port.HasValue) destination.Port = defaultPort; return destination; } } }