/src/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentationOptions.cs

https://github.com/open-telemetry/opentelemetry-dotnet · C# · 156 lines · 99 code · 19 blank · 38 comment · 16 complexity · 7a973af8bcd16e68f023180ab7fdb8e6 MD5 · raw file

  1. // <copyright file="SqlClientInstrumentationOptions.cs" company="OpenTelemetry Authors">
  2. // Copyright The OpenTelemetry Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. // </copyright>
  16. using System;
  17. using System.Collections.Concurrent;
  18. using System.Data;
  19. using System.Diagnostics;
  20. using System.Text.RegularExpressions;
  21. using OpenTelemetry.Trace;
  22. namespace OpenTelemetry.Instrumentation.SqlClient
  23. {
  24. /// <summary>
  25. /// Options for <see cref="SqlClientInstrumentation"/>.
  26. /// </summary>
  27. public class SqlClientInstrumentationOptions
  28. {
  29. /*
  30. * Match...
  31. * serverName
  32. * serverName[ ]\\[ ]instanceName
  33. * serverName[ ],[ ]port
  34. * serverName[ ]\\[ ]instanceName[ ],[ ]port
  35. * [ ] can be any number of white-space, SQL allows it for some reason.
  36. */
  37. private static readonly Regex DataSourceRegex = new Regex("^(.*?)\\s*(?:[\\\\,]|$)\\s*(.*?)\\s*(?:,|$)\\s*(.*)$", RegexOptions.Compiled);
  38. private static readonly ConcurrentDictionary<string, SqlConnectionDetails> ConnectionDetailCache = new ConcurrentDictionary<string, SqlConnectionDetails>(StringComparer.OrdinalIgnoreCase);
  39. /// <summary>
  40. /// Gets or sets a value indicating whether or not the <see cref="SqlClientInstrumentation"/> should add the names of <see cref="CommandType.StoredProcedure"/> commands as the <see cref="SemanticConventions.AttributeDbStatement"/> tag. Default value: True.
  41. /// </summary>
  42. public bool SetStoredProcedureCommandName { get; set; } = true;
  43. /// <summary>
  44. /// Gets or sets a value indicating whether or not the <see cref="SqlClientInstrumentation"/> should add the text of <see cref="CommandType.Text"/> commands as the <see cref="SemanticConventions.AttributeDbStatement"/> tag. Default value: False.
  45. /// </summary>
  46. public bool SetTextCommandContent { get; set; }
  47. /// <summary>
  48. /// Gets or sets a value indicating whether or not the <see cref="SqlClientInstrumentation"/> should parse the DataSource on a SqlConnection into server name, instance name, and/or port connection-level attribute tags. Default value: False.
  49. /// </summary>
  50. /// <remarks>
  51. /// The default behavior is to set the SqlConnection DataSource as the <see cref="SemanticConventions.AttributePeerService"/> tag. If enabled, SqlConnection DataSource will be parsed and the server name will be sent as the <see cref="SemanticConventions.AttributeNetPeerName"/> or <see cref="SemanticConventions.AttributeNetPeerIp"/> tag, the instance name will be sent as the <see cref="SemanticConventions.AttributeDbMsSqlInstanceName"/> tag, and the port will be sent as the <see cref="SemanticConventions.AttributeNetPeerPort"/> tag if it is not 1433 (the default port).
  52. /// </remarks>
  53. public bool EnableConnectionLevelAttributes { get; set; }
  54. internal static SqlConnectionDetails ParseDataSource(string dataSource)
  55. {
  56. Match match = DataSourceRegex.Match(dataSource);
  57. string serverHostName = match.Groups[1].Value;
  58. string serverIpAddress = null;
  59. var uriHostNameType = Uri.CheckHostName(serverHostName);
  60. if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
  61. {
  62. serverIpAddress = serverHostName;
  63. serverHostName = null;
  64. }
  65. string instanceName;
  66. string port;
  67. if (match.Groups[3].Length > 0)
  68. {
  69. instanceName = match.Groups[2].Value;
  70. port = match.Groups[3].Value;
  71. if (port == "1433")
  72. {
  73. port = null;
  74. }
  75. }
  76. else if (int.TryParse(match.Groups[2].Value, out int parsedPort))
  77. {
  78. port = parsedPort == 1433 ? null : match.Groups[2].Value;
  79. instanceName = null;
  80. }
  81. else
  82. {
  83. instanceName = match.Groups[2].Value;
  84. if (string.IsNullOrEmpty(instanceName))
  85. {
  86. instanceName = null;
  87. }
  88. port = null;
  89. }
  90. return new SqlConnectionDetails
  91. {
  92. ServerHostName = serverHostName,
  93. ServerIpAddress = serverIpAddress,
  94. InstanceName = instanceName,
  95. Port = port,
  96. };
  97. }
  98. internal void AddConnectionLevelDetailsToActivity(string dataSource, Activity sqlActivity)
  99. {
  100. if (!this.EnableConnectionLevelAttributes)
  101. {
  102. sqlActivity.SetTag(SemanticConventions.AttributePeerService, dataSource);
  103. }
  104. else
  105. {
  106. if (!ConnectionDetailCache.TryGetValue(dataSource, out SqlConnectionDetails connectionDetails))
  107. {
  108. connectionDetails = ParseDataSource(dataSource);
  109. ConnectionDetailCache.TryAdd(dataSource, connectionDetails);
  110. }
  111. if (!string.IsNullOrEmpty(connectionDetails.ServerHostName))
  112. {
  113. sqlActivity.SetTag(SemanticConventions.AttributeNetPeerName, connectionDetails.ServerHostName);
  114. }
  115. else
  116. {
  117. sqlActivity.SetTag(SemanticConventions.AttributeNetPeerIp, connectionDetails.ServerIpAddress);
  118. }
  119. if (!string.IsNullOrEmpty(connectionDetails.InstanceName))
  120. {
  121. sqlActivity.SetTag(SemanticConventions.AttributeDbMsSqlInstanceName, connectionDetails.InstanceName);
  122. }
  123. if (!string.IsNullOrEmpty(connectionDetails.Port))
  124. {
  125. sqlActivity.SetTag(SemanticConventions.AttributeNetPeerPort, connectionDetails.Port);
  126. }
  127. }
  128. }
  129. internal class SqlConnectionDetails
  130. {
  131. public string ServerHostName { get; set; }
  132. public string ServerIpAddress { get; set; }
  133. public string InstanceName { get; set; }
  134. public string Port { get; set; }
  135. }
  136. }
  137. }