Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenTelemetry #1020

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 6 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
<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.AWS" Version="1.1.0-beta.3" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AWSLambda" Version="1.3.0-beta.1" />
<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
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((p) => p.RecordException = true)
.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
6 changes: 6 additions & 0 deletions src/LondonTravel.Skill/LondonTravel.Skill.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
<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.AWS" />
<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
4 changes: 3 additions & 1 deletion test/LondonTravel.Skill.Tests/LaunchTests.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 = CreateRequest("LaunchRequest");
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
Loading