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

[Instrumentation.AWSLambda] Issue/aws lambda http server semantic conventions attributes #626

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
63a2a72
#583: HTTP Server semantic conventions for AWS Lambdas
rypdal Aug 12, 2022
1ea5224
#583: unit test for getting tags from AWS API Gateway request + comme…
rypdal Aug 12, 2022
f5abd7f
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 2, 2022
1aed8f3
#583: set http.status_code activity tag as string + unit tests
rypdal Sep 2, 2022
04f41c8
#583: added more unit tests
rypdal Sep 5, 2022
f294e75
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 5, 2022
1084c51
#583: port is taken from host after ":" and not from "x-forwarded-por…
rypdal Sep 5, 2022
ebf2c7e
#583: unit tests for common extensions
rypdal Sep 6, 2022
3761a23
#583: additional null-check
rypdal Sep 6, 2022
fde0c99
#583: use original casing for header names
rypdal Sep 6, 2022
8f5a6b5
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 8, 2022
a3a6420
#583: added default ports for setting net.host.port
rypdal Sep 8, 2022
5f86d10
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 9, 2022
b15b36e
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 12, 2022
22549ca
#583: port tag must be integer + additional unit tests
rypdal Sep 12, 2022
0047c5f
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 20, 2022
bf07052
Update src/OpenTelemetry.Instrumentation.AWSLambda/AWSLambdaWrapper.cs
rypdal Sep 23, 2022
baa673f
Update src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/Com…
rypdal Sep 23, 2022
c87c039
#583: review suggestion
rypdal Sep 23, 2022
02c12f3
Merge branch 'issue/AWS-Lambda-HTTP-Server-semantic-conventions-attri…
rypdal Sep 23, 2022
937757b
#583: iterate dict only once when searching for header
rypdal Sep 23, 2022
2bd7cdb
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 23, 2022
5e461ba
#583: removed case-insensitive header search as only lower-case is ex…
rypdal Sep 23, 2022
f7037b4
#583: ignore header names case + added query string to http.target at…
rypdal Sep 26, 2022
184df8c
#583: aligned file names in it's headers
rypdal Sep 26, 2022
212b102
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 27, 2022
5c9c33e
#583: set http.status_code tag as integer
rypdal Sep 27, 2022
3c6f4b2
#583: extracting common methods for getting headers
rypdal Sep 27, 2022
ecd07a8
#583: renamed class according to project's convention
rypdal Sep 27, 2022
440c737
#583: simplified method
rypdal Sep 27, 2022
966e235
#583: changed HTTP query string construction for both versions of AWS…
rypdal Sep 28, 2022
89fcb80
Update src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/Com…
rypdal Sep 28, 2022
26eb882
Update src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWS…
rypdal Sep 28, 2022
74fbe8b
Update src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWS…
rypdal Sep 29, 2022
a6f63c3
#583: additional review suggestions and code improvements
rypdal Sep 29, 2022
1d0db13
Merge branch 'issue/AWS-Lambda-HTTP-Server-semantic-conventions-attri…
rypdal Sep 29, 2022
c4b57ff
#583: optimized GetQueryString method
rypdal Sep 29, 2022
f83857d
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Sep 29, 2022
0d51176
#583: removed unused using
rypdal Sep 29, 2022
632f065
Update src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWS…
rypdal Sep 29, 2022
6823817
#583:
rypdal Sep 29, 2022
9ac42f5
#583: added test for space encoding
rypdal Sep 29, 2022
068123b
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Oct 3, 2022
28bd1e6
#583: CHANGELOG update
rypdal Oct 4, 2022
82396c5
#583: use the last instead of the first value of multi-value headers
rypdal Oct 4, 2022
9d6c173
#583:
rypdal Oct 4, 2022
60561b7
Merge branch 'main' into issue/AWS-Lambda-HTTP-Server-semantic-conven…
rypdal Oct 5, 2022
4bd334b
#583: code rollback: fallback to empty string if path is not set and …
rypdal Oct 5, 2022
b26223b
Update src/OpenTelemetry.Instrumentation.AWSLambda/CHANGELOG.md
rypdal Oct 5, 2022
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
64 changes: 37 additions & 27 deletions src/OpenTelemetry.Instrumentation.AWSLambda/AWSLambdaWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Amazon.Lambda.Core;
Expand Down Expand Up @@ -68,10 +69,7 @@ public static TResult Trace<TInput, TResult>(
ILambdaContext context,
ActivityContext parentContext = default)
{
TResult result = default;
Action action = () => result = lambdaHandler(input, context);
TraceInternal(tracerProvider, action, input, context, parentContext);
return result;
return TraceInternal(tracerProvider, lambdaHandler, input, context, parentContext);
}

/// <summary>
Expand All @@ -95,8 +93,12 @@ public static void Trace<TInput>(
ILambdaContext context,
ActivityContext parentContext = default)
{
Action action = () => lambdaHandler(input, context);
TraceInternal(tracerProvider, action, input, context, parentContext);
Func<TInput, ILambdaContext, object> func = (input, context) =>
{
lambdaHandler(input, context);
return null;
};
TraceInternal(tracerProvider, func, input, context, parentContext);
}

/// <summary>
Expand All @@ -121,8 +123,12 @@ public static Task TraceAsync<TInput>(
ILambdaContext context,
ActivityContext parentContext = default)
{
Func<Task> action = async () => await lambdaHandler(input, context);
return TraceInternalAsync(tracerProvider, action, input, context, parentContext);
Func<TInput, ILambdaContext, Task<object>> func = async (input, context) =>
{
await lambdaHandler(input, context);
return Task.FromResult<object>(null);
};
return TraceInternalAsync(tracerProvider, func, input, context, parentContext);
}

/// <summary>
Expand All @@ -141,17 +147,14 @@ public static Task TraceAsync<TInput>(
/// unless X-Ray propagation is disabled in the configuration for this wrapper.
/// </param>
/// <returns>Task of result.</returns>
public static async Task<TResult> TraceAsync<TInput, TResult>(
public static Task<TResult> TraceAsync<TInput, TResult>(
TracerProvider tracerProvider,
Func<TInput, ILambdaContext, Task<TResult>> lambdaHandler,
TInput input,
ILambdaContext context,
ActivityContext parentContext = default)
{
TResult result = default;
Func<Task> action = async () => result = await lambdaHandler(input, context);
await TraceInternalAsync(tracerProvider, action, input, context, parentContext);
return result;
return TraceInternalAsync(tracerProvider, lambdaHandler, input, context, parentContext);
}

#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
Expand All @@ -167,9 +170,12 @@ internal static Activity OnFunctionStart<TInput>(TInput input, ILambdaContext co
}
}

var tags = AWSLambdaUtils.GetFunctionTags(input, context);
var functionTags = AWSLambdaUtils.GetFunctionTags(input, context);
var httpTags = HttpSemanticConventions.GetHttpTags(input);

// We assume that functionTags and httpTags have no intersection.
var activityName = AWSLambdaUtils.GetFunctionName(context) ?? "AWS Lambda Invoke";
var activity = AWSLambdaActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext, tags);
var activity = AWSLambdaActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext, functionTags.Union(httpTags));

return activity;
}
Expand Down Expand Up @@ -197,51 +203,55 @@ private static void OnException(Activity activity, Exception exception)
}
}

private static void TraceInternal<TInput>(
private static TResult TraceInternal<TInput, TResult>(
TracerProvider tracerProvider,
Action handler,
Func<TInput, ILambdaContext, TResult> handler,
TInput input,
ILambdaContext context,
ActivityContext parentContext = default)
{
var lambdaActivity = OnFunctionStart(input, context, parentContext);
var activity = OnFunctionStart(input, context, parentContext);
try
{
handler();
var result = handler(input, context);
HttpSemanticConventions.SetHttpTagsFromResult(activity, result);
return result;
}
catch (Exception ex)
{
OnException(lambdaActivity, ex);
OnException(activity, ex);

throw;
}
finally
{
OnFunctionStop(lambdaActivity, tracerProvider);
OnFunctionStop(activity, tracerProvider);
}
}

private static async Task TraceInternalAsync<TInput>(
private static async Task<TResult> TraceInternalAsync<TInput, TResult>(
TracerProvider tracerProvider,
Func<Task> handlerAsync,
Func<TInput, ILambdaContext, Task<TResult>> handlerAsync,
TInput input,
ILambdaContext context,
ActivityContext parentContext = default)
{
var lambdaActivity = OnFunctionStart(input, context, parentContext);
var activity = OnFunctionStart(input, context, parentContext);
try
{
await handlerAsync();
var result = await handlerAsync(input, context);
HttpSemanticConventions.SetHttpTagsFromResult(activity, result);
return result;
}
catch (Exception ex)
{
OnException(lambdaActivity, ex);
OnException(activity, ex);

throw;
}
finally
{
OnFunctionStop(lambdaActivity, tracerProvider);
OnFunctionStop(activity, tracerProvider);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,16 +167,11 @@ private static string GetFaasId(string functionArn)
return faasId;
}

private static string GetFaasTrigger<TInput>(TInput input)
{
var trigger = "other";
if (input is APIGatewayProxyRequest || input is APIGatewayHttpApiV2ProxyRequest)
{
trigger = "http";
}
private static string GetFaasTrigger<TInput>(TInput input) =>
IsHttpRequest(input) ? "http" : "other";

return trigger;
}
private static bool IsHttpRequest<TInput>(TInput input) =>
input is APIGatewayProxyRequest || input is APIGatewayHttpApiV2ProxyRequest;

private static ActivityContext ParseXRayTraceHeader(string rawHeader)
{
Expand All @@ -198,6 +193,11 @@ private static IEnumerable<string> GetHeaderValues(APIGatewayProxyRequest reques
{
return values;
}
else if (request.Headers != null &&
request.Headers.TryGetValue(name, out var value))
{
return new[] { value };
}

return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// <copyright file="CommonExtensions.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using System.Collections.Generic;
using System.Linq;

namespace OpenTelemetry.Instrumentation.AWSLambda.Implementation
{
internal static class CommonExtensions
{
internal static bool TryGetValueIgnoringCase<T>(this IDictionary<string, T> dict, string key, out T value)
rypdal marked this conversation as resolved.
Show resolved Hide resolved
{
value = default;
var targetKey = dict.Keys
.Where(s => string.Equals(s, key, StringComparison.CurrentCultureIgnoreCase))
rypdal marked this conversation as resolved.
Show resolved Hide resolved
.FirstOrDefault();
rypdal marked this conversation as resolved.
Show resolved Hide resolved

if (targetKey != default)
{
value = dict[targetKey];
rypdal marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

return false;
}

internal static void AddStringTagIfNotNull(this List<KeyValuePair<string, object>> tags, string tagName, string tagValue)
{
if (tagValue != null)
{
tags.Add(new(tagName, tagValue));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// <copyright file="HttpSemanticConventions.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Amazon.Lambda.APIGatewayEvents;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Instrumentation.AWSLambda.Implementation
{
internal class HttpSemanticConventions
{
// x-forwarded-... headres are described here https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html
private const string HeaderXForwardedProto = "X-Forwarded-Proto";
private const string HeaderHost = "Host";

internal static IEnumerable<KeyValuePair<string, object>> GetHttpTags<TInput>(TInput input)
{
var tags = new List<KeyValuePair<string, object>>();

string httpScheme = null;
string httpTarget = null;
string httpMethod = null;
string hostName = null;
string hostPort = null;

switch (input)
{
case APIGatewayProxyRequest request:
httpScheme = GetHeaderValue(request, HeaderXForwardedProto);
httpTarget = request.Path;
httpMethod = request.HttpMethod;
var hostHeader = GetHeaderValue(request, HeaderHost);
(hostName, hostPort) = GetHostAndPort(httpScheme, hostHeader);
break;
case APIGatewayHttpApiV2ProxyRequest requestV2:
httpScheme = GetHeaderValue(requestV2, HeaderXForwardedProto);
httpTarget = requestV2?.RequestContext?.Http?.Path;
rypdal marked this conversation as resolved.
Show resolved Hide resolved
httpMethod = requestV2?.RequestContext?.Http?.Method;
var hostHeaderV2 = GetHeaderValue(requestV2, HeaderHost);
(hostName, hostPort) = GetHostAndPort(httpScheme, hostHeaderV2);
break;
}

tags.AddStringTagIfNotNull(SemanticConventions.AttributeHttpScheme, httpScheme);
tags.AddStringTagIfNotNull(SemanticConventions.AttributeHttpTarget, httpTarget);
tags.AddStringTagIfNotNull(SemanticConventions.AttributeHttpMethod, httpMethod);
tags.AddStringTagIfNotNull(SemanticConventions.AttributeNetHostName, hostName);
tags.AddStringTagIfNotNull(SemanticConventions.AttributeNetHostPort, hostPort);
rypdal marked this conversation as resolved.
Show resolved Hide resolved

return tags;
}

internal static void SetHttpTagsFromResult<TResult>(Activity activity, TResult result)
rypdal marked this conversation as resolved.
Show resolved Hide resolved
{
switch (result)
{
case APIGatewayProxyResponse response:
activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode.ToString());
break;
case APIGatewayHttpApiV2ProxyResponse responseV2:
activity.SetTag(SemanticConventions.AttributeHttpStatusCode, responseV2.StatusCode.ToString());
break;
}
}

internal static (string Host, string Port) GetHostAndPort(string httpScheme, string hostHeaders)
{
if (hostHeaders == null)
{
return (null, null);
}

// In case of multiple headres we consider only the 1st.
var hostHeader = hostHeaders.Split(',').First();
var hostAndPort = hostHeader.Split(':');
rypdal marked this conversation as resolved.
Show resolved Hide resolved
if (hostAndPort.Length > 1)
{
return (hostAndPort[0], hostAndPort[1]);
}
else
{
string defaultPort = null;
switch (httpScheme)
{
case "http":
defaultPort = "80";
break;
case "https":
defaultPort = "443";
break;
}

return (hostAndPort[0], defaultPort);
}
}

private static string GetHeaderValue(APIGatewayProxyRequest request, string name)
{
if (request.MultiValueHeaders != null &&
request.MultiValueHeaders.TryGetValueIgnoringCase(name, out var values))
{
return string.Join(",", values);
}
else if (request.Headers != null &&
request.Headers.TryGetValueIgnoringCase(name, out var value))
{
return value;
}

return null;
}

private static string GetHeaderValue(APIGatewayHttpApiV2ProxyRequest request, string name)
{
if (request.Headers != null &&
request.Headers.TryGetValueIgnoringCase(name, out var header))
{
// Multiple values for the same header will be separated by a comma.
return header;
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

<ItemGroup>
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Internal\Guard.cs" Link="Includes\Guard.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Contrib.Shared\Api\SemanticConventions.cs" Link="Includes\SemanticConventions.cs"/>
</ItemGroup>

</Project>
Loading