diff --git a/src/Cli.Tests/AddAzureLogAnalyticsTests.cs b/src/Cli.Tests/AddAzureLogAnalyticsTests.cs
new file mode 100644
index 0000000000..5af67592cb
--- /dev/null
+++ b/src/Cli.Tests/AddAzureLogAnalyticsTests.cs
@@ -0,0 +1,201 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Cli.Tests
+{
+ ///
+ /// Tests for verifying the functionality of adding AzureLogAnalytics to the config file.
+ ///
+ [TestClass]
+ public class AddAzureLogAnalyticsTests
+ {
+ public static string RUNTIME_SECTION_WITH_AZURE_LOG_ANALYTICS_SECTION = GenerateRuntimeSection(TELEMETRY_SECTION_WITH_AZURE_LOG_ANALYTICS);
+ public static string RUNTIME_SECTION_WITH_EMPTY_TELEMETRY_SECTION = GenerateRuntimeSection(EMPTY_TELEMETRY_SECTION);
+ public static string RUNTIME_SECTION_WITH_EMPTY_AUTH_SECTION = GenerateRuntimeSection(EMPTY_AUTH_TELEMETRY_SECTION);
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ ILoggerFactory loggerFactory = TestLoggerSupport.ProvisionLoggerFactory();
+
+ ConfigGenerator.SetLoggerForCliConfigGenerator(loggerFactory.CreateLogger());
+ Utils.SetCliUtilsLogger(loggerFactory.CreateLogger());
+ }
+
+ ///
+ /// Testing to check AzureLogAnalytics options are correctly added to the config.
+ /// Verifying scenarios such as enabling/disabling AzureLogAnalytics and providing a valid/empty endpoint.
+ ///
+ [DataTestMethod]
+ [DataRow(CliBool.True, "", "", "", false, DisplayName = "Fail to add AzureLogAnalytics with empty auth properties.")]
+ [DataRow(CliBool.True, "workspaceId", "dcrImmutableId", "dceEndpoint", true, DisplayName = "Successfully adds AzureLogAnalytics with valid endpoint")]
+ [DataRow(CliBool.False, "workspaceId", "dcrImmutableId", "dceEndpoint", true, DisplayName = "Successfully adds AzureLogAnalytics but disabled")]
+ public void TestAddAzureLogAnalytics(CliBool isTelemetryEnabled, string workspaceId, string dcrImmutableId, string dceEndpoint, bool expectSuccess)
+ {
+ MockFileSystem fileSystem = FileSystemUtils.ProvisionMockFileSystem();
+ string configPath = "test-azureloganalytics-config.json";
+ fileSystem.AddFile(configPath, new MockFileData(INITIAL_CONFIG));
+
+ // Initial State
+ Assert.IsTrue(fileSystem.FileExists(configPath));
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(fileSystem.File.ReadAllText(configPath), out RuntimeConfig? config));
+ Assert.IsNotNull(config);
+ Assert.IsNotNull(config.Runtime);
+ Assert.IsNull(config.Runtime.Telemetry);
+
+ // Add AzureLogAnalytics
+ bool isSuccess = ConfigGenerator.TryAddTelemetry(
+ new AddTelemetryOptions(
+ azureLogAnalyticsEnabled: isTelemetryEnabled,
+ azureLogAnalyticsWorkspaceId: workspaceId,
+ azureLogAnalyticsDcrImmutableId: dcrImmutableId,
+ azureLogAnalyticsDceEndpoint: dceEndpoint,
+ config: configPath),
+ new FileSystemRuntimeConfigLoader(fileSystem),
+ fileSystem);
+
+ // Assert after adding AzureLogAnalytics
+ Assert.AreEqual(expectSuccess, isSuccess);
+ if (expectSuccess)
+ {
+ Assert.IsTrue(fileSystem.FileExists(configPath));
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(fileSystem.File.ReadAllText(configPath), out config));
+ Assert.IsNotNull(config);
+ Assert.IsNotNull(config.Runtime);
+ Assert.IsNotNull(config.Runtime.Telemetry);
+ TelemetryOptions telemetryOptions = config.Runtime.Telemetry;
+ Assert.IsNotNull(telemetryOptions.AzureLogAnalytics);
+ Assert.AreEqual(isTelemetryEnabled is CliBool.True ? true : false, telemetryOptions.AzureLogAnalytics.Enabled);
+ Assert.IsNotNull(telemetryOptions.AzureLogAnalytics.Auth);
+ Assert.AreEqual(workspaceId, telemetryOptions.AzureLogAnalytics.Auth.WorkspaceId);
+ Assert.AreEqual(dcrImmutableId, telemetryOptions.AzureLogAnalytics.Auth.DcrImmutableId);
+ Assert.AreEqual(dceEndpoint, telemetryOptions.AzureLogAnalytics.Auth.DceEndpoint);
+ }
+ }
+
+ ///
+ /// Test to verify when Telemetry section is present in the config
+ /// It should add AzureLogAnalytics if telemetry section is empty
+ /// or overwrite the existing AzureLogAnalytics with the given AzureLogAnalytics options.
+ ///
+ [DataTestMethod]
+ [DataRow(true, DisplayName = "Add AzureLogAnalytics when telemetry section is empty.")]
+ [DataRow(false, DisplayName = "Overwrite AzureLogAnalytics when telemetry section already exists.")]
+ public void TestAddAzureLogAnalyticsWhenTelemetryAlreadyExists(bool isEmptyTelemetry)
+ {
+ MockFileSystem fileSystem = FileSystemUtils.ProvisionMockFileSystem();
+ string configPath = "test-azureloganalytics-config.json";
+ string runtimeSection = isEmptyTelemetry ? RUNTIME_SECTION_WITH_EMPTY_TELEMETRY_SECTION : RUNTIME_SECTION_WITH_AZURE_LOG_ANALYTICS_SECTION;
+ string configData = $"{{{SAMPLE_SCHEMA_DATA_SOURCE},{runtimeSection}}}";
+ fileSystem.AddFile(configPath, new MockFileData(configData));
+
+ // Initial State
+ Assert.IsTrue(fileSystem.FileExists(configPath));
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(fileSystem.File.ReadAllText(configPath), out RuntimeConfig? config));
+ Assert.IsNotNull(config);
+ Assert.IsNotNull(config.Runtime);
+ Assert.IsNotNull(config.Runtime.Telemetry);
+
+ if (isEmptyTelemetry)
+ {
+ Assert.IsNull(config.Runtime.Telemetry.AzureLogAnalytics);
+ }
+ else
+ {
+ Assert.IsNotNull(config.Runtime.Telemetry.AzureLogAnalytics);
+ Assert.AreEqual(true, config.Runtime.Telemetry.AzureLogAnalytics.Enabled);
+ Assert.IsNotNull(config.Runtime.Telemetry.AzureLogAnalytics.Auth);
+ Assert.AreEqual("workspaceId", config.Runtime.Telemetry.AzureLogAnalytics.Auth.WorkspaceId);
+ Assert.AreEqual("dcrImmutableId", config.Runtime.Telemetry.AzureLogAnalytics.Auth.DcrImmutableId);
+ Assert.AreEqual("dceEndpoint", config.Runtime.Telemetry.AzureLogAnalytics.Auth.DceEndpoint);
+ }
+
+ // Add AzureLogAnalytics
+ bool isSuccess = ConfigGenerator.TryAddTelemetry(
+ new AddTelemetryOptions(
+ azureLogAnalyticsEnabled: CliBool.False,
+ azureLogAnalyticsWorkspaceId: "newWorkspaceId",
+ azureLogAnalyticsDcrImmutableId: "newDcrImmutableId",
+ azureLogAnalyticsDceEndpoint: "newDceEndpoint",
+ config: configPath),
+ new FileSystemRuntimeConfigLoader(fileSystem),
+ fileSystem);
+
+ // Assert after adding AzureLogAnalytics
+ Assert.IsTrue(isSuccess);
+ Assert.IsTrue(fileSystem.FileExists(configPath));
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(fileSystem.File.ReadAllText(configPath), out config));
+ Assert.IsNotNull(config);
+ Assert.IsNotNull(config.Runtime);
+ Assert.IsNotNull(config.Runtime.Telemetry);
+ Assert.IsNotNull(config.Runtime.Telemetry.AzureLogAnalytics);
+ Assert.IsFalse(config.Runtime.Telemetry.AzureLogAnalytics.Enabled);
+ Assert.IsNotNull(config.Runtime.Telemetry.AzureLogAnalytics.Auth);
+ Assert.AreEqual("newWorkspaceId", config.Runtime.Telemetry.AzureLogAnalytics.Auth.WorkspaceId);
+ Assert.AreEqual("newDcrImmutableId", config.Runtime.Telemetry.AzureLogAnalytics.Auth.DcrImmutableId);
+ Assert.AreEqual("newDceEndpoint", config.Runtime.Telemetry.AzureLogAnalytics.Auth.DceEndpoint);
+ }
+
+ ///
+ /// Generates a JSON string representing a runtime section of the config, with a customizable telemetry section.
+ ///
+ private static string GenerateRuntimeSection(string telemetrySection)
+ {
+ return $@"
+ ""runtime"": {{
+ ""rest"": {{
+ ""path"": ""/api"",
+ ""enabled"": false
+ }},
+ ""graphql"": {{
+ ""path"": ""/graphql"",
+ ""enabled"": false,
+ ""allow-introspection"": true
+ }},
+ ""host"": {{
+ ""mode"": ""development"",
+ ""cors"": {{
+ ""origins"": [],
+ ""allow-credentials"": false
+ }},
+ ""authentication"": {{
+ ""provider"": ""StaticWebApps""
+ }}
+ }},
+ {telemetrySection}
+ }},
+ ""entities"": {{}}";
+ }
+
+ ///
+ /// Represents a JSON string for the telemetry section of the config, with Azure Log Analytics enabled and specified auth properties.
+ ///
+ private const string TELEMETRY_SECTION_WITH_AZURE_LOG_ANALYTICS = @"
+ ""telemetry"": {
+ ""azure-log-analytics"": {
+ ""enabled"": true,
+ ""auth"": {
+ ""workspace-id"": ""workspaceId"",
+ ""dcr-immutable-id"": ""dcrImmutableId"",
+ ""dce-endpoint"": ""dceEndpoint""
+ }
+ }
+ }";
+
+ ///
+ /// Represents a JSON string for the empty telemetry section of the config.
+ ///
+ private const string EMPTY_TELEMETRY_SECTION = @"
+ ""telemetry"": {}";
+
+ ///
+ /// Represents a JSON string for the telemetry section of the config, with Azure Log Analytics enabled and an empty auth section.
+ ///
+ private const string EMPTY_AUTH_TELEMETRY_SECTION = @"
+ ""telemetry"": {
+ ""azure-log-analytics"": {
+ ""enabled"": true,
+ ""auth"": {}
+ }
+ }";
+ }
+}
diff --git a/src/Cli.Tests/ValidateConfigTests.cs b/src/Cli.Tests/ValidateConfigTests.cs
index 6cbc4b54f1..96c4e6df11 100644
--- a/src/Cli.Tests/ValidateConfigTests.cs
+++ b/src/Cli.Tests/ValidateConfigTests.cs
@@ -311,4 +311,29 @@ public async Task TestValidateAKVOptionsWithoutEndpointFails()
JsonSchemaValidationResult result = await validator.ValidateConfigSchema(config, TEST_RUNTIME_CONFIG_FILE, mockLoggerFactory.Object);
Assert.IsFalse(result.IsValid);
}
+
+ ///
+ /// Tests that validation fails when Azure Log Analytics options are configured without the Auth options.
+ ///
+ [TestMethod]
+ public async Task TestValidateAzureLogAnalyticsOptionsWithoutAuthFails()
+ {
+ // Arrange
+ string configData = $"{{{SAMPLE_SCHEMA_DATA_SOURCE},{AddAzureLogAnalyticsTests.RUNTIME_SECTION_WITH_EMPTY_AUTH_SECTION}}}";
+ _fileSystem!.AddFile(TEST_RUNTIME_CONFIG_FILE, new MockFileData(configData));
+ Assert.IsTrue(_fileSystem!.File.Exists(TEST_RUNTIME_CONFIG_FILE));
+ Mock mockRuntimeConfigProvider = new(_runtimeConfigLoader);
+ RuntimeConfigValidator validator = new(mockRuntimeConfigProvider.Object, _fileSystem, new Mock>().Object);
+ Mock mockLoggerFactory = new();
+ Mock> mockLogger = new();
+ mockLoggerFactory
+ .Setup(factory => factory.CreateLogger(typeof(JsonConfigSchemaValidator).FullName!))
+ .Returns(mockLogger.Object);
+
+ // Assert: Settings are configured, config parses, validation fails.
+ string updatedConfig = _fileSystem!.File.ReadAllText(TEST_RUNTIME_CONFIG_FILE);
+ Assert.IsTrue(RuntimeConfigLoader.TryParseConfig(updatedConfig, out RuntimeConfig? config));
+ JsonSchemaValidationResult result = await validator.ValidateConfigSchema(config, TEST_RUNTIME_CONFIG_FILE, mockLoggerFactory.Object);
+ Assert.IsFalse(result.IsValid);
+ }
}
diff --git a/src/Cli/Commands/AddTelemetryOptions.cs b/src/Cli/Commands/AddTelemetryOptions.cs
index 8ec9313d14..d7bab309f2 100644
--- a/src/Cli/Commands/AddTelemetryOptions.cs
+++ b/src/Cli/Commands/AddTelemetryOptions.cs
@@ -27,6 +27,12 @@ public AddTelemetryOptions(
string? openTelemetryHeaders = null,
OtlpExportProtocol? openTelemetryExportProtocol = null,
string? openTelemetryServiceName = null,
+ CliBool? azureLogAnalyticsEnabled = null,
+ string? azureLogAnalyticsLogType = null,
+ int? azureLogAnalyticsFlushIntervalSeconds = null,
+ string? azureLogAnalyticsWorkspaceId = null,
+ string? azureLogAnalyticsDcrImmutableId = null,
+ string? azureLogAnalyticsDceEndpoint = null,
string? config = null) : base(config)
{
AppInsightsConnString = appInsightsConnString;
@@ -36,6 +42,12 @@ public AddTelemetryOptions(
OpenTelemetryHeaders = openTelemetryHeaders;
OpenTelemetryExportProtocol = openTelemetryExportProtocol;
OpenTelemetryServiceName = openTelemetryServiceName;
+ AzureLogAnalyticsEnabled = azureLogAnalyticsEnabled;
+ AzureLogAnalyticsLogType = azureLogAnalyticsLogType;
+ AzureLogAnalyticsFlushIntervalSeconds = azureLogAnalyticsFlushIntervalSeconds;
+ AzureLogAnalyticsWorkspaceId = azureLogAnalyticsWorkspaceId;
+ AzureLogAnalyticsDcrImmutableId = azureLogAnalyticsDcrImmutableId;
+ AzureLogAnalyticsDceEndpoint = azureLogAnalyticsDceEndpoint;
}
// Connection string for the Application Insights resource to which telemetry data should be sent.
@@ -68,6 +80,30 @@ public AddTelemetryOptions(
[Option("otel-service-name", Default = "dab", Required = false, HelpText = "(Default: dab) Headers for Open Telemetry for telemetry data")]
public string? OpenTelemetryServiceName { get; }
+ // To specify whether Azure Log Analytics telemetry should be enabled. This flag is optional and default value is false.
+ [Option("azure-log-analytics-enabled", Default = CliBool.False, Required = false, HelpText = "(Default: false) Enable/Disable Azure Log Analytics")]
+ public CliBool? AzureLogAnalyticsEnabled { get; }
+
+ // Specify the table name for Azure Log Analytics resource to which telemetry data should be sent.
+ [Option("azure-log-analytics-log-type", Required = false, HelpText = "Log Type for Azure Log Analytics to find table to send telemetry data")]
+ public string? AzureLogAnalyticsLogType { get; }
+
+ // Specify the flush interval in seconds for Azure Log Analytics resource to which telemetry data should be sent.
+ [Option("azure-log-analytics-flush-interval-seconds", Required = false, HelpText = "Flush Interval in seconds for Azure Log Analytics for specifying time it takes to send telemetry data")]
+ public int? AzureLogAnalyticsFlushIntervalSeconds { get; }
+
+ // Specify the Workspace ID for Azure Log Analytics resource to which telemetry data should be sent.
+ [Option("azure-log-analytics-auth-workspace-id", Required = false, HelpText = "Workspace ID for Azure Log Analytics used to find workspace to connect")]
+ public string? AzureLogAnalyticsWorkspaceId { get; }
+
+ // Specify the DCR Immutable ID for Azure Log Analytics resource to which telemetry data should be sent.
+ [Option("azure-log-analytics-auth-dcr-immutable-id", Required = false, HelpText = "DCR Immutable ID for Azure Log Analytics to find the data collection rule that defines how data is collected")]
+ public string? AzureLogAnalyticsDcrImmutableId { get; }
+
+ // Specify the DCE Endpoint for Azure Log Analytics resource to which telemetry data should be sent.
+ [Option("azure-log-analytics-auth-dce-endpoint", Required = false, HelpText = "DCE Endpoint for Azure Log Analytics to find table to send telemetry data")]
+ public string? AzureLogAnalyticsDceEndpoint { get; }
+
public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem)
{
logger.LogInformation("{productName} {version}", PRODUCT_NAME, ProductInfo.GetProductVersion());
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index ced4649590..3636bc1f00 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -1961,6 +1961,33 @@ public static bool TryAddTelemetry(AddTelemetryOptions options, FileSystemRuntim
return false;
}
+ if (options.AzureLogAnalyticsEnabled is CliBool.True)
+ {
+ bool returnError = false;
+ if (string.IsNullOrWhiteSpace(options.AzureLogAnalyticsWorkspaceId))
+ {
+ _logger.LogError("Invalid Azure Log Analytics workspace-id provided");
+ returnError = true;
+ }
+
+ if (string.IsNullOrWhiteSpace(options.AzureLogAnalyticsDcrImmutableId))
+ {
+ _logger.LogError("Invalid Azure Log Analytics dcr-immutable-id provided");
+ returnError = true;
+ }
+
+ if (string.IsNullOrWhiteSpace(options.AzureLogAnalyticsDceEndpoint))
+ {
+ _logger.LogError("Invalid Azure Log Analytics dce-endpoint provided");
+ returnError = true;
+ }
+
+ if (returnError)
+ {
+ return false;
+ }
+ }
+
ApplicationInsightsOptions applicationInsightsOptions = new(
Enabled: options.AppInsightsEnabled is CliBool.True ? true : false,
ConnectionString: options.AppInsightsConnString
@@ -1974,22 +2001,23 @@ public static bool TryAddTelemetry(AddTelemetryOptions options, FileSystemRuntim
ServiceName: options.OpenTelemetryServiceName
);
+ AzureLogAnalyticsOptions azureLogAnalyticsOptions = new(
+ enabled: options.AzureLogAnalyticsEnabled is CliBool.True ? true : false,
+ auth: new(
+ workspaceId: options.AzureLogAnalyticsWorkspaceId,
+ dcrImmutableId: options.AzureLogAnalyticsDcrImmutableId,
+ dceEndpoint: options.AzureLogAnalyticsDceEndpoint),
+ logType: options.AzureLogAnalyticsLogType,
+ flushIntervalSeconds: options.AzureLogAnalyticsFlushIntervalSeconds
+ );
+
runtimeConfig = runtimeConfig with
{
Runtime = runtimeConfig.Runtime with
{
Telemetry = runtimeConfig.Runtime.Telemetry is null
- ? new TelemetryOptions(ApplicationInsights: applicationInsightsOptions, OpenTelemetry: openTelemetryOptions)
- : runtimeConfig.Runtime.Telemetry with { ApplicationInsights = applicationInsightsOptions, OpenTelemetry = openTelemetryOptions }
- }
- };
- runtimeConfig = runtimeConfig with
- {
- Runtime = runtimeConfig.Runtime with
- {
- Telemetry = runtimeConfig.Runtime.Telemetry is null
- ? new TelemetryOptions(ApplicationInsights: applicationInsightsOptions)
- : runtimeConfig.Runtime.Telemetry with { ApplicationInsights = applicationInsightsOptions }
+ ? new TelemetryOptions(ApplicationInsights: applicationInsightsOptions, OpenTelemetry: openTelemetryOptions, AzureLogAnalytics: azureLogAnalyticsOptions)
+ : runtimeConfig.Runtime.Telemetry with { ApplicationInsights = applicationInsightsOptions, OpenTelemetry = openTelemetryOptions, AzureLogAnalytics = azureLogAnalyticsOptions }
}
};
diff --git a/src/Config/Converters/AzureLogAnalyticsAuthOptionsConverter.cs b/src/Config/Converters/AzureLogAnalyticsAuthOptionsConverter.cs
index 29f30c8b95..1d790b125d 100644
--- a/src/Config/Converters/AzureLogAnalyticsAuthOptionsConverter.cs
+++ b/src/Config/Converters/AzureLogAnalyticsAuthOptionsConverter.cs
@@ -73,7 +73,6 @@ public AzureLogAnalyticsAuthOptionsConverter(bool replaceEnvVar)
throw new JsonException($"Unexpected property {propertyName}");
}
}
-
}
throw new JsonException("Failed to read the Azure Log Analytics Auth Options");
diff --git a/src/Config/Converters/AzureLogAnalyticsOptionsConverterFactory.cs b/src/Config/Converters/AzureLogAnalyticsOptionsConverterFactory.cs
index c327796ad6..0121cb73f8 100644
--- a/src/Config/Converters/AzureLogAnalyticsOptionsConverterFactory.cs
+++ b/src/Config/Converters/AzureLogAnalyticsOptionsConverterFactory.cs
@@ -141,7 +141,7 @@ public override void Write(Utf8JsonWriter writer, AzureLogAnalyticsOptions value
JsonSerializer.Serialize(writer, value.Enabled, options);
}
- if (value?.Auth is not null)
+ if (value?.Auth is not null && (value.Auth.UserProvidedWorkspaceId || value.Auth.UserProvidedDcrImmutableId || value.Auth.UserProvidedDceEndpoint))
{
AzureLogAnalyticsAuthOptionsConverter authOptionsConverter = options.GetConverter(typeof(AzureLogAnalyticsAuthOptions)) as AzureLogAnalyticsAuthOptionsConverter ??
throw new JsonException("Failed to get azure-log-analytics.auth options converter");