X Tutup
using Npgsql.Internal; using System; using System.Data; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Reflection; namespace Npgsql; // Semantic conventions for database client spans: https://opentelemetry.io/docs/specs/semconv/database/database-spans/ // Semantic conventions for PostgreSQL client operations: https://opentelemetry.io/docs/specs/semconv/database/postgresql/ static class NpgsqlActivitySource { static readonly ActivitySource Source = new("Npgsql", GetLibraryVersion()); internal static bool IsEnabled => Source.HasListeners(); internal static Activity? CommandStart(string commandText, CommandType commandType, bool? prepared, string? spanName) { string? operationName = null; switch (commandType) { case CommandType.StoredProcedure: // We follow the {db.operation.name} {target} pattern of the spec, with the operation being SELECT/CALL and // the target being the stored procedure name. operationName = NpgsqlCommand.EnableStoredProcedureCompatMode ? "SELECT" : "CALL"; spanName ??= $"{operationName} {commandText}"; break; case CommandType.TableDirect: // We follow the {db.operation.name} {target} pattern of the spec, with the operation being SELECT and // the target being the table (collection) name. operationName = "SELECT"; spanName ??= $"{operationName} {commandText}"; break; case CommandType.Text: // We don't have db.query.summary, db.operation.name or target (without parsing SQL), // so we fall back to db.system.name as per the specs. spanName ??= "postgresql"; break; default: throw new ArgumentOutOfRangeException(nameof(commandType), commandType, null); } var activity = Source.StartActivity(spanName, ActivityKind.Client); if (activity is not { IsAllDataRequested: true }) return activity; activity.SetTag("db.query.text", commandText); if (prepared is true) activity.SetTag("db.npgsql.prepared", true); switch (commandType) { case CommandType.StoredProcedure: Debug.Assert(operationName is not null); activity.SetTag("db.operation.name", operationName); activity.SetTag("db.stored_procedure.name", commandText); break; case CommandType.TableDirect: Debug.Assert(operationName is not null); activity.SetTag("db.operation.name", operationName); activity.SetTag("db.collection.name", commandText); break; } return activity; } internal static Activity? PhysicalConnectionOpen(NpgsqlConnector connector) { if (!connector.DataSource.Configuration.TracingOptions.EnablePhysicalOpenTracing) return null; // Note that physical connection open is not part of the OpenTelemetry spec. // We emit it if enabled, following the general name/tags guidelines. var dbName = connector.Settings.Database ?? connector.InferredUserName; var activity = Source.StartActivity("CONNECT " + dbName, ActivityKind.Client); if (activity is not { IsAllDataRequested: true }) return activity; // We set these basic tags on the activity so that they're populated even when the physical open fails. activity.SetTag("db.system.name", "postgresql"); activity.SetTag("db.npgsql.data_source", connector.DataSource.Name); return activity; } internal static void Enrich(Activity activity, NpgsqlConnector connector) { if (!activity.IsAllDataRequested) return; activity.SetTag("db.system.name", "postgresql"); // TODO: For now, we only set the database name, without adding the first schema in the search_path // as per the PG tracing specs (https://opentelemetry.io/docs/specs/semconv/database/postgresql/). // See #6336 activity.SetTag("db.namespace", connector.Settings.Database ?? connector.InferredUserName); var endPoint = connector.ConnectedEndPoint; Debug.Assert(endPoint is not null); activity.SetTag("server.address", connector.Host); switch (endPoint) { case IPEndPoint ipEndPoint: if (ipEndPoint.Port != 5432) activity.SetTag("server.port", ipEndPoint.Port); break; case UnixDomainSocketEndPoint: break; default: throw new UnreachableException("Invalid endpoint type: " + endPoint.GetType()); } // Npgsql-specific tags activity.SetTag("db.npgsql.data_source", connector.DataSource.Name); activity.SetTag("db.npgsql.connection_id", connector.Id); } internal static void ReceivedFirstResponse(Activity activity, NpgsqlTracingOptions tracingOptions) { if (!activity.IsAllDataRequested || !tracingOptions.EnableFirstResponseEvent) return; var activityEvent = new ActivityEvent("received-first-response"); activity.AddEvent(activityEvent); } internal static void SetException(Activity activity, Exception exception, bool escaped = true) { activity.AddException(exception); if (exception is PostgresException { SqlState: var sqlState }) { activity.SetTag("db.response.status_code", sqlState); // error.type SHOULD match the db.response.status_code returned by the database or the client library, or the canonical name of exception that occurred. // Since we don't have a table to map the error code to a textual description, the SQL state is the best we can do. activity.SetTag("error.type", sqlState); } else { if (exception is NpgsqlException { InnerException: Exception innerException }) exception = innerException; activity.SetTag("error.type", exception.GetType().FullName); } var statusDescription = exception is PostgresException pgEx ? pgEx.SqlState : exception.Message; activity.SetStatus(ActivityStatusCode.Error, statusDescription); activity.Dispose(); } internal static Activity? CopyStart(string command, NpgsqlConnector connector, string? spanName, string operation) { var activity = Source.StartActivity(spanName ?? operation, ActivityKind.Client); if (activity is not { IsAllDataRequested: true }) return activity; activity.SetTag("db.query.text", command); activity.SetTag("db.operation.name", operation); Enrich(activity, connector); return activity; } internal static void SetOperation(Activity activity, string operation) { if (!activity.IsAllDataRequested) return; activity.SetTag("db.operation.name", operation); } internal static void CopyStop(Activity activity, ulong? rows = null) { if (rows.HasValue) activity.SetTag("db.npgsql.rows", rows.Value); activity.Dispose(); } static string GetLibraryVersion() => typeof(NpgsqlDataSource).Assembly .GetCustomAttribute()? .InformationalVersion ?? "UNKNOWN"; }
X Tutup