Azure Application Insights 与 GraphQL 的集成 API - HotChocolate Framework

Azure Application Insights integration with GraphQL API - HotChocolate Framework

有没有办法将 Azure App Insights 与 Hotchoclate graphql 框架集成?目前,有多种方法可以一起破解它。

将所有内容都放入应用洞察中的最佳方法是什么,就像您使用 REST 一样 api

因为我必须为我最近构建的应用程序解决这个问题,所以任何人都在那里苦苦挣扎,回答我自己的问题。

在 HC 中,您需要连接到诊断事件侦听器以获取管道的句柄,然后您可以从那里连接到各种事件和日志遥测。这里的关键是确保您将查询与查询名称区分开来,以便它在应用程序洞察中正确显示,否则一切都将在 /graphql 端点

public class AppInsightsDiagnosticEventListener : ExecutionDiagnosticEventListener
{
    private readonly TelemetryClient _telemetryClient;

    public AppInsightsDiagnosticEventListener(TelemetryClient telemetryClient) => _telemetryClient = telemetryClient;

    public override IDisposable ExecuteRequest(IRequestContext context)
    {
        var httpContext = GetHttpContextFrom(context);
        if (httpContext == null)
            return EmptyScope;

        //During debugging every playground action will come here so we want this while debugging
        #if DEBUG
        if (context.Request.OperationName == "IntrospectionQuery")
            return EmptyScope;
        #endif

        //Create a new telemetry request
        var operationPath = $"{context.Request.OperationName ?? "UnknownOperation"} - {context.Request.QueryHash}";
        var requestTelemetry = new RequestTelemetry()
        {
            Name = $"/graphql{operationPath}",
            Url = new Uri(httpContext.Request.GetUri().AbsoluteUri + operationPath),
        };

        requestTelemetry.Context.Operation.Name = $"POST /graphql/{operationPath}";
        requestTelemetry.Context.Operation.Id = GetOperationIdFrom(httpContext);
        requestTelemetry.Context.Operation.ParentId = GetOperationIdFrom(httpContext);
        requestTelemetry.Context.User.AuthenticatedUserId = httpContext.User.Identity?.Name ?? "Not authenticated";

        if (context.Request.Query != null)
            requestTelemetry.Properties.Add("GraphQL Query", context.Request.Query.ToString());

        var operation = _telemetryClient.StartOperation(requestTelemetry);
        return new ScopeWithEndAction(() => OnEndRequest(context, operation));
    }

    private void OnEndRequest(IRequestContext context, IOperationHolder<RequestTelemetry> operation)
    {
        var httpContext = GetHttpContextFrom(context);
        operation.Telemetry.Success = httpContext.Response.StatusCode is >= 200 and <= 299;
        operation.Telemetry.ResponseCode = httpContext.Response.StatusCode.ToString();

        if (context.Exception != null)
        {
            operation.Telemetry.Success = false;
            operation.Telemetry.ResponseCode = "500";
            _telemetryClient.TrackException(context.Exception);
        }

        if (context.ValidationResult?.HasErrors ?? false)
        {
            operation.Telemetry.Success = false;
            operation.Telemetry.ResponseCode = "400";
        }

        if (context.Result?.Errors != null)
        {
            foreach (var error in context.Result.Errors)
            {
                if (error.Exception != null)
                {
                    operation.Telemetry.Success = false;
                    _telemetryClient.TrackException(error.Exception);
                }
            }
        }

        _telemetryClient.StopOperation(operation);
    }

    public override void RequestError(IRequestContext context, Exception exception)
    {
        _telemetryClient.TrackException(exception);
        base.RequestError(context, exception);
    }

    public override void ValidationErrors(IRequestContext context, IReadOnlyList<IError> errors)
    {
        foreach (var error in errors)
        {
            _telemetryClient.TrackTrace("GraphQL validation error: " + error.Message, SeverityLevel.Warning);
        }
        base.ValidationErrors(context, errors);
    }

    private HttpContext GetHttpContextFrom(IRequestContext context)
    {
        // This method is used to enable start/stop events for query.
        if (!context.ContextData.ContainsKey("HttpContext"))
            return null;

        return context.ContextData["HttpContext"] as HttpContext;
    }

    private string GetOperationIdFrom(HttpContext context) => context.TraceIdentifier;
}

internal class ScopeWithEndAction : IDisposable
{
    private readonly Action _disposeAction;

    public ScopeWithEndAction(Action disposeAction) => _disposeAction = disposeAction;

    public void Dispose() => _disposeAction.Invoke();
}

并且在启动中

services.AddApplicationInsightsTelemetry();
        services.AddGraphQLServer()
            .AddDiagnosticEventListener<AppInsightsDiagnosticEventListener>((sp) => new AppInsightsDiagnosticEventListener(sp.GetService<TelemetryClient>()));