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

Implemented Plugin.cs to enable Application Singals Instrumentation. #20

Merged
merged 8 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
21 changes: 21 additions & 0 deletions build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,27 @@
"net8.0" / "AWS.OpenTelemetry.AutoInstrumentation.dll",
this.openTelemetryDistributionFolder / "net");

//TODO: fix build script to copy dependencies without manually setting them here.

Check warning on line 127 in build/Build.cs

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest)

Check warning on line 127 in build/Build.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

FileSystemTasks.CopyFileToDirectory(
RootDirectory / "src" / "AWS.OpenTelemetry.AutoInstrumentation" / "bin" / this.configuration /
"net8.0" / "Newtonsoft.Json.dll",
this.openTelemetryDistributionFolder / "net");

FileSystemTasks.CopyFileToDirectory(
RootDirectory / "src" / "AWS.OpenTelemetry.AutoInstrumentation" / "bin" / this.configuration /
"net8.0" / "OpenTelemetry.Extensions.AWS.dll",
this.openTelemetryDistributionFolder / "net");

FileSystemTasks.CopyFileToDirectory(
RootDirectory / "src" / "AWS.OpenTelemetry.AutoInstrumentation" / "bin" / this.configuration /
"net8.0" / "OpenTelemetry.ResourceDetectors.AWS.dll",
this.openTelemetryDistributionFolder / "net");

FileSystemTasks.CopyFileToDirectory(
RootDirectory / "src" / "AWS.OpenTelemetry.AutoInstrumentation" / "bin" / this.configuration /
"net8.0" / "OpenTelemetry.SemanticConventions.dll",
this.openTelemetryDistributionFolder / "net");

if (EnvironmentInfo.IsWin)
{
FileSystemTasks.CopyFileToDirectory(
Expand Down
6 changes: 5 additions & 1 deletion sample-applications/integration-test-app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ ENV DOTNET_ADDITIONAL_DEPS=${INSTALL_DIR}/AdditionalDeps
ENV DOTNET_SHARED_STORE=${INSTALL_DIR}/store
ENV DOTNET_STARTUP_HOOKS=${INSTALL_DIR}/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll
ENV OTEL_DOTNET_AUTO_HOME=${INSTALL_DIR}
ENV OTEL_DOTNET_AUTO_PLUGINS="AWS.OpenTelemetry.AutoInstrumentation.Plugin, AWS.OpenTelemetry.AutoInstrumentation"
ENV OTEL_DOTNET_AUTO_PLUGINS="AWS.OpenTelemetry.AutoInstrumentation.Plugin, AWS.OpenTelemetry.AutoInstrumentation"
ENV OTEL_AWS_APPLICATION_SIGNALS_ENABLED="true"
ENV OTEL_TRACES_SAMPLER="always_on"
ENV OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
ENV OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT="http://otel:4318/v1/metrics"
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
zhenweig marked this conversation as resolved.
Show resolved Hide resolved
<PackageReference Include="OpenTelemetry" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Api" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Contrib.Extensions.AWSXRay" Version="1.2.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.8.0" />
<PackageReference Include="OpenTelemetry.Extensions.AWS" Version="1.3.0-beta.1" />
<PackageReference Include="OpenTelemetry.ResourceDetectors.AWS" Version="1.4.0-beta.1" />
<PackageReference Include="OpenTelemetry.SemanticConventions" Version="1.0.0-rc9.9" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ internal sealed class AwsAttributeKeys

internal static readonly string AttributeAWSS3Bucket = "aws.s3.bucket";
zhenweig marked this conversation as resolved.
Show resolved Hide resolved

internal static readonly string AttributeHttpStatusCode = "http.status_code";
internal static readonly string AttributeHttpResponseContentLength = "http.response_content_length";

internal static readonly string AttributeValueDynamoDb = "dynamodb";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,9 @@ private static void SetRemoteServiceAndOperation(Activity span, ActivityTagsColl
private static string GenerateRemoteOperation(Activity span)
{
string remoteOperation = UnknownRemoteOperation;
if (IsKeyPresent(span, AttributeHttpUrl))
if (IsKeyPresent(span, AttributeUrlFull))
{
string? httpUrl = (string?)span.GetTagItem(AttributeHttpUrl);
string? httpUrl = (string?)span.GetTagItem(AttributeUrlFull);
try
{
Uri url;
Expand All @@ -259,9 +259,9 @@ private static string GenerateRemoteOperation(Activity span)
}
}

if (IsKeyPresent(span, AttributeHttpMethod))
if (IsKeyPresent(span, AttributeHttpRequestMethod))
{
string? httpMethod = (string?)span.GetTagItem(AttributeHttpMethod);
string? httpMethod = (string?)span.GetTagItem(AttributeHttpRequestMethod);
remoteOperation = httpMethod + " " + remoteOperation;
}

Expand Down Expand Up @@ -294,9 +294,9 @@ private static string GenerateRemoteService(Activity span)
remoteService += ":" + port;
}
}
else if (IsKeyPresent(span, AttributeHttpUrl))
else if (IsKeyPresent(span, AttributeUrlFull))
{
string? httpUrl = (string?)span.GetTagItem(AttributeHttpUrl);
string? httpUrl = (string?)span.GetTagItem(AttributeUrlFull);
try
{
if (httpUrl != null)
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Resources;
using static AWS.OpenTelemetry.AutoInstrumentation.AwsAttributeKeys;

namespace AWS.OpenTelemetry.AutoInstrumentation;

/// <summary>
/// AwsMetricAttributesSpanProcessor is SpanProcessor that generates metrics from spans
/// This processor will generate metrics based on span data. It depends on a MetricAttributeGenerator being provided on
/// instantiation, which will provide a means to determine attributes which should be used to create metrics. A Resource
/// must also be provided, which is used to generate metrics.Finally, three Histogram must be provided, which will be
/// used to actually create desired metrics (see below)
///
/// AwsMetricAttributesSpanProcessor produces metrics for errors (e.g.HTTP 4XX status codes), faults(e.g.HTTP 5XX status
/// codes), and latency(in Milliseconds). Errors and faults are counted, while latency is measured with a histogram.
/// Metrics are emitted with attributes derived from span attributes.
///
/// For highest fidelity metrics, this processor should be coupled with the AlwaysRecordSampler, which will result in
/// 100% of spans being sent to the processor.
/// </summary>
public class AwsMetricAttributesSpanProcessor : BaseProcessor<Activity>
{
private IMetricAttributeGenerator generator;
private Resource resource;
AsakerMohd marked this conversation as resolved.
Show resolved Hide resolved

private AwsMetricAttributesSpanProcessor(
IMetricAttributeGenerator generator,
Resource resource)
{
this.generator = generator;
this.resource = resource;
}

/// <summary>
/// Configure Resource Builder for Logs, Metrics and Traces
/// </summary>
/// <param name="activity"><see cref="Activity"/> to configure</param>
public override void OnStart(Activity activity)
{
}

/// <summary>
/// Configure Resource Builder for Logs, Metrics and Traces
/// TODO: There is an OTEL discussion to add BeforeEnd to allow us to write to spans. Below is a hack and goes
/// against the otel specs (not to edit span in OnEnd)
/// Add BeforeEnd to have a callback where the span is still writeable open-telemetry/opentelemetry-specification#1089
/// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#onendspan
/// </summary>
/// <param name="activity"><see cref="Activity"/> to configure</param>
public override void OnEnd(Activity activity)
vastin marked this conversation as resolved.
Show resolved Hide resolved
{
this.AddMetricAttributes(activity);
}

/// <summary>
/// Use <see cref="AwsMetricAttributesSpanProcessorBuilder"/> to construct this processor
/// </summary>
/// <returns>Configured AwsMetricAttributesSpanProcessor</returns>
internal static AwsMetricAttributesSpanProcessor Create(
IMetricAttributeGenerator generator,
Resource resource)
{
return new AwsMetricAttributesSpanProcessor(generator, resource);
}

private static Activity WrapSpanWithAttributes(Activity span, ActivityTagsCollection attributes)
{
foreach (KeyValuePair<string, object?> attribute in attributes)
{
span.SetTag(attribute.Key, attribute.Value);
}

return span;
}

private Activity AddMetricAttributes(Activity span)
{
/// If the map has no items, no modifications are required. If there is one item, it means the
/// span either produces Service or Dependency metric attributes, and in either case we want to
/// modify the span with them. If there are two items, the span produces both Service and
/// Dependency metric attributes indicating the span is a local dependency root. The Service
/// Attributes must be a subset of the Dependency, with the exception of AttributeAWSSpanKind. The
/// knowledge that the span is a local root is more important that knowing that it is a
/// Dependency metric, so we take all the Dependency metrics but replace AttributeAWSSpanKind with
/// <see cref="AwsSpanProcessingUtil.LocalRoot"/>.
Dictionary<string, ActivityTagsCollection> attributeMap =
this.generator.GenerateMetricAttributeMapFromSpan(span, this.resource);
ActivityTagsCollection attributes = new ActivityTagsCollection();

bool generatesServiceMetrics = AwsSpanProcessingUtil.ShouldGenerateServiceMetricAttributes(span);
bool generatesDependencyMetrics = AwsSpanProcessingUtil.ShouldGenerateDependencyMetricAttributes(span);

if (generatesServiceMetrics && generatesDependencyMetrics)
{
attributes = this.CopyAttributesWithLocalRoot(attributeMap[IMetricAttributeGenerator.DependencyMetric]);
}
else if (generatesServiceMetrics)
{
attributes = attributeMap[IMetricAttributeGenerator.ServiceMetric];
}
else if (generatesDependencyMetrics)
{
attributes = attributeMap[IMetricAttributeGenerator.DependencyMetric];
}

if (attributes.Count != 0)
{
Activity modifiedSpan = WrapSpanWithAttributes(span, attributes);
return modifiedSpan;
}
else
{
return span;
}
}

private ActivityTagsCollection CopyAttributesWithLocalRoot(ActivityTagsCollection attributes)
{
ActivityTagsCollection attributeCollection = new ActivityTagsCollection();
attributeCollection.Concat(attributes);
attributeCollection.Remove(AttributeAWSSpanKind);
attributeCollection.Add(AttributeAWSSpanKind, AwsSpanProcessingUtil.LocalRoot);
return attributeCollection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Metrics;
using OpenTelemetry.Resources;

namespace AWS.OpenTelemetry.AutoInstrumentation;

/// <summary>
/// A builder for <see cref="AwsMetricAttributesSpanProcessor"/>
/// </summary>
public class AwsMetricAttributesSpanProcessorBuilder
{
// Defaults
private static readonly IMetricAttributeGenerator DefaultGenerator = new AwsMetricAttributeGenerator();

private readonly Resource resource;

// Optional builder elements
private IMetricAttributeGenerator generator = DefaultGenerator;

private AwsMetricAttributesSpanProcessorBuilder(Resource resource)
vastin marked this conversation as resolved.
Show resolved Hide resolved
{
this.resource = resource;
}

/// <summary>
/// Configure new AwsMetricAttributesSpanProcessorBuilder
/// </summary>
/// <param name="resource"><see cref="Resource"/>Resource to store</param>
/// <returns>New AwsMetricAttributesSpanProcessorBuilder</returns>
public static AwsMetricAttributesSpanProcessorBuilder Create(Resource resource)
{
return new AwsMetricAttributesSpanProcessorBuilder(resource);
}

/// <summary>
/// Sets the generator used to generate attributes used in metrics produced by span metrics
/// processor. If unset, defaults to <see cref="DefaultGenerator"/>. Must not be null.
/// </summary>
/// <param name="generator"><see cref="IMetricAttributeGenerator"/>generator to store</param>
/// <returns>Returns this instance of the builder</returns>
public AwsMetricAttributesSpanProcessorBuilder SetGenerator(IMetricAttributeGenerator generator)
{
vastin marked this conversation as resolved.
Show resolved Hide resolved
if (generator == null)
{
throw new ArgumentNullException("generator must not be null", nameof(generator));
}

this.generator = generator;
return this;
}

/// <summary>
/// Creates an instance of AwsMetricAttributesSpanProcessor
/// </summary>
/// <returns>Returns AwsMetricAttributesSpanProcessor</returns>
public AwsMetricAttributesSpanProcessor Build()
{
return AwsMetricAttributesSpanProcessor.Create(this.generator, this.resource);
}
}
Loading
Loading