/// <summary> /// Retrieves the secret value, based on the given name /// </summary> /// <param name="secretName">The name of the secret</param> /// <returns>Returns a <see cref="Secret"/> that contains the secret</returns> /// <exception cref="ArgumentException">The <paramref name="secretName"/> must not be empty</exception> /// <exception cref="ArgumentNullException">The <paramref name="secretName"/> must not be null</exception> /// <exception cref="SecretNotFoundException">The secret was not found, using the given name</exception> /// <exception cref="KeyVaultErrorException">The call for a secret resulted in an invalid response</exception> public virtual async Task <Secret> GetSecretAsync(string secretName) { Guard.NotNullOrWhitespace(secretName, nameof(secretName), "Requires a non-blank secret name to request a secret in Azure Key Vault"); Guard.For <FormatException>(() => !SecretNameRegex.IsMatch(secretName), "Requires a secret name in the correct format to request a secret in Azure Key Vault, see https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#objects-identifiers-and-versioning"); var isSuccessful = false; using (DependencyMeasurement measurement = DependencyMeasurement.Start()) { try { Secret secret = await GetSecretCoreAsync(secretName); isSuccessful = true; return(secret); } finally { if (_options.TrackDependency) { Logger.LogDependency(DependencyName, secretName, VaultUri, isSuccessful, measurement); } } } }
private async Task <SecretData> GetTrackedSecretAsync(string secretName) { var context = new Dictionary <string, object> { ["SecretEngine Type"] = "KeyValue", ["SecretEngine Version"] = _options.KeyValueVersion }; var isSuccessful = false; using (DependencyMeasurement measurement = DependencyMeasurement.Start()) { try { _logger.LogTrace("Getting a secret {SecretName} from HashiCorp Vault {VaultUri}...", secretName, _vaultClient.Settings.VaultServerUriWithPort); SecretData result = await ReadSecretDataAsync(_secretPath); isSuccessful = true; return(result); } finally { _logger.LogTrace("{Result} secret from HashiCorp Vault {VaultUri}", isSuccessful ? "Got" : "Couldn't get", _vaultClient.Settings.VaultServerUriWithPort); if (_options.TrackDependency) { _logger.LogDependency(DependencyName, secretName, _vaultClient.Settings.VaultServerUriWithPort, isSuccessful, measurement, context); } } } }
/// <summary> /// Retrieves the secret value, based on the given name /// </summary> /// <param name="secretName">The name of the secret</param> /// <returns>Returns a <see cref="Secret"/> that contains the secret</returns> /// <exception cref="ArgumentException">The <paramref name="secretName"/> must not be empty</exception> /// <exception cref="ArgumentNullException">The <paramref name="secretName"/> must not be null</exception> /// <exception cref="SecretNotFoundException">The secret was not found, using the given name</exception> /// <exception cref="KeyVaultErrorException">The call for a secret resulted in an invalid response</exception> public virtual async Task <Secret> GetSecretAsync(string secretName) { Guard.NotNullOrWhitespace(secretName, nameof(secretName), "Requires a non-blank secret name to request a secret in Azure Key Vault"); var isSuccessful = false; #if NET6_0 using (DurationMeasurement measurement = DurationMeasurement.Start()) #else using (DependencyMeasurement measurement = DependencyMeasurement.Start()) #endif { try { Secret secret = await GetSecretCoreAsync(secretName); isSuccessful = true; return(secret); } finally { if (_options.TrackDependency) { Logger.LogDependency(DependencyName, secretName, VaultUri, isSuccessful, measurement); } } } }
private async Task <HttpResponseMessage> SendRequestToApiAsync(HttpRequestMessage request) { using (var dependencyMeasurement = DependencyMeasurement.Start()) { HttpResponseMessage response = null; try { response = await _httpClient.SendAsync(request); _logger.LogRequest(request, response, dependencyMeasurement.Elapsed); return(response); } finally { try { var statusCode = response?.StatusCode ?? HttpStatusCode.InternalServerError; _logger.LogHttpDependency(request, statusCode, dependencyMeasurement); } catch (Exception ex) { _logger.LogWarning("Failed to log HTTP dependency. Reason: {Message}", ex.Message); } } } }
public async Task DependencyMeasurement_StartMeasuringAction_HoldsStartAndElapsedTime() { // Act using (var measurement = DependencyMeasurement.Start()) { // Assert await Task.Delay(TimeSpan.FromMilliseconds(100)); Assert.NotNull(measurement); Assert.True(DateTimeOffset.UtcNow > measurement.StartTime, "Measurement should have lesser start time"); Assert.True(TimeSpan.Zero < measurement.Elapsed, "Measurement should be running"); } }
public async Task DependencyMeasurement_StopsMeasuringAction_WhenDisposed() { // Arrange var measurement = DependencyMeasurement.Start(); await Task.Delay(TimeSpan.FromMilliseconds(100)); measurement.Dispose(); var elapsed = measurement.Elapsed; // Act await Task.Delay(TimeSpan.FromMilliseconds(100)); // Assert Assert.NotNull(measurement); Assert.Equal(elapsed, measurement.Elapsed); }
/// <summary> /// Retrieves the secret value in the HashiCorp KeyValue Vault on given <paramref name="secretName"/> /// while tracking the dependency interaction call with the vault. /// </summary> /// <param name="secretName">The name of the HashiCorp secret.</param> /// <returns> /// The HashiCorp <see cref="SecretData"/> concrete instance that contains the secret value. /// </returns> /// <exception cref="ArgumentException">Thrown when the <paramref name="secretName"/> is blank.</exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when the <see cref="Options"/>'s <see cref="HashiCorpVaultOptions.KeyValueVersion"/> represents an unknown secret engine version. /// </exception> protected async Task <SecretData> GetTrackedSecretAsync(string secretName) { Guard.NotNullOrWhitespace(secretName, nameof(secretName), $"Requires a non-blank secret name to look up the secret in the HashiCorp Vault {Options.KeyValueVersion} KeyValue secret engine"); var context = new Dictionary <string, object> { ["SecretEngine Type"] = "KeyValue", ["SecretEngine Version"] = Options.KeyValueVersion }; var isSuccessful = false; #if NET6_0 using (DurationMeasurement measurement = DurationMeasurement.Start()) #else using (DependencyMeasurement measurement = DependencyMeasurement.Start()) #endif { try { Logger.LogTrace("Getting a secret {SecretName} from HashiCorp Vault {VaultUri}...", secretName, VaultClient.Settings.VaultServerUriWithPort); SecretData result = await ReadSecretDataAsync(); Logger.LogTrace("Secret '{SecretName}' was successfully retrieved from HashiCorp Vault {VaultUri}", secretName, VaultClient.Settings.VaultServerUriWithPort); isSuccessful = true; return(result); } catch (Exception exception) { Logger.LogError(exception, "Secret '{SecretName}' was not successfully retrieved from HashiCorp Vault {VaultUri}, cause: {Message}", secretName, VaultClient.Settings.VaultServerUriWithPort, exception.Message); throw; } finally { if (Options.TrackDependency) { Logger.LogDependency(DependencyName, secretName, VaultClient.Settings.VaultServerUriWithPort, isSuccessful, measurement, context); } } } }
private async Task <HttpResponseMessage> SendRequestToApiAsync(HttpRequestMessage request) { using (var dependencyMeasurement = DependencyMeasurement.Start()) { HttpResponseMessage response = null; try { _httpClient.BaseAddress = new Uri($"http://{_configuration.CurrentValue.Host}:{_configuration.CurrentValue.Port}"); response = await _httpClient.SendAsync(request); _logger.LogRequest(request, response, dependencyMeasurement.Elapsed); return(response); } finally { var statusCode = response?.StatusCode ?? HttpStatusCode.InternalServerError; _logger.LogHttpDependency(request, statusCode, dependencyMeasurement); } } }
/// <summary> /// Logs an Azure Key Vault dependency. /// </summary> /// <param name="logger">The logger to use.</param> /// <param name="vaultUri">The URI pointing to the Azure Key Vault resource.</param> /// <param name="secretName">The secret that is being used within the Azure Key Vault resource.</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="logger"/> is <c>null</c>.</exception> /// <exception cref="ArgumentException">Thrown when the <paramref name="vaultUri"/> or <paramref name="secretName"/> is blank.</exception> /// <exception cref="FormatException">Thrown when the <paramref name="secretName"/> is not in the correct format.</exception> /// <exception cref="UriFormatException">Thrown when the <paramref name="vaultUri"/> is not in the correct format.</exception> public static void LogAzureKeyVaultDependency( this ILogger logger, string vaultUri, string secretName, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger), "Requires an logger instance to write the Azure Key Vault dependency"); Guard.NotNullOrWhitespace(vaultUri, nameof(vaultUri), "Requires a non-blank URI for the Azure Key Vault"); Guard.NotNullOrWhitespace(secretName, nameof(secretName), "Requires a non-blank secret name for the Azure Key Vault"); Guard.For <FormatException>( () => !SecretNameRegex.IsMatch(secretName), "Requires a Azure Key Vault secret name in the correct format, see https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#objects-identifiers-and-versioning"); Guard.For <UriFormatException>( () => !KeyVaultUriRegex.IsMatch(vaultUri), "Requires the Azure Key Vault host to be in the right format, see https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#objects-identifiers-and-versioning"); context = context ?? new Dictionary <string, object>(); LogAzureKeyVaultDependency(logger, vaultUri, secretName, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }
private async Task <HttpResponseMessage> SendRequestToApiAsync(HttpRequestMessage request) { var client = CreateHttpClient(); using (var dependencyMeasurement = DependencyMeasurement.Start()) { HttpResponseMessage response = null; try { response = await client.SendAsync(request); _logger.LogRequest(request, response, dependencyMeasurement.Elapsed); return(response); } finally { var statusCode = response?.StatusCode ?? HttpStatusCode.InternalServerError; _logger.LogHttpDependency(request, statusCode, dependencyMeasurement); } } }
/// <summary> /// Logs a SQL dependency /// </summary> /// <param name="logger">Logger to use</param> /// <param name="connectionString">SQL connection string</param> /// <param name="tableName">Name of table</param> /// <param name="operationName">Name of the operation that was performed</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the SQL dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogSqlDependency(this ILogger logger, string connectionString, string tableName, string operationName, DependencyMeasurement measurement, bool isSuccessful, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNull(measurement, nameof(measurement)); LogSqlDependency(logger, connectionString, tableName, operationName, measurement.StartTime, measurement.Elapsed, isSuccessful, context); }
/// <summary> /// Logs an Azure Search Dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="searchServiceName">Name of the Azure Search service</param> /// <param name="operationName">Name of the operation to execute on the Azure Search service</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogAzureSearchDependency(this ILogger logger, string searchServiceName, string operationName, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNullOrWhitespace(searchServiceName, nameof(searchServiceName)); Guard.NotNullOrWhitespace(operationName, nameof(operationName)); context = context ?? new Dictionary <string, object>(); LogAzureSearchDependency(logger, searchServiceName, operationName, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }
/// <summary> /// Logs an Azure Service Bus Dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="topicName">Name of the Service Bus topic</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the Service Bus dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogServiceBusTopicDependency(this ILogger logger, string topicName, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNull(measurement, nameof(measurement)); LogServiceBusTopicDependency(logger, topicName, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }
/// <summary> /// Logs an Azure Service Bus Dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="entityName">Name of the Service Bus entity</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the Service Bus dependency</param> /// <param name="entityType">Type of the Service Bus entity</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogServiceBusDependency(this ILogger logger, string entityName, bool isSuccessful, DependencyMeasurement measurement, ServiceBusEntityType entityType = ServiceBusEntityType.Unknown, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNull(measurement, nameof(measurement)); LogServiceBusDependency(logger, entityName, isSuccessful, measurement.StartTime, measurement.Elapsed, entityType, context); }
public async Task <List <Resource> > QueryAsync(string query, List <string> targetSubscriptions) { Guard.NotNullOrWhitespace(query, nameof(query)); var graphClient = await GetOrCreateClient(); bool isSuccessfulDependency = false; using (var dependencyMeasurement = DependencyMeasurement.Start()) { try { var queryRequest = new QueryRequest(targetSubscriptions, query); var response = await graphClient.ResourcesAsync(queryRequest); isSuccessfulDependency = true; var foundResources = ParseQueryResults(response); return(foundResources); } catch (ErrorResponseException responseException) { if (responseException.Response != null) { if (responseException.Response.StatusCode == HttpStatusCode.Forbidden) { var unauthorizedException = new UnauthorizedException(QueryApplicationId, targetSubscriptions); _logger.LogCritical(unauthorizedException, "Unable to query Azure Resource Graph"); throw unauthorizedException; } if (responseException.Response.StatusCode == HttpStatusCode.BadRequest) { var response = JToken.Parse(responseException.Response.Content); var errorDetails = response["error"]?["details"]; if (errorDetails != null) { var errorCodes = new List <string>(); foreach (var detailEntry in errorDetails) { errorCodes.Add(detailEntry["code"]?.ToString()); } if (errorCodes.Any(errorCode => errorCode.Equals("NoValidSubscriptionsInQueryRequest", StringComparison.InvariantCultureIgnoreCase))) { var invalidSubscriptionException = new QueryContainsInvalidSubscriptionException(targetSubscriptions); _logger.LogCritical(invalidSubscriptionException, "Unable to query Azure Resource Graph"); throw invalidSubscriptionException; } } } } throw; } finally { var contextualInformation = new Dictionary <string, object> { { "Query", query }, { "Subscriptions", targetSubscriptions } }; _logger.LogDependency("Azure Resource Graph", query, "Query", isSuccessfulDependency, dependencyMeasurement, contextualInformation); } } }
private async Task <TResponse> InteractWithAzureResourceGraphAsync <TResponse>(string queryName, string query, List <string> targetSubscriptions, Func <ResourceGraphClient, Task <TResponse> > interactionFunc) { Guard.NotNullOrWhitespace(query, nameof(query)); var retryPolicy = Policy.Handle <ErrorResponseException>(ex => ex.Response?.StatusCode == HttpStatusCode.Unauthorized) .RetryAsync(retryCount: 3, OnRetryAsync); return(await retryPolicy.ExecuteAsync(async() => { var graphClient = await GetOrCreateClient(); bool isSuccessfulDependency = false; using (var dependencyMeasurement = DependencyMeasurement.Start()) { try { var response = await interactionFunc(graphClient); isSuccessfulDependency = true; return response; } catch (ErrorResponseException responseException) { if (responseException.Response != null) { if (responseException.Response.StatusCode == HttpStatusCode.Forbidden) { var unauthorizedException = CreateUnauthorizedException(targetSubscriptions); throw unauthorizedException; } if (responseException.Response.StatusCode == HttpStatusCode.BadRequest) { var response = JToken.Parse(responseException.Response.Content); var errorDetails = response["error"]?["details"]; if (errorDetails != null) { var errorCodes = new List <string>(); foreach (var detailEntry in errorDetails) { errorCodes.Add(detailEntry["code"]?.ToString()); } if (errorCodes.Any(errorCode => errorCode.Equals("NoValidSubscriptionsInQueryRequest", StringComparison.InvariantCultureIgnoreCase))) { var invalidSubscriptionException = new QueryContainsInvalidSubscriptionException(targetSubscriptions); _logger.LogCritical(invalidSubscriptionException, "Unable to query Azure Resource Graph"); throw invalidSubscriptionException; } } } } throw; } finally { var contextualInformation = new Dictionary <string, object> { { "Query", query }, { "QueryName", queryName }, { "Subscriptions", targetSubscriptions } }; _logger.LogDependency("Azure Resource Graph", query, "Query", isSuccessfulDependency, dependencyMeasurement, contextualInformation); } } })); }
/// <summary> /// Logs an Azure Iot Hub Dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="iotHubConnectionString">Name of the IoT Hub resource</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogIotHubDependency(this ILogger logger, string iotHubConnectionString, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNullOrWhitespace(iotHubConnectionString, nameof(iotHubConnectionString)); LogIotHubDependency(logger, iotHubConnectionString, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }
/// <summary> /// Logs an Azure Table Storage Dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="accountName">Account of the storage resource</param> /// <param name="tableName">Name of the Table Storage resource</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the Table Storage dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogTableStorageDependency(this ILogger logger, string accountName, string tableName, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNullOrWhitespace(tableName, nameof(tableName)); Guard.NotNullOrWhitespace(accountName, nameof(accountName)); Guard.NotNull(measurement, nameof(measurement)); LogTableStorageDependency(logger, accountName: accountName, tableName: tableName, isSuccessful: isSuccessful, startTime: measurement.StartTime, duration: measurement.Elapsed, context: context); }
/// <summary> /// Logs a Cosmos SQL dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="accountName">Name of the storage resource</param> /// <param name="database">Name of the database</param> /// <param name="container">Name of the container</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency of the dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogCosmosSqlDependency(this ILogger logger, string accountName, string database, string container, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNullOrWhitespace(accountName, nameof(accountName)); Guard.NotNullOrWhitespace(database, nameof(database)); Guard.NotNullOrWhitespace(container, nameof(container)); LogCosmosSqlDependency(logger, accountName, database, container, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }
/// <summary> /// Logs an HTTP dependency /// </summary> /// <param name="logger">Logger to use</param> /// <param name="request">Request that started the HTTP communication</param> /// <param name="statusCode">Status code that was returned by the service for this HTTP communication</param> /// <param name="measurement">Measuring the latency of the HTTP dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogHttpDependency(this ILogger logger, HttpRequestMessage request, HttpStatusCode statusCode, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNull(request, nameof(request)); Guard.NotNull(measurement, nameof(measurement)); LogHttpDependency(logger, request, statusCode, measurement.StartTime, measurement.Elapsed, context); }
/// <summary> /// Logs an Azure Event Hub Dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="namespaceName">Namespace of the resource</param> /// <param name="eventHubName">Name of the Event Hub resource</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogEventHubsDependency(this ILogger logger, string namespaceName, string eventHubName, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNullOrWhitespace(namespaceName, nameof(namespaceName)); Guard.NotNullOrWhitespace(eventHubName, nameof(eventHubName)); LogEventHubsDependency(logger, namespaceName, eventHubName, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }
/// <summary> /// Logs a SQL dependency /// </summary> /// <param name="logger">Logger to use</param> /// <param name="serverName">Name of server hosting the database</param> /// <param name="databaseName">Name of database</param> /// <param name="tableName">Name of table</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the SQL dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogSqlDependency(this ILogger logger, string serverName, string databaseName, string tableName, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNullOrWhitespace(serverName, nameof(serverName)); Guard.NotNullOrWhitespace(databaseName, nameof(databaseName)); Guard.NotNullOrWhitespace(tableName, nameof(tableName)); Guard.NotNull(measurement, nameof(measurement)); LogSqlDependency(logger, serverName, databaseName, tableName, measurement.DependencyData, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }
/// <summary> /// Logs a dependency. /// </summary> /// <param name="logger">Logger to use</param> /// <param name="dependencyType">Custom type of dependency</param> /// <param name="dependencyData">Custom data of dependency</param> /// <param name="isSuccessful">Indication whether or not the operation was successful</param> /// <param name="measurement">Measuring the latency to call the Service Bus dependency</param> /// <param name="context">Context that provides more insights on the dependency that was measured</param> public static void LogDependency(this ILogger logger, string dependencyType, object dependencyData, bool isSuccessful, DependencyMeasurement measurement, Dictionary <string, object> context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNullOrWhitespace(dependencyType, nameof(dependencyType)); Guard.NotNull(dependencyData, nameof(dependencyData)); LogDependency(logger, dependencyType, dependencyData, isSuccessful, measurement.StartTime, measurement.Elapsed, context); }