diff --git a/src/VirtoCommerce.Xapi.Core/Infrastructure/GraphQLHttpMiddlewareWithLogs.cs b/src/VirtoCommerce.Xapi.Core/Infrastructure/GraphQLHttpMiddlewareWithLogs.cs index 061f060..32126a0 100644 --- a/src/VirtoCommerce.Xapi.Core/Infrastructure/GraphQLHttpMiddlewareWithLogs.cs +++ b/src/VirtoCommerce.Xapi.Core/Infrastructure/GraphQLHttpMiddlewareWithLogs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net.WebSockets; using System.Threading.Tasks; @@ -7,10 +8,7 @@ using GraphQL.Server.Transports.AspNetCore; using GraphQL.Transport; using GraphQL.Types; -using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.DataContracts; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -18,11 +16,15 @@ namespace VirtoCommerce.Xapi.Core.Infrastructure { + /// + /// Relies solely on telemetry + /// instead of direct Microsoft.ApplicationInsights types. Any configured + /// OpenTelemetry exporter (Application Insights, OTLP, etc.) picks up the data automatically. + /// public class GraphQLHttpMiddlewareWithLogs : GraphQLHttpMiddleware where TSchema : ISchema { private readonly ILogger _logger; - private readonly TelemetryClient _telemetryClient; public GraphQLHttpMiddlewareWithLogs( RequestDelegate next, @@ -31,58 +33,46 @@ public GraphQLHttpMiddlewareWithLogs( IServiceScopeFactory serviceScopeFactory, GraphQLHttpMiddlewareOptions options, IHostApplicationLifetime hostApplicationLifetime, - ILogger> logger, - TelemetryClient telemetryClient = null) + ILogger> logger) : base(next, serializer, documentExecuter, serviceScopeFactory, options, hostApplicationLifetime) { _logger = logger; - _telemetryClient = telemetryClient; } - protected override async Task ExecuteRequestAsync(HttpContext context, GraphQLRequest request, IServiceProvider serviceProvider, IDictionary userContext) + protected override async Task ExecuteRequestAsync( + HttpContext context, + GraphQLRequest request, + IServiceProvider serviceProvider, + IDictionary userContext) { - // process Playground schema introspection queries without AppInsights logging - if (_telemetryClient is null || request?.OperationName == "IntrospectionQuery") + // skip Playground schema introspection queries + if (request?.OperationName == "IntrospectionQuery") { return await base.ExecuteRequestAsync(context, request, serviceProvider, userContext); } - // prepare AppInsights telemetry - var appInsightsOperationName = $"POST graphql/{request?.OperationName}"; - - var requestTelemetry = new RequestTelemetry + // enrich the server activity created by AspNetCore OTel instrumentation + var activity = Activity.Current; + if (activity != null && request?.OperationName.IsNullOrEmpty() == false) { - Name = appInsightsOperationName, - Url = new Uri(context.Request.GetEncodedUrl()), - }; - - //Replace W3C Trace Context id generation https://www.w3.org/TR/trace-context/ to unique value - requestTelemetry.Context.Operation.Id = Guid.NewGuid().ToString("N"); - requestTelemetry.Context.Operation.Name = appInsightsOperationName; - requestTelemetry.Properties["Type"] = "GraphQL"; - - using var operation = _telemetryClient.StartOperation(requestTelemetry); + activity.DisplayName = $"POST graphql/{request.OperationName}"; + activity.SetTag("url.path", $"graphql/{request.OperationName}"); + activity.SetTag("graphql.type", "GraphQL"); + } - // execute GraphQL query var result = await base.ExecuteRequestAsync(context, request, serviceProvider, userContext); - requestTelemetry.Success = result.Errors.IsNullOrEmpty(); - if (requestTelemetry.Success != true) + if (!result.Errors.IsNullOrEmpty() && activity != null) { - // pass an error response code to trigger AppInsights operation failure state - requestTelemetry.ResponseCode = "500"; - - Exception exception = result.Errors?.Count > 1 - ? new AggregateException(result.Errors) - : result.Errors?.FirstOrDefault(); - - var exceptionTelemetry = new ExceptionTelemetry(exception); - - // link exception with the operation - exceptionTelemetry.Context.Operation.ParentId = requestTelemetry.Context.Operation.Id; - exceptionTelemetry.Context.Operation.Name = appInsightsOperationName; + var exception = result.Errors.Count > 1 + ? (Exception)new AggregateException(result.Errors) + : result.Errors.FirstOrDefault(); - _telemetryClient.TrackException(exceptionTelemetry); + activity.SetStatus(ActivityStatusCode.Error, exception?.Message); + if (exception != null) + { + activity.AddException(exception); + } } return result; diff --git a/src/VirtoCommerce.Xapi.Core/Infrastructure/IgnorePlainCoreXapiGraphQLTelemetryProcessor.cs b/src/VirtoCommerce.Xapi.Core/Infrastructure/IgnorePlainCoreXapiGraphQLTelemetryProcessor.cs index 19f32d0..041b1c8 100644 --- a/src/VirtoCommerce.Xapi.Core/Infrastructure/IgnorePlainCoreXapiGraphQLTelemetryProcessor.cs +++ b/src/VirtoCommerce.Xapi.Core/Infrastructure/IgnorePlainCoreXapiGraphQLTelemetryProcessor.cs @@ -1,31 +1,25 @@ -using Microsoft.ApplicationInsights.Channel; -using Microsoft.ApplicationInsights.DataContracts; -using Microsoft.ApplicationInsights.Extensibility; +using System.Diagnostics; +using OpenTelemetry; using VirtoCommerce.Platform.Core.Common; -namespace VirtoCommerce.Xapi.Core.Infrastructure -{ - /// - /// AppInsights telemetry processor that skips default Graphql queries - /// - public class IgnorePlainCoreXapiGraphQLTelemetryProcessor : ITelemetryProcessor - { - private ITelemetryProcessor Next { get; set; } - public IgnorePlainCoreXapiGraphQLTelemetryProcessor(ITelemetryProcessor next) - { - Next = next; - } +namespace VirtoCommerce.ApplicationInsights.Data.Telemetry; - public void Process(ITelemetry item) +/// +/// OpenTelemetry processor that suppresses activities whose urls match GraphQL related path but +/// graphql.type tag is empty meaning this activity was not generated by GraphQLHttpMiddlewareWithLogs. +/// Clearing the flag prevents the activity +/// from being exported to Application Insights. +/// +public class IgnorePlainCoreXapiGraphQLTelemetryProcessor : BaseProcessor +{ + public override void OnEnd(Activity activity) + { + var urlPath = activity?.GetTagItem("url.path")?.ToString(); + var graphQlTag = activity?.GetTagItem("graphql.type"); + if (urlPath != null && urlPath.ContainsIgnoreCase("/graphql") && graphQlTag == null) { - // skip plain "POST /graphql" (without operation name) requests to reduce AppInsights telemetry flood - if (item is RequestTelemetry request && request.Name?.EqualsIgnoreCase("POST /graphql") == true) - { - return; - } - - Next.Process(item); + activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; } } } diff --git a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj index 51476be..7e9120a 100644 --- a/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj +++ b/src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj @@ -1,4 +1,4 @@ - + Experiene API functionality net10.0 @@ -23,16 +23,16 @@ - + + - diff --git a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj index 2c71222..8b4ff46 100644 --- a/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj +++ b/src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -9,7 +9,7 @@ snupkg - + diff --git a/src/VirtoCommerce.Xapi.Web/Module.cs b/src/VirtoCommerce.Xapi.Web/Module.cs index 23b6931..8c521aa 100644 --- a/src/VirtoCommerce.Xapi.Web/Module.cs +++ b/src/VirtoCommerce.Xapi.Web/Module.cs @@ -10,13 +10,14 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +using OpenTelemetry.Trace; +using VirtoCommerce.ApplicationInsights.Data.Telemetry; using VirtoCommerce.Platform.Core.DeveloperTools; using VirtoCommerce.Platform.Core.Modularity; using VirtoCommerce.Platform.Core.Settings; using VirtoCommerce.StoreModule.Core.Model; using VirtoCommerce.Xapi.Core; using VirtoCommerce.Xapi.Core.Extensions; -using VirtoCommerce.Xapi.Core.Infrastructure; using VirtoCommerce.Xapi.Core.Infrastructure.Validation; using VirtoCommerce.Xapi.Core.Models; using VirtoCommerce.Xapi.Core.Subscriptions; @@ -51,7 +52,7 @@ private bool IsComplexityValidationEnabled public void Initialize(IServiceCollection serviceCollection) { - serviceCollection.AddApplicationInsightsTelemetryProcessor(); + serviceCollection.AddOpenTelemetry().WithTracing(t => t.AddProcessor(new IgnorePlainCoreXapiGraphQLTelemetryProcessor())); #pragma warning disable CS0618 // Type or member is obsolete // Use legacy type naming for backward compatibility diff --git a/src/VirtoCommerce.Xapi.Web/module.manifest b/src/VirtoCommerce.Xapi.Web/module.manifest index c53c57c..8a461c7 100644 --- a/src/VirtoCommerce.Xapi.Web/module.manifest +++ b/src/VirtoCommerce.Xapi.Web/module.manifest @@ -4,9 +4,9 @@ 3.1012.0 - 3.1027.0 + 3.1039.0-alpha.13307-vcst-4717 - +