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
129 lines (112 loc) · 5.16 KB
/
NpgsqlActivitySource.cs
File metadata and controls
129 lines (112 loc) · 5.16 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
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.1.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 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.SetTag("otel.status_code", "OK");
activity.Dispose();
}
internal static void SetException(Activity activity, Exception ex, bool escaped = true)
{
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);
activity.SetTag("otel.status_code", "ERROR");
activity.SetTag("otel.status_description", ex is PostgresException pgEx ? pgEx.SqlState : ex.Message);
activity.Dispose();
}
}