diff --git a/schemas/dab.draft.schema.json b/schemas/dab.draft.schema.json
index bef83d32ac..1228b345b1 100644
--- a/schemas/dab.draft.schema.json
+++ b/schemas/dab.draft.schema.json
@@ -357,11 +357,44 @@
"required": [
"connection-string"
]
+ },
+ "open-telemetry": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "endpoint": {
+ "type": "string",
+ "description": "Open Telemetry connection string"
+ },
+ "headers": {
+ "type": "string",
+ "description": "Open Telemetry headers"
+ },
+ "service-name": {
+ "type": "string",
+ "description": "Open Telemetry service name",
+ "default": "dab"
+ },
+ "exporter-protocol": {
+ "type": "string",
+ "description": "Open Telemetry protocol",
+ "default": "grpc",
+ "enum": [
+ "grpc",
+ "httpprotobuf"
+ ]
+ },
+ "enabled": {
+ "type": "boolean",
+ "description": "Allow enabling/disabling Open Telemetry.",
+ "default": true
+ }
+ },
+ "required": [
+ "endpoint"
+ ]
}
- },
- "required": [
- "application-insights"
- ]
+ }
}
}
},
diff --git a/src/Cli.Tests/AddOpenTelemetryTests.cs b/src/Cli.Tests/AddOpenTelemetryTests.cs
new file mode 100644
index 0000000000..d84473980c
--- /dev/null
+++ b/src/Cli.Tests/AddOpenTelemetryTests.cs
@@ -0,0 +1,166 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Cli.Tests
+{
+ ///
+ /// Tests for verifying the functionality of adding OpenTelemetry to the config file.
+ ///
+ [TestClass]
+ public class AddOpenTelemetryTests
+ {
+ public string RUNTIME_SECTION_WITH_OPEN_TELEMETRY_SECTION = GenerateRuntimeSection(TELEMETRY_SECTION_WITH_OPEN_TELEMETRY);
+ public string RUNTIME_SECTION_WITH_EMPTY_TELEMETRY_SECTION = GenerateRuntimeSection(EMPTY_TELEMETRY_SECTION);
+ [TestInitialize]
+ public void TestInitialize()
+ {
+ ILoggerFactory loggerFactory = TestLoggerSupport.ProvisionLoggerFactory();
+
+ ConfigGenerator.SetLoggerForCliConfigGenerator(loggerFactory.CreateLogger());
+ Utils.SetCliUtilsLogger(loggerFactory.CreateLogger());
+ }
+
+ ///
+ /// Testing to check OpenTelemetry options are correctly added to the config.
+ /// Verifying scenarios such as enabling/disabling OpenTelemetry and providing a valid/empty endpoint.
+ ///
+ [DataTestMethod]
+ [DataRow(CliBool.True, "", false, DisplayName = "Fail to add OpenTelemetry with empty endpoint.")]
+ [DataRow(CliBool.True, "http://localhost:4317", true, DisplayName = "Successfully adds OpenTelemetry with valid endpoint")]
+ [DataRow(CliBool.False, "http://localhost:4317", true, DisplayName = "Successfully adds OpenTelemetry but disabled")]
+ public void TestAddOpenTelemetry(CliBool isTelemetryEnabled, string endpoint, bool expectSuccess)
+ {
+ MockFileSystem fileSystem = FileSystemUtils.ProvisionMockFileSystem();
+ string configPath = "test-opentelemetry-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 OpenTelemetry
+ bool isSuccess = ConfigGenerator.TryAddTelemetry(
+ new AddTelemetryOptions(openTelemetryEndpoint: endpoint, openTelemetryEnabled: isTelemetryEnabled, config: configPath),
+ new FileSystemRuntimeConfigLoader(fileSystem),
+ fileSystem);
+
+ // Assert after adding OpenTelemetry
+ 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.OpenTelemetry);
+ Assert.AreEqual(isTelemetryEnabled is CliBool.True ? true : false, telemetryOptions.OpenTelemetry.Enabled);
+ Assert.AreEqual(endpoint, telemetryOptions.OpenTelemetry.Endpoint);
+ }
+ }
+
+ ///
+ /// Test to verify when Telemetry section is present in the config
+ /// It should add OpenTelemetry if telemetry section is empty
+ /// or overwrite the existing OpenTelemetry with the given OpenTelemetry options.
+ ///
+ [DataTestMethod]
+ [DataRow(true, DisplayName = "Add OpenTelemetry when telemetry section is empty.")]
+ [DataRow(false, DisplayName = "Overwrite OpenTelemetry when telemetry section already exists.")]
+ public void TestAddOpenTelemetryWhenTelemetryAlreadyExists(bool isEmptyTelemetry)
+ {
+ MockFileSystem fileSystem = FileSystemUtils.ProvisionMockFileSystem();
+ string configPath = "test-opentelemetry-config.json";
+ string runtimeSection = isEmptyTelemetry ? RUNTIME_SECTION_WITH_EMPTY_TELEMETRY_SECTION : RUNTIME_SECTION_WITH_OPEN_TELEMETRY_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.OpenTelemetry);
+ }
+ else
+ {
+ Assert.IsNotNull(config.Runtime.Telemetry.OpenTelemetry);
+ Assert.AreEqual(true, config.Runtime.Telemetry.OpenTelemetry.Enabled);
+ Assert.AreEqual("http://localhost:4317", config.Runtime.Telemetry.OpenTelemetry.Endpoint);
+ }
+
+ // Add OpenTelemetry
+ bool isSuccess = ConfigGenerator.TryAddTelemetry(
+ new AddTelemetryOptions(openTelemetryEndpoint: "http://localhost:4318", openTelemetryEnabled: CliBool.False, config: configPath),
+ new FileSystemRuntimeConfigLoader(fileSystem),
+ fileSystem);
+
+ // Assert after adding OpenTelemetry
+ 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.OpenTelemetry);
+ Assert.IsFalse(config.Runtime.Telemetry.OpenTelemetry.Enabled);
+ Assert.AreEqual("http://localhost:4318", config.Runtime.Telemetry.OpenTelemetry.Endpoint);
+ }
+
+ ///
+ /// 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 Application Insights enabled and a specified connection string.
+ ///
+ private const string TELEMETRY_SECTION_WITH_OPEN_TELEMETRY = @"
+ ""telemetry"": {
+ ""open-telemetry"": {
+ ""enabled"": true,
+ ""endpoint"": ""http://localhost:4317""
+ }
+ }";
+
+ ///
+ /// Represents a JSON string for the empty telemetry section of the config.
+ ///
+ private const string EMPTY_TELEMETRY_SECTION = @"
+ ""telemetry"": {}";
+ }
+}
diff --git a/src/Cli.Tests/AddTelemetryTests.cs b/src/Cli.Tests/AddTelemetryTests.cs
index 4a1d878453..ac4fe99a8f 100644
--- a/src/Cli.Tests/AddTelemetryTests.cs
+++ b/src/Cli.Tests/AddTelemetryTests.cs
@@ -45,7 +45,7 @@ public void TestAddApplicationInsightsTelemetry(CliBool isTelemetryEnabled, stri
// Add Telemetry
bool isSuccess = ConfigGenerator.TryAddTelemetry(
- new AddTelemetryOptions(appInsightsConnString, isTelemetryEnabled, configPath),
+ new AddTelemetryOptions(appInsightsConnString: appInsightsConnString, appInsightsEnabled: isTelemetryEnabled, config: configPath),
new FileSystemRuntimeConfigLoader(fileSystem),
fileSystem);
@@ -101,7 +101,7 @@ public void TestAddAppInsightsTelemetryWhenTelemetryAlreadyExists(bool isEmptyTe
// Add Telemetry
bool isSuccess = ConfigGenerator.TryAddTelemetry(
- new AddTelemetryOptions("InstrumentationKey=11111-1111-111-11-1", CliBool.False, configPath),
+ new AddTelemetryOptions(appInsightsConnString: "InstrumentationKey=11111-1111-111-11-1", appInsightsEnabled: CliBool.False, config: configPath),
new FileSystemRuntimeConfigLoader(fileSystem),
fileSystem);
@@ -119,7 +119,7 @@ public void TestAddAppInsightsTelemetryWhenTelemetryAlreadyExists(bool isEmptyTe
///
/// Generates a JSON string representing a runtime section of the config, with a customizable telemetry section.
- ///
+ ///
private static string GenerateRuntimeSection(string telemetrySection)
{
return $@"
diff --git a/src/Cli.Tests/EndToEndTests.cs b/src/Cli.Tests/EndToEndTests.cs
index 4dd3c86d07..c1dfcbf9ce 100644
--- a/src/Cli.Tests/EndToEndTests.cs
+++ b/src/Cli.Tests/EndToEndTests.cs
@@ -291,8 +291,8 @@ public void TestAddTelemetry(string? appInsightsEnabled, string appInsightsConnS
Assert.IsNotNull(updatedConfig.Runtime.Telemetry);
Assert.IsNotNull(updatedConfig.Runtime.Telemetry.ApplicationInsights);
- // if --app-insights-enabled is not provided, it will default to true
- Assert.AreEqual(appInsightsEnabled is null ? true : Boolean.Parse(appInsightsEnabled), updatedConfig.Runtime.Telemetry.ApplicationInsights.Enabled);
+ // if --app-insights-enabled is not provided, it will default to false
+ Assert.AreEqual(appInsightsEnabled is null ? false : Boolean.Parse(appInsightsEnabled), updatedConfig.Runtime.Telemetry.ApplicationInsights.Enabled);
Assert.AreEqual("InstrumentationKey=00000000", updatedConfig.Runtime.Telemetry.ApplicationInsights.ConnectionString);
}
diff --git a/src/Cli/Commands/AddTelemetryOptions.cs b/src/Cli/Commands/AddTelemetryOptions.cs
index 7cd3d38be9..8ec9313d14 100644
--- a/src/Cli/Commands/AddTelemetryOptions.cs
+++ b/src/Cli/Commands/AddTelemetryOptions.cs
@@ -8,6 +8,7 @@
using Cli.Constants;
using CommandLine;
using Microsoft.Extensions.Logging;
+using OpenTelemetry.Exporter;
using static Cli.Utils;
namespace Cli.Commands
@@ -18,20 +19,54 @@ namespace Cli.Commands
[Verb("add-telemetry", isDefault: false, HelpText = "Add telemetry for Data Api builder Application", Hidden = false)]
public class AddTelemetryOptions : Options
{
- public AddTelemetryOptions(string appInsightsConnString, CliBool appInsightsEnabled, string? config) : base(config)
+ public AddTelemetryOptions(
+ string? appInsightsConnString = null,
+ CliBool? appInsightsEnabled = null,
+ string? openTelemetryEndpoint = null,
+ CliBool? openTelemetryEnabled = null,
+ string? openTelemetryHeaders = null,
+ OtlpExportProtocol? openTelemetryExportProtocol = null,
+ string? openTelemetryServiceName = null,
+ string? config = null) : base(config)
{
AppInsightsConnString = appInsightsConnString;
AppInsightsEnabled = appInsightsEnabled;
+ OpenTelemetryEndpoint = openTelemetryEndpoint;
+ OpenTelemetryEnabled = openTelemetryEnabled;
+ OpenTelemetryHeaders = openTelemetryHeaders;
+ OpenTelemetryExportProtocol = openTelemetryExportProtocol;
+ OpenTelemetryServiceName = openTelemetryServiceName;
}
// Connection string for the Application Insights resource to which telemetry data should be sent.
- // This option is required and must be provided with a valid connection string.
- [Option("app-insights-conn-string", Required = true, HelpText = "Connection string for the Application Insights resource for telemetry data")]
- public string AppInsightsConnString { get; }
+ // This option is required and must be provided with a valid connection string when using app insights.
+ [Option("app-insights-conn-string", Required = false, HelpText = "Connection string for the Application Insights resource for telemetry data")]
+ public string? AppInsightsConnString { get; }
- // To specify whether Application Insights telemetry should be enabled. This flag is optional and default value is true.
- [Option("app-insights-enabled", Default = CliBool.True, Required = false, HelpText = "(Default: true) Enable/Disable Application Insights")]
- public CliBool AppInsightsEnabled { get; }
+ // To specify whether Application Insights telemetry should be enabled. This flag is optional and default value is false.
+ [Option("app-insights-enabled", Default = CliBool.False, Required = false, HelpText = "(Default: false) Enable/Disable Application Insights")]
+ public CliBool? AppInsightsEnabled { get; }
+
+ // Connection string for the Open Telemetry resource to which telemetry data should be sent.
+ // This option is required and must be provided with a valid connection string when using open telemetry.
+ [Option("otel-endpoint", Required = false, HelpText = "Endpoint for Open Telemetry for telemetry data")]
+ public string? OpenTelemetryEndpoint { get; }
+
+ // To specify whether Open Telemetry telemetry should be enabled. This flag is optional and default value is false.
+ [Option("otel-enabled", Default = CliBool.False, Required = false, HelpText = "(Default: false) Enable/Disable OTEL")]
+ public CliBool? OpenTelemetryEnabled { get; }
+
+ // Headers for the Open Telemetry resource to which telemetry data should be sent.
+ [Option("otel-headers", Required = false, HelpText = "Headers for Open Telemetry for telemetry data")]
+ public string? OpenTelemetryHeaders { get; }
+
+ // Specify the Open Telemetry protocol. This flag is optional and default value is grpc.
+ [Option("otel-protocol", Default = OtlpExportProtocol.Grpc, Required = false, HelpText = "(Default: grpc) Accepted: grpc/httpprotobuf")]
+ public OtlpExportProtocol? OpenTelemetryExportProtocol { get; }
+
+ // Service Name for the Open Telemetry resource to which telemetry data should be sent. This flag is optional and default value is dab.
+ [Option("otel-service-name", Default = "dab", Required = false, HelpText = "(Default: dab) Headers for Open Telemetry for telemetry data")]
+ public string? OpenTelemetryServiceName { get; }
public int Handler(ILogger logger, FileSystemRuntimeConfigLoader loader, IFileSystem fileSystem)
{
diff --git a/src/Cli/ConfigGenerator.cs b/src/Cli/ConfigGenerator.cs
index e5d2ead2cf..8d4510f31a 100644
--- a/src/Cli/ConfigGenerator.cs
+++ b/src/Cli/ConfigGenerator.cs
@@ -619,8 +619,8 @@ private static bool TryUpdateConfiguredDataSourceOptions(
///
/// Adds CosmosDB-specific options to the provided database options dictionary.
- /// This method checks if the CosmosDB-specific options (database, container, and schema) are provided in the
- /// configuration options. If they are, it converts their names using the provided naming policy and adds them
+ /// This method checks if the CosmosDB-specific options (database, container, and schema) are provided in the
+ /// configuration options. If they are, it converts their names using the provided naming policy and adds them
/// to the database options dictionary.
///
/// The dictionary to which the CosmosDB-specific options will be added.
@@ -1939,17 +1939,40 @@ public static bool TryAddTelemetry(AddTelemetryOptions options, FileSystemRuntim
return false;
}
- if (string.IsNullOrWhiteSpace(options.AppInsightsConnString))
+ if (options.AppInsightsEnabled is CliBool.True && string.IsNullOrWhiteSpace(options.AppInsightsConnString))
{
_logger.LogError("Invalid Application Insights connection string provided.");
return false;
}
+ if (options.OpenTelemetryEnabled is CliBool.True && string.IsNullOrWhiteSpace(options.OpenTelemetryEndpoint))
+ {
+ _logger.LogError("Invalid OTEL endpoint provided.");
+ return false;
+ }
+
ApplicationInsightsOptions applicationInsightsOptions = new(
Enabled: options.AppInsightsEnabled is CliBool.True ? true : false,
ConnectionString: options.AppInsightsConnString
);
+ OpenTelemetryOptions openTelemetryOptions = new(
+ Enabled: options.OpenTelemetryEnabled is CliBool.True ? true : false,
+ Endpoint: options.OpenTelemetryEndpoint,
+ Headers: options.OpenTelemetryHeaders,
+ ExporterProtocol: options.OpenTelemetryExportProtocol,
+ ServiceName: options.OpenTelemetryServiceName
+ );
+
+ 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
diff --git a/src/Config/Azure.DataApiBuilder.Config.csproj b/src/Config/Azure.DataApiBuilder.Config.csproj
index 223f3fee86..25dd0716f9 100644
--- a/src/Config/Azure.DataApiBuilder.Config.csproj
+++ b/src/Config/Azure.DataApiBuilder.Config.csproj
@@ -25,6 +25,7 @@
+
diff --git a/src/Config/ObjectModel/OpenTelemetryOptions.cs b/src/Config/ObjectModel/OpenTelemetryOptions.cs
new file mode 100644
index 0000000000..1f24366d9d
--- /dev/null
+++ b/src/Config/ObjectModel/OpenTelemetryOptions.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using OpenTelemetry.Exporter;
+
+namespace Azure.DataApiBuilder.Config.ObjectModel;
+
+///
+/// Represents the options for configuring Open Telemetry.
+///
+public record OpenTelemetryOptions(bool Enabled = false, string? Endpoint = null, string? Headers = null, OtlpExportProtocol? ExporterProtocol = null, string? ServiceName = null)
+{ }
diff --git a/src/Config/ObjectModel/TelemetryOptions.cs b/src/Config/ObjectModel/TelemetryOptions.cs
index 6d281ea2c2..5b8dae0e3d 100644
--- a/src/Config/ObjectModel/TelemetryOptions.cs
+++ b/src/Config/ObjectModel/TelemetryOptions.cs
@@ -6,5 +6,5 @@ namespace Azure.DataApiBuilder.Config.ObjectModel;
///
/// Represents the options for telemetry.
///
-public record TelemetryOptions(ApplicationInsightsOptions? ApplicationInsights)
+public record TelemetryOptions(ApplicationInsightsOptions? ApplicationInsights = null, OpenTelemetryOptions? OpenTelemetry = null)
{ }
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 12ce71babb..c27d838e5a 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -23,6 +23,11 @@
+
+
+
+
+
diff --git a/src/Service.Tests/Configuration/OpenTelemetryTests.cs b/src/Service.Tests/Configuration/OpenTelemetryTests.cs
new file mode 100644
index 0000000000..dc98c58351
--- /dev/null
+++ b/src/Service.Tests/Configuration/OpenTelemetryTests.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.IO;
+using Azure.DataApiBuilder.Config.ObjectModel;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using OpenTelemetry.Exporter;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+using static Azure.DataApiBuilder.Service.Tests.Configuration.ConfigurationTests;
+
+namespace Azure.DataApiBuilder.Service.Tests.Configuration;
+
+///
+/// Contains tests for OpenTelemetry functionality.
+///
+[TestClass, TestCategory(TestCategory.MSSQL)]
+public class OpenTelemetryTests
+{
+ public TestContext TestContext { get; set; }
+
+ private const string CONFIG_WITH_TELEMETRY = "dab-open-telemetry-test-config.json";
+ private const string CONFIG_WITHOUT_TELEMETRY = "dab-no-open-telemetry-test-config.json";
+ private static RuntimeConfig _configuration;
+
+ ///
+ /// This is a helper function that creates runtime config file with specified telemetry options.
+ ///
+ /// Name of the config file to be created.
+ /// Whether telemetry is enabled or not.
+ /// Telemetry connection string.
+ public static void SetUpTelemetryInConfig(string configFileName, bool isOtelEnabled, string otelEndpoint, string otelHeaders, OtlpExportProtocol? otlpExportProtocol)
+ {
+ DataSource dataSource = new(DatabaseType.MSSQL,
+ GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);
+
+ _configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions: new(), restOptions: new());
+
+ TelemetryOptions _testTelemetryOptions = new(OpenTelemetry: new OpenTelemetryOptions(isOtelEnabled, otelEndpoint, otelHeaders, otlpExportProtocol, "TestServiceName"));
+ _configuration = _configuration with { Runtime = _configuration.Runtime with { Telemetry = _testTelemetryOptions } };
+
+ File.WriteAllText(configFileName, _configuration.ToJson());
+ }
+
+ ///
+ /// Cleans up the test environment by deleting the runtime config with telemetry options.
+ ///
+ [TestCleanup]
+ public void CleanUpTelemetryConfig()
+ {
+ if (File.Exists(CONFIG_WITH_TELEMETRY))
+ {
+ File.Delete(CONFIG_WITH_TELEMETRY);
+ }
+
+ if (File.Exists(CONFIG_WITHOUT_TELEMETRY))
+ {
+ File.Delete(CONFIG_WITHOUT_TELEMETRY);
+ }
+
+ Startup.OpenTelemetryOptions = new();
+ }
+
+ ///
+ /// Tests if the services are correctly enabled for Open Telemetry.
+ ///
+ [TestMethod]
+ public void TestOpenTelemetryServicesEnabled()
+ {
+ // Arrange
+ SetUpTelemetryInConfig(CONFIG_WITH_TELEMETRY, true, "http://localhost:4317", "key=key", OtlpExportProtocol.Grpc);
+
+ string[] args = new[]
+ {
+ $"--ConfigFileName={CONFIG_WITH_TELEMETRY}"
+ };
+ using TestServer server = new(Program.CreateWebHostBuilder(args));
+
+ // Additional assertions to check if OpenTelemetry is enabled correctly in services
+ IServiceProvider serviceProvider = server.Services;
+ TracerProvider tracerProvider = serviceProvider.GetService();
+ MeterProvider meterProvider = serviceProvider.GetService();
+
+ // If tracerProvider and meterProvider are not null, OTEL is enabled
+ Assert.IsNotNull(tracerProvider, "TracerProvider should be registered.");
+ Assert.IsNotNull(meterProvider, "MeterProvider should be registered.");
+ }
+
+ ///
+ /// Tests if the services are correctly disabled for Open Telemetry.
+ ///
+ [TestMethod]
+ public void TestOpenTelemetryServicesDisabled()
+ {
+ // Arrange
+ SetUpTelemetryInConfig(CONFIG_WITHOUT_TELEMETRY, false, null, null, null);
+
+ string[] args = new[]
+ {
+ $"--ConfigFileName={CONFIG_WITHOUT_TELEMETRY}"
+ };
+ using TestServer server = new(Program.CreateWebHostBuilder(args));
+
+ // Additional assertions to check if OpenTelemetry is disabled correctly in services
+ IServiceProvider serviceProvider = server.Services;
+ TracerProvider tracerProvider = serviceProvider.GetService();
+ MeterProvider meterProvider = serviceProvider.GetService();
+
+ // If tracerProvider and meterProvider are null, OTEL is disabled
+ Assert.IsNull(tracerProvider, "TracerProvider should not be registered.");
+ Assert.IsNull(meterProvider, "MeterProvider should not be registered.");
+ }
+}
diff --git a/src/Service/Azure.DataApiBuilder.Service.csproj b/src/Service/Azure.DataApiBuilder.Service.csproj
index a003c10282..e4fbddd825 100644
--- a/src/Service/Azure.DataApiBuilder.Service.csproj
+++ b/src/Service/Azure.DataApiBuilder.Service.csproj
@@ -77,7 +77,13 @@
+
+
+
+
+
+
@@ -95,11 +101,11 @@
-
+
PreserveNewest
-
+
diff --git a/src/Service/Program.cs b/src/Service/Program.cs
index 6a9e8c62df..95940414b9 100644
--- a/src/Service/Program.cs
+++ b/src/Service/Program.cs
@@ -14,6 +14,9 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.ApplicationInsights;
+using OpenTelemetry.Exporter;
+using OpenTelemetry.Logs;
+using OpenTelemetry.Resources;
namespace Azure.DataApiBuilder.Service
{
@@ -149,6 +152,22 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, Tele
.AddFilter(category: string.Empty, logLevel);
}
+ if (Startup.OpenTelemetryOptions.Enabled && !string.IsNullOrWhiteSpace(Startup.OpenTelemetryOptions.Endpoint))
+ {
+ builder.AddOpenTelemetry(logging =>
+ {
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+ logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(Startup.OpenTelemetryOptions.ServiceName!));
+ logging.AddOtlpExporter(configure =>
+ {
+ configure.Endpoint = new Uri(Startup.OpenTelemetryOptions.Endpoint);
+ configure.Headers = Startup.OpenTelemetryOptions.Headers;
+ configure.Protocol = OtlpExportProtocol.Grpc;
+ });
+ });
+ }
+
builder.AddConsole();
});
}
diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs
index cb2b301375..7350c23fe2 100644
--- a/src/Service/Startup.cs
+++ b/src/Service/Startup.cs
@@ -42,6 +42,10 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using OpenTelemetry.Exporter;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
using ZiggyCreatures.Caching.Fusion;
using CorsOptions = Azure.DataApiBuilder.Config.ObjectModel.CorsOptions;
@@ -54,6 +58,7 @@ public class Startup
public static LogLevel MinimumLogLevel = LogLevel.Error;
public static bool IsLogLevelOverriddenByCli;
+ public static OpenTelemetryOptions OpenTelemetryOptions = new();
public static ApplicationInsightsOptions AppInsightsOptions = new();
public const string NO_HTTPS_REDIRECT_FLAG = "--no-https-redirect";
@@ -92,8 +97,10 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton(configLoader);
services.AddSingleton(configProvider);
- if (configProvider.TryGetConfig(out RuntimeConfig? runtimeConfig)
- && runtimeConfig.Runtime?.Telemetry?.ApplicationInsights is not null
+ bool runtimeConfigAvailable = configProvider.TryGetConfig(out RuntimeConfig? runtimeConfig);
+
+ if (runtimeConfigAvailable
+ && runtimeConfig?.Runtime?.Telemetry?.ApplicationInsights is not null
&& runtimeConfig.Runtime.Telemetry.ApplicationInsights.Enabled)
{
// Add ApplicationTelemetry service and register
@@ -102,6 +109,38 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton();
}
+ if (runtimeConfigAvailable
+ && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
+ && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
+ {
+ services.AddOpenTelemetry()
+ .WithMetrics(metrics =>
+ {
+ metrics.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!))
+ .AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddOtlpExporter(configure =>
+ {
+ configure.Endpoint = new Uri(runtimeConfig.Runtime.Telemetry.OpenTelemetry.Endpoint!);
+ configure.Headers = runtimeConfig.Runtime.Telemetry.OpenTelemetry.Headers;
+ configure.Protocol = OtlpExportProtocol.Grpc;
+ })
+ .AddRuntimeInstrumentation();
+ })
+ .WithTracing(tracing =>
+ {
+ tracing.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!))
+ .AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddOtlpExporter(configure =>
+ {
+ configure.Endpoint = new Uri(runtimeConfig.Runtime.Telemetry.OpenTelemetry.Endpoint!);
+ configure.Headers = runtimeConfig.Runtime.Telemetry.OpenTelemetry.Headers;
+ configure.Protocol = OtlpExportProtocol.Grpc;
+ });
+ });
+ }
+
services.AddSingleton(implementationFactory: (serviceProvider) =>
{
ILoggerFactory? loggerFactory = CreateLoggerFactoryForHostedAndNonHostedScenario(serviceProvider);
@@ -433,7 +472,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuntimeC
}
///
- /// If LogLevel is NOT overridden by CLI, attempts to find the
+ /// If LogLevel is NOT overridden by CLI, attempts to find the
/// minimum log level based on host.mode in the runtime config if available.
/// Creates a logger factory with the minimum log level.
///