diff --git a/graphql.go b/graphql.go index 6a852b55..9e957496 100644 --- a/graphql.go +++ b/graphql.go @@ -68,6 +68,7 @@ type Schema struct { logger log.Logger useStringDescriptions bool disableIntrospection bool + isPanicLogEnabled bool } // SchemaOpt is an option to pass to ParseSchema or MustParseSchema. @@ -150,6 +151,19 @@ func (s *Schema) Validate(queryString string, variables map[string]interface{}) return false, validation.Validate(s.schema, doc, variables, s.maxDepth) } +//EnablePanicLogging enables query info logging if panic occurs +func (s *Schema) EnablePanicLogging() { + s.isPanicLogEnabled = true + s.logger = getLogger(s) +} + +func getLogger(s *Schema) log.Logger { + if s != nil && s.isPanicLogEnabled { + return &log.CustomLogger{true} + } + return &log.DefaultLogger{} +} + // Exec executes the given query with the schema's resolver. It panics if the schema was created // without a resolver. If the context get cancelled, no further resolvers will be called and a // the context error will be returned as soon as possible (not immediately). @@ -223,6 +237,7 @@ func (s *Schema) exec(ctx context.Context, queryString string, operationName str varTypes[v.Name.Name] = introspection.WrapType(t) } traceCtx, finish := s.tracer.TraceQuery(ctx, queryString, operationName, variables, varTypes) + r.QInfo = fmt.Sprintf("Query: %s\nVariables: %+v\n", queryString, variables) data, errs := r.Execute(traceCtx, res, op) finish(errs) diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 9f7fb597..0631e4c1 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -24,11 +24,12 @@ type Request struct { Limiter chan struct{} Tracer trace.Tracer Logger log.Logger + QInfo string } func (r *Request) handlePanic(ctx context.Context) { if value := recover(); value != nil { - r.Logger.LogPanic(ctx, value) + r.Logger.LogPanic(ctx, value, r.QInfo) r.AddError(makePanicError(value)) } } @@ -177,7 +178,7 @@ func execFieldSelection(ctx context.Context, r *Request, s *resolvable.Schema, f err = func() (err *errors.QueryError) { defer func() { if panicValue := recover(); panicValue != nil { - r.Logger.LogPanic(ctx, panicValue) + r.Logger.LogPanic(ctx, panicValue, r.QInfo) err = makePanicError(panicValue) err.Path = path.toSlice() } diff --git a/log/log.go b/log/log.go index 25569af7..3105c4a3 100644 --- a/log/log.go +++ b/log/log.go @@ -2,22 +2,42 @@ package log import ( "context" + "fmt" "log" "runtime" ) // Logger is the interface used to log panics that occur during query execution. It is settable via graphql.ParseSchema type Logger interface { - LogPanic(ctx context.Context, value interface{}) + LogPanic(ctx context.Context, value interface{}, info string) } // DefaultLogger is the default logger used to log panics that occur during query execution type DefaultLogger struct{} // LogPanic is used to log recovered panic values that occur during query execution -func (l *DefaultLogger) LogPanic(_ context.Context, value interface{}) { +func (l *DefaultLogger) LogPanic(_ context.Context, value interface{}, info string) { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] - log.Printf("graphql: panic occurred: %v\n%s", value, buf) + log.Printf("graphql: panic occurred: %v\n%s", value, buf) } + +// CustomLogger is the custom logger for use with gqlserver custome config to log panics that occur during query execution +type CustomLogger struct{ + VerbosePanicLog bool +} + +// LogPanic is used to log recovered panic values that occur during query execution +func (l *CustomLogger) LogPanic(_ context.Context, value interface{}, info string) { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + msg := fmt.Sprintf("graphql: panic occurred: %v\n%s", value, buf) + if l.VerbosePanicLog { + msg = fmt.Sprintf("%s\n\n%s", msg, info) + } + + log.Printf(msg) +} +