forked from npgsql/npgsql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNpgsqlActivitySource.cs
More file actions
146 lines (125 loc) · 5.84 KB
/
NpgsqlActivitySource.cs
File metadata and controls
146 lines (125 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
using Npgsql.Internal;
using System;
using System.Data;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace Npgsql;
static class NpgsqlActivitySource
{
static readonly ActivitySource Source = new("Npgsql", "0.2.0");
internal static bool IsEnabled => Source.HasListeners();
internal static Activity? CommandStart(NpgsqlConnectionStringBuilder settings, string commandText, CommandType commandType, string? spanName)
{
var dbName = settings.Database ?? "UNKNOWN";
string? dbOperation = null;
string? dbSqlTable = null;
string activityName;
switch (commandType)
{
case CommandType.StoredProcedure:
dbOperation = NpgsqlCommand.EnableStoredProcedureCompatMode ? "SELECT" : "CALL";
// In this case our activity name follows the concept of the CommandType.TableDirect case
// ("<db.operation> <db.name>.<db.sql.table>") but replaces db.sql.table with the procedure name
// which seems to match the spec's intent without being explicitly specified that way (it suggests
// using the procedure name but doesn't mention using db.operation or db.name in that case).
activityName = $"{dbOperation} {dbName}.{commandText}";
break;
case CommandType.TableDirect:
dbOperation = "SELECT";
// The OpenTelemetry spec actually asks to include the database name into db.sql.table
// but then again mixes the concept of database and schema.
// As I interpret it, it actually wants db.sql.table to include the schema name and not the
// database name if the concept of schemas exists in the database system.
// This also makes sense in the context of the activity name which otherwise would include the
// database name twice.
dbSqlTable = commandText;
activityName = $"{dbOperation} {dbName}.{dbSqlTable}";
break;
case CommandType.Text:
activityName = dbName;
break;
default:
throw new ArgumentOutOfRangeException(nameof(commandType), commandType, null);
}
var activity = Source.StartActivity(spanName ?? activityName, ActivityKind.Client);
if (activity is not { IsAllDataRequested: true })
return activity;
activity.SetTag("db.statement", commandText);
if (dbOperation != null)
activity.SetTag("db.operation", dbOperation);
if (dbSqlTable != null)
activity.SetTag("db.sql.table", dbSqlTable);
return activity;
}
internal static Activity? ConnectionOpen(NpgsqlConnector connector)
{
if (!connector.DataSource.Configuration.TracingOptions.EnablePhysicalOpenTracing)
return null;
var dbName = connector.Settings.Database ?? connector.InferredUserName;
var activity = Source.StartActivity(dbName, ActivityKind.Client);
if (activity is not { IsAllDataRequested: true })
return activity;
activity.SetTag("db.system", "postgresql");
activity.SetTag("db.connection_string", connector.UserFacingConnectionString);
return activity;
}
internal static void Enrich(Activity activity, NpgsqlConnector connector)
{
if (!activity.IsAllDataRequested)
return;
activity.SetTag("db.system", "postgresql");
activity.SetTag("db.connection_string", connector.UserFacingConnectionString);
activity.SetTag("db.user", connector.InferredUserName);
// We trace the actual (maybe inferred) database name we're connected to, even if it
// wasn't specified in the connection string
activity.SetTag("db.name", connector.Settings.Database ?? connector.InferredUserName);
activity.SetTag("db.connection_id", connector.Id);
var endPoint = connector.ConnectedEndPoint;
Debug.Assert(endPoint is not null);
switch (endPoint)
{
case IPEndPoint ipEndPoint:
activity.SetTag("net.transport", "ip_tcp");
activity.SetTag("net.peer.ip", ipEndPoint.Address.ToString());
if (ipEndPoint.Port != 5432)
activity.SetTag("net.peer.port", ipEndPoint.Port);
activity.SetTag("net.peer.name", connector.Host);
break;
case UnixDomainSocketEndPoint:
activity.SetTag("net.transport", "unix");
activity.SetTag("net.peer.name", connector.Host);
break;
default:
throw new ArgumentOutOfRangeException("Invalid endpoint type: " + endPoint.GetType());
}
}
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 CommandStop(Activity activity)
{
activity.SetStatus(ActivityStatusCode.Ok);
activity.Dispose();
}
internal static void SetException(Activity activity, Exception ex, bool escaped = true)
{
// TODO: We can instead use Activity.AddException whenever we start using .NET 9
var tags = new ActivityTagsCollection
{
{ "exception.type", ex.GetType().FullName },
{ "exception.message", ex.Message },
{ "exception.stacktrace", ex.ToString() },
{ "exception.escaped", escaped }
};
var activityEvent = new ActivityEvent("exception", tags: tags);
activity.AddEvent(activityEvent);
var statusDescription = ex is PostgresException pgEx ? pgEx.SqlState : ex.Message;
activity.SetStatus(ActivityStatusCode.Error, statusDescription);
activity.Dispose();
}
}