Skip to content

Commit 3a6cf65

Browse files
authored
Merge pull request #1011 from aws-powertools/feature/tracing-sanitize-metadata
chore: Tracing sanitize metadata
2 parents 5229eae + f5485a1 commit 3a6cf65

18 files changed

+2973
-240
lines changed

docs/core/tracing.md

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,76 @@ context for an operation using any native object.
196196

197197
## Utilities
198198

199-
Tracing modules comes with certain utility method when you don't want to use attribute for capturing a code block
199+
Tracing modules comes with certain utility methods when you don't want to use attribute for capturing a code block
200200
under a subsegment, or you are doing multithreaded programming. Refer examples below.
201201

202+
### Using Statement Pattern
203+
204+
You can create subsegments using the familiar `using` statement pattern for automatic cleanup and exception safety.
205+
206+
=== "Basic Using Statement"
207+
208+
```c# hl_lines="8 9 10 11 12 13"
209+
using AWS.Lambda.Powertools.Tracing;
210+
211+
public class Function
212+
{
213+
public async Task<APIGatewayProxyResponse> FunctionHandler
214+
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
215+
{
216+
using var gatewaySegment = Tracing.BeginSubsegment("PaymentGatewayIntegration");
217+
gatewaySegment.AddAnnotation("Operation", "ProcessPayment");
218+
gatewaySegment.AddAnnotation("PaymentMethod", "CreditCard");
219+
220+
var result = await ProcessPaymentAsync();
221+
gatewaySegment.AddAnnotation("ProcessingTimeMs", result.ProcessingTimeMs);
222+
// Subsegment automatically ends when disposed
223+
}
224+
}
225+
```
226+
227+
=== "With Custom Namespace"
228+
229+
```c# hl_lines="8 9 10"
230+
using AWS.Lambda.Powertools.Tracing;
231+
232+
public class Function
233+
{
234+
public async Task<APIGatewayProxyResponse> FunctionHandler
235+
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
236+
{
237+
using var segment = Tracing.BeginSubsegment("MyCustomNamespace", "DatabaseOperation");
238+
segment.AddAnnotation("TableName", "Users");
239+
segment.AddMetadata("query", "SELECT * FROM Users WHERE Active = 1");
240+
}
241+
}
242+
```
243+
244+
=== "Nested Subsegments"
245+
246+
```c# hl_lines="8 9 10 11 12 13 14 15 16"
247+
using AWS.Lambda.Powertools.Tracing;
248+
249+
public class Function
250+
{
251+
public async Task<APIGatewayProxyResponse> FunctionHandler
252+
(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
253+
{
254+
using var outerSegment = Tracing.BeginSubsegment("PaymentProcessing");
255+
outerSegment.AddAnnotation("Operation", "ProcessPayment");
256+
257+
var result = await ProcessPaymentAsync();
258+
259+
using var postProcessingSegment = Tracing.BeginSubsegment("PaymentPostProcessing");
260+
postProcessingSegment.AddAnnotation("PaymentId", result.PaymentId);
261+
262+
await PostProcessPaymentAsync(result);
263+
}
264+
}
265+
```
266+
267+
### Callback Pattern
268+
202269
=== "Functional Api"
203270

204271
```c# hl_lines="8 9 10 12 13 14"
@@ -244,6 +311,31 @@ under a subsegment, or you are doing multithreaded programming. Refer examples b
244311
}
245312
```
246313

314+
### Subsegment Methods
315+
316+
When using the `using` statement pattern, the returned `TracingSubsegment` object provides direct access to tracing methods:
317+
318+
=== "Available Methods"
319+
320+
```c# hl_lines="8 9 10 11 12 13 14 15 16"
321+
using var segment = Tracing.BeginSubsegment("PaymentProcessing");
322+
323+
// Add annotations (indexed by X-Ray)
324+
segment.AddAnnotation("PaymentMethod", "CreditCard");
325+
segment.AddAnnotation("Amount", 99.99);
326+
327+
// Add metadata (not indexed, for additional context)
328+
segment.AddMetadata("PaymentDetails", paymentObject);
329+
segment.AddMetadata("CustomNamespace", "RequestId", requestId);
330+
331+
// Add exception information
332+
segment.AddException(exception);
333+
334+
// Add HTTP information
335+
segment.AddHttpInformation("response_code", 200);
336+
segment.AddHttpInformation("url", "https://api.payment.com/process");
337+
```
338+
247339
## Instrumenting SDK clients
248340

249341
You should make sure to instrument the SDK clients explicitly based on the function dependency. You can instrument all of your AWS SDK for .NET clients by calling RegisterForAllServices before you create them.
Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using System;
12
using Amazon.XRay.Recorder.Core.Internal.Entities;
3+
using AWS.Lambda.Powertools.Common;
24

35
namespace AWS.Lambda.Powertools.Tracing.Internal;
46

@@ -7,11 +9,106 @@ namespace AWS.Lambda.Powertools.Tracing.Internal;
79
/// It's a wrapper for Subsegment from Amazon.XRay.Recorder.Core.Internal.
810
/// </summary>
911
/// <seealso cref="Subsegment" />
10-
public class TracingSubsegment : Subsegment
12+
public class TracingSubsegment : Subsegment, IDisposable
1113
{
14+
private bool _disposed = false;
15+
private readonly bool _shouldAutoEnd;
16+
1217
/// <summary>
1318
/// Wrapper constructor
1419
/// </summary>
1520
/// <param name="name"></param>
16-
public TracingSubsegment(string name) : base(name) { }
21+
public TracingSubsegment(string name) : base(name)
22+
{
23+
_shouldAutoEnd = false;
24+
}
25+
26+
/// <summary>
27+
/// Constructor for disposable subsegments
28+
/// </summary>
29+
/// <param name="name">The name of the subsegment</param>
30+
/// <param name="shouldAutoEnd">Whether this subsegment should auto-end when disposed</param>
31+
internal TracingSubsegment(string name, bool shouldAutoEnd) : base(name)
32+
{
33+
_shouldAutoEnd = shouldAutoEnd;
34+
}
35+
36+
/// <summary>
37+
/// Adds an annotation to the subsegment
38+
/// </summary>
39+
/// <param name="key">The annotation key</param>
40+
/// <param name="value">The annotation value</param>
41+
public new void AddAnnotation(string key, object value)
42+
{
43+
XRayRecorder.Instance.AddAnnotation(key, value);
44+
}
45+
46+
/// <summary>
47+
/// Adds metadata to the subsegment
48+
/// </summary>
49+
/// <param name="key">The metadata key</param>
50+
/// <param name="value">The metadata value</param>
51+
public new void AddMetadata(string key, object value)
52+
{
53+
XRayRecorder.Instance.AddMetadata(Namespace ?? PowertoolsConfigurations.Instance.Service, key, value);
54+
}
55+
56+
/// <summary>
57+
/// Adds metadata to the subsegment with a specific namespace
58+
/// </summary>
59+
/// <param name="nameSpace">The namespace</param>
60+
/// <param name="key">The metadata key</param>
61+
/// <param name="value">The metadata value</param>
62+
public new void AddMetadata(string nameSpace, string key, object value)
63+
{
64+
XRayRecorder.Instance.AddMetadata(nameSpace, key, value);
65+
}
66+
67+
/// <summary>
68+
/// Adds an exception to the subsegment
69+
/// </summary>
70+
/// <param name="exception">The exception to add</param>
71+
public new void AddException(Exception exception)
72+
{
73+
XRayRecorder.Instance.AddException(exception);
74+
}
75+
76+
/// <summary>
77+
/// Adds HTTP information to the subsegment
78+
/// </summary>
79+
/// <param name="key">The HTTP information key</param>
80+
/// <param name="value">The HTTP information value</param>
81+
public void AddHttpInformation(string key, object value)
82+
{
83+
XRayRecorder.Instance.AddHttpInformation(key, value);
84+
}
85+
86+
/// <summary>
87+
/// Disposes the subsegment and ends it if configured to do so
88+
/// </summary>
89+
public void Dispose()
90+
{
91+
Dispose(true);
92+
GC.SuppressFinalize(this);
93+
}
94+
95+
/// <summary>
96+
/// Protected dispose method
97+
/// </summary>
98+
/// <param name="disposing">Whether we're disposing</param>
99+
protected virtual void Dispose(bool disposing)
100+
{
101+
if (!_disposed && disposing && _shouldAutoEnd)
102+
{
103+
try
104+
{
105+
XRayRecorder.Instance.EndSubsegment();
106+
}
107+
catch
108+
{
109+
// Swallow exceptions during disposal to prevent issues in using blocks
110+
}
111+
_disposed = true;
112+
}
113+
}
17114
}

0 commit comments

Comments
 (0)