Skip to content

Commit

Permalink
Adding Diagnostics capture APIs (#43)
Browse files Browse the repository at this point in the history
* Adding new option

* wiring

* wiring through initialization

* tests

* more tests

* fixing test

* Emulator tests
  • Loading branch information
ealsur authored Jun 18, 2021
1 parent a76b9cc commit d0a1a60
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 33 deletions.
44 changes: 34 additions & 10 deletions src/CosmosCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public byte[] Get(string key)
throw new ArgumentNullException(nameof(key));
}

await this.ConnectAsync().ConfigureAwait(false);
await this.ConnectAsync(token).ConfigureAwait(false);

ItemResponse<CosmosCacheSession> cosmosCacheSessionResponse;
try
Expand All @@ -92,9 +92,12 @@ public byte[] Get(string key)
id: key,
requestOptions: null,
cancellationToken: token).ConfigureAwait(false);

this.options.DiagnosticsHandler?.Invoke(cosmosCacheSessionResponse.Diagnostics);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
this.options.DiagnosticsHandler?.Invoke(ex.Diagnostics);
return null;
}

Expand Down Expand Up @@ -122,7 +125,7 @@ public byte[] Get(string key)
}

cosmosCacheSessionResponse.Resource.PartitionKeyAttribute = this.options.ContainerPartitionKeyAttribute;
await this.cosmosContainer.ReplaceItemAsync(
ItemResponse<CosmosCacheSession> replaceCacheSessionResponse = await this.cosmosContainer.ReplaceItemAsync(
partitionKey: new PartitionKey(key),
id: key,
item: cosmosCacheSessionResponse.Resource,
Expand All @@ -132,9 +135,12 @@ await this.cosmosContainer.ReplaceItemAsync(
EnableContentResponseOnWrite = false,
},
cancellationToken: token).ConfigureAwait(false);

this.options.DiagnosticsHandler?.Invoke(replaceCacheSessionResponse.Diagnostics);
}
catch (CosmosException cosmosException) when (cosmosException.StatusCode == HttpStatusCode.PreconditionFailed)
{
this.options.DiagnosticsHandler?.Invoke(cosmosException.Diagnostics);
if (this.options.RetrySlidingExpirationUpdates)
{
// Race condition on replace, we need to get the latest version of the item
Expand Down Expand Up @@ -164,7 +170,7 @@ public void Refresh(string key)
throw new ArgumentNullException(nameof(key));
}

await this.ConnectAsync().ConfigureAwait(false);
await this.ConnectAsync(token).ConfigureAwait(false);

ItemResponse<CosmosCacheSession> cosmosCacheSessionResponse;
try
Expand All @@ -174,9 +180,12 @@ public void Refresh(string key)
id: key,
requestOptions: null,
cancellationToken: token).ConfigureAwait(false);

this.options.DiagnosticsHandler?.Invoke(cosmosCacheSessionResponse.Diagnostics);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
this.options.DiagnosticsHandler?.Invoke(ex.Diagnostics);
return;
}

Expand All @@ -185,7 +194,7 @@ public void Refresh(string key)
try
{
cosmosCacheSessionResponse.Resource.PartitionKeyAttribute = this.options.ContainerPartitionKeyAttribute;
await this.cosmosContainer.ReplaceItemAsync(
ItemResponse<CosmosCacheSession> replaceCacheSessionResponse = await this.cosmosContainer.ReplaceItemAsync(
partitionKey: new PartitionKey(key),
id: key,
item: cosmosCacheSessionResponse.Resource,
Expand All @@ -195,10 +204,13 @@ await this.cosmosContainer.ReplaceItemAsync(
EnableContentResponseOnWrite = false,
},
cancellationToken: token).ConfigureAwait(false);

this.options.DiagnosticsHandler?.Invoke(replaceCacheSessionResponse.Diagnostics);
}
catch (CosmosException cosmosException) when (cosmosException.StatusCode == HttpStatusCode.PreconditionFailed)
{
// Race condition on replace, we need do not need to refresh it
this.options.DiagnosticsHandler?.Invoke(cosmosException.Diagnostics);
}
}
}
Expand All @@ -221,21 +233,24 @@ public void Remove(string key)
throw new ArgumentNullException(nameof(key));
}

await this.ConnectAsync().ConfigureAwait(false);
await this.ConnectAsync(token).ConfigureAwait(false);
try
{
await this.cosmosContainer.DeleteItemAsync<CosmosCacheSession>(
ItemResponse<CosmosCacheSession> deleteCacheSessionResponse = await this.cosmosContainer.DeleteItemAsync<CosmosCacheSession>(
partitionKey: new PartitionKey(key),
id: key,
requestOptions: new ItemRequestOptions()
{
EnableContentResponseOnWrite = false,
},
cancellationToken: token).ConfigureAwait(false);

this.options.DiagnosticsHandler?.Invoke(deleteCacheSessionResponse.Diagnostics);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// do nothing
this.options.DiagnosticsHandler?.Invoke(ex.Diagnostics);
}
}

Expand Down Expand Up @@ -267,9 +282,9 @@ public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
throw new ArgumentNullException(nameof(options));
}

await this.ConnectAsync().ConfigureAwait(false);
await this.ConnectAsync(token).ConfigureAwait(false);

await this.cosmosContainer.UpsertItemAsync(
ItemResponse<CosmosCacheSession> setCacheSessionResponse = await this.cosmosContainer.UpsertItemAsync(
partitionKey: new PartitionKey(key),
item: CosmosCache.BuildCosmosCacheSession(
key,
Expand All @@ -281,6 +296,8 @@ await this.cosmosContainer.UpsertItemAsync(
EnableContentResponseOnWrite = false,
},
cancellationToken: token).ConfigureAwait(false);

this.options.DiagnosticsHandler?.Invoke(setCacheSessionResponse.Diagnostics);
}

private static CosmosCacheSession BuildCosmosCacheSession(string key, byte[] content, DistributedCacheEntryOptions options, CosmosCacheOptions cosmosCacheOptions)
Expand Down Expand Up @@ -379,25 +396,29 @@ private async Task<Container> CosmosContainerInitializeAsync()
this.cosmosClient = this.GetClientInstance();
if (this.options.CreateIfNotExists)
{
await this.cosmosClient.CreateDatabaseIfNotExistsAsync(this.options.DatabaseName).ConfigureAwait(false);
DatabaseResponse databaseResponse = await this.cosmosClient.CreateDatabaseIfNotExistsAsync(this.options.DatabaseName).ConfigureAwait(false);
this.options.DiagnosticsHandler?.Invoke(databaseResponse.Diagnostics);

int defaultTimeToLive = this.options.DefaultTimeToLiveInMs.HasValue
&& this.options.DefaultTimeToLiveInMs.Value > 0 ? this.options.DefaultTimeToLiveInMs.Value : CosmosCache.DefaultTimeToLive;

try
{
ContainerResponse existingContainer = await this.cosmosClient.GetContainer(this.options.DatabaseName, this.options.ContainerName).ReadContainerAsync().ConfigureAwait(false);
this.options.DiagnosticsHandler?.Invoke(existingContainer.Diagnostics);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
this.options.DiagnosticsHandler?.Invoke(ex.Diagnostics);

// Container is optimized as Key-Value store excluding all properties
string partitionKeyDefinition = CosmosCache.ContainerPartitionKeyPath;
if (!string.IsNullOrWhiteSpace(this.options.ContainerPartitionKeyAttribute))
{
partitionKeyDefinition = $"/{this.options.ContainerPartitionKeyAttribute}";
}

await this.cosmosClient.GetDatabase(this.options.DatabaseName).DefineContainer(this.options.ContainerName, partitionKeyDefinition)
ContainerResponse newContainer = await this.cosmosClient.GetDatabase(this.options.DatabaseName).DefineContainer(this.options.ContainerName, partitionKeyDefinition)
.WithDefaultTimeToLive(defaultTimeToLive)
.WithIndexingPolicy()
.WithIndexingMode(IndexingMode.Consistent)
Expand All @@ -408,16 +429,19 @@ await this.cosmosClient.GetDatabase(this.options.DatabaseName).DefineContainer(t
.Attach()
.Attach()
.CreateAsync(this.options.ContainerThroughput).ConfigureAwait(false);
this.options.DiagnosticsHandler?.Invoke(newContainer.Diagnostics);
}
}
else
{
try
{
ContainerResponse existingContainer = await this.cosmosClient.GetContainer(this.options.DatabaseName, this.options.ContainerName).ReadContainerAsync().ConfigureAwait(false);
this.options.DiagnosticsHandler?.Invoke(existingContainer.Diagnostics);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
this.options.DiagnosticsHandler?.Invoke(ex.Diagnostics);
throw new InvalidOperationException($"Cannot find an existing container named {this.options.ContainerName} within database {this.options.DatabaseName}");
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/CosmosCacheOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ namespace Microsoft.Extensions.Caching.Cosmos
/// </summary>
public class CosmosCacheOptions : IOptions<CosmosCacheOptions>
{
/// <summary>
/// Delegate to receive Diagnostics from the internal Cosmos DB operations.
/// </summary>
/// <param name="diagnostics">An instance of <see cref="CosmosDiagnostics"/> result from a Cosmos DB service operation.</param>
/// <example>
/// <code language="c#">
/// <![CDATA[
/// void captureDiagnostics(CosmosDiagnostics diagnostics)
/// {
/// if (diagnostics.GetClientElapsedTime() > SomePredefinedThresholdTime)
/// {
/// Console.WriteLine(diagnostics.ToString());
/// }
/// }
///
/// CosmosCacheOptions options = new CosmosCacheOptions(){
/// /* Other options */,
/// DiagnosticsHandler = captureDiagnostics
/// };
/// ]]>
/// </code>
/// </example>
public delegate void DiagnosticsDelegate(CosmosDiagnostics diagnostics);

/// <summary>
/// Gets or sets an instance of <see cref="CosmosClientBuilder"/> to build a Cosmos Client with. Either use this or provide an existing <see cref="CosmosClient"/>.
/// </summary>
Expand Down Expand Up @@ -68,6 +92,15 @@ public class CosmosCacheOptions : IOptions<CosmosCacheOptions>
/// <value>Default value is true.</value>
public bool RetrySlidingExpirationUpdates { get; set; } = true;

/// <summary>
/// Gets or sets a delegate to capture operation diagnostics.
/// </summary>
/// <remarks>
/// <para>This delegate captures the <see cref="CosmosDiagnostics"/> from the operations performed on the Cosmos DB service.</para>
/// <para>Once set, it will be called for all executed operations and can be used for conditionally capturing diagnostics. </para>
/// </remarks>
public DiagnosticsDelegate DiagnosticsHandler { get; set; }

/// <summary>
/// Gets the current options values.
/// </summary>
Expand Down
Loading

0 comments on commit d0a1a60

Please sign in to comment.