Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.WebSockets;
using System.Threading.Tasks;
using GraphQL;
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;
using VirtoCommerce.Platform.Core.Common;

namespace VirtoCommerce.Xapi.Core.Infrastructure
{
/// <summary>
/// Relies solely on <see cref="System.Diagnostics.Activity"/> telemetry
/// instead of direct <c>Microsoft.ApplicationInsights</c> types. Any configured
/// OpenTelemetry exporter (Application Insights, OTLP, etc.) picks up the data automatically.
/// </summary>
public class GraphQLHttpMiddlewareWithLogs<TSchema> : GraphQLHttpMiddleware<TSchema>
where TSchema : ISchema
{
private readonly ILogger _logger;
private readonly TelemetryClient _telemetryClient;

public GraphQLHttpMiddlewareWithLogs(
RequestDelegate next,
Expand All @@ -31,58 +33,46 @@ public GraphQLHttpMiddlewareWithLogs(
IServiceScopeFactory serviceScopeFactory,
GraphQLHttpMiddlewareOptions options,
IHostApplicationLifetime hostApplicationLifetime,
ILogger<GraphQLHttpMiddlewareWithLogs<TSchema>> logger,
TelemetryClient telemetryClient = null)
ILogger<GraphQLHttpMiddlewareWithLogs<TSchema>> logger)
: base(next, serializer, documentExecuter, serviceScopeFactory, options, hostApplicationLifetime)
{
_logger = logger;
_telemetryClient = telemetryClient;
}

protected override async Task<ExecutionResult> ExecuteRequestAsync(HttpContext context, GraphQLRequest request, IServiceProvider serviceProvider, IDictionary<string, object> userContext)
protected override async Task<ExecutionResult> ExecuteRequestAsync(
HttpContext context,
GraphQLRequest request,
IServiceProvider serviceProvider,
IDictionary<string, object> 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}");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing leading slash in url.path tag value

Medium Severity

The url.path tag is set to graphql/{OperationName} without a leading /. The OpenTelemetry semantic convention defines url.path as the RFC 3986 URI path component, which for HTTP requests always starts with /. This non-standard value will appear incorrectly in Application Insights dashboards and may break alerts or queries that expect paths starting with /. The value likely needs to be $"/graphql/{request.OperationName}".

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e28b11e. Configure here.

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)
Comment thread
cursor[bot] marked this conversation as resolved.
: 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// AppInsights telemetry processor that skips default Graphql queries
/// </summary>
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)
/// <summary>
/// OpenTelemetry processor that suppresses activities whose urls match GraphQL related path but
/// <c>graphql.type</c> tag is empty meaning this activity was not generated by GraphQLHttpMiddlewareWithLogs.
/// Clearing the <see cref="ActivityTraceFlags.Recorded"/> flag prevents the activity
/// from being exported to Application Insights.
/// </summary>
public class IgnorePlainCoreXapiGraphQLTelemetryProcessor : BaseProcessor<Activity>
{
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;
}
}
}
6 changes: 3 additions & 3 deletions src/VirtoCommerce.Xapi.Core/VirtoCommerce.Xapi.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Experiene API functionality</Description>
<TargetFramework>net10.0</TargetFramework>
Expand All @@ -23,16 +23,16 @@
<PackageReference Include="GraphQL.Server.Ui.GraphiQL" Version="8.2.0" />
<!--Don't update. MediatR 13+ requires a paid license for commercial use.-->
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
<PackageReference Include="PipelineNet" Version="0.9.0" />
<PackageReference Include="RedLock.net" Version="2.3.2" />
<PackageReference Include="System.Reactive" Version="6.1.0" />
<PackageReference Include="VirtoCommerce.ApplicationInsights.Core" Version="3.1004.0-alpha.74-vcst-4717" />
<PackageReference Include="VirtoCommerce.Platform.Core" Version="3.1039.0-alpha.13307-vcst-4717" />
<PackageReference Include="VirtoCommerce.CustomerModule.Core" Version="3.1000.0" />
<PackageReference Include="VirtoCommerce.SearchModule.Core" Version="3.1000.0" />
<PackageReference Include="VirtoCommerce.Seo.Core" Version="3.1000.0" />
<PackageReference Include="VirtoCommerce.StoreModule.Core" Version="3.1000.0" />
<PackageReference Include="VirtoCommerce.TaxModule.Core" Version="3.1000.0" />
</ItemGroup>

</Project>
4 changes: 2 additions & 2 deletions src/VirtoCommerce.Xapi.Data/VirtoCommerce.Xapi.Data.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
Expand All @@ -9,7 +9,7 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="VirtoCommerce.Platform.Security" Version="3.1027.0" />
<PackageReference Include="VirtoCommerce.Platform.Security" Version="3.1039.0-alpha.13307-vcst-4717" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VirtoCommerce.Xapi.Core\VirtoCommerce.Xapi.Core.csproj" />
Expand Down
5 changes: 3 additions & 2 deletions src/VirtoCommerce.Xapi.Web/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,7 +52,7 @@ private bool IsComplexityValidationEnabled

public void Initialize(IServiceCollection serviceCollection)
{
serviceCollection.AddApplicationInsightsTelemetryProcessor<IgnorePlainCoreXapiGraphQLTelemetryProcessor>();
serviceCollection.AddOpenTelemetry().WithTracing(t => t.AddProcessor(new IgnorePlainCoreXapiGraphQLTelemetryProcessor()));

#pragma warning disable CS0618 // Type or member is obsolete
// Use legacy type naming for backward compatibility
Expand Down
4 changes: 2 additions & 2 deletions src/VirtoCommerce.Xapi.Web/module.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<version>3.1012.0</version>
<version-tag></version-tag>

<platformVersion>3.1027.0</platformVersion>
<platformVersion>3.1039.0-alpha.13307-vcst-4717</platformVersion>
<dependencies>
<dependency id="VirtoCommerce.ApplicationInsights" version="3.1001.0" optional="true" />
<dependency id="VirtoCommerce.ApplicationInsights" version="3.1004.0-alpha.74-vcst-4717" optional="true" />
<dependency id="VirtoCommerce.Customer" version="3.1000.0" />
<dependency id="VirtoCommerce.Search" version="3.1000.0" />
<dependency id="VirtoCommerce.Seo" version="3.1000.0" />
Expand Down
Loading