Repository: Azure/data-api-builder
Pull request #2617: 2554 Enhance REST OTEL instrumentation with custom metrics and traces
Author: login: tommasodotNET
Created: 2025-03-14T18:01:24Z | Merged: 2025-04-23T19:09:12Z

## Why make this change?

- Closes #2554
  - Enhances OTEL instrumentation with custom traces and metrics for the REST APIs

## What is this change?

This PR enhances the OTEL instrumentation for the REST APIs by adding custom traces and metrics.

I have removed ASP NET Core standard instrumentation since it does not provide great value given the custom nature of the webservice. I have written two main Helper classes: `TelemetryMetricsHelper` and `TelemetryTracesHelper` to provide a single point of management for custom traces and metrics.

Metrics can be filtered for `status_code`, `api_type`, `endpoint` and `method`.

I have also fixed the loggings which are now sent to the configured OTEL endpoint.

### Logs
![image](https://github.com/user-attachments/assets/2b2b21ab-b16e-4678-8a3f-2c6bc3ab7168)

### Metrics
![Screenshot 2025-03-14 190730](https://github.com/user-attachments/assets/322f6111-8580-49c2-b6ff-5cddc191178c)

### Traces
![image (2)](https://github.com/user-attachments/assets/1e9f320a-7445-44d6-a1c6-b0d5eb21fa8f)
![image (3)](https://github.com/user-attachments/assets/efc754c1-a646-4500-9ebf-4f5cfba34b85)
![image (4)](https://github.com/user-attachments/assets/cadc9665-934f-48bd-a1bc-afedbf5fe395)




## How was this tested?

- [ ] Integration Tests
- [ ] Unit Tests

## Sample Request(s)

To test everything locally I recommend using [this repo](https://github.com/tommasodotNET/dab-workbench) that allows to run the local build of the dab cli and send metrics to the .NET Aspire OTEL endoint.


Linked issue #2554: 🥕⭐ [Enhancement]: Custom DAB OTEL Traces & Counters
## Now that we have OTEL, add DAB info

The current OTEL implementation only has generic/basic ASPNET information. 

## Traces:

https://github.com/Azure/data-api-builder/blob/jerry-otel-design/docs/design/otel-trace.md

```
Trace: DAB Request
│   ├── Tags:
│   │   ├── trace_id: <unique_trace_id>
│   │   ├── service.name: "Data API Builder"
│   │   ├── deployment.environment: "production" (or "development")
│
├── Activity: Request Setup
│   ├── Tags:
│   │   ├── http.method: "GET" | "POST" | "PUT" | "DELETE"
│   │   ├── action.type: "CREATE" | "READ" | "UPDATE" | "DELETE" | "EXECUTE"
│   │   ├── http.status_code: "200" | "400" | "500"
│   │   ├── http.url: "/api/entity"
│   │   ├── http.querystring: "?xyz" {only when mode = development}
│   │   ├── user.role: "Anonymous"
│   │   ├── api.type: "REST" | "GRAPHQL"
│   │   ├── api.entity: "User"
│   │   │
│   ├── Span: Validate Request Context
│   │   ├── Tags:
│   │   │   ├── validation.result: "Success" | "Failure"
│   │   ├── Event: Validation Error (if any)
│   │       ├── Tags: { error.message: "<error_details>", error.type: "<exception_type>" }
│   │
│   ├── Span: Process Request
│   │   │
│   │   ├── Event: Error (if any)
│   │       ├── Tags: { error.message: "<error_details>", error.type: "<exception_type>" }
│   │
│   ├── Span: Format Response
│   │   │
│   │   ├── Event: Response Sent
│   │       ├── Tags: { event.name: "Response Sent", timestamp: <time> }
│
└── Activity: Database Query
    ├── Tags:
    │   ├── data-source.name: {data-source.telemetry.name}
    │   ├── data-source.type: "mssql" | "mysql" | "postgresql" | "cosmos" | "sqldw"
    │   ├── db.operation: "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "EXECUTE"
    │   ├── db.statement: "<SQL_query_text>" {only when mode = development}
    │
    ├── Span: Data Operation
    │   │   ├── query.rows_returned: <num_rows>
    │   │   ├── query.mb_returned: <double>
    │   │   ├── query.used_cache: true | false
    │   │
    │   ├── Event: Cache Returned
    │   │
    │   ├── Event: Connection Opened
    │   │
    │   ├── Event: Query Executed
    │   │
    │   ├── Event: Connection Opened
    │   │
    │   ├── Event: Error (if any)
    │       ├── Tags: { error.message: "<error_details>", error.type: "<exception_type>" }
```

## Metrics:

#### TelemetryHelper.cs

```csharp
using System;
using System.Diagnostics.Metrics;

public static class TelemetryHelper
{
    private static readonly Meter Meter = new("Dab.Metrics");
    private static readonly Counter<long> RequestsTotal = Meter.CreateCounter<long>("requests_total");
    private static readonly UpDownCounter<long> RequestsActive = Meter.CreateUpDownCounter<long>("requests_active");
    private static readonly Counter<long> RequestErrors = Meter.CreateCounter<long>("request_errors");

    public static void TrackRequest(string method, string statusCode, string endpoint, string apiType)
    {
        RequestsTotal.Add(1, 
            new("method", method), 
            new("status_code", statusCode), 
            new("endpoint", endpoint), 
            new("api_type", apiType));
    }

    public static void TrackError(string method, string statusCode, string endpoint, string apiType, Exception ex)
    {
        RequestErrors.Add(1, 
            new("method", method), 
            new("status_code", statusCode), 
            new("endpoint", endpoint), 
            new("api_type", apiType), 
            new("error_type", ex.GetType().Name));
    }

    public static void IncrementActiveRequests() => RequestsActive.Add(1);

    public static void DecrementActiveRequests() => RequestsActive.Add(-1);
}
```

Then use it like.

```csharp
[HttpGet]
public IActionResult GetAsync(...)
{
    TelemetryHelper.IncrementActiveRequests(); 

   try
    {
        // Simulating success
        TelemetryHelper.TrackRequest("GET", "200", "User", "REST");
        return Ok(new { Message = "Hello, World!" });
    }
    catch (Exception ex)
    {
        TelemetryHelper.TrackError("GET", "500", "User", "REST", ex);
        return StatusCode(500, new { Error = "An error occurred" });
    }
    finally
    {
        TelemetryHelper.DecrementActiveRequests();
    }
}
```
Linked issue comments: user: login: jodydonetti
created_at: 2025-04-19T22:16:22Z
body: FusionCache itself has [full support](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/OpenTelemetry.md) for OTEL, in case it may be interesting.
author_association: COLLABORATOR

Diff:
diff --git a/src/Core/Resolvers/SqlQueryEngine.cs b/src/Core/Resolvers/SqlQueryEngine.cs
index 53547a96a3..872b733f60 100644
--- a/src/Core/Resolvers/SqlQueryEngine.cs
+++ b/src/Core/Resolvers/SqlQueryEngine.cs
@@ -332,6 +332,7 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta
                     JsonElement result = await _cache.GetOrSetAsync<JsonElement>(queryExecutor, queryMetadata, cacheEntryTtl: runtimeConfig.GetEntityCacheEntryTtl(entityName: structure.EntityName));
                     byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(result);
                     JsonDocument cacheServiceResponse = JsonDocument.Parse(jsonBytes);
+
                     return cacheServiceResponse;
                 }
             }
@@ -348,6 +349,7 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta
                 httpContext: _httpContextAccessor.HttpContext!,
                 args: null,
                 dataSourceName: dataSourceName);
+
             return response;
         }
 
diff --git a/src/Service.Tests/Configuration/OpenTelemetryTests.cs b/src/Service.Tests/Configuration/OpenTelemetryTests.cs
index dc98c58351..166dc7b001 100644
--- a/src/Service.Tests/Configuration/OpenTelemetryTests.cs
+++ b/src/Service.Tests/Configuration/OpenTelemetryTests.cs
@@ -61,7 +61,6 @@ public void CleanUpTelemetryConfig()
             File.Delete(CONFIG_WITHOUT_TELEMETRY);
         }
 
-        Startup.OpenTelemetryOptions = new();
     }
 
     /// <summary>
diff --git a/src/Service.Tests/dab-config.CosmosDb_NoSql.json b/src/Service.Tests/dab-config.CosmosDb_NoSql.json
index c55314e592..5704dc19be 100644
--- a/src/Service.Tests/dab-config.CosmosDb_NoSql.json
+++ b/src/Service.Tests/dab-config.CosmosDb_NoSql.json
@@ -31,6 +31,12 @@
         "provider": "StaticWebApps"
       },
       "mode": "development"
+    },
+    "telemetry": {
+      "app-insights": {
+        "enabled": true,
+        "connection-string": "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://dc.services.visualstudio.com/v2/track"
+      }
     }
   },
   "entities": {
@@ -794,4 +800,4 @@
       ]
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/Service/Azure.DataApiBuilder.Service.csproj b/src/Service/Azure.DataApiBuilder.Service.csproj
index e4fbddd825..cb7a1a1644 100644
--- a/src/Service/Azure.DataApiBuilder.Service.csproj
+++ b/src/Service/Azure.DataApiBuilder.Service.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
+﻿<Project Sdk="Microsoft.NET.Sdk.Web">
 
     <PropertyGroup>
         <TargetFrameworks>net8.0</TargetFrameworks>
@@ -77,7 +77,7 @@
         <PackageReference Include="System.CommandLine" />
         <PackageReference Include="System.IO.Abstractions" />
         <PackageReference Include="ZiggyCreatures.FusionCache" />
-        <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol"  />
+        <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
         <PackageReference Include="OpenTelemetry.Extensions.Hosting" />
         <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
         <PackageReference Include="OpenTelemetry.Instrumentation.Http" />
diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs
index b570c91406..10e1f4263d 100644
--- a/src/Service/Controllers/RestController.cs
+++ b/src/Service/Controllers/RestController.cs
@@ -2,15 +2,20 @@
 // Licensed under the MIT License.
 
 using System;
+using System.Diagnostics;
+using System.Linq;
 using System.Net;
 using System.Net.Mime;
 using System.Threading.Tasks;
 using Azure.DataApiBuilder.Config.ObjectModel;
+using Azure.DataApiBuilder.Core.Configurations;
 using Azure.DataApiBuilder.Core.Models;
 using Azure.DataApiBuilder.Core.Services;
 using Azure.DataApiBuilder.Service.Exceptions;
+using Azure.DataApiBuilder.Service.Telemetry;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
 using Microsoft.Extensions.Logging;
 
 namespace Azure.DataApiBuilder.Service.Controllers
@@ -47,11 +52,14 @@ public class RestController : ControllerBase
 
         private readonly ILogger<RestController> _logger;
 
+        private readonly RuntimeConfigProvider _runtimeConfigProvider;
+
         /// <summary>
         /// Constructor.
         /// </summary>
-        public RestController(RestService restService, IOpenApiDocumentor openApiDocumentor, ILogger<RestController> logger)
+        public RestController(RuntimeConfigProvider runtimeConfigProvider, RestService restService, IOpenApiDocumentor openApiDocumentor, ILogger<RestController> logger)
         {
+            _runtimeConfigProvider = runtimeConfigProvider;
             _restService = restService;
             _openApiDocumentor = openApiDocumentor;
             _logger = logger;
@@ -185,11 +193,29 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
+            if (route.Equals(REDIRECTED_ROUTE))
+            {
+                return NotFound();
+            }
+
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            // This activity tracks the entire REST request.
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {(route.Split('/').Length > 1 ? route.Split('/')[1] : string.Empty)}");
+
             try
             {
-                if (route.Equals(REDIRECTED_ROUTE))
+                TelemetryMetricsHelper.IncrementActiveRequests(ApiType.REST);
+
+                if (activity is not null)
                 {
-                    return NotFound();
+                    activity.TrackRestControllerActivityStarted(
+                        Enum.Parse<HttpMethod>(HttpContext.Request.Method, ignoreCase: true),
+                        HttpContext.Request.Headers["User-Agent"].ToString(),
+                        operationType.ToString(),
+                        route,
+                        HttpContext.Request.QueryString.ToString(),
+                        HttpContext.Request.Headers["X-MS-API-ROLE"].FirstOrDefault() ?? HttpContext.User.FindFirst("role")?.Value,
+                        ApiType.REST);
                 }
 
                 // Validate the PathBase matches the configured REST path.
@@ -208,8 +234,21 @@ private async Task<IActionResult> HandleOperation(
 
                 (string entityName, string primaryKeyRoute) = _restService.GetEntityNameAndPrimaryKeyRouteFromRoute(routeAfterPathBase);
 
+                // This activity tracks the query execution. This will create a new activity nested under the REST request activity.
+                using Activity? queryActivity = TelemetryTracesHelper.DABActivitySource.StartActivity($"QUERY {entityName}");
                 IActionResult? result = await _restService.ExecuteAsync(entityName, operationType, primaryKeyRoute);
 
+                RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig();
+                string dataSourceName = runtimeConfig.GetDataSourceNameFromEntityName(entityName);
+                DatabaseType databaseType = runtimeConfig.GetDataSourceFromDataSourceName(dataSourceName).DatabaseType;
+
+                if (queryActivity is not null)
+                {
+                    queryActivity.TrackQueryActivityStarted(
+                        databaseType,
+                        dataSourceName);
+                }
+
                 if (result is null)
                 {
                     throw new DataApiBuilderException(
@@ -218,6 +257,13 @@ private async Task<IActionResult> HandleOperation(
                         subStatusCode: DataApiBuilderException.SubStatusCodes.EntityNotFound);
                 }
 
+                int statusCode = (result as ObjectResult)?.StatusCode ?? (result as StatusCodeResult)?.StatusCode ?? (result as JsonResult)?.StatusCode ?? 200;
+                if (activity is not null && activity.IsAllDataRequested)
+                {
+                    HttpStatusCode httpStatusCode = Enum.Parse<HttpStatusCode>(statusCode.ToString(), ignoreCase: true);
+                    activity.TrackRestControllerActivityFinished(httpStatusCode);
+                }
+
                 return result;
             }
             catch (DataApiBuilderException ex)
@@ -228,6 +274,10 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)ex.StatusCode;
+                activity?.TrackRestControllerActivityFinishedWithException(ex, ex.StatusCode);
+
+                HttpMethod method = Enum.Parse<HttpMethod>(HttpContext.Request.Method, ignoreCase: true);
+                TelemetryMetricsHelper.TrackError(method, ex.StatusCode, route, ApiType.REST, ex);
                 return ErrorResponse(ex.SubStatusCode.ToString(), ex.Message, ex.StatusCode);
             }
             catch (Exception ex)
@@ -238,11 +288,26 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+
+                HttpMethod method = Enum.Parse<HttpMethod>(HttpContext.Request.Method, ignoreCase: true);
+                activity?.TrackRestControllerActivityFinishedWithException(ex, HttpStatusCode.InternalServerError);
+
+                TelemetryMetricsHelper.TrackError(method, HttpStatusCode.InternalServerError, route, ApiType.REST, ex);
                 return ErrorResponse(
                     DataApiBuilderException.SubStatusCodes.UnexpectedError.ToString(),
                     SERVER_ERROR,
                     HttpStatusCode.InternalServerError);
             }
+            finally
+            {
+                stopwatch.Stop();
+                HttpMethod method = Enum.Parse<HttpMethod>(HttpContext.Request.Method, ignoreCase: true);
+                HttpStatusCode httpStatusCode = Enum.Parse<HttpStatusCode>(Response.StatusCode.ToString(), ignoreCase: true);
+                TelemetryMetricsHelper.TrackRequest(method, httpStatusCode, route, ApiType.REST);
+                TelemetryMetricsHelper.TrackRequestDuration(method, httpStatusCode, route, ApiType.REST, stopwatch.Elapsed);
+
+                TelemetryMetricsHelper.DecrementActiveRequests(ApiType.REST);
+            }
         }
 
         /// <summary>
diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs
index a871d3354f..a0d3ef3393 100644
--- a/src/Service/Startup.cs
+++ b/src/Service/Startup.cs
@@ -61,9 +61,9 @@ public class Startup
         public static LogLevel MinimumLogLevel = LogLevel.Error;
 
         public static bool IsLogLevelOverriddenByCli;
-        public static OpenTelemetryOptions OpenTelemetryOptions = new();
 
         public static ApplicationInsightsOptions AppInsightsOptions = new();
+        public static OpenTelemetryOptions OpenTelemetryOptions = new();
         public const string NO_HTTPS_REDIRECT_FLAG = "--no-https-redirect";
         private HotReloadEventHandler<HotReloadEventArgs> _hotReloadEventHandler = new();
         private RuntimeConfigProvider? _configProvider;
@@ -119,31 +119,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
+                {
+                    logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!))
+                    .AddOtlpExporter(configure =>
+                    {
+                        configure.Endpoint = new Uri(runtimeConfig.Runtime.Telemetry.OpenTelemetry.Endpoint!);
+                        configure.Headers = runtimeConfig.Runtime.Telemetry.OpenTelemetry.Headers;
+                        configure.Protocol = OtlpExportProtocol.Grpc;
+                    });
+
+                })
                 .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();
+                        .AddMeter(TelemetryMetricsHelper.MeterName);
                 })
                 .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;
-                        });
+                        })
+                        .AddSource(TelemetryTracesHelper.DABActivitySource.Name);
                 });
             }
 
@@ -395,6 +410,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuntimeC
             {
                 // Configure Application Insights Telemetry
                 ConfigureApplicationInsightsTelemetry(app, runtimeConfig);
+                ConfigureOpenTelemetry(runtimeConfig);
 
                 // Config provided before starting the engine.
                 isRuntimeReady = PerformOnConfigChangeAsync(app).Result;
@@ -712,6 +728,37 @@ private void ConfigureApplicationInsightsTelemetry(IApplicationBuilder app, Runt
             }
         }
 
+        /// <summary>
+        /// Configure Open Telemetry based on the loaded runtime configuration. If Open Telemetry
+        /// is enabled, we can track different events and metrics.
+        /// </summary>
+        /// <param name="runtimeConfigurationProvider">The provider used to load runtime configuration.</param>
+        /// <seealso cref="https://docs.microsoft.com/en-us/azure/azure-monitor/app/asp-net-core#enable-application-insights-telemetry-collection"/>
+        private void ConfigureOpenTelemetry(RuntimeConfig runtimeConfig)
+        {
+            if (runtimeConfig?.Runtime?.Telemetry is not null
+                && runtimeConfig.Runtime.Telemetry.OpenTelemetry is not null)
+            {
+                OpenTelemetryOptions = runtimeConfig.Runtime.Telemetry.OpenTelemetry;
+
+                if (!OpenTelemetryOptions.Enabled)
+                {
+                    _logger.LogInformation("Open Telemetry are disabled.");
+                    return;
+                }
+
+                if (string.IsNullOrWhiteSpace(OpenTelemetryOptions?.Endpoint))
+                {
+                    _logger.LogWarning("Logs won't be sent to Open Telemetry because an Open Telemetry connection string is not available in the runtime config.");
+                    return;
+                }
+
+                // Updating Startup Logger to Log from Startup Class.
+                ILoggerFactory? loggerFactory = Program.GetLoggerFactoryForLogLevel(MinimumLogLevel);
+                _logger = loggerFactory.CreateLogger<Startup>();
+            }
+        }
+
         /// <summary>
         /// Sets Static Web Apps EasyAuth as the authentication scheme for the engine.
         /// </summary>
diff --git a/src/Service/Telemetry/TelemetryMetricsHelper.cs b/src/Service/Telemetry/TelemetryMetricsHelper.cs
new file mode 100644
index 0000000000..c8e61032b3
--- /dev/null
+++ b/src/Service/Telemetry/TelemetryMetricsHelper.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Metrics;
+using System.Net;
+using Azure.DataApiBuilder.Config.ObjectModel;
+using Kestral = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    /// <summary>
+    /// Helper class for tracking telemetry metrics such as active requests, errors, total requests,
+    /// and request durations using the .NET Meter and Counter APIs.
+    /// </summary>
+    public static class TelemetryMetricsHelper
+    {
+        public static readonly string MeterName = "DataApiBuilder.Metrics";
+        private static readonly Meter _meter = new(MeterName);
+        private static readonly UpDownCounter<long> _activeRequests = _meter.CreateUpDownCounter<long>("active_requests");
+        private static readonly Counter<long> _errorCounter = _meter.CreateCounter<long>("total_errors");
+        private static readonly Counter<long> _totalRequests = _meter.CreateCounter<long>("total_requests");
+        private static readonly Histogram<double> _requestDuration = _meter.CreateHistogram<double>("request_duration", "ms");
+
+        public static void IncrementActiveRequests(ApiType kind) => _activeRequests.Add(1, new KeyValuePair<string, object?>("api_type", kind));
+
+        public static void DecrementActiveRequests(ApiType kind) => _activeRequests.Add(-1, new KeyValuePair<string, object?>("api_type", kind));
+
+        /// <summary>
+        /// Tracks a request by incrementing the total requests counter and associating it with metadata.
+        /// </summary>
+        /// <param name="method">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        /// <param name="endpoint">The endpoint being accessed.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        public static void TrackRequest(Kestral method, HttpStatusCode statusCode, string endpoint, ApiType apiType)
+        {
+            _totalRequests.Add(1,
+                new("method", method),
+                new("status_code", statusCode),
+                new("endpoint", endpoint),
+                new("api_type", apiType));
+        }
+
+        /// <summary>
+        /// Tracks an error by incrementing the error counter and associating it with metadata.
+        /// </summary>
+        /// <param name="method">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        /// <param name="endpoint">The endpoint being accessed.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        /// <param name="ex">The exception that occurred.</param>
+        public static void TrackError(Kestral method, HttpStatusCode statusCode, string endpoint, ApiType apiType, Exception ex)
+        {
+            _errorCounter.Add(1,
+                new("method", method),
+                new("status_code", statusCode),
+                new("endpoint", endpoint),
+                new("api_type", apiType),
+                new("error_type", ex.GetType().Name));
+        }
+
+        /// <summary>
+        /// Tracks the duration of a request by recording it in a histogram and associating it with metadata.
+        /// </summary>
+        /// <param name="method">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        /// <param name="endpoint">The endpoint being accessed.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        /// <param name="duration">The duration of the request in milliseconds.</param>
+        public static void TrackRequestDuration(Kestral method, HttpStatusCode statusCode, string endpoint, ApiType apiType, TimeSpan duration)
+        {
+            _requestDuration.Record(duration.TotalMilliseconds,
+                new("method", method),
+                new("status_code", statusCode),
+                new("endpoint", endpoint),
+                new("api_type", apiType));
+        }
+    }
+}
diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs
new file mode 100644
index 0000000000..33a6029f43
--- /dev/null
+++ b/src/Service/Telemetry/TelemetryTracesHelper.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using System.Net;
+using Azure.DataApiBuilder.Config.ObjectModel;
+using OpenTelemetry.Trace;
+using Kestral = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        /// <summary>
+        /// Activity source for Data API Builder telemetry.
+        /// </summary>
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        /// <summary>
+        /// Tracks the start of a REST controller activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="httpMethod">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="userAgent">The user agent string from the request.</param>
+        /// <param name="actionType">The type of action being performed (e.g. Read).</param>
+        /// <param name="httpURL">The URL of the request.</param>
+        /// <param name="queryString">The query string of the request, if any.</param>
+        /// <param name="userRole">The role of the user making the request.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        public static void TrackRestControllerActivityStarted(
+            this Activity activity,
+            Kestral httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            ApiType apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (!string.IsNullOrEmpty(queryString))
+                {
+                    activity.SetTag("http.querystring", queryString);
+                }
+
+                if (!string.IsNullOrEmpty(userRole))
+                {
+                    activity.SetTag("user.role", userRole);
+                }
+
+                activity.SetTag("api.type", apiType);
+            }
+        }
+
+        /// <summary>
+        /// Tracks the start of a query activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="databaseType">The type of database being queried.</param>
+        /// <param name="dataSourceName">The name of the data source being queried.</param>
+        public static void TrackQueryActivityStarted(
+            this Activity activity,
+            DatabaseType databaseType,
+            string dataSourceName)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("data-source.type", databaseType);
+                activity.SetTag("data-source.name", dataSourceName);
+            }
+
+        }
+
+        /// <summary>
+        /// Tracks the completion of a REST controller activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        public static void TrackRestControllerActivityFinished(
+            this Activity activity,
+            HttpStatusCode statusCode)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("status.code", statusCode);
+            }
+        }
+
+        /// <summary>
+        /// Tracks the completion of a REST controller activity with an exception.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="ex">The exception that occurred.</param>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        public static void TrackRestControllerActivityFinishedWithException(
+            this Activity activity,
+            Exception ex,
+            HttpStatusCode statusCode)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetStatus(Status.Error.WithDescription(ex.Message));
+                activity.RecordException(ex);
+                activity.SetTag("error.type", ex.GetType().Name);
+                activity.SetTag("error.message", ex.Message);
+                activity.SetTag("status.code", statusCode);
+            }
+        }
+    }
+}


Reviews:
user: login: aaronpowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T01:18:15Z
user: login: sezal98
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T09:50:09Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-31T15:32:28Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T21:07:49Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:06:43Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:07:49Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T10:01:02Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T14:36:07Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T14:38:05Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T14:47:13Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T16:46:42Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T17:19:52Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T17:22:46Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T17:26:39Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T17:28:59Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T22:08:02Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T23:23:22Z
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 7 out of 8 changed files in this pull request and generated no comments.

<details>
<summary>Files not reviewed (1)</summary>

* **src/Service/Azure.DataApiBuilder.Service.csproj**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/Service/Telemetry/TelemetryTracesHelper.cs:94**
* The method name 'TrackRestControllerActivityFinishedWithWithException' contains a duplicate 'With'. Consider renaming it to 'TrackRestControllerActivityFinishedWithException' for clarity.
```
public static void TrackRestControllerActivityFinishedWithWithException(
```
**src/Service/Controllers/RestController.cs:249**
* Manual disposal of 'queryActivity' is redundant since it is declared within a 'using' block, which will automatically dispose it. Removing the explicit call can prevent potential unexpected behavior from double disposal.
```
if (queryActivity is not null && queryActivity.IsAllDataRequested)
                    {
                        queryActivity.Dispose();
                    }
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T17:02:53Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T19:37:22Z
user: login: RubenCerna2079
body: LGTM! I just left one more comment. Thank you for working on this feature, it is really cool.
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T21:01:09Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T22:04:00Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T22:05:18Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T22:06:00Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T22:18:43Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T22:24:21Z
user: login: Aniruddh25
body: LGTM, Thank you for adding this support!
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-04-07T23:10:52Z
user: login: tommasodotNET
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:03:24Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T01:38:37Z
user: login: Aniruddh25
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T22:56:13Z
user: login: Aniruddh25
body: LGTM, given all tests are passing. This has come a long way! 

ship it!!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-22T23:01:20Z
user: login: RubenCerna2079
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T02:04:27Z
user: login: Aniruddh25
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-23T04:35:44Z
user: login: RubenCerna2079
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T19:08:58Z

Review comments:
diff_hunk: @@ -231,6 +277,12 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)ex.StatusCode;
+                if (activity is not null)
+                {
+                    activity.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode);
+                }
path: src/Service/Controllers/RestController.cs
user: login: aaronpowell
body: Can't this be simplified as:

```suggestion
                activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode);
```
created_at: 2025-03-17T01:15:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -241,11 +293,28 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+                if(activity is not null)
+                {
+                    activity.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode);
+                }
path: src/Service/Controllers/RestController.cs
user: login: aaronpowell
body: Can't this be simplified as:

```suggestion
                activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode);
```
created_at: 2025-03-17T01:15:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,14 +185,30 @@ public async Task<IActionResult> UpsertIncremental(
         /// <summary>
         /// Handle the given operation.
         /// </summary>
+        /// <param name="method">The method.</param>
         /// <param name="route">The entire route.</param>
         /// <param name="operationType">The kind of operation to handle.</param>
         private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
-            try
+            var stopwatch = Stopwatch.StartNew();
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}");
path: src/Service/Controllers/RestController.cs
user: login: sezal98
body: Can do some error handling for `route.Split('/')[1]`. Like what if the expected value is not received
created_at: 2025-03-27T09:44:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,14 +185,30 @@ public async Task<IActionResult> UpsertIncremental(
         /// <summary>
         /// Handle the given operation.
         /// </summary>
+        /// <param name="method">The method.</param>
path: src/Service/Controllers/RestController.cs
user: login: sezal98
body: Is this added in the function parameters?
created_at: 2025-03-27T09:44:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,14 +185,30 @@ public async Task<IActionResult> UpsertIncremental(
         /// <summary>
         /// Handle the given operation.
         /// </summary>
+        /// <param name="method">The method.</param>
path: src/Service/Controllers/RestController.cs
user: login: tommasodotNET
body: I've deleted. Should not have been there.
created_at: 2025-03-31T14:45:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,14 +185,30 @@ public async Task<IActionResult> UpsertIncremental(
         /// <summary>
         /// Handle the given operation.
         /// </summary>
+        /// <param name="method">The method.</param>
         /// <param name="route">The entire route.</param>
         /// <param name="operationType">The kind of operation to handle.</param>
         private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
-            try
+            var stopwatch = Stopwatch.StartNew();
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}");
path: src/Service/Controllers/RestController.cs
user: login: tommasodotNET
body: Added null check
created_at: 2025-03-31T14:45:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,8 +191,23 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
-            try
+            var stopwatch = Stopwatch.StartNew();
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: I don't think we should avoid the use of `var` if it is possible 

```suggestion
            Stopwatch stopwatch = Stopwatch.StartNew();
```
created_at: 2025-04-02T17:25:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,8 +191,23 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
-            try
+            var stopwatch = Stopwatch.StartNew();
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route?.Split('/')[1]}");
+            if(activity is not null)
             {
+                activity.TrackRestControllerActivityStarted(HttpContext.Request.Method,
+                    HttpContext.Request.Headers["User-Agent"].ToString(),
+                    operationType.ToString(),
+                    route,
+                    HttpContext.Request.QueryString.ToString(),
+                    HttpContext.User.FindFirst("role")?.Value,
+                    "REST");
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: 
```suggestion
                activity.TrackRestControllerActivityStarted(
                    HttpContext.Request.Method,
                    HttpContext.Request.Headers["User-Agent"].ToString(),
                    operationType.ToString(),
                    route,
                    HttpContext.Request.QueryString.ToString(),
                    HttpContext.User.FindFirst("role")?.Value,
                    "REST");
```
created_at: 2025-04-02T17:27:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,14 +185,30 @@ public async Task<IActionResult> UpsertIncremental(
         /// <summary>
         /// Handle the given operation.
         /// </summary>
+        /// <param name="method">The method.</param>
         /// <param name="route">The entire route.</param>
         /// <param name="operationType">The kind of operation to handle.</param>
         private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
-            try
+            var stopwatch = Stopwatch.StartNew();
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}");
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: I don't think doing a null check would be the right thing to do, since the value that is passed is not null. Maybe you could corroborate this @sezal98?
created_at: 2025-04-02T17:35:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -211,8 +232,25 @@ private async Task<IActionResult> HandleOperation(
 
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: Please update the title and the description of the PR to reflect that the changes being made are only for the REST endpoint.
created_at: 2025-04-02T18:53:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,8 +191,23 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
-            try
+            var stopwatch = Stopwatch.StartNew();
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route?.Split('/')[1]}");
+            if(activity is not null)
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: Could you add a comment to explain what this activity is being used for?
created_at: 2025-04-02T18:55:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -211,8 +232,25 @@ private async Task<IActionResult> HandleOperation(
 
                 (string entityName, string primaryKeyRoute) = _restService.GetEntityNameAndPrimaryKeyRouteFromRoute(routeAfterPathBase);
 
+                using Activity? queryActivity = TelemetryTracesHelper.DABActivitySource.StartActivity($"QUERY {entityName}");
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: Could you add a comment explaining what this activity is being used for and what the difference is from the other activity?
created_at: 2025-04-02T18:56:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,6 +13,7 @@
 using Microsoft.AspNetCore;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
path: src/Service/Program.cs
user: login: RubenCerna2079
body: Why was this library added?
created_at: 2025-04-02T19:00:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,22 +162,6 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, Tele
                         .AddFilter<ApplicationInsightsLoggerProvider>(category: string.Empty, logLevel);
                     }
 
path: src/Service/Program.cs
user: login: RubenCerna2079
body: I'm confused, why was this section deleted?
created_at: 2025-04-02T19:00:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,31 +117,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
path: src/Service/Startup.cs
user: login: RubenCerna2079
body: I'm not familiar with OTEL, can you explain what is the reason to move the open telemetry out of the logger factory and add it all as a separate service?
created_at: 2025-04-02T20:19:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,31 +117,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
+                {
+                    logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!))
+                    .AddOtlpExporter(configure =>
+                    {
+                        configure.Endpoint = new Uri(runtimeConfig.Runtime.Telemetry.OpenTelemetry.Endpoint!);
+                        configure.Headers = runtimeConfig.Runtime.Telemetry.OpenTelemetry.Headers;
+                        configure.Protocol = OtlpExportProtocol.Grpc;
+                    });
+
+                })
                 .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();
+                        .AddMeter(TelemetryMetricsHelper.MeterName);
path: src/Service/Startup.cs
user: login: RubenCerna2079
body: I understand that we have to use `AddMeter`, but does that mean we have to get rid of `AddRuntimeInstrumentation`?
created_at: 2025-04-02T20:33:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics.Metrics;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryMetricsHelper
+    {
+        public static readonly string MeterName = "DataApiBuilder.Metrics";
+        private static readonly Meter _meter = new(MeterName);
+        private static readonly Counter<long> _activeRequests = _meter.CreateCounter<long>("active_requests");
+        private static readonly Counter<long> _errorCounter = _meter.CreateCounter<long>("total_errors");
+        private static readonly Counter<long> _totalRequests = _meter.CreateCounter<long>("total_requests");
+        private static readonly Histogram<double> _requestDuration = _meter.CreateHistogram<double>("request_duration", "ms");
+
+        public static void IncrementActiveRequests() => _activeRequests.Add(1);
+
+        public static void DecrementActiveRequests() => _activeRequests.Add(-1);
+
+        public static void TrackRequest(string method, int statusCode, string endpoint, string apiType)
path: src/Service/Telemetry/TelemetryMetricsHelper.cs
user: login: RubenCerna2079
body: I think it would be good to add comments for the functions in order to have more context
created_at: 2025-04-02T20:41:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics.Metrics;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryMetricsHelper
+    {
+        public static readonly string MeterName = "DataApiBuilder.Metrics";
+        private static readonly Meter _meter = new(MeterName);
+        private static readonly Counter<long> _activeRequests = _meter.CreateCounter<long>("active_requests");
+        private static readonly Counter<long> _errorCounter = _meter.CreateCounter<long>("total_errors");
+        private static readonly Counter<long> _totalRequests = _meter.CreateCounter<long>("total_requests");
+        private static readonly Histogram<double> _requestDuration = _meter.CreateHistogram<double>("request_duration", "ms");
+
+        public static void IncrementActiveRequests() => _activeRequests.Add(1);
+
+        public static void DecrementActiveRequests() => _activeRequests.Add(-1);
+
+        public static void TrackRequest(string method, int statusCode, string endpoint, string apiType)
+        {
+            _totalRequests.Add(1,
+                new("method", method),
+                new("status_code", statusCode),
+                new("endpoint", endpoint),
+                new("api_type", apiType));
+        }
+
+        public static void TrackError(string method, int statusCode, string endpoint, string apiType, Exception ex)
path: src/Service/Telemetry/TelemetryMetricsHelper.cs
user: login: RubenCerna2079
body: Same as comment above
created_at: 2025-04-02T20:42:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics.Metrics;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryMetricsHelper
+    {
+        public static readonly string MeterName = "DataApiBuilder.Metrics";
+        private static readonly Meter _meter = new(MeterName);
+        private static readonly Counter<long> _activeRequests = _meter.CreateCounter<long>("active_requests");
+        private static readonly Counter<long> _errorCounter = _meter.CreateCounter<long>("total_errors");
+        private static readonly Counter<long> _totalRequests = _meter.CreateCounter<long>("total_requests");
+        private static readonly Histogram<double> _requestDuration = _meter.CreateHistogram<double>("request_duration", "ms");
+
+        public static void IncrementActiveRequests() => _activeRequests.Add(1);
+
+        public static void DecrementActiveRequests() => _activeRequests.Add(-1);
+
+        public static void TrackRequest(string method, int statusCode, string endpoint, string apiType)
+        {
+            _totalRequests.Add(1,
+                new("method", method),
+                new("status_code", statusCode),
+                new("endpoint", endpoint),
+                new("api_type", apiType));
+        }
+
+        public static void TrackError(string method, int statusCode, string endpoint, string apiType, Exception ex)
+        {
+            _errorCounter.Add(1,
+                new("method", method),
+                new("status_code", statusCode),
+                new("endpoint", endpoint),
+                new("api_type", apiType),
+                new("error_type", ex.GetType().Name));
+        }
+
+        public static void TrackRequestDuration(string method, int statusCode, string endpoint, string apiType, double duration)
path: src/Service/Telemetry/TelemetryMetricsHelper.cs
user: login: RubenCerna2079
body: Same as comment above
created_at: 2025-04-02T20:42:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics.Metrics;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryMetricsHelper
+    {
+        public static readonly string MeterName = "DataApiBuilder.Metrics";
+        private static readonly Meter _meter = new(MeterName);
+        private static readonly Counter<long> _activeRequests = _meter.CreateCounter<long>("active_requests");
path: src/Service/Telemetry/TelemetryMetricsHelper.cs
user: login: RubenCerna2079
body: Shouldn't this be an `UpDownCounter`? Correct me if I am wrong but I thought `Counter` can only add, not subtract.
created_at: 2025-04-02T20:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        public static void TrackRestControllerActivityStarted(this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString != string.Empty)
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: I think we should also check if `queryString` is null
created_at: 2025-04-02T20:59:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        public static void TrackRestControllerActivityStarted(this Activity activity,
+            string httpMethod,
+            string userAgent,
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: 
```suggestion
        public static void TrackRestControllerActivityStarted(
            this Activity activity,
            string httpMethod,
            string userAgent,
```
created_at: 2025-04-02T20:59:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        public static void TrackRestControllerActivityStarted(this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString != string.Empty)
+                {
+                    activity.SetTag("http.querystring", queryString);
+                }
+
+                activity.SetTag("user.role", userRole);
+                activity.SetTag("api.type", apiType);
+            }
+        }
+
+        public static void TrackQueryActivityStarted(this Activity activity,
+            string databaseType,
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: 
```suggestion
        public static void TrackQueryActivityStarted(
            this Activity activity,
            string databaseType,
```
created_at: 2025-04-02T20:59:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        public static void TrackRestControllerActivityStarted(this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString != string.Empty)
+                {
+                    activity.SetTag("http.querystring", queryString);
+                }
+
+                activity.SetTag("user.role", userRole);
+                activity.SetTag("api.type", apiType);
+            }
+        }
+
+        public static void TrackQueryActivityStarted(this Activity activity,
+            string databaseType,
+            string dataSourceName)
+        {
+            if(activity.IsAllDataRequested)
+            {
+                activity.SetTag("data-source.type", databaseType);
+                activity.SetTag("data-source.name", dataSourceName);
+            }
+
+        }
+
+        public static void TrackRestControllerActivityFinished(this Activity activity,
+            int statusCode)
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: 
```suggestion
        public static void TrackRestControllerActivityFinished(
            this Activity activity,
            int statusCode)
```
created_at: 2025-04-02T21:00:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        public static void TrackRestControllerActivityStarted(this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString != string.Empty)
+                {
+                    activity.SetTag("http.querystring", queryString);
+                }
+
+                activity.SetTag("user.role", userRole);
+                activity.SetTag("api.type", apiType);
+            }
+        }
+
+        public static void TrackQueryActivityStarted(this Activity activity,
+            string databaseType,
+            string dataSourceName)
+        {
+            if(activity.IsAllDataRequested)
+            {
+                activity.SetTag("data-source.type", databaseType);
+                activity.SetTag("data-source.name", dataSourceName);
+            }
+
+        }
+
+        public static void TrackRestControllerActivityFinished(this Activity activity,
+            int statusCode)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("status.code", statusCode);
+            }
+        }
+
+        public static void TrackRestControllerActivityFinishedWithWithException(this Activity activity,
+            Exception ex,
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: 
```suggestion
        public static void TrackRestControllerActivityFinishedWithWithException(
            this Activity activity,
            Exception ex,
```
created_at: 2025-04-02T21:00:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        public static void TrackRestControllerActivityStarted(this Activity activity,
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: Please add comments that describe what this functions are doing in order to have more context
created_at: 2025-04-02T21:02:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -241,11 +289,25 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+                activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode);
+
+                TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex);
                 return ErrorResponse(
                     DataApiBuilderException.SubStatusCodes.UnexpectedError.ToString(),
                     SERVER_ERROR,
                     HttpStatusCode.InternalServerError);
             }
+            finally
+            {
+                stopwatch.Stop();
+                TelemetryMetricsHelper.TrackRequestDuration(HttpContext.Request.Method, Response.StatusCode, route, "REST", stopwatch.Elapsed.TotalMilliseconds);
+                if (activity is not null && activity.IsAllDataRequested)
+                {
+                    activity.Dispose();
+                }
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: Since this pattern is repeated you might want to add it as a function in `TelemetryTracesHelper`
created_at: 2025-04-02T21:05:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,31 +117,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
path: src/Service/Startup.cs
user: login: tommasodotNET
body: Because the current logic for the logger factory seems to not be working. When we reach the logger factory code, the runtime hasn't been read yet and logging was not being enabled. In the previous version I've used the same logic as for AppInsights and via debug found out that the check on OTEL logging enable was false.
created_at: 2025-04-03T09:06:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,31 +117,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
+                {
+                    logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!))
+                    .AddOtlpExporter(configure =>
+                    {
+                        configure.Endpoint = new Uri(runtimeConfig.Runtime.Telemetry.OpenTelemetry.Endpoint!);
+                        configure.Headers = runtimeConfig.Runtime.Telemetry.OpenTelemetry.Headers;
+                        configure.Protocol = OtlpExportProtocol.Grpc;
+                    });
+
+                })
                 .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();
+                        .AddMeter(TelemetryMetricsHelper.MeterName);
path: src/Service/Startup.cs
user: login: tommasodotNET
body: We need to remove standard ASP NET Core instrumentation because it relies on standard metrics for ASP NET Core apps that don't really match the logic we use in DAB (e.g. no filters available, no clarity on path, etc)
created_at: 2025-04-03T09:07:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -211,8 +232,25 @@ private async Task<IActionResult> HandleOperation(
 
path: src/Service/Controllers/RestController.cs
user: login: tommasodotNET
body: I've updated the title. The description already mentioned that. Do you think we could improve the description further?
created_at: 2025-04-03T10:01:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -241,11 +289,25 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+                activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode);
+
+                TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex);
                 return ErrorResponse(
                     DataApiBuilderException.SubStatusCodes.UnexpectedError.ToString(),
                     SERVER_ERROR,
                     HttpStatusCode.InternalServerError);
             }
+            finally
+            {
+                stopwatch.Stop();
+                TelemetryMetricsHelper.TrackRequestDuration(HttpContext.Request.Method, Response.StatusCode, route, "REST", stopwatch.Elapsed.TotalMilliseconds);
+                if (activity is not null && activity.IsAllDataRequested)
+                {
+                    activity.Dispose();
+                }
path: src/Service/Controllers/RestController.cs
user: login: tommasodotNET
body: To do that, I would need to use the activity object, which will result in a warning because activity might be null. That warning could be solved by checking that activity is not null. Therefore I think it's easier to leave it as it is and avoid a double check.
created_at: 2025-04-03T14:36:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -161,22 +162,6 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, Tele
                         .AddFilter<ApplicationInsightsLoggerProvider>(category: string.Empty, logLevel);
                     }
 
path: src/Service/Program.cs
user: login: tommasodotNET
body: This is linked to the question in the `Startup.cs`. 
created_at: 2025-04-03T14:38:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,14 +185,30 @@ public async Task<IActionResult> UpsertIncremental(
         /// <summary>
         /// Handle the given operation.
         /// </summary>
+        /// <param name="method">The method.</param>
         /// <param name="route">The entire route.</param>
         /// <param name="operationType">The kind of operation to handle.</param>
         private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
-            try
+            var stopwatch = Stopwatch.StartNew();
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}");
path: src/Service/Controllers/RestController.cs
user: login: tommasodotNET
body: You are right, @RubenCerna2079. No need to do that. If there is no additional path, it won't create the activity, but won't cause any issue.
created_at: 2025-04-03T14:47:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -211,8 +232,25 @@ private async Task<IActionResult> HandleOperation(
 
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: I think just restating it in the `What is this change?` section should be more than enough
created_at: 2025-04-03T16:46:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,7 +51,14 @@ public static void TrackRestControllerActivityStarted(this Activity activity,
             }
         }
 
-        public static void TrackQueryActivityStarted(this Activity activity,
+        /// <summary>
+        /// Tracks the start of a query activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="databaseType">The type of database being queried.</param>
+        /// <param name="dataSourceName">The name of the data source being queried.</param>
+        public static void TrackQueryActivityStarted(
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: Out of curiosity, why does the `QueryActivity` doesn't need a finished function in comparison to the `RestControllerActivity`?
created_at: 2025-04-03T17:19:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -116,31 +117,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
path: src/Service/Startup.cs
user: login: RubenCerna2079
body: I think this is worth discussing more, if there are any more errors that you found in the logic for the logger factory, please let us know so that we can solve them.
created_at: 2025-04-03T17:22:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,7 +51,14 @@ public static void TrackRestControllerActivityStarted(this Activity activity,
             }
         }
 
-        public static void TrackQueryActivityStarted(this Activity activity,
+        /// <summary>
+        /// Tracks the start of a query activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="databaseType">The type of database being queried.</param>
+        /// <param name="dataSourceName">The name of the data source being queried.</param>
+        public static void TrackQueryActivityStarted(
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: tommasodotNET
body: That is because we don't need to append any tag to this activity when it finishes, so I didn't create a specific function for that. If and when we will have some tags to add when the activity is finished, we can create a `TrackQueryActivityFinished` method.
created_at: 2025-04-03T17:26:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,8 +191,25 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            // This activity tracks the entire REST request.
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}");
+            if(activity is not null)
+            {
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: 
```suggestion
            if (activity is not null)
            {
```
created_at: 2025-04-03T17:28:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        /// <summary>
+        /// Activity source for Data API Builder telemetry.
+        /// </summary>
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        /// <summary>
+        /// Tracks the start of a REST controller activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="httpMethod">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="userAgent">The user agent string from the request.</param>
+        /// <param name="actionType">The type of action being performed (e.g. Read).</param>
+        /// <param name="httpURL">The URL of the request.</param>
+        /// <param name="queryString">The query string of the request, if any.</param>
+        /// <param name="userRole">The role of the user making the request.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        public static void TrackRestControllerActivityStarted(
+            this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString is not null && queryString != string.Empty)
+                {
+                    activity.SetTag("http.querystring", queryString);
+                }
+
+                activity.SetTag("user.role", userRole);
+                activity.SetTag("api.type", apiType);
+            }
+        }
+
+        /// <summary>
+        /// Tracks the start of a query activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="databaseType">The type of database being queried.</param>
+        /// <param name="dataSourceName">The name of the data source being queried.</param>
+        public static void TrackQueryActivityStarted(
+            this Activity activity,
+            string databaseType,
+            string dataSourceName)
+        {
+            if(activity.IsAllDataRequested)
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: RubenCerna2079
body: 
```suggestion
            if (activity.IsAllDataRequested)
```
created_at: 2025-04-03T17:28:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,8 +191,25 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            // This activity tracks the entire REST request.
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}");
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: This is the section that is causing the failure and it is related to what @sezal98 was mentioning. Currently the `route.Split('/')[1]` is expected to retrieve the second value of an array that is split by `/`, but in the test cases that it is failing we have strings that don't have any `/` for example, `favicon.io`.
This is causing the failure as the current logic tries to retrieve the second element of an array that only has an element at `[0]`. 
created_at: 2025-04-04T22:08:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,8 +191,25 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            // This activity tracks the entire REST request.
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}");
path: src/Service/Controllers/RestController.cs
user: login: tommasodotNET
body: I see. I added a check on the length of the split result
created_at: 2025-04-05T23:23:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -185,8 +191,25 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            // This activity tracks the entire REST request.
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {(route.Split('/').Length > 1 ? route.Split('/')[1] : "")}");
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: 
```suggestion
            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {(route.Split('/').Length > 1 ? route.Split('/')[1] : string.Empty)}");
```
created_at: 2025-04-07T19:36:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        /// <summary>
+        /// Activity source for Data API Builder telemetry.
+        /// </summary>
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        /// <summary>
+        /// Tracks the start of a REST controller activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="httpMethod">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="userAgent">The user agent string from the request.</param>
+        /// <param name="actionType">The type of action being performed (e.g. Read).</param>
+        /// <param name="httpURL">The URL of the request.</param>
+        /// <param name="queryString">The query string of the request, if any.</param>
+        /// <param name="userRole">The role of the user making the request.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        public static void TrackRestControllerActivityStarted(
+            this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString is not null && queryString != string.Empty)
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: Aniruddh25
body: could use `!string.IsNullOrEmpty(queryString)`
created_at: 2025-04-07T22:03:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        /// <summary>
+        /// Activity source for Data API Builder telemetry.
+        /// </summary>
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        /// <summary>
+        /// Tracks the start of a REST controller activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="httpMethod">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="userAgent">The user agent string from the request.</param>
+        /// <param name="actionType">The type of action being performed (e.g. Read).</param>
+        /// <param name="httpURL">The URL of the request.</param>
+        /// <param name="queryString">The query string of the request, if any.</param>
+        /// <param name="userRole">The role of the user making the request.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        public static void TrackRestControllerActivityStarted(
+            this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString is not null && queryString != string.Empty)
+                {
+                    activity.SetTag("http.querystring", queryString);
+                }
+
+                activity.SetTag("user.role", userRole);
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: Aniruddh25
body: Do we need a similar check for `!IsNullOrEmpty` for `user.role`?
created_at: 2025-04-07T22:05:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace Azure.DataApiBuilder.Service.Telemetry
+{
+    public static class TelemetryTracesHelper
+    {
+        /// <summary>
+        /// Activity source for Data API Builder telemetry.
+        /// </summary>
+        public static readonly ActivitySource DABActivitySource = new("DataApiBuilder");
+
+        /// <summary>
+        /// Tracks the start of a REST controller activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="httpMethod">The HTTP method of the request (e.g., GET, POST).</param>
+        /// <param name="userAgent">The user agent string from the request.</param>
+        /// <param name="actionType">The type of action being performed (e.g. Read).</param>
+        /// <param name="httpURL">The URL of the request.</param>
+        /// <param name="queryString">The query string of the request, if any.</param>
+        /// <param name="userRole">The role of the user making the request.</param>
+        /// <param name="apiType">The type of API being used (e.g., REST, GraphQL).</param>
+        public static void TrackRestControllerActivityStarted(
+            this Activity activity,
+            string httpMethod,
+            string userAgent,
+            string actionType,
+            string httpURL,
+            string? queryString,
+            string? userRole,
+            string apiType)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("http.method", httpMethod);
+                activity.SetTag("user-agent", userAgent);
+                activity.SetTag("action.type", actionType);
+                activity.SetTag("http.url", httpURL);
+                if (queryString is not null && queryString != string.Empty)
+                {
+                    activity.SetTag("http.querystring", queryString);
+                }
+
+                activity.SetTag("user.role", userRole);
+                activity.SetTag("api.type", apiType);
+            }
+        }
+
+        /// <summary>
+        /// Tracks the start of a query activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="databaseType">The type of database being queried.</param>
+        /// <param name="dataSourceName">The name of the data source being queried.</param>
+        public static void TrackQueryActivityStarted(
+            this Activity activity,
+            string databaseType,
+            string dataSourceName)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("data-source.type", databaseType);
+                activity.SetTag("data-source.name", dataSourceName);
+            }
+
+        }
+
+        /// <summary>
+        /// Tracks the completion of a REST controller activity.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        public static void TrackRestControllerActivityFinished(
+            this Activity activity,
+            int statusCode)
+        {
+            if (activity.IsAllDataRequested)
+            {
+                activity.SetTag("status.code", statusCode);
+            }
+        }
+
+        /// <summary>
+        /// Tracks the completion of a REST controller activity with an exception.
+        /// </summary>
+        /// <param name="activity">The activity instance.</param>
+        /// <param name="ex">The exception that occurred.</param>
+        /// <param name="statusCode">The HTTP status code of the response.</param>
+        public static void TrackRestControllerActivityFinishedWithWithException(
path: src/Service/Telemetry/TelemetryTracesHelper.cs
user: login: Aniruddh25
body: ```suggestion
        public static void TrackRestControllerActivityFinishedWithException(
```
created_at: 2025-04-07T22:06:00Z
author_association: COLLABORATOR
diff_hunk: @@ -116,31 +117,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
path: src/Service/Startup.cs
user: login: Aniruddh25
body: +1 to @RubenCerna2079, please let us know if you find more errors in our current logic of logger factory for AppInsights or anything else.
created_at: 2025-04-07T22:18:43Z
author_association: COLLABORATOR
diff_hunk: @@ -185,8 +191,25 @@ private async Task<IActionResult> HandleOperation(
             string route,
             EntityActionOperation operationType)
         {
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            // This activity tracks the entire REST request.
+            using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {(route.Split('/').Length > 1 ? route.Split('/')[1] : "")}");
+            if (activity is not null)
+            {
+                activity.TrackRestControllerActivityStarted(
+                    HttpContext.Request.Method,
+                    HttpContext.Request.Headers["User-Agent"].ToString(),
+                    operationType.ToString(),
+                    route,
+                    HttpContext.Request.QueryString.ToString(),
+                    HttpContext.User.FindFirst("role")?.Value,
path: src/Service/Controllers/RestController.cs
user: login: Aniruddh25
body: We also check for the value of the request header `X-MS-API-ROLE` (if set) to determine what the role the request should be executed as.. instead of finding the first using `FindFirst`. 
Can we use the value of this header here and default to `FindFirst` if that doesnt exist?
created_at: 2025-04-07T22:24:21Z
author_association: COLLABORATOR
diff_hunk: @@ -116,31 +117,46 @@ public void ConfigureServices(IServiceCollection services)
                 && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null
                 && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled)
             {
+                services.Configure<OpenTelemetryLoggerOptions>(options =>
+                {
+                    options.IncludeScopes = true;
+                    options.ParseStateValues = true;
+                    options.IncludeFormattedMessage = true;
+                });
                 services.AddOpenTelemetry()
+                .WithLogging(logging =>
path: src/Service/Startup.cs
user: login: tommasodotNET
body: We realized app insights was fine
created_at: 2025-04-11T18:03:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -125,17 +126,17 @@ public void ConfigureServices(IServiceCollection services)
                     options.IncludeFormattedMessage = true;
                 });
                 services.AddOpenTelemetry()
-                .WithLogging(logging =>
-                {
-                    logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!))
-                    .AddOtlpExporter(configure =>
-                    {
-                        configure.Endpoint = new Uri(runtimeConfig.Runtime.Telemetry.OpenTelemetry.Endpoint!);
-                        configure.Headers = runtimeConfig.Runtime.Telemetry.OpenTelemetry.Headers;
-                        configure.Protocol = OtlpExportProtocol.Grpc;
-                    });
-
-                })
+                //.WithLogging(logging =>
path: src/Service/Startup.cs
user: login: Aniruddh25
body: if we dont need this to enable logging, please remove the commented out code from Startup.cs
created_at: 2025-04-12T01:38:37Z
author_association: COLLABORATOR
diff_hunk: @@ -271,9 +274,11 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)ex.StatusCode;
-                activity?.TrackRestControllerActivityFinishedWithException(ex, Response.StatusCode);
+                activity?.TrackRestControllerActivityFinishedWithException(ex, Enum.Parse<HttpStatusCode>(Response.StatusCode.ToString(), ignoreCase: true));
path: src/Service/Controllers/RestController.cs
user: login: Aniruddh25
body: could reuse the `httpStatusCode` local variable here if we define it before this call
created_at: 2025-04-22T22:56:13Z
author_association: COLLABORATOR
diff_hunk: @@ -271,9 +274,11 @@ private async Task<IActionResult> HandleOperation(
                     HttpContextExtensions.GetLoggerCorrelationId(HttpContext));
 
                 Response.StatusCode = (int)ex.StatusCode;
-                activity?.TrackRestControllerActivityFinishedWithException(ex, Response.StatusCode);
+                activity?.TrackRestControllerActivityFinishedWithException(ex, Enum.Parse<HttpStatusCode>(Response.StatusCode.ToString(), ignoreCase: true));
path: src/Service/Controllers/RestController.cs
user: login: RubenCerna2079
body: I think we can simplify it even more, now that I see it is a bit redundant to first change the `ex.StatusCode` to `int` and then back to `HttpStatusCode`
created_at: 2025-04-23T02:04:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: JerryNixon
created_at: 2025-03-14T19:10:35Z
body: @Aniruddh25 is March realistic?
author_association: CONTRIBUTOR
user: login: tommasodotNET
created_at: 2025-03-19T10:37:18Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-03-19T10:37:24Z
body: <samp>
Commenter does not have sufficient privileges for PR 2617 in repo Azure/data-api-builder<br>

</samp>
author_association: NONE
user: login: sezal98
created_at: 2025-03-27T09:40:51Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-03-27T09:41:15Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: sezal98
created_at: 2025-04-01T16:10:01Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-01T16:10:25Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-03T17:08:24Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-03T17:08:49Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-03T17:38:54Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-03T17:39:19Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-03T20:28:13Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-03T20:28:37Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-03T20:32:11Z
body: @tommasodotNET just wanted to let you know that I updated the branch with the newest changes done to `main`
author_association: CONTRIBUTOR
user: login: tommasodotNET
created_at: 2025-04-04T08:18:23Z
body: > @tommasodotNET just wanted to let you know that I updated the branch with the newest changes done to `main`

Thanks. I see MS_SQL Tests are failing. Can't really figure out why, though...
author_association: CONTRIBUTOR
user: login: RubenCerna2079
created_at: 2025-04-04T22:08:29Z
body: @tommasodotNET I figured out why the tests are failing, I left a comment in the section that is causing the failure and the reason for it. Also, I updated your branch again :)
author_association: CONTRIBUTOR
user: login: RubenCerna2079
created_at: 2025-04-07T16:10:50Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-07T16:11:16Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: Aniruddh25
created_at: 2025-04-07T23:12:49Z
body: > Copilot reviewed 7 out of 8 changed files in this pull request and generated no comments.
> 
> Files not reviewed (1)
> * **src/Service/Azure.DataApiBuilder.Service.csproj**: Language not supported
> 
> Comments suppressed due to low confidence (2)
> **src/Service/Telemetry/TelemetryTracesHelper.cs:94**
> 
> * The method name 'TrackRestControllerActivityFinishedWithWithException' contains a duplicate 'With'. Consider renaming it to 'TrackRestControllerActivityFinishedWithException' for clarity.
> 
> ```
> public static void TrackRestControllerActivityFinishedWithWithException(
> ```
> 
> **src/Service/Controllers/RestController.cs:249**
> 
> * Manual disposal of 'queryActivity' is redundant since it is declared within a 'using' block, which will automatically dispose it. Removing the explicit call can prevent potential unexpected behavior from double disposal.
> 
> ```
> if (queryActivity is not null && queryActivity.IsAllDataRequested)
>                     {
>                         queryActivity.Dispose();
>                     }
> ```

@tommasodotNET Seems like queryActivity.Dispose() could be removed? Can it?
author_association: COLLABORATOR
user: login: tommasodotNET
created_at: 2025-04-10T18:39:06Z
body: Hey @Aniruddh25, I've addressed copilot review
author_association: CONTRIBUTOR
user: login: RubenCerna2079
created_at: 2025-04-11T16:05:42Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-11T16:06:09Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-11T18:40:40Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-11T18:41:03Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: Aniruddh25
created_at: 2025-04-12T01:44:19Z
body: @tommasodotNET, waiting on all tests to pass.. 
author_association: COLLABORATOR
user: login: Aniruddh25
created_at: 2025-04-13T21:59:43Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-04-13T22:00:08Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: aaronburtle
created_at: 2025-04-16T15:38:49Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-16T15:39:15Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-21T19:14:24Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-21T19:14:48Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-21T19:16:09Z
body: @tommasodotNET I made some changes to your PR, it was mostly changing methods to use more specific classes instead of `string` where I found it possible. I also fixed small formatting issues that were causing the unit tests to fail.
author_association: CONTRIBUTOR
user: login: RubenCerna2079
created_at: 2025-04-21T20:06:56Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-21T20:07:20Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-21T23:29:03Z
body: @tommasodotNET I also found out a new issue with the tests related to a format issue in the `dab-config.CosmosDb_NoSql.json`, so I fixed it as it seems that was the only test failing.
author_association: CONTRIBUTOR
user: login: RubenCerna2079
created_at: 2025-04-21T23:29:09Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-21T23:29:33Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: tommasodotNET
created_at: 2025-04-22T18:27:01Z
body: Thanks @RubenCerna2079 for your support
author_association: CONTRIBUTOR
user: login: Aniruddh25
created_at: 2025-04-22T23:00:06Z
body: /azp run
author_association: COLLABORATOR
user: login: azure-pipelines[bot]
created_at: 2025-04-22T23:00:35Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE
user: login: RubenCerna2079
created_at: 2025-04-23T02:14:12Z
body: /azp run
author_association: CONTRIBUTOR
user: login: azure-pipelines[bot]
created_at: 2025-04-23T02:14:34Z
body: <samp>
Azure Pipelines successfully started running 6 pipeline(s).<br>

</samp>
author_association: NONE