Skip to content

Commit 46d5b86

Browse files
authored
Merge pull request #675 from hjgraca/fix(tracing)-aot-void-task-and-serialization
chore: Tracing Fix async Task Handler exception
2 parents 81acc01 + e304c48 commit 46d5b86

17 files changed

+1951
-330
lines changed

docs/core/tracing.md

+87-14
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ under a subsegment, or you are doing multithreaded programming. Refer examples b
244244
}
245245
```
246246

247-
## Instrumenting SDK clients and HTTP calls
247+
## Instrumenting SDK clients
248248

249249
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.
250250

@@ -277,25 +277,98 @@ To instrument clients for some services and not others, call Register instead of
277277
Tracing.Register<IAmazonDynamoDB>()
278278
```
279279

280-
This functionality is a thin wrapper for AWS X-Ray .NET SDK. Refer details on [how to instrument SDK client with Xray](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet-sdkclients.html) and [outgoing http calls](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet-httpclients.html).
280+
This functionality is a thin wrapper for AWS X-Ray .NET SDK. Refer details on [how to instrument SDK client with Xray](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet-sdkclients.html)
281+
282+
## Instrumenting outgoing HTTP calls
283+
284+
=== "Function.cs"
285+
286+
```c# hl_lines="7"
287+
using Amazon.XRay.Recorder.Handlers.System.Net;
288+
289+
public class Function
290+
{
291+
public Function()
292+
{
293+
var httpClient = new HttpClient(new HttpClientXRayTracingHandler(new HttpClientHandler()));
294+
var myIp = await httpClient.GetStringAsync("https://checkip.amazonaws.com/");
295+
}
296+
}
297+
```
298+
299+
More information about instrumenting [outgoing http calls](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet-httpclients.html).
281300

282301
## AOT Support
283302

284303
Native AOT trims your application code as part of the compilation to ensure that the binary is as small as possible. .NET 8 for Lambda provides improved trimming support compared to previous versions of .NET.
285304

286-
These improvements offer the potential to eliminate build-time trimming warnings, but .NET will never be completely trim safe. This means that parts of libraries that your function relies on may be trimmed out as part of the compilation step. You can manage this by defining TrimmerRootAssemblies as part of your `.csproj` file as shown in the following example.
287305

288-
For the Tracing utility to work correctly and without trim warnings please add the following to your `.csproj` file
306+
### WithTracing()
289307

290-
```xml
291-
<ItemGroup>
292-
<TrimmerRootAssembly Include="AWSSDK.Core" />
293-
<TrimmerRootAssembly Include="AWSXRayRecorder.Core" />
294-
<TrimmerRootAssembly Include="AWSXRayRecorder.Handlers.AwsSdk" />
295-
<TrimmerRootAssembly Include="Amazon.Lambda.APIGatewayEvents" />
296-
</ItemGroup>
297-
```
308+
To use Tracing utility with AOT support you first need to add `WithTracing()` to the source generator you are using either the default `SourceGeneratorLambdaJsonSerializer`
309+
or the Powertools Logging utility [source generator](logging.md#aot-support){:target="_blank"} `PowertoolsSourceGeneratorSerializer`.
310+
311+
Examples:
312+
313+
=== "Without Powertools Logging"
314+
315+
```c# hl_lines="8"
316+
using AWS.Lambda.Powertools.Tracing;
317+
using AWS.Lambda.Powertools.Tracing.Serializers;
318+
319+
private static async Task Main()
320+
{
321+
Func<string, ILambdaContext, string> handler = FunctionHandler;
322+
await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer<LambdaFunctionJsonSerializerContext>()
323+
.WithTracing())
324+
.Build()
325+
.RunAsync();
326+
}
327+
```
328+
329+
=== "With Powertools Logging"
330+
331+
```c# hl_lines="10 11"
332+
using AWS.Lambda.Powertools.Logging;
333+
using AWS.Lambda.Powertools.Logging.Serializers;
334+
using AWS.Lambda.Powertools.Tracing;
335+
using AWS.Lambda.Powertools.Tracing.Serializers;
336+
337+
private static async Task Main()
338+
{
339+
Func<string, ILambdaContext, string> handler = FunctionHandler;
340+
await LambdaBootstrapBuilder.Create(handler,
341+
new PowertoolsSourceGeneratorSerializer<LambdaFunctionJsonSerializerContext>()
342+
.WithTracing())
343+
.Build()
344+
.RunAsync();
345+
}
346+
```
347+
348+
### Publishing
349+
350+
!!! warning "Publishing"
351+
Make sure you are publishing your code with `--self-contained true` and that you have `<TrimMode>partial</TrimMode>` in your `.csproj` file
352+
353+
### Trimming
354+
355+
!!! warning "Trim warnings"
356+
```xml
357+
<ItemGroup>
358+
<TrimmerRootAssembly Include="AWSSDK.Core" />
359+
<TrimmerRootAssembly Include="AWSXRayRecorder.Core" />
360+
<TrimmerRootAssembly Include="AWSXRayRecorder.Handlers.AwsSdk" />
361+
<TrimmerRootAssembly Include="Amazon.Lambda.APIGatewayEvents" />
362+
<TrimmerRootAssembly Include="bootstrap" />
363+
<TrimmerRootAssembly Include="Shared" />
364+
</ItemGroup>
365+
```
366+
367+
Note that when you receive a trim warning, adding the class that generates the warning to TrimmerRootAssembly might not resolve the issue. A trim warning indicates that the class is trying to access some other class that can't be determined until runtime. To avoid runtime errors, add this second class to TrimmerRootAssembly.
368+
369+
To learn more about managing trim warnings, see [Introduction to trim warnings](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/fixing-warnings) in the Microsoft .NET documentation.
298370

299-
Note that when you receive a trim warning, adding the class that generates the warning to TrimmerRootAssembly might not resolve the issue. A trim warning indicates that the class is trying to access some other class that can't be determined until runtime. To avoid runtime errors, add this second class to TrimmerRootAssembly.
371+
### Not supported
300372

301-
To learn more about managing trim warnings, see [Introduction to trim warnings](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/fixing-warnings) in the Microsoft .NET documentation.
373+
!!! warning "Not supported"
374+
Currently instrumenting SDK clients with `Tracing.RegisterForAllServices()` is not supported on AOT mode.

libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PackageReference Include="AWSSDK.XRay" />
1717
<PackageReference Include="AWSXRayRecorder.Core" />
1818
<PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" />
19+
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" />
1920
<ProjectReference Include="..\AWS.Lambda.Powertools.Common\AWS.Lambda.Powertools.Common.csproj" Condition="'$(Configuration)'=='Debug'"/>
2021
</ItemGroup>
2122

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq;
18+
using System.Runtime.ExceptionServices;
19+
using System.Text;
20+
using System.Threading.Tasks;
21+
using AspectInjector.Broker;
22+
using AWS.Lambda.Powertools.Common;
23+
using AWS.Lambda.Powertools.Common.Utils;
24+
25+
namespace AWS.Lambda.Powertools.Tracing.Internal;
26+
27+
/// <summary>
28+
/// Tracing Aspect
29+
/// Scope.Global is singleton
30+
/// </summary>
31+
[Aspect(Scope.Global, Factory = typeof(TracingAspectFactory))]
32+
public class TracingAspect
33+
{
34+
/// <summary>
35+
/// The Powertools for AWS Lambda (.NET) configurations
36+
/// </summary>
37+
private readonly IPowertoolsConfigurations _powertoolsConfigurations;
38+
39+
/// <summary>
40+
/// X-Ray Recorder
41+
/// </summary>
42+
private readonly IXRayRecorder _xRayRecorder;
43+
44+
/// <summary>
45+
/// If true, then is cold start
46+
/// </summary>
47+
private static bool _isColdStart = true;
48+
49+
/// <summary>
50+
/// If true, capture annotations
51+
/// </summary>
52+
private static bool _captureAnnotations = true;
53+
54+
/// <summary>
55+
/// If true, annotations have been captured
56+
/// </summary>
57+
private bool _isAnnotationsCaptured;
58+
59+
/// <summary>
60+
/// Aspect constructor
61+
/// </summary>
62+
/// <param name="powertoolsConfigurations"></param>
63+
/// <param name="xRayRecorder"></param>
64+
public TracingAspect(IPowertoolsConfigurations powertoolsConfigurations, IXRayRecorder xRayRecorder)
65+
{
66+
_powertoolsConfigurations = powertoolsConfigurations;
67+
_xRayRecorder = xRayRecorder;
68+
}
69+
70+
/// <summary>
71+
/// Surrounds the specific method with Tracing aspect
72+
/// </summary>
73+
/// <param name="name"></param>
74+
/// <param name="args"></param>
75+
/// <param name="target"></param>
76+
/// <param name="triggers"></param>
77+
/// <returns></returns>
78+
[Advice(Kind.Around)]
79+
public object Around(
80+
[Argument(Source.Name)] string name,
81+
[Argument(Source.Arguments)] object[] args,
82+
[Argument(Source.Target)] Func<object[], object> target,
83+
[Argument(Source.Triggers)] Attribute[] triggers)
84+
{
85+
var trigger = triggers.OfType<TracingAttribute>().First();
86+
87+
if (TracingDisabled())
88+
return target(args);
89+
90+
var @namespace = !string.IsNullOrWhiteSpace(trigger.Namespace)
91+
? trigger.Namespace
92+
: _powertoolsConfigurations.Service;
93+
94+
var (segmentName, metadataName) = string.IsNullOrWhiteSpace(trigger.SegmentName)
95+
? ($"## {name}", name)
96+
: (trigger.SegmentName, trigger.SegmentName);
97+
98+
BeginSegment(segmentName, @namespace);
99+
100+
try
101+
{
102+
var result = target(args);
103+
104+
if (result is Task task)
105+
{
106+
if (task.IsFaulted)
107+
{
108+
// Force the exception to be thrown
109+
task.Exception?.Handle(ex => false);
110+
}
111+
112+
// Only handle response if it's not a void Task
113+
if (task.GetType().IsGenericType)
114+
{
115+
var taskResult = task.GetType().GetProperty("Result")?.GetValue(task);
116+
HandleResponse(metadataName, taskResult, trigger.CaptureMode, @namespace);
117+
}
118+
_xRayRecorder.EndSubsegment();
119+
return task;
120+
}
121+
122+
HandleResponse(metadataName, result, trigger.CaptureMode, @namespace);
123+
124+
_xRayRecorder.EndSubsegment();
125+
return result;
126+
}
127+
catch (Exception ex)
128+
{
129+
var actualException = ex is AggregateException ae ? ae.InnerException! : ex;
130+
HandleException(actualException, metadataName, trigger.CaptureMode, @namespace);
131+
_xRayRecorder.EndSubsegment();
132+
133+
// Capture and rethrow the original exception preserving the stack trace
134+
ExceptionDispatchInfo.Capture(actualException).Throw();
135+
throw;
136+
}
137+
finally
138+
{
139+
if (_isAnnotationsCaptured)
140+
_captureAnnotations = true;
141+
}
142+
}
143+
144+
private void BeginSegment(string segmentName, string @namespace)
145+
{
146+
_xRayRecorder.BeginSubsegment(segmentName);
147+
_xRayRecorder.SetNamespace(@namespace);
148+
149+
if (_captureAnnotations)
150+
{
151+
_xRayRecorder.AddAnnotation("ColdStart", _isColdStart);
152+
153+
_captureAnnotations = false;
154+
_isAnnotationsCaptured = true;
155+
156+
if (_powertoolsConfigurations.IsServiceDefined)
157+
_xRayRecorder.AddAnnotation("Service", _powertoolsConfigurations.Service);
158+
}
159+
160+
_isColdStart = false;
161+
}
162+
163+
private void HandleResponse(string name, object result, TracingCaptureMode captureMode, string @namespace)
164+
{
165+
if (!CaptureResponse(captureMode)) return;
166+
if (result == null) return; // Don't try to serialize null results
167+
168+
// Skip if the result is VoidTaskResult
169+
if (result.GetType().Name == "VoidTaskResult") return;
170+
171+
#if NET8_0_OR_GREATER
172+
if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) // is AOT
173+
{
174+
_xRayRecorder.AddMetadata(
175+
@namespace,
176+
$"{name} response",
177+
Serializers.PowertoolsTracingSerializer.Serialize(result)
178+
);
179+
return;
180+
}
181+
#endif
182+
183+
_xRayRecorder.AddMetadata(
184+
@namespace,
185+
$"{name} response",
186+
result
187+
);
188+
}
189+
190+
private void HandleException(Exception exception, string name, TracingCaptureMode captureMode, string @namespace)
191+
{
192+
if (!CaptureError(captureMode)) return;
193+
194+
var sb = new StringBuilder();
195+
sb.AppendLine($"Exception type: {exception.GetType()}");
196+
sb.AppendLine($"Exception message: {exception.Message}");
197+
sb.AppendLine($"Stack trace: {exception.StackTrace}");
198+
199+
if (exception.InnerException != null)
200+
{
201+
sb.AppendLine("---BEGIN InnerException--- ");
202+
sb.AppendLine($"Exception type {exception.InnerException.GetType()}");
203+
sb.AppendLine($"Exception message: {exception.InnerException.Message}");
204+
sb.AppendLine($"Stack trace: {exception.InnerException.StackTrace}");
205+
sb.AppendLine("---END Inner Exception");
206+
}
207+
208+
_xRayRecorder.AddMetadata(
209+
@namespace,
210+
$"{name} error",
211+
sb.ToString()
212+
);
213+
}
214+
215+
private bool TracingDisabled()
216+
{
217+
if (_powertoolsConfigurations.TracingDisabled)
218+
{
219+
Console.WriteLine("Tracing has been disabled via env var POWERTOOLS_TRACE_DISABLED");
220+
return true;
221+
}
222+
223+
if (!_powertoolsConfigurations.IsLambdaEnvironment)
224+
{
225+
Console.WriteLine("Running outside Lambda environment; disabling Tracing");
226+
return true;
227+
}
228+
229+
return false;
230+
}
231+
232+
private bool CaptureResponse(TracingCaptureMode captureMode)
233+
{
234+
return captureMode switch
235+
{
236+
TracingCaptureMode.EnvironmentVariable => _powertoolsConfigurations.TracerCaptureResponse,
237+
TracingCaptureMode.Response => true,
238+
TracingCaptureMode.ResponseAndError => true,
239+
_ => false
240+
};
241+
}
242+
243+
private bool CaptureError(TracingCaptureMode captureMode)
244+
{
245+
return captureMode switch
246+
{
247+
TracingCaptureMode.EnvironmentVariable => _powertoolsConfigurations.TracerCaptureError,
248+
TracingCaptureMode.Error => true,
249+
TracingCaptureMode.ResponseAndError => true,
250+
_ => false
251+
};
252+
}
253+
254+
internal static void ResetForTest()
255+
{
256+
_isColdStart = true;
257+
_captureAnnotations = true;
258+
}
259+
}

0 commit comments

Comments
 (0)