Skip to content

Commit

Permalink
Add tracing instrumentation to storage (Azure#6962)
Browse files Browse the repository at this point in the history
  • Loading branch information
pakrym authored Jul 18, 2019
1 parent 77689ef commit 163e4f7
Show file tree
Hide file tree
Showing 36 changed files with 3,834 additions and 1,878 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public virtual Response<ConfigurationSetting> Add(string key, string value, stri
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual async Task<Response<ConfigurationSetting>> AddAsync(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Add");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Add");
scope.Start();

try
Expand Down Expand Up @@ -122,7 +122,7 @@ public virtual async Task<Response<ConfigurationSetting>> AddAsync(Configuration
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual Response<ConfigurationSetting> Add(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Add");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Add");
scope.AddAttribute("key", setting?.Key);
scope.Start();

Expand Down Expand Up @@ -205,7 +205,7 @@ public virtual Response<ConfigurationSetting> Set(string key, string value, stri
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual async Task<Response<ConfigurationSetting>> SetAsync(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Set");
scope.AddAttribute("key", setting?.Key);
scope.Start();

Expand Down Expand Up @@ -238,7 +238,7 @@ public virtual async Task<Response<ConfigurationSetting>> SetAsync(Configuration
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual Response<ConfigurationSetting> Set(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Set");
scope.AddAttribute("key", setting?.Key);
scope.Start();

Expand Down Expand Up @@ -324,7 +324,7 @@ public virtual Response<ConfigurationSetting> Update(string key, string value, s
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual async Task<Response<ConfigurationSetting>> UpdateAsync(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Update");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Update");
scope.AddAttribute("key", setting?.Key);
scope.Start();

Expand Down Expand Up @@ -355,7 +355,7 @@ public virtual async Task<Response<ConfigurationSetting>> UpdateAsync(Configurat
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual Response<ConfigurationSetting> Update(ConfigurationSetting setting, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Update");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Update");
scope.AddAttribute("key", setting?.Key);
scope.Start();

Expand Down Expand Up @@ -418,7 +418,7 @@ private Request CreateUpdateRequest(ConfigurationSetting setting)
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual async Task<Response> DeleteAsync(string key, string label = default, ETag etag = default, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Delete");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Delete");
scope.AddAttribute("key", key);
scope.Start();

Expand Down Expand Up @@ -454,7 +454,7 @@ public virtual async Task<Response> DeleteAsync(string key, string label = defau
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual Response Delete(string key, string label = default, ETag etag = default, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Delete");
scope.AddAttribute("key", key);
scope.Start();

Expand Down Expand Up @@ -505,7 +505,7 @@ private Request CreateDeleteRequest(string key, string label, ETag etag)
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual async Task<Response<ConfigurationSetting>> GetAsync(string key, string label = default, DateTimeOffset acceptDateTime = default, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Get");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Get");
scope.AddAttribute("key", key);
scope.Start();

Expand Down Expand Up @@ -538,22 +538,30 @@ public virtual async Task<Response<ConfigurationSetting>> GetAsync(string key, s
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
public virtual Response<ConfigurationSetting> Get(string key, string label = default, DateTimeOffset acceptDateTime = default, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Get");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.Get");
scope.AddAttribute(nameof(key), key);
scope.Start();

using (Request request = CreateGetRequest(key, label, acceptDateTime))
try
{
Response response = _pipeline.SendRequest(request, cancellationToken);

switch (response.Status)
using (Request request = CreateGetRequest(key, label, acceptDateTime))
{
case 200:
return CreateResponse(response);
default:
throw response.CreateRequestFailedException();
Response response = _pipeline.SendRequest(request, cancellationToken);

switch (response.Status)
{
case 200:
return CreateResponse(response);
default:
throw response.CreateRequestFailedException();
}
}
}
catch (Exception e)
{
scope.Failed(e);
throw;
}
}

/// <summary>
Expand Down Expand Up @@ -624,7 +632,7 @@ private Request CreateGetRequest(string key, string label, DateTimeOffset accept
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
private async Task<PageResponse<ConfigurationSetting>> GetSettingsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.GetSettingsPage");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetSettingsPage");
scope.Start();

try
Expand Down Expand Up @@ -657,7 +665,7 @@ private async Task<PageResponse<ConfigurationSetting>> GetSettingsPageAsync(Sett
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
private PageResponse<ConfigurationSetting> GetSettingsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.GetSettingsPage");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetSettingsPage");
scope.Start();

try
Expand Down Expand Up @@ -706,7 +714,7 @@ private Request CreateBatchRequest(SettingSelector selector, string pageLink)
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
private async Task<PageResponse<ConfigurationSetting>> GetRevisionsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetRevisionsPage");
scope.Start();

try
Expand Down Expand Up @@ -739,7 +747,7 @@ private async Task<PageResponse<ConfigurationSetting>> GetRevisionsPageAsync(Set
/// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
private PageResponse<ConfigurationSetting> GetRevisionsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("ConfigurationClient.Set");
using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetRevisionsPage");
scope.Start();

try
Expand Down
15 changes: 15 additions & 0 deletions sdk/core/Azure.Core/src/Shared/ForwardsClientCallsAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;

namespace Azure.Core
{
/// <summary>
/// Marks methods that call methods on other client and don't need their diagnostics verified
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
internal class ForwardsClientCallsAttribute : Attribute
{
}
}
1 change: 1 addition & 0 deletions sdk/core/Azure.Core/tests/ClientTestBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class ClientTestBaseTests : ClientTestBase
{
public ClientTestBaseTests(bool isAsync) : base(isAsync)
{
TestDiagnostics = false;
}

[Test]
Expand Down
15 changes: 13 additions & 2 deletions sdk/core/Azure.Core/tests/TestFramework/ClientTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Castle.DynamicProxy;
Expand All @@ -17,9 +18,12 @@ public class ClientTestBase

private static readonly IInterceptor _useSyncInterceptor = new UseSyncMethodsInterceptor(forceSync: true);
private static readonly IInterceptor _avoidSyncInterceptor = new UseSyncMethodsInterceptor(forceSync: false);
private static readonly IInterceptor _diagnosticScopeValidatingInterceptor = new DiagnosticScopeValidatingInterceptor();

public bool IsAsync { get; }

public bool TestDiagnostics { get; set; } = true;

public ClientTestBase(bool isAsync)
{
IsAsync = isAsync;
Expand Down Expand Up @@ -55,9 +59,16 @@ public virtual TClient InstrumentClient<TClient>(TClient client) where TClient:
throw ClientValidation<TClient>.ValidationException;
}

var interceptor = IsAsync ? _avoidSyncInterceptor : _useSyncInterceptor;
List<IInterceptor> interceptors = new List<IInterceptor>();

if (TestDiagnostics)
{
interceptors.Add(_diagnosticScopeValidatingInterceptor);
}

interceptors.Add(IsAsync ? _avoidSyncInterceptor : _useSyncInterceptor);

return _proxyGenerator.CreateClassProxyWithTarget(client, interceptor);
return _proxyGenerator.CreateClassProxyWithTarget(client, interceptors.ToArray());
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Azure.Core.Tests;
using Castle.DynamicProxy;

namespace Azure.Core.Testing
{
public class DiagnosticScopeValidatingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
if (methodName.EndsWith("Async"))
{
var expectedEventPrefix = invocation.Method.DeclaringType.FullName + "." + methodName.Substring(0, methodName.Length - 5);
var expectedEvents = new List<string>();
expectedEvents.Add(expectedEventPrefix + ".Start");

TestDiagnosticListener diagnosticListener = new TestDiagnosticListener("Azure.Clients");
invocation.Proceed();

bool strict = !invocation.Method.GetCustomAttributes(true).Any(a => a.GetType().FullName == "Azure.Core.ConvenienceMethodAttribute");
if (invocation.Method.ReturnType.Name.Contains("AsyncCollection") ||
invocation.Method.ReturnType.Name.Contains("IAsyncEnumerable"))
{
return;
}

var result = (Task)invocation.ReturnValue;
try
{
result.GetAwaiter().GetResult();
expectedEvents.Add(expectedEventPrefix + ".Stop");
}
catch (Exception ex)
{
expectedEvents.Add(expectedEventPrefix + ".Exception");

if (ex is ArgumentException)
{
// Don't expect scope for argument validation failures
expectedEvents.Clear();
}
}
finally
{
diagnosticListener.Dispose();
if (strict)
{
foreach (var expectedEvent in expectedEvents)
{
if (!diagnosticListener.Events.Any(e => e.Key == expectedEvent))
{
throw new InvalidOperationException($"Expected diagnostic event not fired {expectedEvent} {Environment.NewLine} fired events {string.Join(", ", diagnosticListener.Events)}");
}
}
}
else
{
if (!diagnosticListener.Events.Any())
{
throw new InvalidOperationException($"Expected some diagnostic event to fire found none");
}
}
}
}
else
{
invocation.Proceed();
}
}
}
}
5 changes: 3 additions & 2 deletions sdk/core/Azure.Core/tests/TestFramework/RecordMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public RecordMatcher(RecordedTestSanitizer sanitizer)
"Date",
"x-ms-date",
"x-ms-client-request-id",
"User-Agent"
"User-Agent",
"Request-Id"
};

public HashSet<string> ExcludeResponseHeaders = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
Expand Down Expand Up @@ -176,7 +177,7 @@ private int CompareHeaderDictionaries(SortedDictionary<string, string[]> headers
difference++;
}
}
else
else if (!ExcludeHeaders.Contains(header.Key))
{
difference++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class TestDiagnosticListener : IObserver<DiagnosticListener>, IObserver<K
{
private readonly string _diagnosticSourceName;

private readonly List<IDisposable> _subscriptions = new List<IDisposable>();
private List<IDisposable> _subscriptions = new List<IDisposable>();

public Queue<KeyValuePair<string, object>> Events { get; } = new Queue<KeyValuePair<string, object>>();

Expand All @@ -33,26 +33,43 @@ public void OnError(Exception error)

public void OnNext(KeyValuePair<string, object> value)
{
Events.Enqueue(value);
lock (Events)
{
Events.Enqueue(value);
}
}

public void OnNext(DiagnosticListener value)
{
if (value.Name == _diagnosticSourceName)
List<IDisposable> subscriptions = _subscriptions;
if (value.Name == _diagnosticSourceName && subscriptions != null)
{
_subscriptions.Add(value.Subscribe(this, IsEnabled));
lock (subscriptions)
{
subscriptions.Add(value.Subscribe(this, IsEnabled));
}
}
}

private bool IsEnabled(string arg1, object arg2, object arg3)
{
IsEnabledCalls.Enqueue((arg1, arg2, arg3));
lock (IsEnabledCalls)
{
IsEnabledCalls.Enqueue((arg1, arg2, arg3));
}
return true;
}

public void Dispose()
{
foreach (IDisposable subscription in _subscriptions)
List<IDisposable> subscriptions;
lock (_subscriptions)
{
subscriptions = _subscriptions;
_subscriptions = null;
}

foreach (IDisposable subscription in subscriptions)
{
subscription.Dispose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class KeyClientLiveTests : KeysTestBase

public KeyClientLiveTests(bool isAsync) : base(isAsync)
{
TestDiagnostics = false;
}

[Test]
Expand Down Expand Up @@ -298,7 +299,7 @@ public async Task RestoreKey()
await WaitForPurgedKey(keyName);

Assert.ThrowsAsync<RequestFailedException>(() => Client.GetKeyAsync(keyName));

Key restoredResult = await Client.RestoreKeyAsync(backup);
RegisterForCleanup(restoredResult);

Expand Down
Loading

0 comments on commit 163e4f7

Please sign in to comment.