package server import ( "context" "encoding/json" "errors" "fmt" "io" "log" rtdebug "runtime/debug" "strings" "github.com/99designs/gqlgen/graphql" "github.com/emersion/go-message/mail" "git.sr.ht/~sircmpwn/core-go/auth" "git.sr.ht/~sircmpwn/core-go/config" "git.sr.ht/~sircmpwn/core-go/email" ) // Provides a graphql.RecoverFunc which will print the stack trace, and if // debug mode is not enabled, email it to the administrator. func EmailRecover(ctx context.Context, _origErr any) error { origErr, ok := _origErr.(error) if !ok { log.Printf("Unexpected error in recover: %v\n", _origErr) return fmt.Errorf("internal system error") } log.Println(origErr) if errors.Is(origErr, context.Canceled) { return origErr } if errors.Is(origErr, context.DeadlineExceeded) { return origErr } if origErr.Error() == "pq: canceling statement due to user request" { return origErr } stack := string(rtdebug.Stack()) log.Println(stack) if debug { return fmt.Errorf("internal system error") } var header mail.Header header.SetSubject(fmt.Sprintf("[%s] GraphQL query error: %v", config.ServiceName(ctx), origErr)) conf := config.ForContext(ctx) to, _ := conf.Get("mail", "error-to") if to == "" { log.Println("Warning: mail::error-to is not set") return fmt.Errorf("internal system error") } rcpt, err := mail.ParseAddress(to) if err != nil { log.Printf("Warning: mail::error-to is invalid: %v", err) return fmt.Errorf("internal system error") } header.SetAddressList("To", []*mail.Address{rcpt}) var reader io.Reader func() { defer func() { if err := recover(); err != nil { reader = strings.NewReader(fmt.Sprintf(`An error occured outside of the GraphQL context: %s`, stack)) } }() quser := auth.ForContext(ctx) octx := graphql.GetOperationContext(ctx) vars, _ := json.Marshal(octx.Variables) reader = strings.NewReader( fmt.Sprintf(`Error occured processing GraphQL request: %v When running the following query on behalf of %s <%s>: %s With these variables: %s The following stack trace was produced: %s`, origErr, quser.Username, quser.Email, octx.RawQuery, string(vars), stack)) }() email.EnqueueStd(ctx, header, reader, nil) return fmt.Errorf("internal system error") }