Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bc9ec00

Browse files
committed
Adds .NET distributed tracing instrumentation.
1 parent aa6a03c commit bc9ec00

File tree

3 files changed

+227
-36
lines changed

3 files changed

+227
-36
lines changed

‎src/FirebirdSql.Data.FirebirdClient/FirebirdClient/FbCommand.cs‎

Lines changed: 89 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@
2020
using System.ComponentModel;
2121
using System.Data;
2222
using System.Data.Common;
23+
using System.Diagnostics;
2324
using System.Text;
2425
using System.Threading;
2526
using System.Threading.Tasks;
2627
using FirebirdSql.Data.Common;
2728
using FirebirdSql.Data.Logging;
29+
using FirebirdSql.Data.Trace;
2830

2931
namespace FirebirdSql.Data.FirebirdClient;
3032

@@ -49,6 +51,7 @@ public sealed class FbCommand : DbCommand, IFbPreparedCommand, IDescriptorFiller
4951
private int? _commandTimeout;
5052
private int _fetchSize;
5153
private Type[] _expectedColumnTypes;
54+
private Activity _currentActivity;
5255

5356
#endregion
5457

@@ -1094,6 +1097,13 @@ internal void Release()
10941097
_statement.Dispose2();
10951098
_statement = null;
10961099
}
1100+
1101+
if (_currentActivity != null)
1102+
{
1103+
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1104+
_currentActivity.Dispose();
1105+
_currentActivity = null;
1106+
}
10971107
}
10981108
Task IFbPreparedCommand.ReleaseAsync(CancellationToken cancellationToken) => ReleaseAsync(cancellationToken);
10991109
internal async Task ReleaseAsync(CancellationToken cancellationToken = default)
@@ -1112,6 +1122,13 @@ internal async Task ReleaseAsync(CancellationToken cancellationToken = default)
11121122
await _statement.Dispose2Async(cancellationToken).ConfigureAwait(false);
11131123
_statement = null;
11141124
}
1125+
1126+
if (_currentActivity != null)
1127+
{
1128+
// Do not set status to Ok: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
1129+
_currentActivity.Dispose();
1130+
_currentActivity = null;
1131+
}
11151132
}
11161133

11171134
void IFbPreparedCommand.TransactionCompleted() => TransactionCompleted();
@@ -1332,6 +1349,26 @@ private async ValueTask UpdateParameterValuesAsync(Descriptor descriptor, Cancel
13321349

13331350
#endregion
13341351

1352+
#region Tracing
1353+
1354+
private void TraceCommandStart()
1355+
{
1356+
Debug.Assert(_currentActivity == null);
1357+
if (FbActivitySource.Source.HasListeners())
1358+
_currentActivity = FbActivitySource.CommandStart(this);
1359+
}
1360+
1361+
private void TraceCommandException(Exception e)
1362+
{
1363+
if (_currentActivity != null)
1364+
{
1365+
FbActivitySource.CommandException(_currentActivity, e);
1366+
_currentActivity = null;
1367+
}
1368+
}
1369+
1370+
#endregion Tracing
1371+
13351372
#region Private Methods
13361373

13371374
private void Prepare(bool returnsSet)
@@ -1476,57 +1513,73 @@ private async Task PrepareAsync(bool returnsSet, CancellationToken cancellationT
14761513
private void ExecuteCommand(CommandBehavior behavior, bool returnsSet)
14771514
{
14781515
LogMessages.CommandExecution(Log, this);
1516+
TraceCommandStart();
1517+
try
1518+
{
1519+
Prepare(returnsSet);
14791520

1480-
Prepare(returnsSet);
1521+
if ((behavior & CommandBehavior.SequentialAccess) == CommandBehavior.SequentialAccess ||
1522+
(behavior & CommandBehavior.SingleResult) == CommandBehavior.SingleResult ||
1523+
(behavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow ||
1524+
(behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection ||
1525+
behavior == CommandBehavior.Default)
1526+
{
1527+
// Set the fetch size
1528+
_statement.FetchSize = _fetchSize;
14811529

1482-
if ((behavior & CommandBehavior.SequentialAccess) == CommandBehavior.SequentialAccess ||
1483-
(behavior & CommandBehavior.SingleResult) == CommandBehavior.SingleResult ||
1484-
(behavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow ||
1485-
(behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection ||
1486-
behavior == CommandBehavior.Default)
1487-
{
1488-
// Set the fetch size
1489-
_statement.FetchSize = _fetchSize;
1530+
// Set if it's needed the Records Affected information
1531+
_statement.ReturnRecordsAffected = _connection.ConnectionOptions.ReturnRecordsAffected;
14901532

1491-
// Set if it's needed the Records Affected information
1492-
_statement.ReturnRecordsAffected = _connection.ConnectionOptions.ReturnRecordsAffected;
1533+
// Validate input parameter count
1534+
if (_namedParameters.Count > 0 && !HasParameters)
1535+
{
1536+
throw FbException.Create("Must declare command parameters.");
1537+
}
14931538

1494-
// Validate input parameter count
1495-
if (_namedParameters.Count > 0 && !HasParameters)
1496-
{
1497-
throw FbException.Create("Must declare command parameters.");
1539+
// Execute
1540+
_statement.Execute(CommandTimeout * 1000, this);
14981541
}
1499-
1500-
// Execute
1501-
_statement.Execute(CommandTimeout * 1000, this);
1542+
}
1543+
catch (Exception e)
1544+
{
1545+
TraceCommandException(e);
1546+
throw;
15021547
}
15031548
}
15041549
private async Task ExecuteCommandAsync(CommandBehavior behavior, bool returnsSet, CancellationToken cancellationToken = default)
15051550
{
15061551
LogMessages.CommandExecution(Log, this);
1552+
TraceCommandStart();
1553+
try
1554+
{
1555+
await PrepareAsync(returnsSet, cancellationToken).ConfigureAwait(false);
15071556

1508-
await PrepareAsync(returnsSet, cancellationToken).ConfigureAwait(false);
1557+
if ((behavior & CommandBehavior.SequentialAccess) == CommandBehavior.SequentialAccess ||
1558+
(behavior & CommandBehavior.SingleResult) == CommandBehavior.SingleResult ||
1559+
(behavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow ||
1560+
(behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection ||
1561+
behavior == CommandBehavior.Default)
1562+
{
1563+
// Set the fetch size
1564+
_statement.FetchSize = _fetchSize;
15091565

1510-
if ((behavior & CommandBehavior.SequentialAccess) == CommandBehavior.SequentialAccess ||
1511-
(behavior & CommandBehavior.SingleResult) == CommandBehavior.SingleResult ||
1512-
(behavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow ||
1513-
(behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection ||
1514-
behavior == CommandBehavior.Default)
1515-
{
1516-
// Set the fetch size
1517-
_statement.FetchSize = _fetchSize;
1566+
// Set if it's needed the Records Affected information
1567+
_statement.ReturnRecordsAffected = _connection.ConnectionOptions.ReturnRecordsAffected;
15181568

1519-
// Set if it's needed the Records Affected information
1520-
_statement.ReturnRecordsAffected = _connection.ConnectionOptions.ReturnRecordsAffected;
1569+
// Validate input parameter count
1570+
if (_namedParameters.Count > 0 && !HasParameters)
1571+
{
1572+
throw FbException.Create("Must declare command parameters.");
1573+
}
15211574

1522-
// Validate input parameter count
1523-
if (_namedParameters.Count > 0 && !HasParameters)
1524-
{
1525-
throw FbException.Create("Must declare command parameters.");
1575+
// Execute
1576+
await _statement.ExecuteAsync(CommandTimeout * 1000, this, cancellationToken).ConfigureAwait(false);
15261577
}
1527-
1528-
// Execute
1529-
await _statement.ExecuteAsync(CommandTimeout * 1000, this, cancellationToken).ConfigureAwait(false);
1578+
}
1579+
catch (Exception e)
1580+
{
1581+
TraceCommandException(e);
1582+
throw;
15301583
}
15311584
}
15321585

‎src/FirebirdSql.Data.FirebirdClient/FirebirdSql.Data.FirebirdClient.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<PrivateAssets>all</PrivateAssets>
6262
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
6363
</PackageReference>
64+
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
6465
</ItemGroup>
6566
<ItemGroup>
6667
<Compile Update="FirebirdClient\FbBatchCommand.cs" />
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Data;
4+
using System.Diagnostics;
5+
using FirebirdSql.Data.FirebirdClient;
6+
7+
namespace FirebirdSql.Data.Trace
8+
{
9+
internal static class FbActivitySource
10+
{
11+
internal static readonly ActivitySource Source = new("FirebirdSql.Data", "1.0.0");
12+
13+
internal static Activity CommandStart(FbCommand command)
14+
{
15+
// Reference: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md
16+
var dbName = command.Connection.Database;
17+
18+
string dbOperationName = null;
19+
string dbCollectionName = null;
20+
string activityName;
21+
22+
switch (command.CommandType)
23+
{
24+
case CommandType.StoredProcedure:
25+
dbOperationName = "EXECUTE PROCEDURE";
26+
activityName = $"{dbOperationName} {command.CommandText}";
27+
break;
28+
29+
case CommandType.TableDirect:
30+
dbOperationName = "SELECT";
31+
dbCollectionName = command.CommandText;
32+
activityName = $"{dbOperationName} {dbCollectionName}";
33+
break;
34+
35+
case CommandType.Text:
36+
activityName = dbName;
37+
break;
38+
39+
default:
40+
throw new InvalidEnumArgumentException($"Invalid value for 'System.Data.CommandType' ({(int)command.CommandType}).");
41+
}
42+
43+
var activity = Source.StartActivity(activityName, ActivityKind.Client);
44+
if (activity.IsAllDataRequested)
45+
{
46+
activity.SetTag("db.system", "firebird");
47+
48+
if (dbCollectionName != null)
49+
{
50+
activity.SetTag("db.collection.name", dbCollectionName);
51+
}
52+
53+
// db.namespace
54+
55+
if (dbOperationName != null)
56+
{
57+
activity.SetTag("db.operation.name", dbOperationName);
58+
}
59+
60+
// db.response.status_code
61+
62+
// error.type (handled by RecordException)
63+
64+
// server.port
65+
66+
// db.operation.batch.size
67+
68+
// db.query_summary
69+
70+
activity.SetTag("db.query.text", command.CommandText);
71+
72+
// network.peer.address
73+
74+
// network.peer.port
75+
76+
if (command.Connection.DataSource != null)
77+
{
78+
activity.SetTag("server.address", command.Connection.DataSource);
79+
}
80+
81+
foreach (FbParameter p in command.Parameters)
82+
{
83+
var name = p.ParameterName;
84+
var value = NormalizeDbNull(p.InternalValue);
85+
activity.SetTag($"db.query.parameter.{name}", value);
86+
87+
}
88+
89+
// Only for explicit transactions.
90+
if (command.Transaction != null)
91+
{
92+
FbTransactionInfo fbInfo = new FbTransactionInfo(command.Transaction);
93+
94+
var transactionId = fbInfo.GetTransactionId();
95+
activity.SetTag($"db.transaction_id", transactionId);
96+
97+
// TODO: Firebird 4+ only (or remove?)
98+
/*
99+
var snapshotId = fbInfo.GetTransactionSnapshotNumber();
100+
if (snapshotId != 0)
101+
{
102+
activity.SetTag($"db.snapshot_id", snapshotId);
103+
}
104+
*/
105+
}
106+
}
107+
108+
return activity;
109+
}
110+
111+
internal static void CommandException(Activity activity, Exception exception, bool escaped = true)
112+
{
113+
// Reference: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md
114+
activity.AddEvent(
115+
new("exception", tags: new()
116+
{
117+
{ "exception.message", exception.Message },
118+
{ "exception.type", exception.GetType().FullName },
119+
{ "exception.escaped", escaped },
120+
{ "exception.stacktrace", exception.ToString() },
121+
})
122+
);
123+
124+
string errorDescription = exception is FbException fbException
125+
? fbException.SQLSTATE
126+
: exception.Message;
127+
128+
activity.SetStatus(ActivityStatusCode.Error, errorDescription);
129+
activity.Dispose();
130+
}
131+
132+
private static object NormalizeDbNull(object value) =>
133+
value == DBNull.Value || value == null
134+
? null
135+
: value;
136+
}
137+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /