diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Azure.Data.AppConfiguration.csproj b/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Azure.Data.AppConfiguration.csproj
index e4cd0ebb37552..4fd3ac0f8b739 100644
--- a/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Azure.Data.AppConfiguration.csproj
+++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Azure.Data.AppConfiguration.csproj
@@ -20,6 +20,7 @@
+
diff --git a/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Properties/AssemblyInfo.cs b/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Properties/AssemblyInfo.cs
index a19313a939005..df6399f1e1944 100644
--- a/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Properties/AssemblyInfo.cs
+++ b/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Properties/AssemblyInfo.cs
@@ -7,3 +7,4 @@
// See https://github.com/castleproject/Core/blob/master/src/Castle.Core/Core/Internal/InternalsVisible.cs for values
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
+[assembly: Azure.Core.AzureResourceProviderNamespace("Microsoft.AppConfiguration")]
\ No newline at end of file
diff --git a/sdk/core/Azure.Core/src/Azure.Core.csproj b/sdk/core/Azure.Core/src/Azure.Core.csproj
index f65bb51cc2cc8..a19a45107f58e 100644
--- a/sdk/core/Azure.Core/src/Azure.Core.csproj
+++ b/sdk/core/Azure.Core/src/Azure.Core.csproj
@@ -21,13 +21,13 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs
index 1c8c33c72727e..6ad94a2c06f9f 100644
--- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs
+++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs
@@ -75,7 +75,7 @@ public static HttpPipeline Build(ClientOptions options, HttpPipelinePolicy[] per
policies.Add(BufferResponsePolicy.Shared);
- policies.Add(new RequestActivityPolicy(isDistributedTracingEnabled));
+ policies.Add(new RequestActivityPolicy(isDistributedTracingEnabled, ClientDiagnostics.GetResourceProviderNamespace(options.GetType().Assembly)));
policies.RemoveAll(policy => policy == null);
diff --git a/sdk/core/Azure.Core/src/Pipeline/Internal/RequestActivityPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/Internal/RequestActivityPolicy.cs
index 7dba1acf836f9..309bbd055d1b6 100644
--- a/sdk/core/Azure.Core/src/Pipeline/Internal/RequestActivityPolicy.cs
+++ b/sdk/core/Azure.Core/src/Pipeline/Internal/RequestActivityPolicy.cs
@@ -11,6 +11,7 @@ namespace Azure.Core.Pipeline
internal class RequestActivityPolicy : HttpPipelinePolicy
{
private readonly bool _isDistributedTracingEnabled;
+ private readonly string? _resourceProviderNamespace;
private const string TraceParentHeaderName = "traceparent";
private const string TraceStateHeaderName = "tracestate";
@@ -18,9 +19,10 @@ internal class RequestActivityPolicy : HttpPipelinePolicy
private static readonly DiagnosticListener s_diagnosticSource = new DiagnosticListener("Azure.Core");
- public RequestActivityPolicy(bool isDistributedTracingEnabled)
+ public RequestActivityPolicy(bool isDistributedTracingEnabled, string? resourceProviderNamespace)
{
_isDistributedTracingEnabled = isDistributedTracingEnabled;
+ _resourceProviderNamespace = resourceProviderNamespace;
}
public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory pipeline)
@@ -62,6 +64,11 @@ private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory
+ /// This attribute should be set on all client assemblies with value of one of the resource providers
+ /// from the https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/azure-services-resource-providers list.
+ ///
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
+ internal class AzureResourceProviderNamespaceAttribute : Attribute
+ {
+ public string ResourceProviderNamespace { get; }
+
+ public AzureResourceProviderNamespaceAttribute(string resourceProviderNamespace)
+ {
+ ResourceProviderNamespace = resourceProviderNamespace;
+ }
+ }
+}
\ No newline at end of file
diff --git a/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs b/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs
index a7e629a750bd8..b9310bd0866d6 100644
--- a/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs
+++ b/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System;
using System.Diagnostics;
+using System.Reflection;
#nullable enable
@@ -11,12 +13,14 @@ namespace Azure.Core.Pipeline
internal sealed class ClientDiagnostics
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
{
+ private readonly string? _resourceProviderNamespace;
private readonly DiagnosticListener? _source;
public bool IsActivityEnabled { get; }
- public ClientDiagnostics(string clientNamespace, bool isActivityEnabled)
+ public ClientDiagnostics(string clientNamespace, string? resourceProviderNamespace, bool isActivityEnabled)
{
+ _resourceProviderNamespace = resourceProviderNamespace;
IsActivityEnabled = isActivityEnabled;
if (IsActivityEnabled)
{
@@ -24,7 +28,10 @@ public ClientDiagnostics(string clientNamespace, bool isActivityEnabled)
}
}
- public ClientDiagnostics(ClientOptions options) : this(options.GetType().Namespace, options.Diagnostics.IsDistributedTracingEnabled)
+ public ClientDiagnostics(ClientOptions options) : this(
+ options.GetType().Namespace,
+ GetResourceProviderNamespace(options.GetType().Assembly),
+ options.Diagnostics.IsDistributedTracingEnabled)
{
}
@@ -34,7 +41,28 @@ public DiagnosticScope CreateScope(string name)
{
return default;
}
- return new DiagnosticScope(name, _source);
+ var scope = new DiagnosticScope(name, _source);
+
+ if (_resourceProviderNamespace != null)
+ {
+ scope.AddAttribute("az.namespace", _resourceProviderNamespace);
+ }
+ return scope;
+ }
+
+ internal static string? GetResourceProviderNamespace(Assembly assembly)
+ {
+ foreach (var customAttribute in assembly.GetCustomAttributes(true))
+ {
+ // Weak bind internal shared type
+ var attributeType = customAttribute.GetType();
+ if (attributeType.Name == "AzureResourceProviderNamespaceAttribute")
+ {
+ return attributeType.GetProperty("ResourceProviderNamespace")?.GetValue(customAttribute) as string;
+ }
+ }
+
+ return null;
}
}
}
diff --git a/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj b/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj
index dac25eea6ca1b..394d2ab585884 100644
--- a/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj
+++ b/sdk/core/Azure.Core/tests/Azure.Core.Tests.csproj
@@ -29,6 +29,7 @@
+
diff --git a/sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs b/sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs
index 1f44345c68510..18a7a069bd7ad 100644
--- a/sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs
+++ b/sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs
@@ -5,10 +5,12 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using Azure.Core.Diagnostics;
+using Azure.Core;
using Azure.Core.Pipeline;
using NUnit.Framework;
+[assembly:AzureResourceProviderNamespace("Microsoft.Azure.Core.Cool.Tests")]
+
namespace Azure.Core.Tests
{
public class ClientDiagnosticsTests
@@ -18,7 +20,7 @@ public void CreatesActivityWithNameAndTags()
{
using var testListener = new TestDiagnosticListener("Azure.Clients");
- ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", true);
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", true);
DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName");
@@ -44,13 +46,40 @@ public void CreatesActivityWithNameAndTags()
CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute1", "Value1"));
CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute2", "2"));
CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute3", "3"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("az.namespace", "Microsoft.Azure.Core.Cool.Tests"));
+ }
+
+ [Test]
+ public void ResourceNameIsOptional()
+ {
+
+ using var testListener = new TestDiagnosticListener("Azure.Clients");
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", null, true);
+
+ DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName");
+ scope.Start();
+
+ (string Key, object Value, DiagnosticListener) startEvent = testListener.Events.Dequeue();
+
+ Activity activity = Activity.Current;
+
+ scope.Dispose();
+
+ (string Key, object Value, DiagnosticListener) stopEvent = testListener.Events.Dequeue();
+
+ Assert.Null(Activity.Current);
+ Assert.AreEqual("ActivityName.Start", startEvent.Key);
+ Assert.AreEqual("ActivityName.Stop", stopEvent.Key);
+
+ Assert.AreEqual(ActivityIdFormat.W3C, activity.IdFormat);
+ CollectionAssert.IsEmpty(activity.Tags);
}
[Test]
public void AddLinkCallsPassesLinksAsPartOfStartPayload()
{
using var testListener = new TestDiagnosticListener("Azure.Clients");
- ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", true);
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests",true);
DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName");
@@ -91,7 +120,7 @@ public void AddLinkCallsPassesLinksAsPartOfStartPayload()
public void FailedStopsActivityAndWritesExceptionEvent()
{
using var testListener = new TestDiagnosticListener("Azure.Clients");
- ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", true);
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", true);
DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName");
@@ -120,12 +149,13 @@ public void FailedStopsActivityAndWritesExceptionEvent()
CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute1", "Value1"));
CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute2", "2"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("az.namespace", "Microsoft.Azure.Core.Cool.Tests"));
}
[Test]
public void NoopsWhenDisabled()
{
- ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", false);
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Clients", "Microsoft.Azure.Core.Cool.Tests", false);
DiagnosticScope scope = clientDiagnostics.CreateScope("");
scope.AddAttribute("Attribute1", "Value1");
@@ -133,5 +163,11 @@ public void NoopsWhenDisabled()
scope.Failed(new Exception());
scope.Dispose();
}
+
+ [Test]
+ public void GetResourceProviderNamespaceReturnsAttributeValue()
+ {
+ Assert.AreEqual("Microsoft.Azure.Core.Cool.Tests", ClientDiagnostics.GetResourceProviderNamespace(GetType().Assembly));
+ }
}
}
diff --git a/sdk/core/Azure.Core/tests/ClientTestBaseDiagnosticScopeTests.cs b/sdk/core/Azure.Core/tests/ClientTestBaseDiagnosticScopeTests.cs
index 68a9dd107425d..3d94e73936f09 100644
--- a/sdk/core/Azure.Core/tests/ClientTestBaseDiagnosticScopeTests.cs
+++ b/sdk/core/Azure.Core/tests/ClientTestBaseDiagnosticScopeTests.cs
@@ -23,7 +23,7 @@ public void ThrowsWhenNoDiagnosticScope()
{
InvalidDiagnosticScopeTestClient client = InstrumentClient(new InvalidDiagnosticScopeTestClient());
InvalidOperationException ex = Assert.ThrowsAsync(async () => await client.NoScopeAsync());
- StringAssert.Contains("Expected some diagnostic event to fire", ex.Message);
+ StringAssert.Contains("Expected some diagnostic scopes to be created, found none", ex.Message);
}
[Test]
@@ -57,7 +57,7 @@ public class InvalidDiagnosticScopeTestClient
{
private void FireScope(string method)
{
- ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Core.Tests", true);
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics("Azure.Core.Tests", "random", true);
string activityName = $"{typeof(InvalidDiagnosticScopeTestClient).Name}.{method}";
DiagnosticScope scope = clientDiagnostics.CreateScope(activityName);
scope.Start();
diff --git a/sdk/core/Azure.Core/tests/RequestActivityPolicyTests.cs b/sdk/core/Azure.Core/tests/RequestActivityPolicyTests.cs
index 0fca228090bee..92cc73058cdc5 100644
--- a/sdk/core/Azure.Core/tests/RequestActivityPolicyTests.cs
+++ b/sdk/core/Azure.Core/tests/RequestActivityPolicyTests.cs
@@ -17,7 +17,7 @@ public RequestActivityPolicyTests(bool isAsync) : base(isAsync)
{
}
- private static readonly RequestActivityPolicy s_enabledPolicy = new RequestActivityPolicy(true);
+ private static readonly RequestActivityPolicy s_enabledPolicy = new RequestActivityPolicy(true, "Microsoft.Azure.Core.Cool.Tests");
[Test]
[NonParallelizable]
@@ -61,9 +61,42 @@ public async Task ActivityIsCreatedForRequest()
CollectionAssert.Contains(activity.Tags, new KeyValuePair("requestId", clientRequestId));
CollectionAssert.Contains(activity.Tags, new KeyValuePair("serviceRequestId", "server request id"));
CollectionAssert.Contains(activity.Tags, new KeyValuePair("kind", "client"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("az.namespace", "Microsoft.Azure.Core.Cool.Tests"));
}
+ [Test]
+ [NonParallelizable]
+ public void ActivityShouldBeStoppedWhenTransportThrows()
+ {
+ Activity activity = null;
+ (string Key, object Value, DiagnosticListener) startEvent = default;
+ using var testListener = new TestDiagnosticListener("Azure.Core");
+
+ MockTransport mockTransport = CreateMockTransport(_ =>
+ {
+ activity = Activity.Current;
+ startEvent = testListener.Events.Dequeue();
+ throw new Exception();
+ });
+
+ string clientRequestId = null;
+ Assert.ThrowsAsync(async () => await SendRequestAsync(mockTransport, request =>
+ {
+ request.Method = RequestMethod.Get;
+ request.Uri.Reset(new Uri("http://example.com"));
+ request.Headers.Add("User-Agent", "agent");
+ clientRequestId = request.ClientRequestId;
+ }, s_enabledPolicy));
+
+ (string Key, object Value, DiagnosticListener) stopEvent = testListener.Events.Dequeue();
+
+ Assert.AreEqual("Azure.Core.Http.Request.Start", startEvent.Key);
+ Assert.AreEqual("Azure.Core.Http.Request.Stop", stopEvent.Key);
+
+ Assert.AreEqual("Azure.Core.Http.Request", activity.OperationName);
+ }
+
[Test]
[NonParallelizable]
public async Task ActivityIdIsStampedOnRequest()
@@ -180,7 +213,7 @@ public async Task ActivityIsNotCreatedWhenDisabled()
var transport = new MockTransport(new MockResponse(200));
- await SendGetRequest(transport, new RequestActivityPolicy(isDistributedTracingEnabled: false));
+ await SendGetRequest(transport, new RequestActivityPolicy(isDistributedTracingEnabled: false, "Microsoft.Azure.Core.Cool.Tests"));
Assert.AreEqual(0, testListener.Events.Count);
}
diff --git a/sdk/core/Azure.Core/tests/TestFramework/ClientDiagnosticListener.cs b/sdk/core/Azure.Core/tests/TestFramework/ClientDiagnosticListener.cs
index b0b78e1ad0665..be93836cc6cd2 100644
--- a/sdk/core/Azure.Core/tests/TestFramework/ClientDiagnosticListener.cs
+++ b/sdk/core/Azure.Core/tests/TestFramework/ClientDiagnosticListener.cs
@@ -11,7 +11,7 @@ namespace Azure.Core.Tests
{
public class ClientDiagnosticListener : IObserver>, IObserver, IDisposable
{
- private readonly string _diagnosticSourceName;
+ private readonly Func _sourceNameFilter;
private List _subscriptions = new List();
@@ -19,7 +19,13 @@ public class ClientDiagnosticListener : IObserver>,
public ClientDiagnosticListener(string name)
{
- _diagnosticSourceName = name;
+ _sourceNameFilter = n => n == name;
+ DiagnosticListener.AllListeners.Subscribe(this);
+ }
+
+ public ClientDiagnosticListener(Func filter)
+ {
+ _sourceNameFilter = filter;
DiagnosticListener.AllListeners.Subscribe(this);
}
@@ -51,6 +57,7 @@ public void OnNext(KeyValuePair value)
Activity = Activity.Current,
Links = links.Select(a => a.ParentId).ToList()
};
+
Scopes.Add(scope);
}
else if (value.Key.EndsWith(stopSuffix))
@@ -71,14 +78,14 @@ public void OnNext(KeyValuePair value)
var name = value.Key.Substring(0, value.Key.Length - exceptionSuffix.Length);
foreach (ProducedDiagnosticScope producedDiagnosticScope in Scopes)
{
- if (producedDiagnosticScope.IsCompleted)
+ if (producedDiagnosticScope.Activity.Id == Activity.Current.Id)
{
- throw new InvalidOperationException("Scope should not be stopped when calling Failed");
- }
+ if (producedDiagnosticScope.IsCompleted)
+ {
+ throw new InvalidOperationException("Scope should not be stopped when calling Failed");
+ }
- if (producedDiagnosticScope.Name == name)
- {
- producedDiagnosticScope.Exception = (Exception)value.Value;
+ producedDiagnosticScope.Exception = (Exception) value.Value;
}
}
}
@@ -88,7 +95,7 @@ public void OnNext(KeyValuePair value)
public void OnNext(DiagnosticListener value)
{
List subscriptions = _subscriptions;
- if (value.Name == _diagnosticSourceName && subscriptions != null)
+ if (_sourceNameFilter(value.Name) && subscriptions != null)
{
lock (subscriptions)
{
@@ -99,6 +106,11 @@ public void OnNext(DiagnosticListener value)
public void Dispose()
{
+ if (_subscriptions == null)
+ {
+ return;
+ }
+
List subscriptions;
lock (_subscriptions)
{
@@ -115,7 +127,13 @@ public void Dispose()
{
if (!producedDiagnosticScope.IsCompleted)
{
- throw new InvalidOperationException($"'{producedDiagnosticScope.Name}' is not completed");
+ // https://github.com/Azure/azure-sdk-for-net/issues/9656
+ // A known issue with Azure.Core that is fixed but not all libraries are on latest Azure.Core yet
+ if (producedDiagnosticScope.Name == "Azure.Core.Http.Request")
+ {
+ continue;
+ }
+ throw new InvalidOperationException($"'{producedDiagnosticScope.Name}' scope is not completed");
}
}
}
@@ -173,8 +191,14 @@ public class ProducedDiagnosticScope
public string Name { get; set; }
public Activity Activity { get; set; }
public bool IsCompleted { get; set; }
+ public bool IsFailed => Exception != null;
public Exception Exception { get; set; }
public List Links { get; set; } = new List();
+
+ public override string ToString()
+ {
+ return Name;
+ }
}
}
}
diff --git a/sdk/core/Azure.Core/tests/TestFramework/DiagnosticScopeValidatingInterceptor.cs b/sdk/core/Azure.Core/tests/TestFramework/DiagnosticScopeValidatingInterceptor.cs
index f8a04bfc8ad15..0c3eeb8636aa6 100644
--- a/sdk/core/Azure.Core/tests/TestFramework/DiagnosticScopeValidatingInterceptor.cs
+++ b/sdk/core/Azure.Core/tests/TestFramework/DiagnosticScopeValidatingInterceptor.cs
@@ -21,15 +21,13 @@ public void Intercept(IInvocation invocation)
{
Type declaringType = invocation.Method.DeclaringType;
var ns = declaringType.Namespace;
- var expectedEventPrefix = declaringType.Name + "." + methodName.Substring(0, methodName.Length - 5);
- var expectedEvents = new List
- {
- expectedEventPrefix + ".Start"
- };
-
- using TestDiagnosticListener diagnosticListener = new TestDiagnosticListener(s => s.Name.StartsWith("Azure."));
+ var expectedName = declaringType.Name + "." + methodName.Substring(0, methodName.Length - 5);
+ using ClientDiagnosticListener diagnosticListener = new ClientDiagnosticListener(s => s.StartsWith("Azure."));
invocation.Proceed();
+ bool expectFailure = false;
+ bool skipChecks = false;
+
bool strict = !invocation.Method.GetCustomAttributes(true).Any(a => a.GetType().FullName == "Azure.Core.ForwardsClientCallsAttribute");
if (invocation.Method.ReturnType.Name.Contains("Pageable") ||
invocation.Method.ReturnType.Name.Contains("IAsyncEnumerable"))
@@ -54,48 +52,49 @@ public void Intercept(IInvocation invocation)
getResultMethod.Invoke(
getAwaiterMethod.Invoke(returnValue, Array.Empty