diff --git a/docs/preview/features/writing-different-telemetry-types.md b/docs/preview/features/writing-different-telemetry-types.md index 186dc0f5..48b0aef2 100644 --- a/docs/preview/features/writing-different-telemetry-types.md +++ b/docs/preview/features/writing-different-telemetry-types.md @@ -103,6 +103,8 @@ _logger.LogEventHubsDependency(namespaceName: "be.sensors.contoso", eventHubName We allow you to measure Azure IoT Hub dependencies. +**Example** + Here is how you can report a dependency call: ```csharp @@ -116,6 +118,31 @@ _logger.logger.LogIotHubDependency(iotHubName: "sensors", isSuccessful: true, st // Output: "Dependency Azure IoT Hub named sensors in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" ``` +Or, alternatively you can pass allong the IoT connection string itself so the host name will be selected for you. + +**Installation** + +This feature requires to install our NuGet package + +```shell +PM > Install-Package Arcus.Observability.Telemetry.IoT +``` + +**Example** + +Here is how you can report a dependency call: + +```csharp +var durationMeasurement = new Stopwatch(); + +// Start measuring +durationMeasurement.Start(); +var startTime = DateTimeOffset.UtcNow; + +_logger.logger.LogIotHubDependency(iotHubConnectionString: "Hostname=sensors;", isSuccessful: true, startTime: startTime, duration: durationMeasurement.Elapsed); +// Output: "Dependency Azure IoT Hub named sensors in 00:00:00.2521801 at 03/23/2020 09:56:31 +00:00 (Successful: True - Context: )" +``` + ### Measuring Azure Service Bus dependencies We allow you to measure Azure Service Bus dependencies for both queues & topics. diff --git a/src/Arcus.Observability.Telemetry.Core/Extensions/ILoggerExtensions.cs b/src/Arcus.Observability.Telemetry.Core/Extensions/ILoggerExtensions.cs index 726d853e..f538f0c4 100644 --- a/src/Arcus.Observability.Telemetry.Core/Extensions/ILoggerExtensions.cs +++ b/src/Arcus.Observability.Telemetry.Core/Extensions/ILoggerExtensions.cs @@ -557,17 +557,18 @@ public static void LogHttpDependency(this ILogger logger, HttpRequestMessage req logger.LogInformation(HttpDependencyFormat, targetName, dependencyName, (int) statusCode, duration, startTime.ToString(CultureInfo.InvariantCulture), isSuccessful, context); } - /// - /// Logs a SQL dependency - /// - /// Logger to use - /// SQL connection string - /// Name of table - /// Name of the operation that was performed - /// Indication whether or not the operation was successful - /// Measuring the latency to call the SQL dependency + /// + /// Logs a SQL dependency + /// + /// Logger to use + /// SQL connection string + /// Name of table + /// Name of the operation that was performed + /// Indication whether or not the operation was successful + /// Measuring the latency to call the SQL dependency /// Context that provides more insights on the dependency that was measured - public static void LogSqlDependency(this ILogger logger, string connectionString, string tableName, string operationName, DependencyMeasurement measurement, bool isSuccessful, Dictionary context = null) + [Obsolete("Will be moved to separate package 'Arcus.Observability.Telemetry.Sql' in the future")] + public static void LogSqlDependency(this ILogger logger, string connectionString, string tableName, string operationName, DependencyMeasurement measurement, bool isSuccessful, Dictionary context = null) { Guard.NotNull(logger, nameof(logger)); Guard.NotNull(measurement, nameof(measurement)); @@ -575,18 +576,19 @@ public static void LogSqlDependency(this ILogger logger, string connectionString LogSqlDependency(logger, connectionString, tableName, operationName, measurement.StartTime, measurement.Elapsed, isSuccessful, context); } - /// - /// Logs a SQL dependency - /// - /// Logger to use - /// SQL connection string - /// Name of table - /// Name of the operation that was performed - /// Point in time when the interaction with the HTTP dependency was started - /// Duration of the operation - /// Indication whether or not the operation was successful + /// + /// Logs a SQL dependency + /// + /// Logger to use + /// SQL connection string + /// Name of table + /// Name of the operation that was performed + /// Point in time when the interaction with the HTTP dependency was started + /// Duration of the operation + /// Indication whether or not the operation was successful /// Context that provides more insights on the dependency that was measured - public static void LogSqlDependency(this ILogger logger, string connectionString, string tableName, string operationName, DateTimeOffset startTime, TimeSpan duration, bool isSuccessful, Dictionary context = null) + [Obsolete("Will be moved to separate package 'Arcus.Observability.Telemetry.Sql' in the future")] + public static void LogSqlDependency(this ILogger logger, string connectionString, string tableName, string operationName, DateTimeOffset startTime, TimeSpan duration, bool isSuccessful, Dictionary context = null) { Guard.NotNullOrEmpty(connectionString, nameof(connectionString)); var connection = new SqlConnectionStringBuilder(connectionString); diff --git a/src/Arcus.Observability.Telemetry.IoT/Arcus.Observability.Telemetry.IoT.csproj b/src/Arcus.Observability.Telemetry.IoT/Arcus.Observability.Telemetry.IoT.csproj new file mode 100644 index 00000000..932bec97 --- /dev/null +++ b/src/Arcus.Observability.Telemetry.IoT/Arcus.Observability.Telemetry.IoT.csproj @@ -0,0 +1,31 @@ + + + + netstandard2.0 + Arcus.Observability.Telemetry.IoT + Arcus + Arcus + Provides capability to improve IoT telemetry with Serilog in applications + Copyright (c) Arcus + https://github.com/arcus-azure/arcus.observability/blob/master/LICENSE + https://github.com/arcus-azure/arcus.observability + https://raw.githubusercontent.com/arcus-azure/arcus/master/media/arcus.png + https://github.com/arcus-azure/arcus.observability + Git + Azure;Observability;Telemetry;Serilog;IoT + true + true + Arcus.Observability.Telemetry.IoT + Arcus.Observability.Telemetry.IoT + + + + + + + + + + + + diff --git a/src/Arcus.Observability.Telemetry.IoT/Extensions/ILoggerExtensions.cs b/src/Arcus.Observability.Telemetry.IoT/Extensions/ILoggerExtensions.cs new file mode 100644 index 00000000..ea25fd7f --- /dev/null +++ b/src/Arcus.Observability.Telemetry.IoT/Extensions/ILoggerExtensions.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Arcus.Observability.Telemetry.Core; +using GuardNet; +using Microsoft.Azure.Devices.Client; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.Logging +{ + /// + /// Extensions on the related to tracking IoT dependencies. + /// + // ReSharper disable once InconsistentNaming + public static class ILoggerExtensions + { + /// + /// Logs an Azure Iot Hub Dependency. + /// + /// Logger to use + /// Name of the IoT Hub resource + /// Indication whether or not the operation was successful + /// Measuring the latency to call the dependency + /// Context that provides more insights on the dependency that was measured + public static void LogIotHubDependency(this ILogger logger, string iotHubConnectionString, bool isSuccessful, DependencyMeasurement measurement, Dictionary context = null) + { + Guard.NotNull(logger, nameof(logger)); + Guard.NotNullOrWhitespace(iotHubConnectionString, nameof(iotHubConnectionString)); + + LogIotHubDependency(logger, iotHubConnectionString, isSuccessful, measurement.StartTime, measurement.Elapsed, context); + } + + /// + /// Logs an Azure Iot Hub Dependency. + /// + /// Logger to use + /// Name of the Event Hub resource + /// Indication whether or not the operation was successful + /// Point in time when the interaction with the dependency was started + /// Duration of the operation + /// Context that provides more insights on the dependency that was measured + public static void LogIotHubDependency(this ILogger logger, string iotHubConnectionString, bool isSuccessful, DateTimeOffset startTime, TimeSpan duration, Dictionary context = null) + { + Guard.NotNull(logger, nameof(logger)); + Guard.NotNullOrWhitespace(iotHubConnectionString, nameof(iotHubConnectionString)); + + context = context ?? new Dictionary(); + + var iotHubConnection = IotHubConnectionStringBuilder.Create(iotHubConnectionString); + logger.LogIotHubDependency(iotHubName: iotHubConnection.HostName, isSuccessful: isSuccessful, startTime: startTime, duration: duration, context: context); + } + } +} diff --git a/src/Arcus.Observability.Tests.Unit/Arcus.Observability.Tests.Unit.csproj b/src/Arcus.Observability.Tests.Unit/Arcus.Observability.Tests.Unit.csproj index 2b0f5c5d..0ab86175 100644 --- a/src/Arcus.Observability.Tests.Unit/Arcus.Observability.Tests.Unit.csproj +++ b/src/Arcus.Observability.Tests.Unit/Arcus.Observability.Tests.Unit.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Arcus.Observability.Tests.Unit/Serilog/ApplicationInsightsTelemetryConverterTests.cs b/src/Arcus.Observability.Tests.Unit/Serilog/ApplicationInsightsTelemetryConverterTests.cs index 89acb867..5bb53b5f 100644 --- a/src/Arcus.Observability.Tests.Unit/Serilog/ApplicationInsightsTelemetryConverterTests.cs +++ b/src/Arcus.Observability.Tests.Unit/Serilog/ApplicationInsightsTelemetryConverterTests.cs @@ -481,7 +481,7 @@ public void LogIoTHubDependency_WithTableStorageDependency_CreatesDependencyTele { ["DeviceName"] = "Sensor #102" }; - logger.LogIotHubDependency(iotHubName, isSuccessful: true, startTime: startTime, duration: duration, context: telemetryContext); + logger.LogIotHubDependency(iotHubName: iotHubName, isSuccessful: true, startTime: startTime, duration: duration, context: telemetryContext); LogEvent logEvent = Assert.Single(spySink.CurrentLogEmits); Assert.NotNull(logEvent); diff --git a/src/Arcus.Observability.Tests.Unit/Telemetry/ILoggerExtensionsTests.cs b/src/Arcus.Observability.Tests.Unit/Telemetry/ILoggerExtensionsTests.cs index 9ed840e9..73f2d15e 100644 --- a/src/Arcus.Observability.Tests.Unit/Telemetry/ILoggerExtensionsTests.cs +++ b/src/Arcus.Observability.Tests.Unit/Telemetry/ILoggerExtensionsTests.cs @@ -536,7 +536,7 @@ public void LogIotHubDependency_ValidArguments_Succeeds() TimeSpan duration = _bogusGenerator.Date.Timespan(); // Act - logger.LogIotHubDependency(iotHubName, isSuccessful, startTime, duration); + logger.LogIotHubDependency(iotHubName: iotHubName, isSuccessful: isSuccessful, startTime: startTime, duration: duration); // Assert var logMessage = logger.WrittenMessage; @@ -562,7 +562,62 @@ public void LogIotHubDependencyWithDependencyMeasurement_ValidArguments_Succeeds TimeSpan duration = measurement.Elapsed; // Act - logger.LogIotHubDependency(iotHubName, isSuccessful, measurement); + logger.LogIotHubDependency(iotHubName: iotHubName, isSuccessful: isSuccessful, measurement: measurement); + + // Assert + var logMessage = logger.WrittenMessage; + Assert.StartsWith(MessagePrefixes.Dependency, logMessage); + Assert.Contains(iotHubName, logMessage); + Assert.Contains(iotHubName, logMessage); + Assert.Contains(isSuccessful.ToString(), logMessage); + Assert.Contains(startTime.ToString(CultureInfo.InvariantCulture), logMessage); + Assert.Contains(duration.ToString(), logMessage); + } + + [Fact] + public void LogIotHubConnectionStringDependency_ValidArguments_Succeeds() + { + // Arrange + var logger = new TestLogger(); + string iotHubName = _bogusGenerator.Commerce.ProductName().Replace(" ", String.Empty); + string deviceId = _bogusGenerator.Internet.Ip(); + string sharedAccessKey = _bogusGenerator.Random.Hash(); + string iotHubConnectionString = $"HostName={iotHubName}.;DeviceId={deviceId};SharedAccessKey={sharedAccessKey}"; + bool isSuccessful = _bogusGenerator.Random.Bool(); + DateTimeOffset startTime = _bogusGenerator.Date.PastOffset(); + TimeSpan duration = _bogusGenerator.Date.Timespan(); + + // Act + logger.LogIotHubDependency(iotHubConnectionString: iotHubConnectionString, isSuccessful: isSuccessful, startTime: startTime, duration: duration); + + // Assert + var logMessage = logger.WrittenMessage; + Assert.StartsWith(MessagePrefixes.Dependency, logMessage); + Assert.Contains(iotHubName, logMessage); + Assert.Contains(iotHubName, logMessage); + Assert.Contains(isSuccessful.ToString(), logMessage); + Assert.Contains(startTime.ToString(CultureInfo.InvariantCulture), logMessage); + Assert.Contains(duration.ToString(), logMessage); + } + + [Fact] + public void LogIotHubDependencyConnectionStringWithDependencyMeasurement_ValidArguments_Succeeds() + { + // Arrange + var logger = new TestLogger(); + string iotHubName = _bogusGenerator.Commerce.ProductName().Replace(" ", String.Empty); + string deviceId = _bogusGenerator.Internet.Ip(); + string sharedAccessKey = _bogusGenerator.Random.Hash(); + string iotHubConnectionString = $"HostName={iotHubName}.;DeviceId={deviceId};SharedAccessKey={sharedAccessKey}"; + bool isSuccessful = _bogusGenerator.Random.Bool(); + + var measurement = DependencyMeasurement.Start(); + DateTimeOffset startTime = measurement.StartTime; + measurement.Dispose(); + TimeSpan duration = measurement.Elapsed; + + // Act + logger.LogIotHubDependency(iotHubConnectionString: iotHubConnectionString, isSuccessful: isSuccessful, measurement: measurement); // Assert var logMessage = logger.WrittenMessage; diff --git a/src/Arcus.Observability.sln b/src/Arcus.Observability.sln index cb131aeb..2dbdeab2 100644 --- a/src/Arcus.Observability.sln +++ b/src/Arcus.Observability.sln @@ -19,7 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus.Observability.Telemet EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus.Observability.Telemetry.Serilog.Filters", "Arcus.Observability.Telemetry.Serilog.Filters\Arcus.Observability.Telemetry.Serilog.Filters.csproj", "{ED747983-87B8-47B5-AAF8-DDFCB69E98EF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arcus.Observability.Tests.Core", "Arcus.Observability.Tests.Core\Arcus.Observability.Tests.Core.csproj", "{DC18426F-4D03-4DBD-8D92-224C026BFADC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arcus.Observability.Tests.Core", "Arcus.Observability.Tests.Core\Arcus.Observability.Tests.Core.csproj", "{DC18426F-4D03-4DBD-8D92-224C026BFADC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arcus.Observability.Telemetry.IoT", "Arcus.Observability.Telemetry.IoT\Arcus.Observability.Telemetry.IoT.csproj", "{E4A6624B-C1A4-4956-9FC2-E77D6C634717}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -59,6 +61,10 @@ Global {DC18426F-4D03-4DBD-8D92-224C026BFADC}.Debug|Any CPU.Build.0 = Debug|Any CPU {DC18426F-4D03-4DBD-8D92-224C026BFADC}.Release|Any CPU.ActiveCfg = Release|Any CPU {DC18426F-4D03-4DBD-8D92-224C026BFADC}.Release|Any CPU.Build.0 = Release|Any CPU + {E4A6624B-C1A4-4956-9FC2-E77D6C634717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4A6624B-C1A4-4956-9FC2-E77D6C634717}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4A6624B-C1A4-4956-9FC2-E77D6C634717}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4A6624B-C1A4-4956-9FC2-E77D6C634717}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE