From 343e62c5822ce8922c2af312f32b0024c1c4e0b9 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 27 Feb 2025 19:39:34 +0100 Subject: [PATCH 01/37] initialize custom metrics/traces for REST endpoint --- .../Azure.DataApiBuilder.Service.csproj | 4 +- src/Service/Controllers/RestController.cs | 57 ++++++++++++++++++- src/Service/Startup.cs | 15 ++++- .../Telemetry/TelemetryMetricsHelper.cs | 39 +++++++++++++ .../Telemetry/TelemetryTracesHelper.cs | 12 ++++ 5 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 src/Service/Telemetry/TelemetryMetricsHelper.cs create mode 100644 src/Service/Telemetry/TelemetryTracesHelper.cs 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 @@ - + net8.0 @@ -77,7 +77,7 @@ - + diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 7071110cfc..e47570dcb3 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.Net; using System.Net.Mime; using System.Threading.Tasks; @@ -9,9 +10,12 @@ 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.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using OpenTelemetry.Trace; namespace Azure.DataApiBuilder.Service.Controllers { @@ -179,14 +183,29 @@ public async Task UpsertIncremental( /// /// Handle the given operation. /// + /// The method. /// The entire route. /// The kind of operation to handle. private async Task HandleOperation( string route, EntityActionOperation operationType) { - try + using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}"); + if(activity is not null && activity.IsAllDataRequested) { + activity.SetTag("http.method", HttpContext.Request.Method); + activity.SetTag("user-agent", HttpContext.Request.Headers["User-Agent"].ToString()); + activity.SetTag("action.type", operationType.ToString()); + activity.SetTag("http.url", route); + activity.SetTag("http.querystring", HttpContext.Request.QueryString.ToString()); + activity.SetTag("user.role", HttpContext.User.FindFirst("role")?.Value); + activity.SetTag("api.type", "REST"); + } + + TelemetryMetricsHelper.IncrementActiveRequests(); + try + { + if (route.Equals(REDIRECTED_ROUTE)) { throw new DataApiBuilderException( @@ -221,6 +240,13 @@ private async Task 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) + { + activity.SetTag("status.code", Response.StatusCode); + } + + TelemetryMetricsHelper.TrackRequest(HttpContext.Request.Method, statusCode, route, "REST"); return result; } catch (DataApiBuilderException ex) @@ -231,6 +257,16 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)ex.StatusCode; + if (activity is not null && 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", Response.StatusCode); + } + + TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); return ErrorResponse(ex.SubStatusCode.ToString(), ex.Message, ex.StatusCode); } catch (Exception ex) @@ -241,11 +277,30 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)HttpStatusCode.InternalServerError; + if (activity is not null && 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", Response.StatusCode); + } + + TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); return ErrorResponse( DataApiBuilderException.SubStatusCodes.UnexpectedError.ToString(), SERVER_ERROR, HttpStatusCode.InternalServerError); } + finally + { + if(activity is not null && activity.IsAllDataRequested) + { + activity.Dispose(); + } + + TelemetryMetricsHelper.DecrementActiveRequests(); + } } /// diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 62088fb173..cc9a4f91b6 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -24,6 +24,7 @@ using Azure.DataApiBuilder.Service.Controllers; using Azure.DataApiBuilder.Service.Exceptions; using Azure.DataApiBuilder.Service.HealthCheck; +using Azure.DataApiBuilder.Service.Telemetry; using HotChocolate.AspNetCore; using HotChocolate.Execution; using HotChocolate.Execution.Configuration; @@ -128,19 +129,27 @@ public void ConfigureServices(IServiceCollection services) configure.Headers = runtimeConfig.Runtime.Telemetry.OpenTelemetry.Headers; configure.Protocol = OtlpExportProtocol.Grpc; }) - .AddRuntimeInstrumentation(); + .AddRuntimeInstrumentation() + .AddMeter(TelemetryMetricsHelper.MeterName); }) .WithTracing(tracing => { tracing.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!)) - .AddAspNetCoreInstrumentation() + //.AddAspNetCoreInstrumentation(options => + //{ + // options.EnrichWithHttpRequest = (activity, httpRequest) => + // { + // activity.DisplayName = httpRequest.Path; + // }; + //}) .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); }); } diff --git a/src/Service/Telemetry/TelemetryMetricsHelper.cs b/src/Service/Telemetry/TelemetryMetricsHelper.cs new file mode 100644 index 0000000000..8d8532385b --- /dev/null +++ b/src/Service/Telemetry/TelemetryMetricsHelper.cs @@ -0,0 +1,39 @@ +// 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 _activeRequests = _meter.CreateCounter("active_requests"); + private static readonly Counter _errorCounter = _meter.CreateCounter("error_counter"); + private static readonly Counter _totalRequests = _meter.CreateCounter("total_requests"); + + 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)); + } + } +} diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs new file mode 100644 index 0000000000..e6bddfc29e --- /dev/null +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; + +namespace Azure.DataApiBuilder.Service.Telemetry +{ + public static class TelemetryTracesHelper + { + public static readonly ActivitySource DABActivitySource = new("DataApiBuilder"); + } +} From f807727fb59f0bc4383cee46ac71609e36d0e3ba Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 27 Feb 2025 19:40:01 +0100 Subject: [PATCH 02/37] initialize custom traces on sql db --- src/Core/Resolvers/SqlQueryEngine.cs | 46 +++++++++++++++++++++ src/Core/Services/Cache/DabCacheService.cs | 4 ++ src/Core/Services/TelemetryMetricsHelper.cs | 37 +++++++++++++++++ src/Core/Services/TelemetryTracesHelper.cs | 12 ++++++ 4 files changed, 99 insertions(+) create mode 100644 src/Core/Services/TelemetryMetricsHelper.cs create mode 100644 src/Core/Services/TelemetryTracesHelper.cs diff --git a/src/Core/Resolvers/SqlQueryEngine.cs b/src/Core/Resolvers/SqlQueryEngine.cs index 53547a96a3..df4db4266c 100644 --- a/src/Core/Resolvers/SqlQueryEngine.cs +++ b/src/Core/Resolvers/SqlQueryEngine.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -12,6 +13,7 @@ using Azure.DataApiBuilder.Core.Services; using Azure.DataApiBuilder.Core.Services.Cache; using Azure.DataApiBuilder.Core.Services.MetadataProviders; +using Azure.DataApiBuilder.Core.Telemetry; using Azure.DataApiBuilder.Service.GraphQLBuilder; using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries; using HotChocolate.Resolvers; @@ -299,8 +301,15 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta // private async Task ExecuteAsync(SqlQueryStructure structure, string dataSourceName, bool isMultipleCreateOperation = false) { + using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"QUERY {structure.EntityName}"); + RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); + string? databaseName = runtimeConfig.GetDataSourceFromDataSourceName(dataSourceName).ConnectionString + .Split(';') + .FirstOrDefault(x => x.StartsWith("Database=", StringComparison.OrdinalIgnoreCase)) + ?.Split('=')[1]; DatabaseType databaseType = runtimeConfig.GetDataSourceFromDataSourceName(dataSourceName).DatabaseType; + string? tableName = runtimeConfig.GetDataSourceNameFromEntityName(structure.EntityName); IQueryBuilder queryBuilder = _queryFactory.GetQueryBuilder(databaseType); IQueryExecutor queryExecutor = _queryFactory.GetQueryExecutor(databaseType); @@ -332,6 +341,23 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta JsonElement result = await _cache.GetOrSetAsync(queryExecutor, queryMetadata, cacheEntryTtl: runtimeConfig.GetEntityCacheEntryTtl(entityName: structure.EntityName)); byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(result); JsonDocument cacheServiceResponse = JsonDocument.Parse(jsonBytes); + + if (activity is not null && activity.IsAllDataRequested) + { + activity.SetTag("data-source.name", databaseName); + activity.SetTag("data-source.type", databaseType); + activity.SetTag("data-source.entity", tableName); + activity.SetTag("db.operation", queryString.Split(' ').First()); + activity.SetTag("db.statement", queryString); + //activity.SetTag("query.rows_returned", ); + activity.SetTag("query.mb_returned", jsonBytes.Length / 1024); + //activity.SetTag("query.used_cache", useCache); + } + + if (activity is not null && activity.IsAllDataRequested) + { + activity.Dispose(); + } return cacheServiceResponse; } } @@ -348,6 +374,26 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta httpContext: _httpContextAccessor.HttpContext!, args: null, dataSourceName: dataSourceName); + + byte[] responseByte = JsonSerializer.SerializeToUtf8Bytes(response); + + if (activity is not null && activity.IsAllDataRequested) + { + activity.SetTag("data-source.name", databaseName); + activity.SetTag("data-source.type", databaseType); + activity.SetTag("data-source.entity", tableName); + activity.SetTag("db.operation", queryString.Split(' ').First()); + activity.SetTag("db.statement", queryString); + //activity.SetTag("query.rows_returned", response.); + activity.SetTag("query.mb_returned", responseByte.Length / 1024); + //activity.SetTag("query.used_cache", useCache); + } + + if (activity is not null && activity.IsAllDataRequested) + { + activity.Dispose(); + } + return response; } diff --git a/src/Core/Services/Cache/DabCacheService.cs b/src/Core/Services/Cache/DabCacheService.cs index 20ab2905d4..8910ff7020 100644 --- a/src/Core/Services/Cache/DabCacheService.cs +++ b/src/Core/Services/Cache/DabCacheService.cs @@ -1,11 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics; using System.Text; using System.Text.Json; using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Resolvers; +using Azure.DataApiBuilder.Core.Telemetry; using Microsoft.AspNetCore.Http; +using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging; using ZiggyCreatures.Caching.Fusion; @@ -77,6 +80,7 @@ public DabCacheService(IFusionCache cache, ILogger? logger, IHt return result; }); + byte[] responseByte = JsonSerializer.SerializeToUtf8Bytes(result); return result; } diff --git a/src/Core/Services/TelemetryMetricsHelper.cs b/src/Core/Services/TelemetryMetricsHelper.cs new file mode 100644 index 0000000000..187dca4e52 --- /dev/null +++ b/src/Core/Services/TelemetryMetricsHelper.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.Metrics; + +namespace Azure.DataApiBuilder.Core.Telemetry +{ + public static class TelemetryMetricsHelper + { + private static readonly Meter _meter = new("DataApiBuilder.Metrics"); + private static readonly Counter _activeRequests = _meter.CreateCounter("active_requests"); + private static readonly Counter _errorCounter = _meter.CreateCounter("error_counter"); + private static readonly Counter _totalRequests = _meter.CreateCounter("total_requests"); + + 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)); + } + } +} diff --git a/src/Core/Services/TelemetryTracesHelper.cs b/src/Core/Services/TelemetryTracesHelper.cs new file mode 100644 index 0000000000..afced4734d --- /dev/null +++ b/src/Core/Services/TelemetryTracesHelper.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics; + +namespace Azure.DataApiBuilder.Core.Telemetry +{ + public static class TelemetryTracesHelper + { + public static readonly ActivitySource DABActivitySource = new("DataApiBuilder"); + } +} From ef47e1775d91a9a1c592d94cdb21c293282f5eae Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Wed, 12 Mar 2025 19:29:19 +0100 Subject: [PATCH 03/37] traces refactor --- src/Core/Resolvers/SqlQueryEngine.cs | 44 ------------- src/Service/Controllers/RestController.cs | 64 +++++++++++-------- src/Service/Startup.cs | 10 --- .../Telemetry/TelemetryMetricsHelper.cs | 13 +++- .../Telemetry/TelemetryTracesHelper.cs | 62 ++++++++++++++++++ 5 files changed, 113 insertions(+), 80 deletions(-) diff --git a/src/Core/Resolvers/SqlQueryEngine.cs b/src/Core/Resolvers/SqlQueryEngine.cs index df4db4266c..872b733f60 100644 --- a/src/Core/Resolvers/SqlQueryEngine.cs +++ b/src/Core/Resolvers/SqlQueryEngine.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Diagnostics; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -13,7 +12,6 @@ using Azure.DataApiBuilder.Core.Services; using Azure.DataApiBuilder.Core.Services.Cache; using Azure.DataApiBuilder.Core.Services.MetadataProviders; -using Azure.DataApiBuilder.Core.Telemetry; using Azure.DataApiBuilder.Service.GraphQLBuilder; using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries; using HotChocolate.Resolvers; @@ -301,15 +299,8 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta // private async Task ExecuteAsync(SqlQueryStructure structure, string dataSourceName, bool isMultipleCreateOperation = false) { - using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"QUERY {structure.EntityName}"); - RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); - string? databaseName = runtimeConfig.GetDataSourceFromDataSourceName(dataSourceName).ConnectionString - .Split(';') - .FirstOrDefault(x => x.StartsWith("Database=", StringComparison.OrdinalIgnoreCase)) - ?.Split('=')[1]; DatabaseType databaseType = runtimeConfig.GetDataSourceFromDataSourceName(dataSourceName).DatabaseType; - string? tableName = runtimeConfig.GetDataSourceNameFromEntityName(structure.EntityName); IQueryBuilder queryBuilder = _queryFactory.GetQueryBuilder(databaseType); IQueryExecutor queryExecutor = _queryFactory.GetQueryExecutor(databaseType); @@ -342,22 +333,6 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(result); JsonDocument cacheServiceResponse = JsonDocument.Parse(jsonBytes); - if (activity is not null && activity.IsAllDataRequested) - { - activity.SetTag("data-source.name", databaseName); - activity.SetTag("data-source.type", databaseType); - activity.SetTag("data-source.entity", tableName); - activity.SetTag("db.operation", queryString.Split(' ').First()); - activity.SetTag("db.statement", queryString); - //activity.SetTag("query.rows_returned", ); - activity.SetTag("query.mb_returned", jsonBytes.Length / 1024); - //activity.SetTag("query.used_cache", useCache); - } - - if (activity is not null && activity.IsAllDataRequested) - { - activity.Dispose(); - } return cacheServiceResponse; } } @@ -375,25 +350,6 @@ public object ResolveList(JsonElement array, IObjectField fieldSchema, ref IMeta args: null, dataSourceName: dataSourceName); - byte[] responseByte = JsonSerializer.SerializeToUtf8Bytes(response); - - if (activity is not null && activity.IsAllDataRequested) - { - activity.SetTag("data-source.name", databaseName); - activity.SetTag("data-source.type", databaseType); - activity.SetTag("data-source.entity", tableName); - activity.SetTag("db.operation", queryString.Split(' ').First()); - activity.SetTag("db.statement", queryString); - //activity.SetTag("query.rows_returned", response.); - activity.SetTag("query.mb_returned", responseByte.Length / 1024); - //activity.SetTag("query.used_cache", useCache); - } - - if (activity is not null && activity.IsAllDataRequested) - { - activity.Dispose(); - } - return response; } diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index e47570dcb3..ad86df4b23 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -7,6 +7,7 @@ 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; @@ -14,8 +15,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using OpenTelemetry.Trace; namespace Azure.DataApiBuilder.Service.Controllers { @@ -51,11 +50,14 @@ public class RestController : ControllerBase private readonly ILogger _logger; + private readonly RuntimeConfigProvider _runtimeConfigProvider; + /// /// Constructor. /// - public RestController(RestService restService, IOpenApiDocumentor openApiDocumentor, ILogger logger) + public RestController(RuntimeConfigProvider runtimeConfigProvider, RestService restService, IOpenApiDocumentor openApiDocumentor, ILogger logger) { + _runtimeConfigProvider = runtimeConfigProvider; _restService = restService; _openApiDocumentor = openApiDocumentor; _logger = logger; @@ -190,16 +192,17 @@ private async Task HandleOperation( string route, EntityActionOperation operationType) { + var stopwatch = Stopwatch.StartNew(); using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}"); - if(activity is not null && activity.IsAllDataRequested) + if(activity is not null) { - activity.SetTag("http.method", HttpContext.Request.Method); - activity.SetTag("user-agent", HttpContext.Request.Headers["User-Agent"].ToString()); - activity.SetTag("action.type", operationType.ToString()); - activity.SetTag("http.url", route); - activity.SetTag("http.querystring", HttpContext.Request.QueryString.ToString()); - activity.SetTag("user.role", HttpContext.User.FindFirst("role")?.Value); - activity.SetTag("api.type", "REST"); + activity.TrackRestControllerActivityStarted(HttpContext.Request.Method, + HttpContext.Request.Headers["User-Agent"].ToString(), + operationType.ToString(), + route, + HttpContext.Request.QueryString.ToString(), + HttpContext.User.FindFirst("role")?.Value, + "REST"); } TelemetryMetricsHelper.IncrementActiveRequests(); @@ -230,8 +233,25 @@ private async Task HandleOperation( (string entityName, string primaryKeyRoute) = _restService.GetEntityNameAndPrimaryKeyRouteFromRoute(routeAfterPathBase); + 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.ToString(), + dataSourceName); + } + + if (queryActivity is not null && queryActivity.IsAllDataRequested) + { + queryActivity.Dispose(); + } + if (result is null) { throw new DataApiBuilderException( @@ -243,7 +263,7 @@ private async Task HandleOperation( int statusCode = (result as ObjectResult)?.StatusCode ?? (result as StatusCodeResult)?.StatusCode ?? (result as JsonResult)?.StatusCode ?? 200; if (activity is not null && activity.IsAllDataRequested) { - activity.SetTag("status.code", Response.StatusCode); + activity.TrackRestControllerActivityFinished(statusCode); } TelemetryMetricsHelper.TrackRequest(HttpContext.Request.Method, statusCode, route, "REST"); @@ -257,13 +277,9 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)ex.StatusCode; - if (activity is not null && activity.IsAllDataRequested) + if (activity is not null) { - 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", Response.StatusCode); + activity.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); } TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); @@ -277,13 +293,9 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)HttpStatusCode.InternalServerError; - if (activity is not null && activity.IsAllDataRequested) + if(activity is not null) { - 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", Response.StatusCode); + activity.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); } TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); @@ -294,7 +306,9 @@ private async Task HandleOperation( } finally { - if(activity is not null && activity.IsAllDataRequested) + stopwatch.Stop(); + TelemetryMetricsHelper.TrackRequestDuration(HttpContext.Request.Method, Response.StatusCode, route, "REST", stopwatch.Elapsed.TotalMilliseconds); + if (activity is not null && activity.IsAllDataRequested) { activity.Dispose(); } diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index cc9a4f91b6..19d725137a 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -121,27 +121,17 @@ public void ConfigureServices(IServiceCollection services) .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(options => - //{ - // options.EnrichWithHttpRequest = (activity, httpRequest) => - // { - // activity.DisplayName = httpRequest.Path; - // }; - //}) .AddHttpClientInstrumentation() .AddOtlpExporter(configure => { diff --git a/src/Service/Telemetry/TelemetryMetricsHelper.cs b/src/Service/Telemetry/TelemetryMetricsHelper.cs index 8d8532385b..e91db17b36 100644 --- a/src/Service/Telemetry/TelemetryMetricsHelper.cs +++ b/src/Service/Telemetry/TelemetryMetricsHelper.cs @@ -11,10 +11,12 @@ public static class TelemetryMetricsHelper public static readonly string MeterName = "DataApiBuilder.Metrics"; private static readonly Meter _meter = new(MeterName); private static readonly Counter _activeRequests = _meter.CreateCounter("active_requests"); - private static readonly Counter _errorCounter = _meter.CreateCounter("error_counter"); + private static readonly Counter _errorCounter = _meter.CreateCounter("total_errors"); private static readonly Counter _totalRequests = _meter.CreateCounter("total_requests"); + private static readonly Histogram _requestDuration = _meter.CreateHistogram("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) @@ -35,5 +37,14 @@ public static void TrackError(string method, int statusCode, string endpoint, st new("api_type", apiType), new("error_type", ex.GetType().Name)); } + + public static void TrackRequestDuration(string method, int statusCode, string endpoint, string apiType, double duration) + { + _requestDuration.Record(duration, + 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 index e6bddfc29e..2eb1f85349 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -1,12 +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, + int 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); + } + } } } From 6b2d1f7d0448738986540069a210c2c1103f60b2 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Fri, 14 Mar 2025 18:24:08 +0100 Subject: [PATCH 04/37] fixes otel logging --- .../Configuration/OpenTelemetryTests.cs | 1 - src/Service/Program.cs | 22 +++++-------------- src/Service/Startup.cs | 18 ++++++++++++++- 3 files changed, 22 insertions(+), 19 deletions(-) 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(); } /// diff --git a/src/Service/Program.cs b/src/Service/Program.cs index 6577d219af..f69695be19 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.ApplicationInsights; @@ -48,7 +49,10 @@ public static bool StartEngine(string[] args) Console.WriteLine("Starting the runtime engine..."); try { - CreateHostBuilder(args).Build().Run(); + IHostBuilder hostingBuilger = CreateHostBuilder(args); + IHost host = hostingBuilger.Build(); + ILogger logger = host.Services.GetRequiredService>(); + host.Run(); return true; } // Catch exception raised by explicit call to IHostApplicationLifetime.StopApplication() @@ -161,22 +165,6 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, Tele .AddFilter(category: string.Empty, logLevel); } - if (Startup.OpenTelemetryOptions.Enabled && !string.IsNullOrWhiteSpace(Startup.OpenTelemetryOptions.Endpoint)) - { - builder.AddOpenTelemetry(logging => - { - logging.IncludeFormattedMessage = true; - logging.IncludeScopes = true; - logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(Startup.OpenTelemetryOptions.ServiceName!)); - logging.AddOtlpExporter(configure => - { - configure.Endpoint = new Uri(Startup.OpenTelemetryOptions.Endpoint); - configure.Headers = Startup.OpenTelemetryOptions.Headers; - configure.Protocol = OtlpExportProtocol.Grpc; - }); - }); - } - builder.AddConsole(); }); } diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 19d725137a..1a0c9f303d 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -45,6 +45,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenTelemetry.Exporter; +using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; @@ -60,7 +61,6 @@ public class Startup public static LogLevel MinimumLogLevel = LogLevel.Error; public static bool IsLogLevelOverriddenByCli; - public static OpenTelemetryOptions OpenTelemetryOptions = new(); public static ApplicationInsightsOptions AppInsightsOptions = new(); public const string NO_HTTPS_REDIRECT_FLAG = "--no-https-redirect"; @@ -117,7 +117,23 @@ public void ConfigureServices(IServiceCollection services) && runtimeConfig?.Runtime?.Telemetry?.OpenTelemetry is not null && runtimeConfig.Runtime.Telemetry.OpenTelemetry.Enabled) { + services.Configure(options => + { + options.IncludeScopes = true; + options.ParseStateValues = true; + options.IncludeFormattedMessage = true; + }); services.AddOpenTelemetry() + .WithLogging(logging => + { + logging.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!)) From 970360c71363cf15e4082d3af641751ff4850c1f Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Fri, 14 Mar 2025 19:11:46 +0100 Subject: [PATCH 05/37] fixes otel logging resource name --- src/Service/Startup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 1a0c9f303d..e84f919b54 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -126,7 +126,8 @@ public void ConfigureServices(IServiceCollection services) services.AddOpenTelemetry() .WithLogging(logging => { - logging.AddOtlpExporter(configure => + 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; From 2199d6853b1a1b9279d7a4e3b7ff9fd30326c6a2 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Fri, 14 Mar 2025 19:29:30 +0100 Subject: [PATCH 06/37] cleans up pr --- src/Core/Services/Cache/DabCacheService.cs | 4 --- src/Core/Services/TelemetryMetricsHelper.cs | 37 --------------------- src/Core/Services/TelemetryTracesHelper.cs | 12 ------- src/Service/Program.cs | 5 +-- 4 files changed, 1 insertion(+), 57 deletions(-) delete mode 100644 src/Core/Services/TelemetryMetricsHelper.cs delete mode 100644 src/Core/Services/TelemetryTracesHelper.cs diff --git a/src/Core/Services/Cache/DabCacheService.cs b/src/Core/Services/Cache/DabCacheService.cs index 8910ff7020..20ab2905d4 100644 --- a/src/Core/Services/Cache/DabCacheService.cs +++ b/src/Core/Services/Cache/DabCacheService.cs @@ -1,14 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Diagnostics; using System.Text; using System.Text.Json; using Azure.DataApiBuilder.Core.Models; using Azure.DataApiBuilder.Core.Resolvers; -using Azure.DataApiBuilder.Core.Telemetry; using Microsoft.AspNetCore.Http; -using Microsoft.Azure.Cosmos; using Microsoft.Extensions.Logging; using ZiggyCreatures.Caching.Fusion; @@ -80,7 +77,6 @@ public DabCacheService(IFusionCache cache, ILogger? logger, IHt return result; }); - byte[] responseByte = JsonSerializer.SerializeToUtf8Bytes(result); return result; } diff --git a/src/Core/Services/TelemetryMetricsHelper.cs b/src/Core/Services/TelemetryMetricsHelper.cs deleted file mode 100644 index 187dca4e52..0000000000 --- a/src/Core/Services/TelemetryMetricsHelper.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics.Metrics; - -namespace Azure.DataApiBuilder.Core.Telemetry -{ - public static class TelemetryMetricsHelper - { - private static readonly Meter _meter = new("DataApiBuilder.Metrics"); - private static readonly Counter _activeRequests = _meter.CreateCounter("active_requests"); - private static readonly Counter _errorCounter = _meter.CreateCounter("error_counter"); - private static readonly Counter _totalRequests = _meter.CreateCounter("total_requests"); - - 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)); - } - } -} diff --git a/src/Core/Services/TelemetryTracesHelper.cs b/src/Core/Services/TelemetryTracesHelper.cs deleted file mode 100644 index afced4734d..0000000000 --- a/src/Core/Services/TelemetryTracesHelper.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Diagnostics; - -namespace Azure.DataApiBuilder.Core.Telemetry -{ - public static class TelemetryTracesHelper - { - public static readonly ActivitySource DABActivitySource = new("DataApiBuilder"); - } -} diff --git a/src/Service/Program.cs b/src/Service/Program.cs index f69695be19..bc7f359ae4 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -49,10 +49,7 @@ public static bool StartEngine(string[] args) Console.WriteLine("Starting the runtime engine..."); try { - IHostBuilder hostingBuilger = CreateHostBuilder(args); - IHost host = hostingBuilger.Build(); - ILogger logger = host.Services.GetRequiredService>(); - host.Run(); + CreateHostBuilder(args).Build().Run(); return true; } // Catch exception raised by explicit call to IHostApplicationLifetime.StopApplication() From 0176cfd9231aff00f07ee7c566a8a0d319f46355 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 17 Mar 2025 19:33:49 +0100 Subject: [PATCH 07/37] Update src/Service/Controllers/RestController.cs Co-authored-by: Aaron Powell --- src/Service/Controllers/RestController.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index ad86df4b23..9ca08ba693 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -277,10 +277,7 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)ex.StatusCode; - if (activity is not null) - { - activity.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); - } + activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); return ErrorResponse(ex.SubStatusCode.ToString(), ex.Message, ex.StatusCode); From 1393def0468f030f5b1c17babfbd2ec1c6dabf8e Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 17 Mar 2025 19:33:56 +0100 Subject: [PATCH 08/37] Update src/Service/Controllers/RestController.cs Co-authored-by: Aaron Powell --- src/Service/Controllers/RestController.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 9ca08ba693..9eda662b90 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -290,10 +290,7 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)HttpStatusCode.InternalServerError; - if(activity is not null) - { - activity.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); - } + activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); return ErrorResponse( From 5db6e92fa052abdba657b46a9e250e161eac52f3 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 31 Mar 2025 16:44:05 +0200 Subject: [PATCH 09/37] adding null check on route --- src/Service/Controllers/RestController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index ad86df4b23..7175258fbe 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -185,7 +185,6 @@ public async Task UpsertIncremental( /// /// Handle the given operation. /// - /// The method. /// The entire route. /// The kind of operation to handle. private async Task HandleOperation( @@ -193,7 +192,7 @@ private async Task HandleOperation( EntityActionOperation operationType) { var stopwatch = Stopwatch.StartNew(); - using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}"); + using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route?.Split('/')[1]}"); if(activity is not null) { activity.TrackRestControllerActivityStarted(HttpContext.Request.Method, From fc44a2112aa2b41c121337572fe2d62663c6672f Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 11:00:53 +0200 Subject: [PATCH 10/37] Update src/Service/Controllers/RestController.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Controllers/RestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 5fba698bc1..aa7e4a9a5c 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -191,7 +191,7 @@ private async Task HandleOperation( string route, EntityActionOperation operationType) { - var stopwatch = Stopwatch.StartNew(); + Stopwatch stopwatch = Stopwatch.StartNew(); using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route?.Split('/')[1]}"); if(activity is not null) { From e5ff44765821cbc3a0450b30268b89da070db7d8 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 11:01:10 +0200 Subject: [PATCH 11/37] Update src/Service/Controllers/RestController.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Controllers/RestController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index aa7e4a9a5c..f7bffa32cc 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -195,7 +195,8 @@ private async Task HandleOperation( using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route?.Split('/')[1]}"); if(activity is not null) { - activity.TrackRestControllerActivityStarted(HttpContext.Request.Method, + activity.TrackRestControllerActivityStarted( + HttpContext.Request.Method, HttpContext.Request.Headers["User-Agent"].ToString(), operationType.ToString(), route, From 1bb648aadd8b4935c50fb1add90fbca98d592def Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 11:58:18 +0200 Subject: [PATCH 12/37] Update src/Service/Telemetry/TelemetryTracesHelper.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Telemetry/TelemetryTracesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index 2eb1f85349..de75cce136 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -57,7 +57,8 @@ public static void TrackRestControllerActivityFinished(this Activity activity, } } - public static void TrackRestControllerActivityFinishedWithWithException(this Activity activity, + public static void TrackRestControllerActivityFinishedWithWithException( + this Activity activity, Exception ex, int statusCode) { From eb23e321d5fd1bc9add2e3adf30d91d8b5f2f2ac Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 11:58:28 +0200 Subject: [PATCH 13/37] Update src/Service/Telemetry/TelemetryTracesHelper.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Telemetry/TelemetryTracesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index de75cce136..26a559c699 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -48,7 +48,8 @@ public static void TrackQueryActivityStarted(this Activity activity, } - public static void TrackRestControllerActivityFinished(this Activity activity, + public static void TrackRestControllerActivityFinished( + this Activity activity, int statusCode) { if (activity.IsAllDataRequested) From 0d07c5507d888cdffb16d29c58b8aea58fd8f158 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 11:58:38 +0200 Subject: [PATCH 14/37] Update src/Service/Telemetry/TelemetryTracesHelper.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Telemetry/TelemetryTracesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index 26a559c699..132e99b7a0 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -36,7 +36,8 @@ public static void TrackRestControllerActivityStarted(this Activity activity, } } - public static void TrackQueryActivityStarted(this Activity activity, + public static void TrackQueryActivityStarted( + this Activity activity, string databaseType, string dataSourceName) { From 5859684297b94b503476bb9bcaf104b0c75c9c86 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 11:59:27 +0200 Subject: [PATCH 15/37] Update src/Service/Telemetry/TelemetryTracesHelper.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Telemetry/TelemetryTracesHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index 132e99b7a0..f323d768c3 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -11,7 +11,8 @@ public static class TelemetryTracesHelper { public static readonly ActivitySource DABActivitySource = new("DataApiBuilder"); - public static void TrackRestControllerActivityStarted(this Activity activity, + public static void TrackRestControllerActivityStarted( + this Activity activity, string httpMethod, string userAgent, string actionType, From a87f54d96f55bcd02270b01608db9539864ee0f3 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:27:56 +0200 Subject: [PATCH 16/37] adds docs in TelemetryMetricsHelper --- .../Telemetry/TelemetryMetricsHelper.cs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryMetricsHelper.cs b/src/Service/Telemetry/TelemetryMetricsHelper.cs index e91db17b36..e57fe93de4 100644 --- a/src/Service/Telemetry/TelemetryMetricsHelper.cs +++ b/src/Service/Telemetry/TelemetryMetricsHelper.cs @@ -6,9 +6,12 @@ namespace Azure.DataApiBuilder.Service.Telemetry { + /// + /// Helper class for tracking telemetry metrics such as active requests, errors, total requests, + /// and request durations using the .NET Meter and Counter APIs. + /// public static class TelemetryMetricsHelper { - public static readonly string MeterName = "DataApiBuilder.Metrics"; private static readonly Meter _meter = new(MeterName); private static readonly Counter _activeRequests = _meter.CreateCounter("active_requests"); private static readonly Counter _errorCounter = _meter.CreateCounter("total_errors"); @@ -19,6 +22,13 @@ public static class TelemetryMetricsHelper public static void DecrementActiveRequests() => _activeRequests.Add(-1); + /// + /// Tracks a request by incrementing the total requests counter and associating it with metadata. + /// + /// The HTTP method of the request (e.g., GET, POST). + /// The HTTP status code of the response. + /// The endpoint being accessed. + /// The type of API being used (e.g., REST, GraphQL). public static void TrackRequest(string method, int statusCode, string endpoint, string apiType) { _totalRequests.Add(1, @@ -28,6 +38,14 @@ public static void TrackRequest(string method, int statusCode, string endpoint, new("api_type", apiType)); } + /// + /// Tracks an error by incrementing the error counter and associating it with metadata. + /// + /// The HTTP method of the request (e.g., GET, POST). + /// The HTTP status code of the response. + /// The endpoint being accessed. + /// The type of API being used (e.g., REST, GraphQL). + /// The exception that occurred. public static void TrackError(string method, int statusCode, string endpoint, string apiType, Exception ex) { _errorCounter.Add(1, @@ -38,6 +56,14 @@ public static void TrackError(string method, int statusCode, string endpoint, st new("error_type", ex.GetType().Name)); } + /// + /// Tracks the duration of a request by recording it in a histogram and associating it with metadata. + /// + /// The HTTP method of the request (e.g., GET, POST). + /// The HTTP status code of the response. + /// The endpoint being accessed. + /// The type of API being used (e.g., REST, GraphQL). + /// The duration of the request in milliseconds. public static void TrackRequestDuration(string method, int statusCode, string endpoint, string apiType, double duration) { _requestDuration.Record(duration, From 4c3e8e9b5318ff77aa20a7bc343f9b1c97bdb975 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:31:51 +0200 Subject: [PATCH 17/37] adds doc on TelemetryTracesHelper --- .../Telemetry/TelemetryTracesHelper.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index f323d768c3..111e2208a9 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -9,8 +9,22 @@ namespace Azure.DataApiBuilder.Service.Telemetry { public static class TelemetryTracesHelper { + /// + /// Activity source for Data API Builder telemetry. + /// public static readonly ActivitySource DABActivitySource = new("DataApiBuilder"); + /// + /// Tracks the start of a REST controller activity. + /// + /// The activity instance. + /// The HTTP method of the request (e.g., GET, POST). + /// The user agent string from the request. + /// The type of action being performed (e.g. Read). + /// The URL of the request. + /// The query string of the request, if any. + /// The role of the user making the request. + /// The type of API being used (e.g., REST, GraphQL). public static void TrackRestControllerActivityStarted( this Activity activity, string httpMethod, @@ -37,6 +51,12 @@ public static void TrackRestControllerActivityStarted( } } + /// + /// Tracks the start of a query activity. + /// + /// The activity instance. + /// The type of database being queried. + /// The name of the data source being queried. public static void TrackQueryActivityStarted( this Activity activity, string databaseType, @@ -50,6 +70,11 @@ public static void TrackQueryActivityStarted( } + /// + /// Tracks the completion of a REST controller activity. + /// + /// The activity instance. + /// The HTTP status code of the response. public static void TrackRestControllerActivityFinished( this Activity activity, int statusCode) @@ -60,6 +85,12 @@ public static void TrackRestControllerActivityFinished( } } + /// + /// Tracks the completion of a REST controller activity with an exception. + /// + /// The activity instance. + /// The exception that occurred. + /// The HTTP status code of the response. public static void TrackRestControllerActivityFinishedWithWithException( this Activity activity, Exception ex, From ebea19dc13be9428ac92eef6046f6deb675587cb Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:32:21 +0200 Subject: [PATCH 18/37] adds check queryString is not null --- src/Service/Telemetry/TelemetryTracesHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index 111e2208a9..72eed2394f 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -41,7 +41,7 @@ public static void TrackRestControllerActivityStarted( activity.SetTag("user-agent", userAgent); activity.SetTag("action.type", actionType); activity.SetTag("http.url", httpURL); - if (queryString != string.Empty) + if (queryString is not null && queryString != string.Empty) { activity.SetTag("http.querystring", queryString); } From 2e3f011f139144e292ea321f7821f3640e0c9abf Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:34:37 +0200 Subject: [PATCH 19/37] adds comments on activities --- src/Service/Controllers/RestController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index f7bffa32cc..d0ba7addc7 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -192,6 +192,7 @@ private async Task HandleOperation( 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) { @@ -207,8 +208,8 @@ private async Task HandleOperation( TelemetryMetricsHelper.IncrementActiveRequests(); try - { - + { + if (route.Equals(REDIRECTED_ROUTE)) { throw new DataApiBuilderException( @@ -233,6 +234,7 @@ private async Task 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); From 96deeaf940eed7405a19d04d2104ee759406e621 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:37:00 +0200 Subject: [PATCH 20/37] removes unnecessary usings in program.cs --- src/Service/Program.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Service/Program.cs b/src/Service/Program.cs index bc7f359ae4..950a819fa6 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -13,13 +13,9 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.ApplicationInsights; -using OpenTelemetry.Exporter; -using OpenTelemetry.Logs; -using OpenTelemetry.Resources; namespace Azure.DataApiBuilder.Service { From 0dd2a92a879bc752822cbe6bc0ee8da369f4b90d Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:39:38 +0200 Subject: [PATCH 21/37] fixes missing meter name --- src/Service/Telemetry/TelemetryMetricsHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Service/Telemetry/TelemetryMetricsHelper.cs b/src/Service/Telemetry/TelemetryMetricsHelper.cs index e57fe93de4..12ec3b33cb 100644 --- a/src/Service/Telemetry/TelemetryMetricsHelper.cs +++ b/src/Service/Telemetry/TelemetryMetricsHelper.cs @@ -12,6 +12,7 @@ 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 _activeRequests = _meter.CreateCounter("active_requests"); private static readonly Counter _errorCounter = _meter.CreateCounter("total_errors"); From 3e8010516bc5e55ca6effd9dc83b3b2f4af17334 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:48:59 +0200 Subject: [PATCH 22/37] uses updowncounter for active requests --- src/Service/Telemetry/TelemetryMetricsHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryMetricsHelper.cs b/src/Service/Telemetry/TelemetryMetricsHelper.cs index 12ec3b33cb..d3410e935a 100644 --- a/src/Service/Telemetry/TelemetryMetricsHelper.cs +++ b/src/Service/Telemetry/TelemetryMetricsHelper.cs @@ -14,7 +14,7 @@ public static class TelemetryMetricsHelper { public static readonly string MeterName = "DataApiBuilder.Metrics"; private static readonly Meter _meter = new(MeterName); - private static readonly Counter _activeRequests = _meter.CreateCounter("active_requests"); + private static readonly UpDownCounter _activeRequests = _meter.CreateUpDownCounter("active_requests"); private static readonly Counter _errorCounter = _meter.CreateCounter("total_errors"); private static readonly Counter _totalRequests = _meter.CreateCounter("total_requests"); private static readonly Histogram _requestDuration = _meter.CreateHistogram("request_duration", "ms"); From 7ff76ce19258ac63dcc67f1c07911a02c46e6096 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 16:49:09 +0200 Subject: [PATCH 23/37] removes check on route --- src/Service/Controllers/RestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index d0ba7addc7..daf122de96 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -193,7 +193,7 @@ private async Task HandleOperation( { Stopwatch stopwatch = Stopwatch.StartNew(); // This activity tracks the entire REST request. - using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route?.Split('/')[1]}"); + using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}"); if(activity is not null) { activity.TrackRestControllerActivityStarted( From 9bc58228e6e04b0f4c976234d358bd7a6e8026ca Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 19:30:47 +0200 Subject: [PATCH 24/37] Update src/Service/Telemetry/TelemetryTracesHelper.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Telemetry/TelemetryTracesHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index 72eed2394f..fcc0243ebb 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -62,7 +62,7 @@ public static void TrackQueryActivityStarted( string databaseType, string dataSourceName) { - if(activity.IsAllDataRequested) + if (activity.IsAllDataRequested) { activity.SetTag("data-source.type", databaseType); activity.SetTag("data-source.name", dataSourceName); From 0cad4a814876925cab5993bd95383a2f41c47760 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 3 Apr 2025 19:31:19 +0200 Subject: [PATCH 25/37] Update src/Service/Controllers/RestController.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Controllers/RestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index daf122de96..e902333e9b 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -194,7 +194,7 @@ private async Task HandleOperation( 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) + if (activity is not null) { activity.TrackRestControllerActivityStarted( HttpContext.Request.Method, From c6414b6601492474fef6023191341043d7e1e7f4 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Sun, 6 Apr 2025 01:22:24 +0200 Subject: [PATCH 26/37] checks route split --- src/Service/Controllers/RestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index e902333e9b..b142e06a73 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -193,7 +193,7 @@ private async Task HandleOperation( { Stopwatch stopwatch = Stopwatch.StartNew(); // This activity tracks the entire REST request. - using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {route.Split('/')[1]}"); + using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {(route.Split('/').Length > 1 ? route.Split('/')[1] : "")}"); if (activity is not null) { activity.TrackRestControllerActivityStarted( From 30c61ca3ac95156f37cde50138490d5f85e04616 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Tue, 8 Apr 2025 09:09:46 +0200 Subject: [PATCH 27/37] removes activity disposal --- src/Service/Controllers/RestController.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 1c8d1189f2..f165cdf61f 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -246,11 +246,6 @@ private async Task HandleOperation( dataSourceName); } - if (queryActivity is not null && queryActivity.IsAllDataRequested) - { - queryActivity.Dispose(); - } - if (result is null) { throw new DataApiBuilderException( @@ -301,10 +296,6 @@ private async Task HandleOperation( { stopwatch.Stop(); TelemetryMetricsHelper.TrackRequestDuration(HttpContext.Request.Method, Response.StatusCode, route, "REST", stopwatch.Elapsed.TotalMilliseconds); - if (activity is not null && activity.IsAllDataRequested) - { - activity.Dispose(); - } TelemetryMetricsHelper.DecrementActiveRequests(); } From 045e5faac626a3ce11024e6e5791d99e6383fc2a Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Thu, 10 Apr 2025 20:38:14 +0200 Subject: [PATCH 28/37] fixes typo on request finished with exception tracking --- src/Service/Controllers/RestController.cs | 4 ++-- src/Service/Telemetry/TelemetryTracesHelper.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index f165cdf61f..8922c361e8 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -271,7 +271,7 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)ex.StatusCode; - activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); + activity?.TrackRestControllerActivityFinishedWithException(ex, Response.StatusCode); TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); return ErrorResponse(ex.SubStatusCode.ToString(), ex.Message, ex.StatusCode); @@ -284,7 +284,7 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)HttpStatusCode.InternalServerError; - activity?.TrackRestControllerActivityFinishedWithWithException(ex, Response.StatusCode); + activity?.TrackRestControllerActivityFinishedWithException(ex, Response.StatusCode); TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); return ErrorResponse( diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index fcc0243ebb..2f97b70a3e 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -91,7 +91,7 @@ public static void TrackRestControllerActivityFinished( /// The activity instance. /// The exception that occurred. /// The HTTP status code of the response. - public static void TrackRestControllerActivityFinishedWithWithException( + public static void TrackRestControllerActivityFinishedWithException( this Activity activity, Exception ex, int statusCode) From f8a4b20f7221e5b9bff55481922413101e049dda Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Fri, 11 Apr 2025 12:44:00 +0200 Subject: [PATCH 29/37] Update src/Service/Controllers/RestController.cs Co-authored-by: RubenCerna2079 <32799214+RubenCerna2079@users.noreply.github.com> --- src/Service/Controllers/RestController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 8922c361e8..fd2c55c5c3 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -193,7 +193,7 @@ private async Task HandleOperation( { 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] : "")}"); + using Activity? activity = TelemetryTracesHelper.DABActivitySource.StartActivity($"{HttpContext.Request.Method} {(route.Split('/').Length > 1 ? route.Split('/')[1] : string.Empty)}"); if (activity is not null) { activity.TrackRestControllerActivityStarted( From 18dd1f37dc7512dd003c85bc8e2d120e149302c2 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Fri, 11 Apr 2025 12:51:55 +0200 Subject: [PATCH 30/37] handle userRole with X-MS-API-ROLE and add check for nullability --- src/Service/Controllers/RestController.cs | 3 ++- src/Service/Telemetry/TelemetryTracesHelper.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 8922c361e8..b5c1c71b84 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Net; using System.Net.Mime; using System.Threading.Tasks; @@ -202,7 +203,7 @@ private async Task HandleOperation( operationType.ToString(), route, HttpContext.Request.QueryString.ToString(), - HttpContext.User.FindFirst("role")?.Value, + HttpContext.Request.Headers["X-MS-API-ROLE"].FirstOrDefault() ?? HttpContext.User.FindFirst("role")?.Value, "REST"); } diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index 2f97b70a3e..e18b5dcc72 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -41,12 +41,16 @@ public static void TrackRestControllerActivityStarted( activity.SetTag("user-agent", userAgent); activity.SetTag("action.type", actionType); activity.SetTag("http.url", httpURL); - if (queryString is not null && queryString != string.Empty) + if (!string.IsNullOrEmpty(queryString)) { activity.SetTag("http.querystring", queryString); } - activity.SetTag("user.role", userRole); + if (!string.IsNullOrEmpty(userRole)) + { + activity.SetTag("user.role", userRole); + } + activity.SetTag("api.type", apiType); } } From d2a9cff2bda0a0e663af77045002733af7dcc197 Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Fri, 11 Apr 2025 20:02:21 +0200 Subject: [PATCH 31/37] fixes logs --- .../dab-config.CosmosDb_NoSql.json | 1501 +++++++++-------- src/Service/Controllers/RestController.cs | 14 +- src/Service/Program.cs | 19 + src/Service/Startup.cs | 55 +- 4 files changed, 823 insertions(+), 766 deletions(-) diff --git a/src/Service.Tests/dab-config.CosmosDb_NoSql.json b/src/Service.Tests/dab-config.CosmosDb_NoSql.json index c55314e592..2a555d93ba 100644 --- a/src/Service.Tests/dab-config.CosmosDb_NoSql.json +++ b/src/Service.Tests/dab-config.CosmosDb_NoSql.json @@ -31,767 +31,772 @@ "provider": "StaticWebApps" }, "mode": "development" - } - }, - "entities": { - "PlanetAlias": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Planet", - "plural": "Planets" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [], - "include": [ - "*" - ] - } - }, - { - "action": "create" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "limited-read-role", - "actions": [ - { - "action": "read" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read" - } - ] - } - ] - }, - "Character": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Character", - "plural": "Characters" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read" - } - ] - } - ] - }, - "Star": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Star", - "plural": "Stars" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] - }, - "Tag": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Tag", - "plural": "Tags" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] - }, - "Moon": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Moon", - "plural": "Moons" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] - }, - "Earth": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Earth", - "plural": "Earths" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "field-mutation-with-read-permission", - "actions": [ - { - "action": "read" - } - ] - }, - { - "role": "anonymous", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "*" - ], - "include": [ - "*" - ] - } - }, - { - "action": "create" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "limited-read-role", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read", - "policy": { - "database": "@item.type eq 'earth0'" - } - } - ] - } - ] }, - "Sun": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { + "telemetry": { + "app-insights": { "enabled": true, - "type": { - "singular": "Sun", - "plural": "Suns" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "*" - ] - } - }, - { - "action": "create" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] + "connection-string": "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://dc.services.visualstudio.com/v2/track" + } }, - "AdditionalAttribute": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "AdditionalAttribute", - "plural": "AdditionalAttributes" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "*" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read", - "policy": { - "database": "@item.name eq 'volcano0'" - } - } - ] - } - ] - }, - "MoonAdditionalAttribute": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "MoonAdditionalAttribute", - "plural": "MoonAdditionalAttributes" - } + "entities": { + "PlanetAlias": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Planet", + "plural": "Planets" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [], + "include": [ + "*" + ] + } + }, + { + "action": "create" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "limited-read-role", + "actions": [ + { + "action": "read" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read" + } + ] + } + ] }, - "rest": { - "enabled": false + "Character": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Character", + "plural": "Characters" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read" + } + ] + } + ] }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "*" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read", - "policy": { - "database": "@item.name eq 'moonattr0'" - } - } - ] - } - ] - }, - "MoreAttribute": { - "source": { - "object": "graphqldb.planet" + "Star": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Star", + "plural": "Stars" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] }, - "graphql": { - "enabled": true, - "type": { - "singular": "MoreAttribute", - "plural": "MoreAttributes" - } + "Tag": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Tag", + "plural": "Tags" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] }, - "rest": { - "enabled": false + "Moon": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Moon", + "plural": "Moons" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "update", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] - }, - "PlanetAgain": { - "source": { - "object": "graphqldb.newcontainer" + "Earth": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Earth", + "plural": "Earths" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "field-mutation-with-read-permission", + "actions": [ + { + "action": "read" + } + ] + }, + { + "role": "anonymous", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "*" + ], + "include": [ + "*" + ] + } + }, + { + "action": "create" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "limited-read-role", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read", + "policy": { + "database": "@item.type eq 'earth0'" + } + } + ] + } + ] }, - "graphql": { - "enabled": true, - "type": { - "singular": "PlanetAgain", - "plural": "PlanetAgains" - } + "Sun": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Sun", + "plural": "Suns" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "*" + ] + } + }, + { + "action": "create" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] }, - "rest": { - "enabled": false + "AdditionalAttribute": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "AdditionalAttribute", + "plural": "AdditionalAttributes" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "*" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read", + "policy": { + "database": "@item.name eq 'volcano0'" + } + } + ] + } + ] }, - "permissions": [ - { - "role": "field-mutation-with-read-permission", - "actions": [ - { - "action": "update", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - }, - { - "action": "delete", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "read" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "limited-read-role", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - } - ] - }, - { - "role": "wildcard-exclude-fields-role", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "delete", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "update", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "*" - ] - } - } - ] - }, - { - "role": "only-create-role", - "actions": [ - { - "action": "create" - } - ] - }, - { - "role": "only-update-role", - "actions": [ - { - "action": "update" - } - ] - }, - { - "role": "only-delete-role", - "actions": [ - { - "action": "delete" - } - ] - } - ] - }, - "InvalidAuthModel": { - "source": { - "object": "graphqldb.invalidAuthModelContainer" + "MoonAdditionalAttribute": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "MoonAdditionalAttribute", + "plural": "MoonAdditionalAttributes" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "*" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read", + "policy": { + "database": "@item.name eq 'moonattr0'" + } + } + ] + } + ] }, - "graphql": { - "enabled": true, - "type": { - "singular": "InvalidAuthModel", - "plural": "InvalidAuthModels" - } + "MoreAttribute": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "MoreAttribute", + "plural": "MoreAttributes" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "update", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] }, - "rest": { - "enabled": false + "PlanetAgain": { + "source": { + "object": "graphqldb.newcontainer" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "PlanetAgain", + "plural": "PlanetAgains" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "field-mutation-with-read-permission", + "actions": [ + { + "action": "update", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + }, + { + "action": "delete", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "read" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "limited-read-role", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + } + ] + }, + { + "role": "wildcard-exclude-fields-role", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "delete", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "update", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "*" + ] + } + } + ] + }, + { + "role": "only-create-role", + "actions": [ + { + "action": "create" + } + ] + }, + { + "role": "only-update-role", + "actions": [ + { + "action": "update" + } + ] + }, + { + "role": "only-delete-role", + "actions": [ + { + "action": "delete" + } + ] + } + ] }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "update", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] + "InvalidAuthModel": { + "source": { + "object": "graphqldb.invalidAuthModelContainer" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "InvalidAuthModel", + "plural": "InvalidAuthModels" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "update", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] + } } } -} \ No newline at end of file diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index ac773ecc30..ad6ce3728e 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -192,6 +192,11 @@ private async Task 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)}"); @@ -210,12 +215,6 @@ private async Task HandleOperation( TelemetryMetricsHelper.IncrementActiveRequests(); try { - - if (route.Equals(REDIRECTED_ROUTE)) - { - return NotFound(); - } - // Validate the PathBase matches the configured REST path. string routeAfterPathBase = _restService.GetRouteAfterPathBase(route); @@ -261,7 +260,7 @@ private async Task HandleOperation( activity.TrackRestControllerActivityFinished(statusCode); } - TelemetryMetricsHelper.TrackRequest(HttpContext.Request.Method, statusCode, route, "REST"); + return result; } catch (DataApiBuilderException ex) @@ -296,6 +295,7 @@ private async Task HandleOperation( finally { stopwatch.Stop(); + TelemetryMetricsHelper.TrackRequest(HttpContext.Request.Method, Response.StatusCode, route, "REST"); TelemetryMetricsHelper.TrackRequestDuration(HttpContext.Request.Method, Response.StatusCode, route, "REST", stopwatch.Elapsed.TotalMilliseconds); TelemetryMetricsHelper.DecrementActiveRequests(); diff --git a/src/Service/Program.cs b/src/Service/Program.cs index 4e7bb8c340..716f0e2b6b 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -17,6 +17,9 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.ApplicationInsights; +using OpenTelemetry.Exporter; +using OpenTelemetry.Logs; +using OpenTelemetry.Resources; namespace Azure.DataApiBuilder.Service { @@ -175,6 +178,22 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel(LogLevel logLevel, Tele } } + if (Startup.OpenTelemetryOptions.Enabled && !string.IsNullOrWhiteSpace(Startup.OpenTelemetryOptions.Endpoint)) + { + builder.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(Startup.OpenTelemetryOptions.ServiceName!)); + logging.AddOtlpExporter(configure => + { + configure.Endpoint = new Uri(Startup.OpenTelemetryOptions.Endpoint); + configure.Headers = Startup.OpenTelemetryOptions.Headers; + configure.Protocol = OtlpExportProtocol.Grpc; + }); + }); + } + builder.AddConsole(); }); } diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 3986256130..cc97b3fe0e 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -63,6 +63,7 @@ public class Startup public static bool IsLogLevelOverriddenByCli; public static ApplicationInsightsOptions AppInsightsOptions = new(); + public static OpenTelemetryOptions OpenTelemetryOptions = new(); public const string NO_HTTPS_REDIRECT_FLAG = "--no-https-redirect"; private HotReloadEventHandler _hotReloadEventHandler = new(); private RuntimeConfigProvider? _configProvider; @@ -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 => + //{ + // 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!)) @@ -409,6 +410,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuntimeC { // Configure Application Insights Telemetry ConfigureApplicationInsightsTelemetry(app, runtimeConfig); + ConfigureOpenTelemetry(app, runtimeConfig); // Config provided before starting the engine. isRuntimeReady = PerformOnConfigChangeAsync(app).Result; @@ -726,6 +728,37 @@ private void ConfigureApplicationInsightsTelemetry(IApplicationBuilder app, Runt } } + /// + /// Configure Open Telemetry based on the loaded runtime configuration. If Open Telemetry + /// is enabled, we can track different events and metrics. + /// + /// The provider used to load runtime configuration. + /// + private void ConfigureOpenTelemetry(IApplicationBuilder app, 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(); + } + } + /// /// Sets Static Web Apps EasyAuth as the authentication scheme for the engine. /// From cf1b821385282b944592f725f84adb58c88b178b Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Sun, 13 Apr 2025 11:12:25 +0200 Subject: [PATCH 32/37] removes commented otel logs --- src/Service/Startup.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index cc97b3fe0e..75e3e256e9 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -126,17 +126,6 @@ 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; - // }); - - //}) .WithMetrics(metrics => { metrics.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!)) From b5d3f7b65d9bbaf93225fc0eb922dd98bc05ea5f Mon Sep 17 00:00:00 2001 From: Tommaso Stocchi Date: Mon, 14 Apr 2025 23:18:13 +0200 Subject: [PATCH 33/37] adds asp net core base logging --- src/Service/Startup.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 75e3e256e9..5581141c57 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -126,6 +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; + }); + + }) .WithMetrics(metrics => { metrics.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(runtimeConfig.Runtime.Telemetry.OpenTelemetry.ServiceName!)) From 0a21b84be36a33a02de913020b3ad5a886c8a92f Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 21 Apr 2025 12:08:25 -0700 Subject: [PATCH 34/37] Changes to Tomaso Branch --- src/Service/Controllers/RestController.cs | 53 +++++++++++-------- src/Service/Startup.cs | 4 +- .../Telemetry/TelemetryMetricsHelper.cs | 16 +++--- .../Telemetry/TelemetryTracesHelper.cs | 13 +++-- 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index ad6ce3728e..562fdce4df 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -15,6 +15,7 @@ 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 @@ -200,21 +201,23 @@ private async Task HandleOperation( 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)}"); - if (activity is not null) - { - activity.TrackRestControllerActivityStarted( - HttpContext.Request.Method, - 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, - "REST"); - } - TelemetryMetricsHelper.IncrementActiveRequests(); try { + TelemetryMetricsHelper.IncrementActiveRequests(ApiType.REST); + + if (activity is not null) + { + activity.TrackRestControllerActivityStarted( + Enum.Parse(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. string routeAfterPathBase = _restService.GetRouteAfterPathBase(route); @@ -242,7 +245,7 @@ private async Task HandleOperation( if (queryActivity is not null) { queryActivity.TrackQueryActivityStarted( - databaseType.ToString(), + databaseType, dataSourceName); } @@ -257,7 +260,8 @@ private async Task HandleOperation( int statusCode = (result as ObjectResult)?.StatusCode ?? (result as StatusCodeResult)?.StatusCode ?? (result as JsonResult)?.StatusCode ?? 200; if (activity is not null && activity.IsAllDataRequested) { - activity.TrackRestControllerActivityFinished(statusCode); + HttpStatusCode httpStatusCode = Enum.Parse(statusCode.ToString(), ignoreCase: true); + activity.TrackRestControllerActivityFinished(httpStatusCode); } @@ -271,9 +275,11 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)ex.StatusCode; - activity?.TrackRestControllerActivityFinishedWithException(ex, Response.StatusCode); + activity?.TrackRestControllerActivityFinishedWithException(ex, Enum.Parse(Response.StatusCode.ToString(), ignoreCase: true)); - TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); + HttpMethod method = Enum.Parse(HttpContext.Request.Method, ignoreCase: true); + HttpStatusCode httpStatusCode = Enum.Parse(Response.StatusCode.ToString(), ignoreCase: true); + TelemetryMetricsHelper.TrackError(method, httpStatusCode, route, ApiType.REST, ex); return ErrorResponse(ex.SubStatusCode.ToString(), ex.Message, ex.StatusCode); } catch (Exception ex) @@ -284,9 +290,12 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)HttpStatusCode.InternalServerError; - activity?.TrackRestControllerActivityFinishedWithException(ex, Response.StatusCode); - TelemetryMetricsHelper.TrackError(HttpContext.Request.Method, Response.StatusCode, route, "REST", ex); + HttpMethod method = Enum.Parse(HttpContext.Request.Method, ignoreCase: true); + HttpStatusCode httpStatusCode = Enum.Parse(Response.StatusCode.ToString(), ignoreCase: true); + activity?.TrackRestControllerActivityFinishedWithException(ex, httpStatusCode); + + TelemetryMetricsHelper.TrackError(method, httpStatusCode, route, ApiType.REST, ex); return ErrorResponse( DataApiBuilderException.SubStatusCodes.UnexpectedError.ToString(), SERVER_ERROR, @@ -295,10 +304,12 @@ private async Task HandleOperation( finally { stopwatch.Stop(); - TelemetryMetricsHelper.TrackRequest(HttpContext.Request.Method, Response.StatusCode, route, "REST"); - TelemetryMetricsHelper.TrackRequestDuration(HttpContext.Request.Method, Response.StatusCode, route, "REST", stopwatch.Elapsed.TotalMilliseconds); + HttpMethod method = Enum.Parse(HttpContext.Request.Method, ignoreCase: true); + HttpStatusCode httpStatusCode = Enum.Parse(Response.StatusCode.ToString(), ignoreCase: true); + TelemetryMetricsHelper.TrackRequest(method, httpStatusCode, route, ApiType.REST); + TelemetryMetricsHelper.TrackRequestDuration(method, httpStatusCode, route, ApiType.REST, stopwatch.Elapsed); - TelemetryMetricsHelper.DecrementActiveRequests(); + TelemetryMetricsHelper.DecrementActiveRequests(ApiType.REST); } } diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index 5581141c57..a0d3ef3393 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -410,7 +410,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, RuntimeC { // Configure Application Insights Telemetry ConfigureApplicationInsightsTelemetry(app, runtimeConfig); - ConfigureOpenTelemetry(app, runtimeConfig); + ConfigureOpenTelemetry(runtimeConfig); // Config provided before starting the engine. isRuntimeReady = PerformOnConfigChangeAsync(app).Result; @@ -734,7 +734,7 @@ private void ConfigureApplicationInsightsTelemetry(IApplicationBuilder app, Runt /// /// The provider used to load runtime configuration. /// - private void ConfigureOpenTelemetry(IApplicationBuilder app, RuntimeConfig runtimeConfig) + private void ConfigureOpenTelemetry(RuntimeConfig runtimeConfig) { if (runtimeConfig?.Runtime?.Telemetry is not null && runtimeConfig.Runtime.Telemetry.OpenTelemetry is not null) diff --git a/src/Service/Telemetry/TelemetryMetricsHelper.cs b/src/Service/Telemetry/TelemetryMetricsHelper.cs index d3410e935a..c8e61032b3 100644 --- a/src/Service/Telemetry/TelemetryMetricsHelper.cs +++ b/src/Service/Telemetry/TelemetryMetricsHelper.cs @@ -2,7 +2,11 @@ // 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 { @@ -19,9 +23,9 @@ public static class TelemetryMetricsHelper private static readonly Counter _totalRequests = _meter.CreateCounter("total_requests"); private static readonly Histogram _requestDuration = _meter.CreateHistogram("request_duration", "ms"); - public static void IncrementActiveRequests() => _activeRequests.Add(1); + public static void IncrementActiveRequests(ApiType kind) => _activeRequests.Add(1, new KeyValuePair("api_type", kind)); - public static void DecrementActiveRequests() => _activeRequests.Add(-1); + public static void DecrementActiveRequests(ApiType kind) => _activeRequests.Add(-1, new KeyValuePair("api_type", kind)); /// /// Tracks a request by incrementing the total requests counter and associating it with metadata. @@ -30,7 +34,7 @@ public static class TelemetryMetricsHelper /// The HTTP status code of the response. /// The endpoint being accessed. /// The type of API being used (e.g., REST, GraphQL). - public static void TrackRequest(string method, int statusCode, string endpoint, string apiType) + public static void TrackRequest(Kestral method, HttpStatusCode statusCode, string endpoint, ApiType apiType) { _totalRequests.Add(1, new("method", method), @@ -47,7 +51,7 @@ public static void TrackRequest(string method, int statusCode, string endpoint, /// The endpoint being accessed. /// The type of API being used (e.g., REST, GraphQL). /// The exception that occurred. - public static void TrackError(string method, int statusCode, string endpoint, string apiType, Exception ex) + public static void TrackError(Kestral method, HttpStatusCode statusCode, string endpoint, ApiType apiType, Exception ex) { _errorCounter.Add(1, new("method", method), @@ -65,9 +69,9 @@ public static void TrackError(string method, int statusCode, string endpoint, st /// The endpoint being accessed. /// The type of API being used (e.g., REST, GraphQL). /// The duration of the request in milliseconds. - public static void TrackRequestDuration(string method, int statusCode, string endpoint, string apiType, double duration) + public static void TrackRequestDuration(Kestral method, HttpStatusCode statusCode, string endpoint, ApiType apiType, TimeSpan duration) { - _requestDuration.Record(duration, + _requestDuration.Record(duration.TotalMilliseconds, new("method", method), new("status_code", statusCode), new("endpoint", endpoint), diff --git a/src/Service/Telemetry/TelemetryTracesHelper.cs b/src/Service/Telemetry/TelemetryTracesHelper.cs index e18b5dcc72..33a6029f43 100644 --- a/src/Service/Telemetry/TelemetryTracesHelper.cs +++ b/src/Service/Telemetry/TelemetryTracesHelper.cs @@ -3,7 +3,10 @@ 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 { @@ -27,13 +30,13 @@ public static class TelemetryTracesHelper /// The type of API being used (e.g., REST, GraphQL). public static void TrackRestControllerActivityStarted( this Activity activity, - string httpMethod, + Kestral httpMethod, string userAgent, string actionType, string httpURL, string? queryString, string? userRole, - string apiType) + ApiType apiType) { if (activity.IsAllDataRequested) { @@ -63,7 +66,7 @@ public static void TrackRestControllerActivityStarted( /// The name of the data source being queried. public static void TrackQueryActivityStarted( this Activity activity, - string databaseType, + DatabaseType databaseType, string dataSourceName) { if (activity.IsAllDataRequested) @@ -81,7 +84,7 @@ public static void TrackQueryActivityStarted( /// The HTTP status code of the response. public static void TrackRestControllerActivityFinished( this Activity activity, - int statusCode) + HttpStatusCode statusCode) { if (activity.IsAllDataRequested) { @@ -98,7 +101,7 @@ public static void TrackRestControllerActivityFinished( public static void TrackRestControllerActivityFinishedWithException( this Activity activity, Exception ex, - int statusCode) + HttpStatusCode statusCode) { if (activity.IsAllDataRequested) { From 3d09aca71dab06094a8e67e35f93904500385ecd Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 21 Apr 2025 13:05:20 -0700 Subject: [PATCH 35/37] Fix Unit Test errors --- src/Service/Controllers/RestController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index 562fdce4df..b7f39cd0d6 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -264,7 +264,6 @@ private async Task HandleOperation( activity.TrackRestControllerActivityFinished(httpStatusCode); } - return result; } catch (DataApiBuilderException ex) From de36de5c217301c8d6617495c1d9066389b30bb9 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 21 Apr 2025 16:27:06 -0700 Subject: [PATCH 36/37] Fixed Unit Test Failure --- .../dab-config.CosmosDb_NoSql.json | 1499 +++++++++-------- 1 file changed, 750 insertions(+), 749 deletions(-) diff --git a/src/Service.Tests/dab-config.CosmosDb_NoSql.json b/src/Service.Tests/dab-config.CosmosDb_NoSql.json index 2a555d93ba..5704dc19be 100644 --- a/src/Service.Tests/dab-config.CosmosDb_NoSql.json +++ b/src/Service.Tests/dab-config.CosmosDb_NoSql.json @@ -37,766 +37,767 @@ "enabled": true, "connection-string": "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://dc.services.visualstudio.com/v2/track" } + } + }, + "entities": { + "PlanetAlias": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Planet", + "plural": "Planets" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [], + "include": [ + "*" + ] + } + }, + { + "action": "create" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "limited-read-role", + "actions": [ + { + "action": "read" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read" + } + ] + } + ] }, - "entities": { - "PlanetAlias": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Planet", - "plural": "Planets" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [], - "include": [ - "*" - ] - } - }, - { - "action": "create" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "limited-read-role", - "actions": [ - { - "action": "read" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read" - } - ] - } - ] + "Character": { + "source": { + "object": "graphqldb.planet" }, - "Character": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Character", - "plural": "Characters" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read" - } - ] - } - ] + "graphql": { + "enabled": true, + "type": { + "singular": "Character", + "plural": "Characters" + } }, - "Star": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Star", - "plural": "Stars" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] + "rest": { + "enabled": false }, - "Tag": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Tag", - "plural": "Tags" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read" + } + ] + } + ] + }, + "Star": { + "source": { + "object": "graphqldb.planet" }, - "Moon": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Moon", - "plural": "Moons" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] + "graphql": { + "enabled": true, + "type": { + "singular": "Star", + "plural": "Stars" + } }, - "Earth": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Earth", - "plural": "Earths" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "field-mutation-with-read-permission", - "actions": [ - { - "action": "read" - } - ] - }, - { - "role": "anonymous", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "*" - ], - "include": [ - "*" - ] - } - }, - { - "action": "create" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "limited-read-role", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read", - "policy": { - "database": "@item.type eq 'earth0'" - } - } - ] - } - ] + "rest": { + "enabled": false }, - "Sun": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "Sun", - "plural": "Suns" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "*" - ] - } - }, - { - "action": "create" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] + }, + "Tag": { + "source": { + "object": "graphqldb.planet" }, - "AdditionalAttribute": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "AdditionalAttribute", - "plural": "AdditionalAttributes" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "*" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read", - "policy": { - "database": "@item.name eq 'volcano0'" - } - } - ] - } - ] + "graphql": { + "enabled": true, + "type": { + "singular": "Tag", + "plural": "Tags" + } }, - "MoonAdditionalAttribute": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "MoonAdditionalAttribute", - "plural": "MoonAdditionalAttributes" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "*" - } - ] - }, - { - "role": "item-level-permission-role", - "actions": [ - { - "action": "read", - "policy": { - "database": "@item.name eq 'moonattr0'" - } - } - ] - } - ] + "rest": { + "enabled": false }, - "MoreAttribute": { - "source": { - "object": "graphqldb.planet" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "MoreAttribute", - "plural": "MoreAttributes" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "update", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] + }, + "Moon": { + "source": { + "object": "graphqldb.planet" }, - "PlanetAgain": { - "source": { - "object": "graphqldb.newcontainer" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "PlanetAgain", - "plural": "PlanetAgains" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "field-mutation-with-read-permission", - "actions": [ - { - "action": "update", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - }, - { - "action": "delete", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "read" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - }, - { - "role": "limited-read-role", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id", - "type" - ] - } - } - ] - }, - { - "role": "wildcard-exclude-fields-role", - "actions": [ - { - "action": "read", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "delete", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "update", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "*" - ] - } - } - ] - }, - { - "role": "only-create-role", - "actions": [ - { - "action": "create" - } - ] - }, - { - "role": "only-update-role", - "actions": [ - { - "action": "update" - } - ] - }, - { - "role": "only-delete-role", - "actions": [ - { - "action": "delete" - } - ] - } - ] + "graphql": { + "enabled": true, + "type": { + "singular": "Moon", + "plural": "Moons" + } }, - "InvalidAuthModel": { - "source": { - "object": "graphqldb.invalidAuthModelContainer" - }, - "graphql": { - "enabled": true, - "type": { - "singular": "InvalidAuthModel", - "plural": "InvalidAuthModels" - } - }, - "rest": { - "enabled": false - }, - "permissions": [ - { - "role": "anonymous", - "actions": [ - { - "action": "update", - "fields": { - "exclude": [ - "*" - ] - } - }, - { - "action": "read", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "create", - "fields": { - "exclude": [ - "name" - ], - "include": [ - "id" - ] - } - }, - { - "action": "delete" - } - ] - }, - { - "role": "authenticated", - "actions": [ - { - "action": "create" - }, - { - "action": "read" - }, - { - "action": "update" - }, - { - "action": "delete" - } - ] - } - ] - } + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] + }, + "Earth": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Earth", + "plural": "Earths" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "field-mutation-with-read-permission", + "actions": [ + { + "action": "read" + } + ] + }, + { + "role": "anonymous", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "*" + ], + "include": [ + "*" + ] + } + }, + { + "action": "create" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "limited-read-role", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read", + "policy": { + "database": "@item.type eq 'earth0'" + } + } + ] + } + ] + }, + "Sun": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "Sun", + "plural": "Suns" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "*" + ] + } + }, + { + "action": "create" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] + }, + "AdditionalAttribute": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "AdditionalAttribute", + "plural": "AdditionalAttributes" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "*" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read", + "policy": { + "database": "@item.name eq 'volcano0'" + } + } + ] + } + ] + }, + "MoonAdditionalAttribute": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "MoonAdditionalAttribute", + "plural": "MoonAdditionalAttributes" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "*" + } + ] + }, + { + "role": "item-level-permission-role", + "actions": [ + { + "action": "read", + "policy": { + "database": "@item.name eq 'moonattr0'" + } + } + ] + } + ] + }, + "MoreAttribute": { + "source": { + "object": "graphqldb.planet" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "MoreAttribute", + "plural": "MoreAttributes" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "update", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] + }, + "PlanetAgain": { + "source": { + "object": "graphqldb.newcontainer" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "PlanetAgain", + "plural": "PlanetAgains" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "field-mutation-with-read-permission", + "actions": [ + { + "action": "update", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + }, + { + "action": "delete", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "read" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + }, + { + "role": "limited-read-role", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id", + "type" + ] + } + } + ] + }, + { + "role": "wildcard-exclude-fields-role", + "actions": [ + { + "action": "read", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "delete", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "update", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "*" + ] + } + } + ] + }, + { + "role": "only-create-role", + "actions": [ + { + "action": "create" + } + ] + }, + { + "role": "only-update-role", + "actions": [ + { + "action": "update" + } + ] + }, + { + "role": "only-delete-role", + "actions": [ + { + "action": "delete" + } + ] + } + ] + }, + "InvalidAuthModel": { + "source": { + "object": "graphqldb.invalidAuthModelContainer" + }, + "graphql": { + "enabled": true, + "type": { + "singular": "InvalidAuthModel", + "plural": "InvalidAuthModels" + } + }, + "rest": { + "enabled": false + }, + "permissions": [ + { + "role": "anonymous", + "actions": [ + { + "action": "update", + "fields": { + "exclude": [ + "*" + ] + } + }, + { + "action": "read", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "create", + "fields": { + "exclude": [ + "name" + ], + "include": [ + "id" + ] + } + }, + { + "action": "delete" + } + ] + }, + { + "role": "authenticated", + "actions": [ + { + "action": "create" + }, + { + "action": "read" + }, + { + "action": "update" + }, + { + "action": "delete" + } + ] + } + ] } } +} From 8780c393d236c2483c659c00fe2b1ec8e728eaa2 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 22 Apr 2025 19:12:20 -0700 Subject: [PATCH 37/37] Changes based on comments --- src/Service/Controllers/RestController.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Service/Controllers/RestController.cs b/src/Service/Controllers/RestController.cs index b7f39cd0d6..10e1f4263d 100644 --- a/src/Service/Controllers/RestController.cs +++ b/src/Service/Controllers/RestController.cs @@ -274,11 +274,10 @@ private async Task HandleOperation( HttpContextExtensions.GetLoggerCorrelationId(HttpContext)); Response.StatusCode = (int)ex.StatusCode; - activity?.TrackRestControllerActivityFinishedWithException(ex, Enum.Parse(Response.StatusCode.ToString(), ignoreCase: true)); + activity?.TrackRestControllerActivityFinishedWithException(ex, ex.StatusCode); HttpMethod method = Enum.Parse(HttpContext.Request.Method, ignoreCase: true); - HttpStatusCode httpStatusCode = Enum.Parse(Response.StatusCode.ToString(), ignoreCase: true); - TelemetryMetricsHelper.TrackError(method, httpStatusCode, route, ApiType.REST, ex); + TelemetryMetricsHelper.TrackError(method, ex.StatusCode, route, ApiType.REST, ex); return ErrorResponse(ex.SubStatusCode.ToString(), ex.Message, ex.StatusCode); } catch (Exception ex) @@ -291,10 +290,9 @@ private async Task HandleOperation( Response.StatusCode = (int)HttpStatusCode.InternalServerError; HttpMethod method = Enum.Parse(HttpContext.Request.Method, ignoreCase: true); - HttpStatusCode httpStatusCode = Enum.Parse(Response.StatusCode.ToString(), ignoreCase: true); - activity?.TrackRestControllerActivityFinishedWithException(ex, httpStatusCode); + activity?.TrackRestControllerActivityFinishedWithException(ex, HttpStatusCode.InternalServerError); - TelemetryMetricsHelper.TrackError(method, httpStatusCode, route, ApiType.REST, ex); + TelemetryMetricsHelper.TrackError(method, HttpStatusCode.InternalServerError, route, ApiType.REST, ex); return ErrorResponse( DataApiBuilderException.SubStatusCodes.UnexpectedError.ToString(), SERVER_ERROR,