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

Create tracing spans to cover the SDK operations #3352

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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
using Amazon.Util;
using Amazon.Runtime.Internal.Util;
using Amazon.Runtime.Internal.Compression;
using Amazon.Runtime.Telemetry.Tracing;
using Amazon.Runtime.Telemetry;

namespace Amazon.Runtime.Internal
{
Expand Down Expand Up @@ -105,6 +107,7 @@ protected virtual void PreInvoke(IExecutionContext executionContext)
if (input.Length >= minCompressionSize)
{
executionContext.RequestContext.Metrics.AddProperty(Metric.UncompressedRequestSize, input.Length);
using (var traceSpan = TracingUtilities.CreateSpan(executionContext.RequestContext, TelemetryConstants.RequestCompressionSpanName))
using (executionContext.RequestContext.Metrics.StartEvent(Metric.RequestCompressionTime))
{
request.Content = compressionAlgorithm.Compress(input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

using Amazon.Runtime.Internal.Auth;
using Amazon.Runtime.Internal.Util;
using Amazon.Runtime.Telemetry;
using Amazon.Runtime.Telemetry.Tracing;
using Amazon.Util;
using System;
using System.IO;
Expand Down Expand Up @@ -51,6 +53,7 @@ protected virtual void PreInvoke(IExecutionContext executionContext)
ImmutableCredentials ic = null;
if (Credentials != null && !(Credentials is AnonymousAWSCredentials) && !(executionContext.RequestContext.Signer is BearerTokenSigner))
{
using (var traceSpan = TracingUtilities.CreateSpan(executionContext.RequestContext, TelemetryConstants.CredentialsRetrievalSpanName))
using(executionContext.RequestContext.Metrics.StartEvent(Metric.CredentialsRequestTime))
{
ic = Credentials.GetCredentials();
Expand Down Expand Up @@ -87,6 +90,7 @@ public override async System.Threading.Tasks.Task<T> InvokeAsync<T>(IExecutionCo
ImmutableCredentials ic = null;
if (Credentials != null && !(Credentials is AnonymousAWSCredentials))
{
using (var traceSpan = TracingUtilities.CreateSpan(executionContext.RequestContext, TelemetryConstants.CredentialsRetrievalSpanName))
using(executionContext.RequestContext.Metrics.StartEvent(Metric.CredentialsRequestTime))
{
ic = await Credentials.GetCredentialsAsync().ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
using System.Net;
using Amazon.Util;

namespace Amazon.Runtime.Internal
{
Expand Down Expand Up @@ -178,23 +179,13 @@ private static IEnumerable<DiscoveryEndpointBase> ProcessEndpointDiscovery(IRequ
var operationName = string.Empty;
if (endpointDiscoveryData.Identifiers != null && endpointDiscoveryData.Identifiers.Count > 0)
{
operationName = OperationNameFromRequestName(requestContext.RequestName);
operationName = AWSSDKUtils.ExtractOperationName(requestContext.RequestName);
}
return options.EndpointOperation(new EndpointOperationContext(requestContext.ImmutableCredentials.AccessKey, operationName, endpointDiscoveryData, evictCacheKey, evictUri));
}

return null;
}

private static string OperationNameFromRequestName(string requestName)
{
if (requestName.EndsWith("Request", StringComparison.Ordinal))
{
return requestName.Substring(0, requestName.Length - 7);
}

return requestName;
}

private static bool IsInvalidEndpointException(Exception exception)
{
Expand Down
32 changes: 30 additions & 2 deletions sdk/src/Core/Amazon.Runtime/Pipeline/Handlers/MetricsHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
using Amazon.Runtime.Internal.Util;
using System.Globalization;
using System;
using Amazon.Runtime.Telemetry.Tracing;
using Amazon.Runtime.Telemetry;

namespace Amazon.Runtime.Internal
{
Expand All @@ -34,15 +36,28 @@ public class MetricsHandler : PipelineHandler
public override void InvokeSync(IExecutionContext executionContext)
{
executionContext.RequestContext.Metrics.AddProperty(Metric.AsyncCall, false);

var operationName = AWSSDKUtils.ExtractOperationName(executionContext.RequestContext.RequestName);
var spanName = $"{executionContext.RequestContext.ServiceMetaData.ServiceId}.{operationName}";
var span = TracingUtilities.CreateSpan(executionContext.RequestContext, spanName);

try
{
executionContext.RequestContext.Metrics.StartEvent(Metric.ClientExecuteTime);
base.InvokeSync(executionContext);

span.SetAttribute(TelemetryConstants.RequestIdAttributeKey, executionContext.ResponseContext.Response.ResponseMetadata.RequestId);
}
catch(Exception ex)
{
span.SetExceptionAttributes(ex);
throw;
}
finally
{
executionContext.RequestContext.Metrics.StopEvent(Metric.ClientExecuteTime);
this.LogMetrics(executionContext);
span.Dispose();
}
}

Expand All @@ -59,16 +74,29 @@ public override void InvokeSync(IExecutionContext executionContext)
public override async System.Threading.Tasks.Task<T> InvokeAsync<T>(IExecutionContext executionContext)
{
executionContext.RequestContext.Metrics.AddProperty(Metric.AsyncCall, true);

var operationName = AWSSDKUtils.ExtractOperationName(executionContext.RequestContext.RequestName);
var spanName = $"{executionContext.RequestContext.ServiceMetaData.ServiceId}.{operationName}";
var span = TracingUtilities.CreateSpan(executionContext.RequestContext, spanName);

try
{
executionContext.RequestContext.Metrics.StartEvent(Metric.ClientExecuteTime);
var response = await base.InvokeAsync<T>(executionContext).ConfigureAwait(false);
return response;
var response = await base.InvokeAsync<T>(executionContext).ConfigureAwait(false);

span.SetAttribute(TelemetryConstants.RequestIdAttributeKey, executionContext.ResponseContext.Response.ResponseMetadata.RequestId);
return response;
}
catch (Exception ex)
{
span.SetExceptionAttributes(ex);
throw;
}
finally
{
executionContext.RequestContext.Metrics.StopEvent(Metric.ClientExecuteTime);
this.LogMetrics(executionContext);
span.Dispose();
}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I intentionally left out adding traces for AWS_APM_API (.NET Framework 3.5) because we plan to remove it in v4 and including it would introduce unnecessary complexity to store the spans and retrieve them in the InvokeCallback, which will not be needed in the newer versions.
Additionally, none of our customers will benefit from it as OTel is not supported for .NET Framework versions older than 4.6.2.

Expand Down
82 changes: 52 additions & 30 deletions sdk/src/Core/Amazon.Runtime/Pipeline/HttpHandler/HttpHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
using Amazon.Runtime.Internal.Auth;
using Amazon.Runtime.Internal.Transform;
using Amazon.Runtime.Internal.Util;
using Amazon.Runtime.Telemetry;
using Amazon.Runtime.Telemetry.Tracing;
using Amazon.Util;
using Amazon.Util.Internal;
using System;
Expand Down Expand Up @@ -68,6 +70,7 @@ public override void InvokeSync(IExecutionContext executionContext)
httpRequest.SetRequestHeaders(wrappedRequest.Headers);

using (executionContext.RequestContext.Metrics.StartEvent(Metric.HttpRequestTime))
using (var traceSpan = TracingUtilities.CreateSpan(executionContext.RequestContext, TelemetryConstants.HTTPRequestSpanName))
{
// Send request body if present.
if (wrappedRequest.HasRequestBody())
Expand All @@ -77,14 +80,16 @@ public override void InvokeSync(IExecutionContext executionContext)
var requestContent = httpRequest.GetRequestContent();
WriteContentToRequestBody(requestContent, httpRequest, executionContext.RequestContext);
}
catch
catch(Exception ex)
{
CompleteFailedRequest(httpRequest);
traceSpan.SetExceptionAttributes(ex);
throw;
}
}

executionContext.ResponseContext.HttpResponse = httpRequest.GetResponse();
SetTraceSpanHttpAttributes(traceSpan, httpRequest.Method, wrappedRequest, executionContext.ResponseContext.HttpResponse);
}
}
finally
Expand Down Expand Up @@ -130,8 +135,22 @@ private static void CompleteFailedRequest(IHttpRequest<TRequestContent> httpRequ
}
catch { }
}

#if AWS_ASYNC_API

private static void SetTraceSpanHttpAttributes(TraceSpan traceSpan, string method, IRequest request, IWebResponseData response)
{
traceSpan.SetAttribute(TelemetryConstants.HTTPMethodAttributeKey, method);
traceSpan.SetAttribute(TelemetryConstants.HTTPStatusCodeAttributeKey, ((int)response.StatusCode));

var contentLengthHeader = request.GetHeaderValue(HeaderKeys.ContentLengthHeader);
if (!string.IsNullOrEmpty(contentLengthHeader) && long.TryParse(contentLengthHeader, out var contentLength))
{
traceSpan.SetAttribute(TelemetryConstants.HTTPRequestContentLengthAttributeKey, contentLength);
}

traceSpan.SetAttribute(TelemetryConstants.HTTPResponseContentLengthAttributeKey, response.ContentLength);
}

#if AWS_ASYNC_API

/// <summary>
/// Issues an HTTP request for the current request context.
Expand All @@ -149,42 +168,45 @@ public override async System.Threading.Tasks.Task<T> InvokeAsync<T>(IExecutionCo
IRequest wrappedRequest = executionContext.RequestContext.Request;
httpRequest = CreateWebRequest(executionContext.RequestContext);
httpRequest.SetRequestHeaders(wrappedRequest.Headers);

using(executionContext.RequestContext.Metrics.StartEvent(Metric.HttpRequestTime))
{
// Send request body if present.
if (wrappedRequest.HasRequestBody())

using (executionContext.RequestContext.Metrics.StartEvent(Metric.HttpRequestTime))
using (var traceSpan = TracingUtilities.CreateSpan(executionContext.RequestContext, TelemetryConstants.HTTPRequestSpanName))
{
System.Runtime.ExceptionServices.ExceptionDispatchInfo edi = null;
try
// Send request body if present.
if (wrappedRequest.HasRequestBody())
{
// In .NET Framework, there needs to be a cancellation token in this method since GetRequestStreamAsync
// does not accept a cancellation token. A workaround is used. This isn't necessary in .NET Standard
// where the stream is a property of the request.
System.Runtime.ExceptionServices.ExceptionDispatchInfo edi = null;
try
{
// In .NET Framework, there needs to be a cancellation token in this method since GetRequestStreamAsync
// does not accept a cancellation token. A workaround is used. This isn't necessary in .NET Standard
// where the stream is a property of the request.
#if BCL45
var requestContent = await httpRequest.GetRequestContentAsync(executionContext.RequestContext.CancellationToken).ConfigureAwait(false);
await WriteContentToRequestBodyAsync(requestContent, httpRequest, executionContext.RequestContext).ConfigureAwait(false);
#else
var requestContent = await httpRequest.GetRequestContentAsync().ConfigureAwait(false);
WriteContentToRequestBody(requestContent, httpRequest, executionContext.RequestContext);
var requestContent = await httpRequest.GetRequestContentAsync().ConfigureAwait(false);
WriteContentToRequestBody(requestContent, httpRequest, executionContext.RequestContext);
#endif
}
catch (Exception e)
{
traceSpan.SetExceptionAttributes(e);
edi = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e);
}

if (edi != null)
{
await CompleteFailedRequest(executionContext, httpRequest).ConfigureAwait(false);

edi.Throw();
}
}
catch(Exception e)
{
edi = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e);
}

if (edi != null)
{
await CompleteFailedRequest(executionContext, httpRequest).ConfigureAwait(false);

edi.Throw();
}
}

var response = await httpRequest.GetResponseAsync(executionContext.RequestContext.CancellationToken).
ConfigureAwait(false);
executionContext.ResponseContext.HttpResponse = response;
var response = await httpRequest.GetResponseAsync(executionContext.RequestContext.CancellationToken).
ConfigureAwait(false);
executionContext.ResponseContext.HttpResponse = response;
SetTraceSpanHttpAttributes(traceSpan, httpRequest.Method, wrappedRequest, executionContext.ResponseContext.HttpResponse);
}
// The response is not unmarshalled yet.
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* permissions and limitations under the License.
*/

using System;
using Amazon.Runtime.Telemetry.Metrics;
using Amazon.Runtime.Telemetry.Metrics.NoOp;
using Amazon.Runtime.Telemetry.Tracing;
Expand Down Expand Up @@ -56,6 +57,8 @@ public DefaultTelemetryProvider()
/// <param name="tracerProvider">The tracer provider to register.</param>
public override void RegisterTracerProvider(TracerProvider tracerProvider)
{
if (tracerProvider == null)
throw new ArgumentNullException(nameof(tracerProvider));
TracerProvider = tracerProvider;
}

Expand All @@ -67,6 +70,8 @@ public override void RegisterTracerProvider(TracerProvider tracerProvider)
/// <param name="meterProvider">The meter provider to register.</param>
public override void RegisterMeterProvider(MeterProvider meterProvider)
{
if (meterProvider == null)
throw new ArgumentNullException(nameof(meterProvider));
MeterProvider = meterProvider;
}
}
Expand Down
50 changes: 50 additions & 0 deletions sdk/src/Core/Amazon.Runtime/Telemetry/TelemetryConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

namespace Amazon.Runtime.Telemetry
{
/// <summary>
/// Contains constants used for telemetry within the SDK.
/// </summary>
public static class TelemetryConstants
{
/// <summary>
/// The prefix used for telemetry scopes within the SDK.
/// </summary>
public const string TelemetryScopePrefix = "AWSSDK";

public const string HTTPRequestSpanName = "HttpRequest";
public const string RequestCompressionSpanName = "RequestCompression";
public const string CredentialsRetrievalSpanName = "CredentialsRetrieval";

public const string SystemAttributeKey = "rpc.system";
public const string SystemAttributeValue = "aws-api";
public const string MethodAttributeKey = "rpc.method";
public const string ServiceAttributeKey = "rpc.service";

public const string RequestIdAttributeKey = "aws.request_id";

public const string HTTPStatusCodeAttributeKey = "http.status_code";
public const string HTTPRequestContentLengthAttributeKey = "http.request_content_length";
public const string HTTPResponseContentLengthAttributeKey = "http.response_content_length";
public const string HTTPMethodAttributeKey = "http.method";

public const string ExceptionMessageAttributeKey = "exception.message";
public const string ExceptionStackTraceAttributeKey = "exception.stacktrace";
public const string ExceptionTypeAttributeKey = "exception.type";
public const string ErrorAttributeKey = "error";
public const string AWSErrorCodeAttributeKey = "aws.error_code";
}
}
Loading