Skip to content

Commit

Permalink
Add OpenTelemetry
Browse files Browse the repository at this point in the history
Add OpenTelemetry to the skill.
  • Loading branch information
martincostello committed Jan 24, 2024
1 parent 5e5cd98 commit 0160e90
Show file tree
Hide file tree
Showing 20 changed files with 120 additions and 42 deletions.
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ updates:
- package-ecosystem: nuget
directory: "/"
groups:
opentelemetry:
patterns:
- OpenTelemetry*
polly:
patterns:
- Polly*
Expand Down
Binary file not shown.
Binary file not shown.
5 changes: 5 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<PackageVersion Include="Microsoft.Extensions.Telemetry" Version="8.1.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="OpenTelemetry" Version="1.7.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.7.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.7.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AWSLambda" Version="1.2.0-beta.1.168" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.7.0" />
<PackageVersion Include="Polly.Core" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.Extensions" Version="$(PollyVersion)" />
<PackageVersion Include="Polly.RateLimiting" Version="$(PollyVersion)" />
Expand Down
4 changes: 4 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
<configuration>
<packageSources>
<clear />
<add key="Local" value=".packages" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="Local">
<package pattern="OpenTelemetry.Instrumentation.AWSLambda" />
</packageSource>
<packageSource key="NuGet">
<package pattern="*" />
</packageSource>
Expand Down
56 changes: 40 additions & 16 deletions src/LondonTravel.Skill/AlexaFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using Amazon.Lambda.Core;
using MartinCostello.LondonTravel.Skill.Extensions;
using MartinCostello.LondonTravel.Skill.Intents;
using MartinCostello.LondonTravel.Skill.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenTelemetry.Instrumentation.AWSLambda;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace MartinCostello.LondonTravel.Skill;

Expand Down Expand Up @@ -55,21 +60,15 @@ public async virtual ValueTask DisposeAsync()
/// Handles a request to the skill as an asynchronous operation.
/// </summary>
/// <param name="request">The skill request.</param>
/// <param name="context">The Lamda request context.</param>
/// <returns>
/// A <see cref="Task{TResult}"/> representing the asynchronous operation to get the skill's response.
/// </returns>
public async Task<SkillResponse> HandlerAsync(SkillRequest request)
public async Task<SkillResponse> HandlerAsync(SkillRequest request, ILambdaContext context)
{
EnsureInitialized();

var handler = _serviceProvider.GetRequiredService<FunctionHandler>();
var logger = _serviceProvider.GetRequiredService<ILogger<AlexaFunction>>();

using var activity = SkillTelemetry.ActivitySource.StartActivity("Skill Request");

Log.InvokingSkillRequest(logger, request.Request.Type);

return await handler.HandleAsync(request);
var tracerProvider = _serviceProvider.GetRequiredService<TracerProvider>();
return await AWSLambdaWrapper.TraceAsync(tracerProvider, HandlerCoreAsync, request, context);
}

/// <summary>
Expand Down Expand Up @@ -132,6 +131,21 @@ protected virtual void ConfigureServices(IServiceCollection services)
services.AddTransient<CommuteIntent>();
services.AddTransient<DisruptionIntent>();
services.AddTransient<StatusIntent>();

services.AddSingleton((_) => SkillTelemetry.ActivitySource);
services.AddOpenTelemetry()
.ConfigureResource((builder) => builder.AddService(SkillTelemetry.ServiceName, serviceVersion: SkillTelemetry.ServiceVersion))
.WithTracing((builder) =>
{
builder.AddHttpClientInstrumentation()
.AddSource(SkillTelemetry.ServiceName);

if (IsRunningInAwsLambda())
{
builder.AddAWSLambdaConfigurations()
.AddOtlpExporter();
}
});
}

protected virtual void Dispose(bool disposing)
Expand All @@ -147,12 +161,22 @@ protected virtual void Dispose(bool disposing)
}
}

/// <summary>
/// Creates the <see cref="ServiceProvider"/> to use.
/// </summary>
/// <returns>
/// The <see cref="ServiceProvider"/> to use.
/// </returns>
private static bool IsRunningInAwsLambda()
=> Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME") is { Length: > 0 } &&
Environment.GetEnvironmentVariable("AWS_REGION") is { Length: > 0 };

private async Task<SkillResponse> HandlerCoreAsync(SkillRequest request, ILambdaContext context)
{
var handler = _serviceProvider!.GetRequiredService<FunctionHandler>();
var logger = _serviceProvider!.GetRequiredService<ILogger<AlexaFunction>>();

using var activity = SkillTelemetry.ActivitySource.StartActivity("Skill Request");

Log.InvokingSkillRequest(logger, request.Request.Type);

return await handler.HandleAsync(request);
}

private ServiceProvider CreateServiceProvider()
{
var services = new ServiceCollection();
Expand Down
5 changes: 5 additions & 0 deletions src/LondonTravel.Skill/LondonTravel.Skill.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Microsoft.Extensions.Telemetry" />
<PackageReference Include="OpenTelemetry" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AWSLambda" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="Polly.Core" />
<PackageReference Include="Polly.Extensions" />
<PackageReference Include="Polly.RateLimiting" />
Expand Down
2 changes: 2 additions & 0 deletions src/LondonTravel.Skill/Models/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

namespace MartinCostello.LondonTravel.Skill.Models;

#pragma warning disable CA1724
public sealed class Context
#pragma warning restore CA1724
{
[JsonPropertyName("System")]
public AlexaSystem System { get; set; } = default!;
Expand Down
12 changes: 8 additions & 4 deletions test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Amazon.Lambda.TestUtilities;
using MartinCostello.LondonTravel.Skill.Models;

namespace MartinCostello.LondonTravel.Skill;
Expand All @@ -12,13 +13,14 @@ public async Task Cannot_Invoke_Function_If_Application_Id_Incorrect()
{
// Arrange
AlexaFunction function = await CreateFunctionAsync();
TestLambdaContext context = new();

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

// Act
InvalidOperationException exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => function.HandlerAsync(request));
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => function.HandlerAsync(request, context));

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

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
ResponseBody response = AssertResponse(actual, shouldEndSession: false);
Expand All @@ -53,6 +56,7 @@ public async Task Cannot_Invoke_Function_With_System_Failure()
{
// Arrange
AlexaFunction function = await CreateFunctionAsync();
TestLambdaContext context = new();

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
ResponseBody response = AssertResponse(actual);
Expand Down
4 changes: 3 additions & 1 deletion test/LondonTravel.Skill.Tests/CancelTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Amazon.Lambda.TestUtilities;
using MartinCostello.LondonTravel.Skill.Models;

namespace MartinCostello.LondonTravel.Skill;
Expand All @@ -12,11 +13,12 @@ public async Task Can_Invoke_Function()
{
// Arrange
AlexaFunction function = await CreateFunctionAsync();
TestLambdaContext context = new();

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
ResponseBody response = AssertResponse(actual);
Expand Down
19 changes: 13 additions & 6 deletions test/LondonTravel.Skill.Tests/CommuteTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Amazon.Lambda.TestUtilities;
using JustEat.HttpClientInterception;
using MartinCostello.LondonTravel.Skill.Models;

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

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

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

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

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
AssertResponse(
Expand All @@ -112,9 +117,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_One_Favor

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
AssertResponse(
Expand All @@ -132,9 +138,10 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_Two_Favor

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

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
AssertResponse(
Expand Down
13 changes: 9 additions & 4 deletions test/LondonTravel.Skill.Tests/DisruptionTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Amazon.Lambda.TestUtilities;
using JustEat.HttpClientInterception;
using MartinCostello.LondonTravel.Skill.Models;

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

AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
TestLambdaContext context = new();

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
AssertResponse(
Expand All @@ -35,9 +37,10 @@ public async Task Can_Invoke_Function_When_There_Is_One_Disruption()

AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
TestLambdaContext context = new();

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
AssertResponse(
Expand All @@ -54,9 +57,10 @@ public async Task Can_Invoke_Function_When_There_Are_Multiple_Disruptions()

AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
TestLambdaContext context = new();

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
AssertResponse(
Expand All @@ -71,9 +75,10 @@ public async Task Can_Invoke_Function_When_The_Api_Fails()
// Arrange
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
TestLambdaContext context = new();

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
ResponseBody response = AssertResponse(actual);
Expand Down
4 changes: 3 additions & 1 deletion test/LondonTravel.Skill.Tests/HelpTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Amazon.Lambda.TestUtilities;
using MartinCostello.LondonTravel.Skill.Models;

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

SkillRequest request = CreateIntentRequest("AMAZON.HelpIntent");
TestLambdaContext context = new();

// Act
SkillResponse actual = await function.HandlerAsync(request);
SkillResponse actual = await function.HandlerAsync(request, context);

// Assert
ResponseBody response = AssertResponse(actual, shouldEndSession: false);
Expand Down
Loading

0 comments on commit 0160e90

Please sign in to comment.