Skip to content

Commit f837f47

Browse files
Add OpenTelemetry
Add OpenTelemetry to the skill.
1 parent f1527ea commit f837f47

17 files changed

+118
-42
lines changed

.github/dependabot.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ updates:
1111
- package-ecosystem: nuget
1212
directory: "/"
1313
groups:
14+
opentelemetry:
15+
patterns:
16+
- OpenTelemetry*
1417
polly:
1518
patterns:
1619
- Polly*

Directory.Packages.props

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
<PackageVersion Include="Microsoft.Extensions.Telemetry" Version="8.1.0" />
2727
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
2828
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
29+
<PackageVersion Include="OpenTelemetry" Version="1.7.0" />
30+
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
31+
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
32+
<PackageVersion Include="OpenTelemetry.Instrumentation.AWS" Version="1.1.0-beta.3" />
33+
<PackageVersion Include="OpenTelemetry.Instrumentation.AWSLambda" Version="1.3.0-beta.1" />
34+
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.7.0" />
2935
<PackageVersion Include="Polly.Core" Version="$(PollyVersion)" />
3036
<PackageVersion Include="Polly.Extensions" Version="$(PollyVersion)" />
3137
<PackageVersion Include="Polly.RateLimiting" Version="$(PollyVersion)" />

src/LondonTravel.Skill/AlexaFunction.cs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

44
using System.Diagnostics.CodeAnalysis;
5+
using Amazon.Lambda.Core;
56
using MartinCostello.LondonTravel.Skill.Extensions;
67
using MartinCostello.LondonTravel.Skill.Intents;
78
using MartinCostello.LondonTravel.Skill.Models;
89
using Microsoft.Extensions.Configuration;
910
using Microsoft.Extensions.DependencyInjection;
1011
using Microsoft.Extensions.Logging;
1112
using Microsoft.Extensions.Options;
13+
using OpenTelemetry.Instrumentation.AWSLambda;
14+
using OpenTelemetry.Metrics;
15+
using OpenTelemetry.Resources;
16+
using OpenTelemetry.Trace;
1217

1318
namespace MartinCostello.LondonTravel.Skill;
1419

@@ -55,21 +60,15 @@ public async virtual ValueTask DisposeAsync()
5560
/// Handles a request to the skill as an asynchronous operation.
5661
/// </summary>
5762
/// <param name="request">The skill request.</param>
63+
/// <param name="context">The Lamda request context.</param>
5864
/// <returns>
5965
/// A <see cref="Task{TResult}"/> representing the asynchronous operation to get the skill's response.
6066
/// </returns>
61-
public async Task<SkillResponse> HandlerAsync(SkillRequest request)
67+
public async Task<SkillResponse> HandlerAsync(SkillRequest request, ILambdaContext context)
6268
{
6369
EnsureInitialized();
64-
65-
var handler = _serviceProvider.GetRequiredService<FunctionHandler>();
66-
var logger = _serviceProvider.GetRequiredService<ILogger<AlexaFunction>>();
67-
68-
using var activity = SkillTelemetry.ActivitySource.StartActivity("Skill Request");
69-
70-
Log.InvokingSkillRequest(logger, request.Request.Type);
71-
72-
return await handler.HandleAsync(request);
70+
var tracerProvider = _serviceProvider.GetRequiredService<TracerProvider>();
71+
return await AWSLambdaWrapper.TraceAsync(tracerProvider, HandlerCoreAsync, request, context);
7372
}
7473

7574
/// <summary>
@@ -132,6 +131,21 @@ protected virtual void ConfigureServices(IServiceCollection services)
132131
services.AddTransient<CommuteIntent>();
133132
services.AddTransient<DisruptionIntent>();
134133
services.AddTransient<StatusIntent>();
134+
135+
services.AddSingleton((_) => SkillTelemetry.ActivitySource);
136+
services.AddOpenTelemetry()
137+
.ConfigureResource((builder) => builder.AddService(SkillTelemetry.ServiceName, serviceVersion: SkillTelemetry.ServiceVersion))
138+
.WithTracing((builder) =>
139+
{
140+
builder.AddHttpClientInstrumentation((p) => p.RecordException = true)
141+
.AddSource(SkillTelemetry.ServiceName);
142+
143+
if (IsRunningInAwsLambda())
144+
{
145+
builder.AddAWSLambdaConfigurations()
146+
.AddOtlpExporter();
147+
}
148+
});
135149
}
136150

137151
protected virtual void Dispose(bool disposing)
@@ -147,12 +161,22 @@ protected virtual void Dispose(bool disposing)
147161
}
148162
}
149163

150-
/// <summary>
151-
/// Creates the <see cref="ServiceProvider"/> to use.
152-
/// </summary>
153-
/// <returns>
154-
/// The <see cref="ServiceProvider"/> to use.
155-
/// </returns>
164+
private static bool IsRunningInAwsLambda()
165+
=> Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME") is { Length: > 0 } &&
166+
Environment.GetEnvironmentVariable("AWS_REGION") is { Length: > 0 };
167+
168+
private async Task<SkillResponse> HandlerCoreAsync(SkillRequest request, ILambdaContext context)
169+
{
170+
var handler = _serviceProvider!.GetRequiredService<FunctionHandler>();
171+
var logger = _serviceProvider!.GetRequiredService<ILogger<AlexaFunction>>();
172+
173+
using var activity = SkillTelemetry.ActivitySource.StartActivity("Skill Request");
174+
175+
Log.InvokingSkillRequest(logger, request.Request.Type);
176+
177+
return await handler.HandleAsync(request);
178+
}
179+
156180
private ServiceProvider CreateServiceProvider()
157181
{
158182
var services = new ServiceCollection();

src/LondonTravel.Skill/LondonTravel.Skill.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
2929
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
3030
<PackageReference Include="Microsoft.Extensions.Telemetry" />
31+
<PackageReference Include="OpenTelemetry" />
32+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
33+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
34+
<PackageReference Include="OpenTelemetry.Instrumentation.AWS" />
35+
<PackageReference Include="OpenTelemetry.Instrumentation.AWSLambda" />
36+
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
3137
<PackageReference Include="Polly.Core" />
3238
<PackageReference Include="Polly.Extensions" />
3339
<PackageReference Include="Polly.RateLimiting" />

src/LondonTravel.Skill/Models/Context.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
namespace MartinCostello.LondonTravel.Skill.Models;
77

8+
#pragma warning disable CA1724
89
public sealed class Context
10+
#pragma warning restore CA1724
911
{
1012
[JsonPropertyName("System")]
1113
public AlexaSystem System { get; set; } = default!;

test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Martin Costello, 2017. All rights reserved.
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

4+
using Amazon.Lambda.TestUtilities;
45
using MartinCostello.LondonTravel.Skill.Models;
56

67
namespace MartinCostello.LondonTravel.Skill;
@@ -12,13 +13,14 @@ public async Task Cannot_Invoke_Function_If_Application_Id_Incorrect()
1213
{
1314
// Arrange
1415
AlexaFunction function = await CreateFunctionAsync();
16+
TestLambdaContext context = new();
1517

1618
SkillRequest request = CreateIntentRequest("AMAZON.HelpIntent");
1719
request.Session.Application.ApplicationId = "not-my-skill-id";
1820

1921
// Act
20-
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(
21-
() => function.HandlerAsync(request));
22+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
23+
() => function.HandlerAsync(request, context));
2224

2325
// Assert
2426
exception.Message.ShouldBe("Request application Id 'not-my-skill-id' and configured skill Id 'my-skill-id' mismatch.");
@@ -34,12 +36,13 @@ public async Task Can_Invoke_Function_If_Locale_Is_Invalid(string? locale)
3436
{
3537
// Arrange
3638
AlexaFunction function = await CreateFunctionAsync();
39+
TestLambdaContext context = new();
3740

3841
SkillRequest request = CreateIntentRequest("AMAZON.HelpIntent");
3942
request.Request.Locale = locale!;
4043

4144
// Act
42-
SkillResponse actual = await function.HandlerAsync(request);
45+
SkillResponse actual = await function.HandlerAsync(request, context);
4346

4447
// Assert
4548
ResponseBody response = AssertResponse(actual, shouldEndSession: false);
@@ -53,6 +56,7 @@ public async Task Cannot_Invoke_Function_With_System_Failure()
5356
{
5457
// Arrange
5558
AlexaFunction function = await CreateFunctionAsync();
59+
TestLambdaContext context = new();
5660

5761
var error = new Request()
5862
{
@@ -70,7 +74,7 @@ public async Task Cannot_Invoke_Function_With_System_Failure()
7074
var request = CreateRequest("System.ExceptionEncountered", error);
7175

7276
// Act
73-
SkillResponse actual = await function.HandlerAsync(request);
77+
SkillResponse actual = await function.HandlerAsync(request, context);
7478

7579
// Assert
7680
ResponseBody response = AssertResponse(actual);

test/LondonTravel.Skill.Tests/CancelTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Martin Costello, 2017. All rights reserved.
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

4+
using Amazon.Lambda.TestUtilities;
45
using MartinCostello.LondonTravel.Skill.Models;
56

67
namespace MartinCostello.LondonTravel.Skill;
@@ -12,11 +13,12 @@ public async Task Can_Invoke_Function()
1213
{
1314
// Arrange
1415
AlexaFunction function = await CreateFunctionAsync();
16+
TestLambdaContext context = new();
1517

1618
SkillRequest request = CreateIntentRequest("AMAZON.CancelIntent");
1719

1820
// Act
19-
SkillResponse actual = await function.HandlerAsync(request);
21+
SkillResponse actual = await function.HandlerAsync(request, context);
2022

2123
// Assert
2224
ResponseBody response = AssertResponse(actual);

test/LondonTravel.Skill.Tests/CommuteTests.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Martin Costello, 2017. All rights reserved.
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

4+
using Amazon.Lambda.TestUtilities;
45
using JustEat.HttpClientInterception;
56
using MartinCostello.LondonTravel.Skill.Models;
67

@@ -14,9 +15,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Not_Linked()
1415
// Arrange
1516
AlexaFunction function = await CreateFunctionAsync();
1617
SkillRequest request = CreateIntentRequestWithToken(accessToken: null);
18+
TestLambdaContext context = new();
1719

1820
// Act
19-
SkillResponse actual = await function.HandlerAsync(request);
21+
SkillResponse actual = await function.HandlerAsync(request, context);
2022

2123
// Assert
2224
ResponseBody response = AssertResponse(actual);
@@ -41,9 +43,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Token_Is_Invalid()
4143

4244
AlexaFunction function = await CreateFunctionAsync();
4345
SkillRequest request = CreateIntentRequestWithToken(accessToken: "invalid-access-token");
46+
TestLambdaContext context = new();
4447

4548
// Act
46-
SkillResponse actual = await function.HandlerAsync(request);
49+
SkillResponse actual = await function.HandlerAsync(request, context);
4750

4851
// Assert
4952
ResponseBody response = AssertResponse(actual);
@@ -66,9 +69,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Api_Fails()
6669
// Arrange
6770
AlexaFunction function = await CreateFunctionAsync();
6871
SkillRequest request = CreateIntentRequestWithToken(accessToken: "random-access-token");
72+
TestLambdaContext context = new();
6973

7074
// Act
71-
SkillResponse actual = await function.HandlerAsync(request);
75+
SkillResponse actual = await function.HandlerAsync(request, context);
7276

7377
// Assert
7478
ResponseBody response = AssertResponse(actual);
@@ -92,9 +96,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_No_Favori
9296

9397
AlexaFunction function = await CreateFunctionAsync();
9498
SkillRequest request = CreateIntentRequestWithToken(accessToken: "token-for-no-favorites");
99+
TestLambdaContext context = new();
95100

96101
// Act
97-
SkillResponse actual = await function.HandlerAsync(request);
102+
SkillResponse actual = await function.HandlerAsync(request, context);
98103

99104
// Assert
100105
AssertResponse(
@@ -112,9 +117,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_One_Favor
112117

113118
AlexaFunction function = await CreateFunctionAsync();
114119
SkillRequest request = CreateIntentRequestWithToken(accessToken: "token-for-one-favorite");
120+
TestLambdaContext context = new();
115121

116122
// Act
117-
SkillResponse actual = await function.HandlerAsync(request);
123+
SkillResponse actual = await function.HandlerAsync(request, context);
118124

119125
// Assert
120126
AssertResponse(
@@ -132,9 +138,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_Two_Favor
132138

133139
AlexaFunction function = await CreateFunctionAsync();
134140
SkillRequest request = CreateIntentRequestWithToken(accessToken: "token-for-two-favorites");
141+
TestLambdaContext context = new();
135142

136143
// Act
137-
SkillResponse actual = await function.HandlerAsync(request);
144+
SkillResponse actual = await function.HandlerAsync(request, context);
138145

139146
// Assert
140147
AssertResponse(

test/LondonTravel.Skill.Tests/DisruptionTests.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Martin Costello, 2017. All rights reserved.
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

4+
using Amazon.Lambda.TestUtilities;
45
using JustEat.HttpClientInterception;
56
using MartinCostello.LondonTravel.Skill.Models;
67

@@ -16,9 +17,10 @@ public async Task Can_Invoke_Function_When_There_Are_No_Disruptions()
1617

1718
AlexaFunction function = await CreateFunctionAsync();
1819
SkillRequest request = CreateIntentRequest();
20+
TestLambdaContext context = new();
1921

2022
// Act
21-
SkillResponse actual = await function.HandlerAsync(request);
23+
SkillResponse actual = await function.HandlerAsync(request, context);
2224

2325
// Assert
2426
AssertResponse(
@@ -35,9 +37,10 @@ public async Task Can_Invoke_Function_When_There_Is_One_Disruption()
3537

3638
AlexaFunction function = await CreateFunctionAsync();
3739
SkillRequest request = CreateIntentRequest();
40+
TestLambdaContext context = new();
3841

3942
// Act
40-
SkillResponse actual = await function.HandlerAsync(request);
43+
SkillResponse actual = await function.HandlerAsync(request, context);
4144

4245
// Assert
4346
AssertResponse(
@@ -54,9 +57,10 @@ public async Task Can_Invoke_Function_When_There_Are_Multiple_Disruptions()
5457

5558
AlexaFunction function = await CreateFunctionAsync();
5659
SkillRequest request = CreateIntentRequest();
60+
TestLambdaContext context = new();
5761

5862
// Act
59-
SkillResponse actual = await function.HandlerAsync(request);
63+
SkillResponse actual = await function.HandlerAsync(request, context);
6064

6165
// Assert
6266
AssertResponse(
@@ -71,9 +75,10 @@ public async Task Can_Invoke_Function_When_The_Api_Fails()
7175
// Arrange
7276
AlexaFunction function = await CreateFunctionAsync();
7377
SkillRequest request = CreateIntentRequest();
78+
TestLambdaContext context = new();
7479

7580
// Act
76-
SkillResponse actual = await function.HandlerAsync(request);
81+
SkillResponse actual = await function.HandlerAsync(request, context);
7782

7883
// Assert
7984
ResponseBody response = AssertResponse(actual);

test/LondonTravel.Skill.Tests/HelpTests.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Martin Costello, 2017. All rights reserved.
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

4+
using Amazon.Lambda.TestUtilities;
45
using MartinCostello.LondonTravel.Skill.Models;
56

67
namespace MartinCostello.LondonTravel.Skill;
@@ -14,9 +15,10 @@ public async Task Can_Invoke_Function()
1415
AlexaFunction function = await CreateFunctionAsync();
1516

1617
SkillRequest request = CreateIntentRequest("AMAZON.HelpIntent");
18+
TestLambdaContext context = new();
1719

1820
// Act
19-
SkillResponse actual = await function.HandlerAsync(request);
21+
SkillResponse actual = await function.HandlerAsync(request, context);
2022

2123
// Assert
2224
ResponseBody response = AssertResponse(actual, shouldEndSession: false);

0 commit comments

Comments
 (0)