diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props
index bbe48c3f04a9e..2d56ef86f70f9 100755
--- a/eng/Packages.Data.props
+++ b/eng/Packages.Data.props
@@ -31,7 +31,6 @@
-
@@ -81,7 +80,6 @@
-
@@ -95,7 +93,6 @@
-
@@ -103,4 +100,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs
index c67b0c793eeda..2b101ca868623 100644
--- a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs
+++ b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs
@@ -10,7 +10,6 @@
using Azure.Core;
using Azure.Core.Http;
using Azure.Core.Pipeline;
-using Azure.Core.Pipeline.Policies;
namespace Azure.ApplicationModel.Configuration
{
@@ -67,7 +66,8 @@ public ConfigurationClient(string connectionString, ConfigurationClientOptions o
/// A controlling the request lifetime.
public virtual async Task> AddAsync(string key, string value, string label = default, CancellationToken cancellationToken = default)
{
- if (string.IsNullOrEmpty(key)) throw new ArgumentNullException($"{nameof(key)}");
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException($"{nameof(key)}");
return await AddAsync(new ConfigurationSetting(key, value, label), cancellationToken).ConfigureAwait(false);
}
@@ -80,7 +80,8 @@ public virtual async Task> AddAsync(string key, s
/// A controlling the request lifetime.
public virtual Response Add(string key, string value, string label = default, CancellationToken cancellationToken = default)
{
- if (string.IsNullOrEmpty(key)) throw new ArgumentNullException($"{nameof(key)}");
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException($"{nameof(key)}");
return Add(new ConfigurationSetting(key, value, label), cancellationToken);
}
@@ -91,8 +92,12 @@ public virtual Response Add(string key, string value, stri
/// A controlling the request lifetime.
public virtual async Task> AddAsync(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
- using (Request request = CreateAddRequest(setting))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Add");
+ scope.Start();
+
+ try
{
+ using Request request = CreateAddRequest(setting);
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
switch (response.Status)
@@ -104,6 +109,11 @@ public virtual async Task> AddAsync(Configuration
throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false);
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
///
@@ -113,8 +123,13 @@ public virtual async Task> AddAsync(Configuration
/// A controlling the request lifetime.
public virtual Response Add(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
- using (Request request = CreateAddRequest(setting))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Add");
+ scope.AddAttribute("key", setting?.Key);
+ scope.Start();
+
+ try
{
+ using Request request = CreateAddRequest(setting);
Response response = _pipeline.SendRequest(request, cancellationToken);
switch (response.Status)
@@ -126,6 +141,11 @@ public virtual Response Add(ConfigurationSetting setting,
throw response.CreateRequestFailedException();
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
private Request CreateAddRequest(ConfigurationSetting setting)
@@ -160,7 +180,8 @@ private Request CreateAddRequest(ConfigurationSetting setting)
/// A controlling the request lifetime.
public virtual async Task> SetAsync(string key, string value, string label = default, CancellationToken cancellationToken = default)
{
- if (string.IsNullOrEmpty(key)) throw new ArgumentNullException($"{nameof(key)}");
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException($"{nameof(key)}");
return await SetAsync(new ConfigurationSetting(key, value, label), cancellationToken).ConfigureAwait(false);
}
@@ -173,7 +194,8 @@ public virtual async Task> SetAsync(string key, s
/// A controlling the request lifetime.
public virtual Response Set(string key, string value, string label = default, CancellationToken cancellationToken = default)
{
- if (string.IsNullOrEmpty(key)) throw new ArgumentNullException($"{nameof(key)}");
+ if (string.IsNullOrEmpty(key))
+ throw new ArgumentNullException($"{nameof(key)}");
return Set(new ConfigurationSetting(key, value, label), cancellationToken);
}
@@ -184,8 +206,13 @@ public virtual Response Set(string key, string value, stri
/// A controlling the request lifetime.
public virtual async Task> SetAsync(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
- using (Request request = CreateSetRequest(setting))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
+ scope.AddAttribute("key", setting?.Key);
+ scope.Start();
+
+ try
{
+ using Request request = CreateSetRequest(setting);
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
switch (response.Status)
@@ -198,6 +225,11 @@ public virtual async Task> SetAsync(Configuration
throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false);
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
///
@@ -207,9 +239,15 @@ public virtual async Task> SetAsync(Configuration
/// A controlling the request lifetime.
public virtual Response Set(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
- using (Request request = CreateSetRequest(setting))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
+ scope.AddAttribute("key", setting?.Key);
+ scope.Start();
+
+ try
{
- var response = _pipeline.SendRequest(request, cancellationToken);
+ using Request request = CreateSetRequest(setting);
+
+ Response response = _pipeline.SendRequest(request, cancellationToken);
switch (response.Status)
{
@@ -221,6 +259,11 @@ public virtual Response Set(ConfigurationSetting setting,
throw response.CreateRequestFailedException();
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
private Request CreateSetRequest(ConfigurationSetting setting)
@@ -283,8 +326,13 @@ public virtual Response Update(string key, string value, s
/// A controlling the request lifetime.
public virtual async Task> UpdateAsync(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
- using (Request request = CreateUpdateRequest(setting))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Update");
+ scope.AddAttribute("key", setting?.Key);
+ scope.Start();
+
+ try
{
+ using Request request = CreateUpdateRequest(setting);
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
switch (response.Status)
@@ -295,6 +343,11 @@ public virtual async Task> UpdateAsync(Configurat
throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false);
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
///
@@ -304,8 +357,13 @@ public virtual async Task> UpdateAsync(Configurat
/// A controlling the request lifetime.
public virtual Response Update(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
- using (Request request = CreateUpdateRequest(setting))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Update");
+ scope.AddAttribute("key", setting?.Key);
+ scope.Start();
+
+ try
{
+ using Request request = CreateUpdateRequest(setting);
Response response = _pipeline.SendRequest(request, cancellationToken);
switch (response.Status)
@@ -316,6 +374,11 @@ public virtual Response Update(ConfigurationSetting settin
throw response.CreateRequestFailedException();
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
private Request CreateUpdateRequest(ConfigurationSetting setting)
@@ -357,8 +420,13 @@ private Request CreateUpdateRequest(ConfigurationSetting setting)
/// A controlling the request lifetime.
public virtual async Task DeleteAsync(string key, string label = default, ETag etag = default, CancellationToken cancellationToken = default)
{
- using (Request request = CreateDeleteRequest(key, label, etag))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Delete");
+ scope.AddAttribute("key", key);
+ scope.Start();
+
+ try
{
+ using Request request = CreateDeleteRequest(key, label, etag);
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
switch (response.Status)
@@ -370,6 +438,11 @@ public virtual async Task DeleteAsync(string key, string label = defau
throw response.CreateRequestFailedException();
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
///
@@ -383,8 +456,13 @@ public virtual async Task DeleteAsync(string key, string label = defau
/// A controlling the request lifetime.
public virtual Response Delete(string key, string label = default, ETag etag = default, CancellationToken cancellationToken = default)
{
- using (Request request = CreateDeleteRequest(key, label, etag))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
+ scope.AddAttribute("key", key);
+ scope.Start();
+
+ try
{
+ using Request request = CreateDeleteRequest(key, label, etag);
Response response = _pipeline.SendRequest(request, cancellationToken);
switch (response.Status)
@@ -396,6 +474,11 @@ public virtual Response Delete(string key, string label = default, ETag etag = d
throw response.CreateRequestFailedException();
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
private Request CreateDeleteRequest(string key, string label, ETag etag)
@@ -424,8 +507,13 @@ private Request CreateDeleteRequest(string key, string label, ETag etag)
/// A controlling the request lifetime.
public virtual async Task> GetAsync(string key, string label = default, DateTimeOffset acceptDateTime = default, CancellationToken cancellationToken = default)
{
- using (Request request = CreateGetRequest(key, label, acceptDateTime))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Get");
+ scope.AddAttribute("key", key);
+ scope.Start();
+
+ try
{
+ using Request request = CreateGetRequest(key, label, acceptDateTime);
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
switch (response.Status)
@@ -436,6 +524,11 @@ public virtual async Task> GetAsync(string key, s
throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false);
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
///
@@ -447,6 +540,10 @@ public virtual async Task> GetAsync(string key, s
/// A controlling the request lifetime.
public virtual Response Get(string key, string label = default, DateTimeOffset acceptDateTime = default, CancellationToken cancellationToken = default)
{
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Get");
+ scope.AddAttribute(nameof(key), key);
+ scope.Start();
+
using (Request request = CreateGetRequest(key, label, acceptDateTime))
{
Response response = _pipeline.SendRequest(request, cancellationToken);
@@ -529,8 +626,12 @@ private Request CreateGetRequest(string key, string label, DateTimeOffset accept
/// A controlling the request lifetime.
private async Task> GetSettingsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
- using (Request request = CreateBatchRequest(selector, pageLink))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.GetSettingsPage");
+ scope.Start();
+
+ try
{
+ using Request request = CreateBatchRequest(selector, pageLink);
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
switch (response.Status)
@@ -543,6 +644,11 @@ private async Task> GetSettingsPageAsync(Sett
throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false);
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
///
@@ -553,8 +659,12 @@ private async Task> GetSettingsPageAsync(Sett
/// A controlling the request lifetime.
private PageResponse GetSettingsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
- using (Request request = CreateBatchRequest(selector, pageLink))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.GetSettingsPage");
+ scope.Start();
+
+ try
{
+ using Request request = CreateBatchRequest(selector, pageLink);
Response response = _pipeline.SendRequest(request, cancellationToken);
switch (response.Status)
@@ -567,6 +677,11 @@ private PageResponse GetSettingsPage(SettingSelector selec
throw response.CreateRequestFailedException();
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
private Request CreateBatchRequest(SettingSelector selector, string pageLink)
@@ -593,8 +708,12 @@ private Request CreateBatchRequest(SettingSelector selector, string pageLink)
/// A controlling the request lifetime.
private async Task> GetRevisionsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
- using (Request request = CreateGetRevisionsRequest(selector, pageLink))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
+ scope.Start();
+
+ try
{
+ using Request request = CreateGetRevisionsRequest(selector, pageLink);
Response response = await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
switch (response.Status)
{
@@ -606,6 +725,11 @@ private async Task> GetRevisionsPageAsync(Set
throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false);
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
///
@@ -617,8 +741,12 @@ private async Task> GetRevisionsPageAsync(Set
/// A controlling the request lifetime.
private PageResponse GetRevisionsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
- using (Request request = CreateGetRevisionsRequest(selector, pageLink))
+ using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
+ scope.Start();
+
+ try
{
+ using Request request = CreateGetRevisionsRequest(selector, pageLink);
Response response = _pipeline.SendRequest(request, cancellationToken);
switch (response.Status)
{
@@ -630,6 +758,11 @@ private PageResponse GetRevisionsPage(SettingSelector sele
throw response.CreateRequestFailedException();
}
}
+ catch (Exception e)
+ {
+ scope.Failed(e);
+ throw;
+ }
}
private Request CreateGetRevisionsRequest(SettingSelector selector, string pageLink)
diff --git a/sdk/core/Azure.Core/src/Azure.Core.csproj b/sdk/core/Azure.Core/src/Azure.Core.csproj
index e2f7afc740e2d..395497e9db038 100644
--- a/sdk/core/Azure.Core/src/Azure.Core.csproj
+++ b/sdk/core/Azure.Core/src/Azure.Core.csproj
@@ -20,6 +20,7 @@
+
diff --git a/sdk/core/Azure.Core/src/Pipeline/AzureOperationScope.cs b/sdk/core/Azure.Core/src/Pipeline/AzureOperationScope.cs
new file mode 100644
index 0000000000000..bf6c61b3d1b4f
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Pipeline/AzureOperationScope.cs
@@ -0,0 +1,86 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+
+namespace Azure.Core.Pipeline
+{
+ public struct DiagnosticScope: IDisposable
+ {
+ private Activity _activity;
+
+ private readonly string _name;
+
+ private readonly DiagnosticListener _source;
+
+ internal DiagnosticScope(string name, DiagnosticListener source)
+ {
+ _name = name;
+ _source = source;
+ _activity = _source.IsEnabled() ? new Activity(_name) : null;
+ }
+
+ public bool IsEnabled => _activity != null;
+
+ public void AddAttribute(string name, string value)
+ {
+ _activity?.AddTag(name, value);
+ }
+
+ public void AddAttribute(string name, T value)
+ {
+ if (_activity != null)
+ {
+ AddAttribute(name, value.ToString());
+ }
+ }
+
+ public void AddAttribute(string name, T value, Func format)
+ {
+ if (_activity != null)
+ {
+ AddAttribute(name, format(value));
+ }
+ }
+
+ public void Start()
+ {
+ if (_activity != null && _source.IsEnabled(_name))
+ {
+ _source.StartActivity(_activity, null);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_activity == null)
+ {
+ return;
+ }
+
+ if (_source != null)
+ {
+ _source.StopActivity(_activity, null);
+ }
+ else
+ {
+ _activity?.Stop();
+ }
+ }
+
+ public void Failed(Exception e)
+ {
+ if (_activity == null)
+ {
+ return;
+ }
+
+ _source?.Write(_activity.OperationName + ".Exception", e);
+ _activity?.Stop();
+
+ _activity = null;
+
+ }
+ }
+}
diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs
index 6e93a3fed3dd5..5e3433aadcbfc 100644
--- a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs
+++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs
@@ -15,11 +15,13 @@ public class HttpPipeline
private readonly ResponseClassifier _responseClassifier;
private readonly ReadOnlyMemory _pipeline;
- public HttpPipeline(HttpPipelineTransport transport, HttpPipelinePolicy[] policies = null, ResponseClassifier responseClassifier = null)
+ public HttpPipeline(HttpPipelineTransport transport, HttpPipelinePolicy[] policies = null, ResponseClassifier responseClassifier = null, ClientDiagnostics clientDiagnostics = null)
{
_transport = transport ?? throw new ArgumentNullException(nameof(transport));
_responseClassifier = responseClassifier ?? new ResponseClassifier();
+ Diagnostics = clientDiagnostics ?? new ClientDiagnostics(true);
+
policies = policies ?? Array.Empty();
var all = new HttpPipelinePolicy[policies.Length + 1];
@@ -32,6 +34,8 @@ public HttpPipeline(HttpPipelineTransport transport, HttpPipelinePolicy[] polici
public Request CreateRequest()
=> _transport.CreateRequest();
+ public ClientDiagnostics Diagnostics { get; }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public async Task SendRequestAsync(Request request, CancellationToken cancellationToken)
{
diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs
index e4f134f31f5f7..54e13b95d0cfd 100644
--- a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs
+++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs
@@ -40,9 +40,11 @@ public static HttpPipeline Build(ClientOptions options, bool bufferResponse = tr
policies.Add(BufferResponsePolicy.Shared);
}
+ policies.Add(new RequestActivityPolicy());
+
policies.RemoveAll(policy => policy == null);
- return new HttpPipeline(options.Transport, policies.ToArray(), options.ResponseClassifier);
+ return new HttpPipeline(options.Transport, policies.ToArray(), options.ResponseClassifier, new ClientDiagnostics(options.Diagnostics.IsLoggingEnabled));
}
// internal for testing
diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipelineDiagnostics.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineDiagnostics.cs
new file mode 100644
index 0000000000000..a3370a3eba238
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipelineDiagnostics.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Diagnostics;
+
+namespace Azure.Core.Pipeline
+{
+ public sealed class ClientDiagnostics
+ {
+ private readonly bool _isActivityEnabled;
+
+ public ClientDiagnostics(bool isActivityEnabled)
+ {
+ _isActivityEnabled = isActivityEnabled;
+ }
+
+ private static readonly DiagnosticListener s_source = new DiagnosticListener("Azure.Clients");
+
+ public DiagnosticScope CreateScope(string name)
+ {
+ if (!_isActivityEnabled)
+ {
+ return default;
+ }
+ return new DiagnosticScope(name, s_source);
+ }
+ }
+}
diff --git a/sdk/core/Azure.Core/src/Pipeline/Policies/RequestActivityPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/Policies/RequestActivityPolicy.cs
new file mode 100644
index 0000000000000..958483f2e9067
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Pipeline/Policies/RequestActivityPolicy.cs
@@ -0,0 +1,117 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+using System.Threading.Tasks;
+
+namespace Azure.Core.Pipeline.Policies
+{
+ internal class RequestActivityPolicy : HttpPipelinePolicy
+ {
+ private const string TraceParentHeaderName = "traceparent";
+ private const string TraceStateHeaderName = "tracestate";
+ private const string RequestIdHeaderName = "Request-Id";
+
+ public static RequestActivityPolicy Shared { get; } = new RequestActivityPolicy();
+
+ private static readonly DiagnosticListener s_diagnosticSource = new DiagnosticListener("Azure.Pipeline");
+
+ public override Task ProcessAsync(HttpPipelineMessage message, ReadOnlyMemory pipeline)
+ {
+ return ProcessAsync(message, pipeline, true);
+ }
+
+ public override void Process(HttpPipelineMessage message, ReadOnlyMemory pipeline)
+ {
+ ProcessAsync(message, pipeline, false).EnsureCompleted();
+ }
+
+ private static async Task ProcessAsync(HttpPipelineMessage message, ReadOnlyMemory pipeline, bool isAsync)
+ {
+ if (!s_diagnosticSource.IsEnabled())
+ {
+ await ProcessNextAsync(message, pipeline, isAsync).ConfigureAwait(false);
+
+ return;
+ }
+
+ var activity = new Activity("Azure.Core.Http.Request");
+ activity.AddTag("http.method", message.Request.Method.Method);
+ activity.AddTag("http.url", message.Request.UriBuilder.ToString());
+ if (message.Request.Headers.TryGetValue("User-Agent", out string userAgent))
+ {
+ activity.AddTag("http.user_agent", userAgent);
+ }
+
+ var diagnosticSourceActivityEnabled = s_diagnosticSource.IsEnabled(activity.OperationName, message);
+
+ if (diagnosticSourceActivityEnabled)
+ {
+ s_diagnosticSource.StartActivity(activity, message);
+ }
+ else
+ {
+ activity.Start();
+ }
+
+
+ if (isAsync)
+ {
+ await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
+ }
+ else
+ {
+ ProcessNext(message, pipeline);
+ }
+
+ activity.AddTag("http.status_code", message.Response.Status.ToString(CultureInfo.InvariantCulture));
+
+ if (diagnosticSourceActivityEnabled)
+ {
+ s_diagnosticSource.StopActivity(activity, message);
+ }
+ else
+ {
+ activity.Stop();
+ }
+ }
+
+ private static async Task ProcessNextAsync(HttpPipelineMessage message, ReadOnlyMemory pipeline, bool isAsync)
+ {
+ var currentActivity = Activity.Current;
+
+ if (currentActivity != null)
+ {
+ if (currentActivity.IdFormat == ActivityIdFormat.W3C)
+ {
+ if (!message.Request.Headers.Contains(TraceParentHeaderName))
+ {
+ message.Request.Headers.Add(TraceParentHeaderName, currentActivity.Id);
+ if (currentActivity.TraceStateString != null)
+ {
+ message.Request.Headers.Add(TraceStateHeaderName, currentActivity.TraceStateString);
+ }
+ }
+ }
+ else
+ {
+ if (!message.Request.Headers.Contains(RequestIdHeaderName))
+ {
+ message.Request.Headers.Add(RequestIdHeaderName, currentActivity.Id);
+ }
+ }
+ }
+
+ if (isAsync)
+ {
+ await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
+ }
+ else
+ {
+ ProcessNext(message, pipeline);
+ }
+ }
+ }
+}
diff --git a/sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs b/sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs
new file mode 100644
index 0000000000000..2bfeb7a8828b8
--- /dev/null
+++ b/sdk/core/Azure.Core/tests/ClientDiagnosticsTests.cs
@@ -0,0 +1,91 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Azure.Core.Pipeline;
+using NUnit.Framework;
+
+namespace Azure.Core.Tests
+{
+ public class ClientDiagnosticsTests
+ {
+ [Test]
+ public void CreatesActivityWithNameAndTags()
+ {
+
+ using var testListener = new TestDiagnosticListener("Azure.Clients");
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics(true);
+
+ DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName");
+
+ scope.AddAttribute("Attribute1", "Value1");
+ scope.AddAttribute("Attribute2", 2, i => i.ToString());
+ scope.AddAttribute("Attribute3", 3);
+
+ scope.Start();
+
+ KeyValuePair startEvent = testListener.Events.Dequeue();
+
+ Activity activity = Activity.Current;
+
+ scope.Dispose();
+
+ KeyValuePair stopEvent = testListener.Events.Dequeue();
+
+ Assert.Null(Activity.Current);
+ Assert.AreEqual("ActivityName.Start", startEvent.Key);
+ Assert.AreEqual("ActivityName.Stop", stopEvent.Key);
+
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute1", "Value1"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute2", "2"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute3", "3"));
+ }
+
+ [Test]
+ public void FailedStopsActivityAndWritesExceptionEvent()
+ {
+ using var testListener = new TestDiagnosticListener("Azure.Clients");
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics(true);
+
+ DiagnosticScope scope = clientDiagnostics.CreateScope("ActivityName");
+
+ scope.AddAttribute("Attribute1", "Value1");
+ scope.AddAttribute("Attribute2", 2, i => i.ToString());
+
+ scope.Start();
+
+ KeyValuePair startEvent = testListener.Events.Dequeue();
+
+ Activity activity = Activity.Current;
+
+ var exception = new Exception();
+ scope.Failed(exception);
+ scope.Dispose();
+
+ KeyValuePair stopEvent = testListener.Events.Dequeue();
+
+ Assert.Null(Activity.Current);
+ Assert.AreEqual("ActivityName.Start", startEvent.Key);
+ Assert.AreEqual("ActivityName.Exception", stopEvent.Key);
+ Assert.AreEqual(exception, stopEvent.Value);
+ Assert.AreEqual(0, testListener.Events.Count);
+
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute1", "Value1"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("Attribute2", "2"));
+ }
+
+ [Test]
+ public void NoopsWhenDisabled()
+ {
+ ClientDiagnostics clientDiagnostics = new ClientDiagnostics(false);
+ DiagnosticScope scope = clientDiagnostics.CreateScope("");
+
+ scope.AddAttribute("Attribute1", "Value1");
+ scope.AddAttribute("Attribute2", 2, i => i.ToString());
+ scope.Failed(new Exception());
+ scope.Dispose();
+ }
+ }
+}
diff --git a/sdk/core/Azure.Core/tests/RequestActivityPolicyTests.cs b/sdk/core/Azure.Core/tests/RequestActivityPolicyTests.cs
new file mode 100644
index 0000000000000..7de97844d65d3
--- /dev/null
+++ b/sdk/core/Azure.Core/tests/RequestActivityPolicyTests.cs
@@ -0,0 +1,133 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Azure.Core.Http;
+using Azure.Core.Pipeline;
+using Azure.Core.Pipeline.Policies;
+using Azure.Core.Testing;
+using NUnit.Framework;
+
+namespace Azure.Core.Tests
+{
+ public class RequestActivityPolicyTests : SyncAsyncPolicyTestBase
+ {
+ public RequestActivityPolicyTests(bool isAsync) : base(isAsync)
+ {
+ }
+
+ [Test]
+ [NonParallelizable]
+ public async Task ActivityIsCreatedForRequest()
+ {
+ Activity activity = null;
+ KeyValuePair startEvent = default;
+ using var testListener = new TestDiagnosticListener("Azure.Pipeline");
+
+ MockTransport mockTransport = CreateMockTransport(_ =>
+ {
+ activity = Activity.Current;
+ startEvent = testListener.Events.Dequeue();
+ return new MockResponse(201);
+ });
+
+ using Request request = mockTransport.CreateRequest();
+ request.Method = RequestMethod.Get;
+ request.UriBuilder.Uri = new Uri("http://example.com");
+ request.Headers.Add("User-Agent", "agent");
+
+ Task requestTask = SendRequestAsync(mockTransport, request, RequestActivityPolicy.Shared);
+
+ await requestTask;
+
+ KeyValuePair 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);
+
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("http.status_code", "201"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("http.url", "http://example.com/"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("http.method", "GET"));
+ CollectionAssert.Contains(activity.Tags, new KeyValuePair("http.user_agent", "agent"));
+ }
+
+ [Test]
+ [NonParallelizable]
+ public async Task CurrentActivityIsInjectedIntoRequest()
+ {
+ var transport = new MockTransport(new MockResponse(200));
+
+ var activity = new Activity("Dummy");
+
+ activity.Start();
+
+ await SendGetRequest(transport, RequestActivityPolicy.Shared);
+
+ activity.Stop();
+
+ Assert.True(transport.SingleRequest.TryGetHeader("Request-Id", out string requestId));
+ Assert.AreEqual(activity.Id, requestId);
+ }
+
+ [Test]
+ [NonParallelizable]
+ public async Task CurrentActivityIsInjectedIntoRequestW3C()
+ {
+ var previousFormat = Activity.DefaultIdFormat;
+ Activity.DefaultIdFormat = ActivityIdFormat.W3C;
+ try
+ {
+ var transport = new MockTransport(new MockResponse(200));
+
+ var activity = new Activity("Dummy");
+
+ activity.Start();
+ activity.TraceStateString = "trace";
+
+ await SendGetRequest(transport, RequestActivityPolicy.Shared);
+
+ activity.Stop();
+
+ Assert.True(transport.SingleRequest.TryGetHeader("traceparent", out string requestId));
+ Assert.AreEqual(activity.Id, requestId);
+
+ Assert.True(transport.SingleRequest.TryGetHeader("tracestate", out string traceState));
+ Assert.AreEqual("trace", traceState);
+ }
+ finally
+ {
+ Activity.DefaultIdFormat = previousFormat;
+ }
+ }
+
+ [Test]
+ [NonParallelizable]
+ public async Task PassesMessageIntoIsEnabledStartAndStopEvents()
+ {
+ using var testListener = new TestDiagnosticListener("Azure.Pipeline");
+
+ var transport = new MockTransport(new MockResponse(200));
+
+ await SendGetRequest(transport, RequestActivityPolicy.Shared);
+
+ KeyValuePair startEvent = testListener.Events.Dequeue();
+ KeyValuePair stopEvent = testListener.Events.Dequeue();
+ var isEnabledCall = testListener.IsEnabledCalls.Dequeue();
+
+ Assert.AreEqual("Azure.Core.Http.Request.Start", startEvent.Key);
+ Assert.IsInstanceOf(startEvent.Value);
+
+ Assert.AreEqual("Azure.Core.Http.Request.Stop", stopEvent.Key);
+ Assert.IsInstanceOf(stopEvent.Value);
+
+ Assert.AreEqual("Azure.Core.Http.Request", isEnabledCall.Item1);
+ Assert.IsInstanceOf(isEnabledCall.Item2);
+ }
+
+ }
+}
diff --git a/sdk/core/Azure.Core/tests/TestDiagnosticListener.cs b/sdk/core/Azure.Core/tests/TestDiagnosticListener.cs
new file mode 100644
index 0000000000000..1c42a40913db4
--- /dev/null
+++ b/sdk/core/Azure.Core/tests/TestDiagnosticListener.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Azure.Core.Tests
+{
+ public class TestDiagnosticListener : IObserver, IObserver>, IDisposable
+ {
+ private readonly string _diagnosticSourceName;
+
+ private readonly List _subscriptions = new List();
+
+ public Queue> Events { get; } = new Queue>();
+
+ public Queue<(string, object, object)> IsEnabledCalls { get; } = new Queue<(string, object, object)>();
+
+ public TestDiagnosticListener(string diagnosticSourceName)
+ {
+ _diagnosticSourceName = diagnosticSourceName;
+ DiagnosticListener.AllListeners.Subscribe(this);
+ }
+
+ public void OnCompleted()
+ {
+ }
+
+ public void OnError(Exception error)
+ {
+ }
+
+ public void OnNext(KeyValuePair value)
+ {
+ Events.Enqueue(value);
+ }
+
+ public void OnNext(DiagnosticListener value)
+ {
+ if (value.Name == _diagnosticSourceName)
+ {
+ _subscriptions.Add(value.Subscribe(this, IsEnabled));
+ }
+ }
+
+ private bool IsEnabled(string arg1, object arg2, object arg3)
+ {
+ IsEnabledCalls.Enqueue((arg1, arg2, arg3));
+ return true;
+ }
+
+ public void Dispose()
+ {
+ foreach (IDisposable subscription in _subscriptions)
+ {
+ subscription.Dispose();
+ }
+ }
+ }
+}
diff --git a/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncPolicyTestBase.cs b/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncPolicyTestBase.cs
index ef74ff10fbd20..eb88f7f029170 100644
--- a/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncPolicyTestBase.cs
+++ b/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncPolicyTestBase.cs
@@ -23,17 +23,20 @@ protected Task SendRequestAsync(HttpPipeline pipeline, Request request
return IsAsync ? pipeline.SendRequestAsync(request, cancellationToken) : Task.FromResult(pipeline.SendRequest(request, cancellationToken));
}
- protected async Task SendGetRequest(HttpPipelineTransport transport, HttpPipelinePolicy policy, ResponseClassifier responseClassifier = null)
+ protected async Task SendRequestAsync(HttpPipelineTransport transport, Request request, HttpPipelinePolicy policy, ResponseClassifier responseClassifier = null)
{
await Task.Yield();
- using (Request request = transport.CreateRequest())
- {
- request.Method = RequestMethod.Get;
- request.UriBuilder.Uri = new Uri("http://example.com");
- var pipeline = new HttpPipeline(transport, new [] { policy }, responseClassifier);
- return await SendRequestAsync(pipeline, request, CancellationToken.None);
- }
+ var pipeline = new HttpPipeline(transport, new [] { policy }, responseClassifier);
+ return await SendRequestAsync(pipeline, request, CancellationToken.None);
+ }
+
+ protected async Task SendGetRequest(HttpPipelineTransport transport, HttpPipelinePolicy policy, ResponseClassifier responseClassifier = null)
+ {
+ using Request request = transport.CreateRequest();
+ request.Method = RequestMethod.Get;
+ request.UriBuilder.Uri = new Uri("http://example.com");
+ return await SendRequestAsync(transport, request, policy, responseClassifier);
}
}
}
diff --git a/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncTestBase.cs b/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncTestBase.cs
index 0ac57e84ed013..b52a8db6e8c64 100644
--- a/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncTestBase.cs
+++ b/sdk/core/Azure.Core/tests/TestFramework/SyncAsyncTestBase.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System;
using System.IO;
namespace Azure.Core.Testing
@@ -22,6 +23,14 @@ protected MockTransport CreateMockTransport()
};
}
+ protected MockTransport CreateMockTransport(Func responseFactory)
+ {
+ return new MockTransport(responseFactory)
+ {
+ ExpectSyncPipeline = !IsAsync
+ };
+ }
+
protected MockTransport CreateMockTransport(params MockResponse[] responses)
{
return new MockTransport(responses)
diff --git a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs
index 1bfca87e6cf68..247d523fc842d 100644
--- a/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs
+++ b/sdk/storage/Azure.Storage.Blobs/src/Generated/BlobRestClient.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.