diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs
index f13de811e8b9f..11aa7d65bd1a1 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/AzureMonitorAspNetCoreEventSource.cs
@@ -53,5 +53,14 @@ public void ConfigureFailed(Exception ex)
[Event(1, Message = "Failed to configure AzureMonitorOptions using the connection string from environment variables due to an exception: {0}", Level = EventLevel.Error)]
public void ConfigureFailed(string exceptionMessage) => WriteEvent(1, exceptionMessage);
+
+ [Event(2, Message = "Package reference for {0} found. Backing off from default included instrumentation. Action Required: You must manually configure this instrumentation.", Level = EventLevel.Warning)]
+ public void FoundInstrumentationPackageReference(string packageName) => WriteEvent(2, packageName);
+
+ [Event(3, Message = "No instrumentation package found with name: {0}.", Level = EventLevel.Verbose)]
+ public void NoInstrumentationPackageReference(string packageName) => WriteEvent(3, packageName);
+
+ [Event(4, Message = "Vendor instrumentation added for: {0}.", Level = EventLevel.Verbose)]
+ public void VendorInstrumentationAdded(string packageName) => WriteEvent(4, packageName);
}
}
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs
index f557a1c4c7a06..7864054779c7b 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/OpenTelemetryBuilderExtensions.cs
@@ -1,9 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+#nullable enable
+
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Reflection;
using Azure.Monitor.OpenTelemetry.Exporter;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -23,6 +26,10 @@ namespace Azure.Monitor.OpenTelemetry.AspNetCore
///
public static class OpenTelemetryBuilderExtensions
{
+ private const string AspNetCoreInstrumentationPackageName = "OpenTelemetry.Instrumentation.AspNetCore";
+ private const string HttpClientInstrumentationPackageName = "OpenTelemetry.Instrumentation.Http";
+ private const string SqlClientInstrumentationPackageName = "OpenTelemetry.Instrumentation.SqlClient";
+
///
/// Configures Azure Monitor for logging, distributed tracing, and metrics.
///
@@ -102,23 +109,7 @@ public static OpenTelemetryBuilder UseAzureMonitor(this OpenTelemetryBuilder bui
builder.WithTracing(b => b
.AddSource("Azure.*")
- .AddAspNetCoreInstrumentation()
- .AddHttpClientInstrumentation(o => o.FilterHttpRequestMessage = (_) =>
- {
- // Azure SDKs create their own client span before calling the service using HttpClient
- // In this case, we would see two spans corresponding to the same operation
- // 1) created by Azure SDK 2) created by HttpClient
- // To prevent this duplication we are filtering the span from HttpClient
- // as span from Azure SDK contains all relevant information needed.
- var parentActivity = Activity.Current?.Parent;
- if (parentActivity != null && parentActivity.Source.Name.Equals("Azure.Core.Http"))
- {
- return false;
- }
-
- return true;
- })
- .AddSqlClientInstrumentation()
+ .AddVendorInstrumentationIfPackageNotReferenced()
.AddAzureMonitorTraceExporter());
builder.WithMetrics(b => b
@@ -158,5 +149,55 @@ public static OpenTelemetryBuilder UseAzureMonitor(this OpenTelemetryBuilder bui
return builder;
}
+
+ private static TracerProviderBuilder AddVendorInstrumentationIfPackageNotReferenced(this TracerProviderBuilder tracerProviderBuilder)
+ {
+ var vendorInstrumentationActions = new Dictionary
+ {
+ { AspNetCoreInstrumentationPackageName, () => tracerProviderBuilder.AddAspNetCoreInstrumentation() },
+ { SqlClientInstrumentationPackageName, () => tracerProviderBuilder.AddSqlClientInstrumentation() },
+ {
+ HttpClientInstrumentationPackageName,
+ () => tracerProviderBuilder.AddHttpClientInstrumentation(o => o.FilterHttpRequestMessage = (_) =>
+ {
+ // Azure SDKs create their own client span before calling the service using HttpClient
+ // In this case, we would see two spans corresponding to the same operation
+ // 1) created by Azure SDK 2) created by HttpClient
+ // To prevent this duplication we are filtering the span from HttpClient
+ // as span from Azure SDK contains all relevant information needed.
+ var parentActivity = Activity.Current?.Parent;
+ if (parentActivity != null && parentActivity.Source.Name.Equals("Azure.Core.Http"))
+ {
+ return false;
+ }
+
+ return true;
+ })
+ },
+ };
+
+ foreach (var packageActionPair in vendorInstrumentationActions)
+ {
+ Assembly? instrumentationAssembly = null;
+
+ try
+ {
+ instrumentationAssembly = Assembly.Load(packageActionPair.Key + ".dll");
+ AzureMonitorAspNetCoreEventSource.Log.FoundInstrumentationPackageReference(packageActionPair.Key);
+ }
+ catch
+ {
+ AzureMonitorAspNetCoreEventSource.Log.NoInstrumentationPackageReference(packageActionPair.Key);
+ }
+
+ if (instrumentationAssembly == null)
+ {
+ packageActionPair.Value.Invoke();
+ AzureMonitorAspNetCoreEventSource.Log.VendorInstrumentationAdded(packageActionPair.Key);
+ }
+ }
+
+ return tracerProviderBuilder;
+ }
}
}
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/OpenTelemetry.Instrumentation.AspNetCore/GrpcTagHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/OpenTelemetry.Instrumentation.AspNetCore/GrpcTagHelper.cs
new file mode 100644
index 0000000000000..14f5d7b6164d0
--- /dev/null
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/OpenTelemetry.Instrumentation.AspNetCore/GrpcTagHelper.cs
@@ -0,0 +1,89 @@
+//
+// 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.
+//
+
+using System.Diagnostics;
+using System.Text.RegularExpressions;
+using OpenTelemetry.Trace;
+
+namespace OpenTelemetry.Instrumentation.GrpcNetClient;
+
+internal static class GrpcTagHelper
+{
+ public const string RpcSystemGrpc = "grpc";
+
+ // The Grpc.Net.Client library adds its own tags to the activity.
+ // These tags are used to source the tags added by the OpenTelemetry instrumentation.
+ public const string GrpcMethodTagName = "grpc.method";
+ public const string GrpcStatusCodeTagName = "grpc.status_code";
+
+ private static readonly Regex GrpcMethodRegex = new(@"^/?(?.*)/(?.*)$", RegexOptions.Compiled);
+
+ public static string GetGrpcMethodFromActivity(Activity activity)
+ {
+ return activity.GetTagValue(GrpcMethodTagName) as string;
+ }
+
+ public static bool TryGetGrpcStatusCodeFromActivity(Activity activity, out int statusCode)
+ {
+ statusCode = -1;
+ var grpcStatusCodeTag = activity.GetTagValue(GrpcStatusCodeTagName);
+ if (grpcStatusCodeTag == null)
+ {
+ return false;
+ }
+
+ return int.TryParse(grpcStatusCodeTag as string, out statusCode);
+ }
+
+ public static bool TryParseRpcServiceAndRpcMethod(string grpcMethod, out string rpcService, out string rpcMethod)
+ {
+ var match = GrpcMethodRegex.Match(grpcMethod);
+ if (match.Success)
+ {
+ rpcService = match.Groups["service"].Value;
+ rpcMethod = match.Groups["method"].Value;
+ return true;
+ }
+ else
+ {
+ rpcService = string.Empty;
+ rpcMethod = string.Empty;
+ return false;
+ }
+ }
+
+ ///
+ /// Helper method that populates span properties from RPC status code according
+ /// to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md#status.
+ ///
+ /// RPC status code.
+ /// Resolved span for the Grpc status code.
+ public static ActivityStatusCode ResolveSpanStatusForGrpcStatusCode(int statusCode)
+ {
+ var status = ActivityStatusCode.Error;
+
+ if (typeof(StatusCanonicalCode).IsEnumDefined(statusCode))
+ {
+ status = ((StatusCanonicalCode)statusCode) switch
+ {
+ StatusCanonicalCode.Ok => ActivityStatusCode.Unset,
+ _ => ActivityStatusCode.Error,
+ };
+ }
+
+ return status;
+ }
+}
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/OpenTelemetry.Instrumentation.AspNetCore/StatusCanonicalCode.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/OpenTelemetry.Instrumentation.AspNetCore/StatusCanonicalCode.cs
new file mode 100644
index 0000000000000..bd8be72a74a95
--- /dev/null
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/OpenTelemetry.Instrumentation.AspNetCore/StatusCanonicalCode.cs
@@ -0,0 +1,148 @@
+//
+// 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.
+//
+
+namespace OpenTelemetry.Instrumentation.GrpcNetClient;
+
+///
+/// Canonical result code of span execution.
+///
+///
+/// This follows the standard GRPC codes.
+/// https://github.com/grpc/grpc/blob/master/doc/statuscodes.md.
+///
+internal enum StatusCanonicalCode
+{
+ ///
+ /// The operation completed successfully.
+ ///
+ Ok = 0,
+
+ ///
+ /// The operation was cancelled (typically by the caller).
+ ///
+ Cancelled = 1,
+
+ ///
+ /// Unknown error. An example of where this error may be returned is if a Status value received
+ /// from another address space belongs to an error-space that is not known in this address space.
+ /// Also errors raised by APIs that do not return enough error information may be converted to
+ /// this error.
+ ///
+ Unknown = 2,
+
+ ///
+ /// Client specified an invalid argument. Note that this differs from FAILED_PRECONDITION.
+ /// INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the
+ /// system (e.g., a malformed file name).
+ ///
+ InvalidArgument = 3,
+
+ ///
+ /// Deadline expired before operation could complete. For operations that change the state of the
+ /// system, this error may be returned even if the operation has completed successfully. For
+ /// example, a successful response from a server could have been delayed long enough for the
+ /// deadline to expire.
+ ///
+ DeadlineExceeded = 4,
+
+ ///
+ /// Some requested entity (e.g., file or directory) was not found.
+ ///
+ NotFound = 5,
+
+ ///
+ /// Some entity that we attempted to create (e.g., file or directory) already exists.
+ ///
+ AlreadyExists = 6,
+
+ ///
+ /// The caller does not have permission to execute the specified operation. PERMISSION_DENIED
+ /// must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED
+ /// instead for those errors). PERMISSION_DENIED must not be used if the caller cannot be
+ /// identified (use UNAUTHENTICATED instead for those errors).
+ ///
+ PermissionDenied = 7,
+
+ ///
+ /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
+ /// is out of space.
+ ///
+ ResourceExhausted = 8,
+
+ ///
+ /// Operation was rejected because the system is not in a state required for the operation's
+ /// execution. For example, directory to be deleted may be non-empty, an rmdir operation is
+ /// applied to a non-directory, etc.
+ /// A litmus test that may help a service implementor in deciding between FAILED_PRECONDITION,
+ /// ABORTED, and UNAVAILABLE: (a) Use UNAVAILABLE if the client can retry just the failing call.
+ /// (b) Use ABORTED if the client should retry at a higher-level (e.g., restarting a
+ /// read-modify-write sequence). (c) Use FAILED_PRECONDITION if the client should not retry until
+ /// the system state has been explicitly fixed. E.g., if an "rmdir" fails because the directory
+ /// is non-empty, FAILED_PRECONDITION should be returned since the client should not retry unless
+ /// they have first fixed up the directory by deleting files from it.
+ ///
+ FailedPrecondition = 9,
+
+ ///
+ /// The operation was aborted, typically due to a concurrency issue like sequencer check
+ /// failures, transaction aborts, etc.
+ ///
+ Aborted = 10,
+
+ ///
+ /// Operation was attempted past the valid range. E.g., seeking or reading past end of file.
+ ///
+ /// Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system
+ /// state changes. For example, a 32-bit file system will generate INVALID_ARGUMENT if asked to
+ /// read at an offset that is not in the range [0,2^32-1], but it will generate OUT_OF_RANGE if
+ /// asked to read from an offset past the current file size.
+ ///
+ /// There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE. We recommend
+ /// using OUT_OF_RANGE (the more specific error) when it applies so that callers who are
+ /// iterating through a space can easily look for an OUT_OF_RANGE error to detect when they are
+ /// done.
+ ///
+ OutOfRange = 11,
+
+ ///
+ /// Operation is not implemented or not supported/enabled in this service.
+ ///
+ Unimplemented = 12,
+
+ ///
+ /// Internal errors. Means some invariants expected by underlying system has been broken. If you
+ /// see one of these errors, something is very broken.
+ ///
+ Internal = 13,
+
+ ///
+ /// The service is currently unavailable. This is a most likely a transient condition and may be
+ /// corrected by retrying with a backoff.
+ ///
+ /// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE.
+ ///
+ Unavailable = 14,
+
+ ///
+ /// Unrecoverable data loss or corruption.
+ ///
+ DataLoss = 15,
+
+ ///
+ /// The request does not have valid authentication credentials for the operation.
+ ///
+ Unauthenticated = 16,
+}
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationProvider.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationProvider.cs
index 8d58d64180206..f1a1c35f4a66e 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationProvider.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationProvider.cs
@@ -2,6 +2,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#if NETSTANDARD2_0
#nullable enable
using System;
@@ -83,3 +84,4 @@ private void AddIfNormalizedKeyMatchesPrefix(Dictionary data, s
private static string Normalize(string key) => key.Replace("__", ConfigurationPath.KeyDelimiter);
}
}
+#endif
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationSource.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationSource.cs
index 2785b2174756e..23fcdd3e0e09c 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationSource.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesConfigurationSource.cs
@@ -2,6 +2,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#if NETSTANDARD2_0
#nullable enable
namespace Microsoft.Extensions.Configuration.EnvironmentVariables
@@ -27,3 +28,4 @@ public IConfigurationProvider Build(IConfigurationBuilder builder)
}
}
}
+#endif
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesExtensions.cs
index 5b97e90ce7743..0b651f5d94823 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesExtensions.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/src/Vendoring/Shared/EnvironmentVariables/EnvironmentVariablesExtensions.cs
@@ -2,6 +2,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#if NETSTANDARD2_0
#nullable enable
using System;
@@ -50,3 +51,4 @@ public static IConfigurationBuilder AddEnvironmentVariables(this IConfigurationB
=> builder.Add(configureSource);
}
}
+#endif