|
using System; |
|
using System.Collections.Generic; |
|
using HotChocolate; |
|
using HotChocolate.Execution; |
|
using HotChocolate.Execution.Instrumentation; |
|
using HotChocolate.Resolvers; |
|
using Microsoft.Extensions.Logging; |
|
|
|
namespace MyCompany.GraphQL.Execution.Instrumentation |
|
{ |
|
/// <summary> |
|
/// A listener for events in HotChocolate. |
|
/// </summary> |
|
public class OurDiagnosticEventListener : DiagnosticEventListener |
|
{ |
|
private readonly bool _shouldLogToNewRelic; |
|
private readonly ILogger<OurDiagnosticEventListener> _logger; |
|
|
|
public OurDiagnosticEventListener( |
|
bool shouldLogToNewRelic, |
|
ILogger<OurDiagnosticEventListener> logger) |
|
{ |
|
_shouldLogToNewRelic = shouldLogToNewRelic; |
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
|
} |
|
|
|
public override bool EnableResolveFieldValue => true; |
|
|
|
public override IActivityScope ExecuteRequest(IRequestContext context) |
|
{ |
|
// Add basic context to logger that will be recorded with every log entry |
|
var basicScopedContextData = GetBasicScopeContextData(context); |
|
using var scope = _logger.BeginScope(basicScopedContextData); |
|
|
|
if (_shouldLogToNewRelic) |
|
{ |
|
var currentTransaction = NewRelic.Api.Agent.NewRelic.GetAgent().CurrentTransaction; |
|
|
|
foreach (var kvp in basicScopedContextData) |
|
{ |
|
currentTransaction.AddCustomAttribute(kvp.Key, kvp.Value); |
|
} |
|
} |
|
|
|
// Log the full query when on Information level |
|
if (_logger.IsEnabled(LogLevel.Information)) |
|
{ |
|
_logger.LogInformation( |
|
"Query: {query} QueryId: {queryId} Operation: {operationName}", |
|
context.Request.Query?.ToString() ?? "null", |
|
GetQueryId(context) ?? "null", |
|
context.Request.OperationName ?? "null"); |
|
} |
|
|
|
var result = base.ExecuteRequest(context); |
|
|
|
//context.Result will now have a value |
|
|
|
return result; |
|
} |
|
|
|
public override void TaskError(IExecutionTask task, IError error) |
|
=> LogError(error); |
|
|
|
public override void RequestError(IRequestContext context, Exception exception) |
|
{ |
|
_logger.LogError(exception, "Request error"); |
|
NewRelic.Api.Agent.NewRelic.NoticeError(exception, GetDetailedScopeContextData(context)); |
|
} |
|
|
|
public override void SyntaxError(IRequestContext context, IError error) |
|
=> LogError(error, GetDetailedScopeContextData(context)); |
|
|
|
public override void ValidationErrors(IRequestContext context, IReadOnlyList<IError> errors) |
|
{ |
|
foreach (var error in errors) |
|
{ |
|
LogError(error, GetDetailedScopeContextData(context)); |
|
} |
|
} |
|
|
|
public override void ResolverError(IMiddlewareContext context, IError error) |
|
=> LogError(error, GetDetailedScopeContextData(context)); |
|
|
|
private void LogError(IError error, Dictionary<string, object>? additionalContextData = null) |
|
{ |
|
additionalContextData ??= new Dictionary<string, object>(); |
|
additionalContextData.Add("errorPath", error.Path?.Print() ?? "unknown"); |
|
|
|
using var scope = _logger.BeginScope(additionalContextData); |
|
|
|
if (error.Exception == null) |
|
{ |
|
_logger.LogError(error.Message); |
|
|
|
if (_shouldLogToNewRelic) |
|
{ |
|
NewRelic.Api.Agent.NewRelic.NoticeError(error.Message, additionalContextData); |
|
} |
|
} |
|
else |
|
{ |
|
_logger.LogError(error.Exception, error.Message); |
|
|
|
if (_shouldLogToNewRelic) |
|
{ |
|
NewRelic.Api.Agent.NewRelic.NoticeError(error.Exception, additionalContextData); |
|
} |
|
} |
|
} |
|
|
|
override |
|
|
|
private static Dictionary<string, object> GetBasicScopeContextData(IRequestContext requestContext) |
|
{ |
|
var data = new Dictionary<string, object> |
|
{ |
|
{ "schemaName", requestContext.Schema.Name.Value }, |
|
{ "queryId", GetQueryId(requestContext) ?? "null" }, |
|
{ "operationName", requestContext.Operation?.Name?.Value ?? "unnamed" }, |
|
}; |
|
|
|
if (requestContext.Request.VariableValues != null) |
|
{ |
|
foreach (var (key, value) in requestContext.Request.VariableValues) |
|
{ |
|
data.Add($"variable[{key}]", value?.ToString() ?? "null"); |
|
} |
|
} |
|
|
|
return data; |
|
} |
|
|
|
private static Dictionary<string, object> GetDetailedScopeContextData(IRequestContext requestContext) |
|
{ |
|
return new Dictionary<string, object> |
|
{ |
|
{ "query", requestContext.Request.Query?.ToString() ?? "null" }, |
|
}; |
|
} |
|
|
|
private static Dictionary<string, object> GetDetailedScopeContextData(IMiddlewareContext middlewareContext) |
|
{ |
|
return new Dictionary<string, object> |
|
{ |
|
{ "document", middlewareContext.Document.ToString(indented: true) }, |
|
}; |
|
} |
|
|
|
private static string? GetQueryId(IRequestContext requestContext) |
|
{ |
|
return |
|
requestContext.Request.QueryId ?? |
|
requestContext.DocumentId ?? |
|
requestContext.DocumentHash ?? |
|
requestContext.Request.QueryHash; |
|
} |
|
} |
|
} |