Log errors returned from graphql (#3562)

* Add func methods to logger
* Log errors returned from the graphql interface
* Log authentication
* Log when credentials changed
This commit is contained in:
WithoutPants 2023-04-17 15:27:25 +10:00 committed by GitHub
parent 75f22042b7
commit 32cefea524
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 177 additions and 4 deletions

39
internal/api/error.go Normal file
View File

@ -0,0 +1,39 @@
package api
import (
"context"
"encoding/json"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/logger"
"github.com/vektah/gqlparser/v2/gqlerror"
)
func gqlErrorHandler(ctx context.Context, e error) *gqlerror.Error {
// log all errors - for now just log the error message
// we can potentially add more context later
fc := graphql.GetFieldContext(ctx)
if fc != nil {
logger.Errorf("%s: %v", fc.Path(), e)
// log the args in debug level
logger.DebugFunc(func() (string, []interface{}) {
var args interface{}
args = fc.Args
s, _ := json.Marshal(args)
if len(s) > 0 {
args = string(s)
}
return "%s: %v", []interface{}{
fc.Path(),
args,
}
})
}
// we may also want to transform the error message for the response
// for now just return the original error
return graphql.DefaultErrorPresenter(ctx, e)
}

View File

@ -228,8 +228,13 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
c.Set(config.GalleryCoverRegex, *input.GalleryCoverRegex)
}
if input.Username != nil {
if input.Username != nil && *input.Username != c.GetUsername() {
c.Set(config.Username, input.Username)
if *input.Password == "" {
logger.Info("Username cleared")
} else {
logger.Info("Username changed")
}
}
if input.Password != nil {
@ -238,6 +243,11 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
currentPWHash := c.GetPasswordHash()
if *input.Password != currentPWHash {
if *input.Password == "" {
logger.Info("Password cleared")
} else {
logger.Info("Password changed")
}
c.SetPassword(*input.Password)
}
}

View File

@ -120,6 +120,8 @@ func Start() error {
gqlSrv.SetQueryCache(gqlLru.New(1000))
gqlSrv.Use(gqlExtension.Introspection{})
gqlSrv.SetErrorPresenter(gqlErrorHandler)
gqlHandlerFunc := func(w http.ResponseWriter, r *http.Request) {
gqlSrv.ServeHTTP(w, r)
}

View File

@ -9,6 +9,7 @@ import (
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/session"
)
@ -61,7 +62,14 @@ func handleLogin(loginUIBox embed.FS) http.HandlerFunc {
}
err := manager.GetInstance().SessionStore.Login(w, r)
if errors.Is(err, session.ErrInvalidCredentials) {
if err != nil {
// always log the error
logger.Errorf("Error logging in: %v", err)
}
var invalidCredentialsError *session.InvalidCredentialsError
if errors.As(err, &invalidCredentialsError) {
// redirect back to the login page with an error
redirectToLogin(loginUIBox, w, url, "Username or password is invalid")
return

View File

@ -235,6 +235,13 @@ func (log *Logger) Tracef(format string, args ...interface{}) {
log.addLogItem(l)
}
func (log *Logger) TraceFunc(fn func() (string, []interface{})) {
if log.logger.Level >= logrus.TraceLevel {
msg, args := fn()
log.Tracef(msg, args...)
}
}
func (log *Logger) Debug(args ...interface{}) {
log.logger.Debug(args...)
l := &LogItem{
@ -253,6 +260,17 @@ func (log *Logger) Debugf(format string, args ...interface{}) {
log.addLogItem(l)
}
func (log *Logger) logFunc(level logrus.Level, logFn func(format string, args ...interface{}), fn func() (string, []interface{})) {
if log.logger.Level >= level {
msg, args := fn()
logFn(msg, args...)
}
}
func (log *Logger) DebugFunc(fn func() (string, []interface{})) {
log.logFunc(logrus.DebugLevel, log.logger.Debugf, fn)
}
func (log *Logger) Info(args ...interface{}) {
log.logger.Info(args...)
l := &LogItem{
@ -271,6 +289,10 @@ func (log *Logger) Infof(format string, args ...interface{}) {
log.addLogItem(l)
}
func (log *Logger) InfoFunc(fn func() (string, []interface{})) {
log.logFunc(logrus.InfoLevel, log.logger.Infof, fn)
}
func (log *Logger) Warn(args ...interface{}) {
log.logger.Warn(args...)
l := &LogItem{
@ -289,6 +311,10 @@ func (log *Logger) Warnf(format string, args ...interface{}) {
log.addLogItem(l)
}
func (log *Logger) WarnFunc(fn func() (string, []interface{})) {
log.logFunc(logrus.WarnLevel, log.logger.Warnf, fn)
}
func (log *Logger) Error(args ...interface{}) {
log.logger.Error(args...)
l := &LogItem{
@ -307,6 +333,10 @@ func (log *Logger) Errorf(format string, args ...interface{}) {
log.addLogItem(l)
}
func (log *Logger) ErrorFunc(fn func() (string, []interface{})) {
log.logFunc(logrus.ErrorLevel, log.logger.Errorf, fn)
}
func (log *Logger) Fatal(args ...interface{}) {
log.logger.Fatal(args...)
}

View File

@ -31,6 +31,11 @@ func (log *BasicLogger) Tracef(format string, args ...interface{}) {
log.printf("Trace", format, args...)
}
func (log *BasicLogger) TraceFunc(fn func() (string, []interface{})) {
format, args := fn()
log.printf("Trace", format, args...)
}
func (log *BasicLogger) Debug(args ...interface{}) {
log.print("Debug", args...)
}
@ -39,6 +44,11 @@ func (log *BasicLogger) Debugf(format string, args ...interface{}) {
log.printf("Debug", format, args...)
}
func (log *BasicLogger) DebugFunc(fn func() (string, []interface{})) {
format, args := fn()
log.printf("Debug", format, args...)
}
func (log *BasicLogger) Info(args ...interface{}) {
log.print("Info", args...)
}
@ -47,6 +57,11 @@ func (log *BasicLogger) Infof(format string, args ...interface{}) {
log.printf("Info", format, args...)
}
func (log *BasicLogger) InfoFunc(fn func() (string, []interface{})) {
format, args := fn()
log.printf("Info", format, args...)
}
func (log *BasicLogger) Warn(args ...interface{}) {
log.print("Warn", args...)
}
@ -55,6 +70,11 @@ func (log *BasicLogger) Warnf(format string, args ...interface{}) {
log.printf("Warn", format, args...)
}
func (log *BasicLogger) WarnFunc(fn func() (string, []interface{})) {
format, args := fn()
log.printf("Warn", format, args...)
}
func (log *BasicLogger) Error(args ...interface{}) {
log.print("Error", args...)
}
@ -63,6 +83,11 @@ func (log *BasicLogger) Errorf(format string, args ...interface{}) {
log.printf("Error", format, args...)
}
func (log *BasicLogger) ErrorFunc(fn func() (string, []interface{})) {
format, args := fn()
log.printf("Error", format, args...)
}
func (log *BasicLogger) Fatal(args ...interface{}) {
log.print("Fatal", args...)
os.Exit(1)

View File

@ -16,18 +16,23 @@ type LoggerImpl interface {
Trace(args ...interface{})
Tracef(format string, args ...interface{})
TraceFunc(fn func() (string, []interface{}))
Debug(args ...interface{})
Debugf(format string, args ...interface{})
DebugFunc(fn func() (string, []interface{}))
Info(args ...interface{})
Infof(format string, args ...interface{})
InfoFunc(fn func() (string, []interface{}))
Warn(args ...interface{})
Warnf(format string, args ...interface{})
WarnFunc(fn func() (string, []interface{}))
Error(args ...interface{})
Errorf(format string, args ...interface{})
ErrorFunc(fn func() (string, []interface{}))
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
@ -61,6 +66,14 @@ func Tracef(format string, args ...interface{}) {
}
}
// TraceFunc calls TraceFunc with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func TraceFunc(fn func() (string, []interface{})) {
if Logger != nil {
Logger.TraceFunc(fn)
}
}
// Debug calls Debug with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func Debug(args ...interface{}) {
@ -77,6 +90,14 @@ func Debugf(format string, args ...interface{}) {
}
}
// DebugFunc calls DebugFunc with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func DebugFunc(fn func() (string, []interface{})) {
if Logger != nil {
Logger.DebugFunc(fn)
}
}
// Info calls Info with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func Info(args ...interface{}) {
@ -93,6 +114,14 @@ func Infof(format string, args ...interface{}) {
}
}
// InfoFunc calls InfoFunc with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func InfoFunc(fn func() (string, []interface{})) {
if Logger != nil {
Logger.InfoFunc(fn)
}
}
// Warn calls Warn with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func Warn(args ...interface{}) {
@ -109,6 +138,14 @@ func Warnf(format string, args ...interface{}) {
}
}
// WarnFunc calls WarnFunc with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func WarnFunc(fn func() (string, []interface{})) {
if Logger != nil {
Logger.WarnFunc(fn)
}
}
// Error calls Error with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func Error(args ...interface{}) {
@ -125,6 +162,14 @@ func Errorf(format string, args ...interface{}) {
}
}
// ErrorFunc calls ErrorFunc with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func ErrorFunc(fn func() (string, []interface{})) {
if Logger != nil {
Logger.ErrorFunc(fn)
}
}
// Fatal calls Fatal with the Logger registered using RegisterLogger.
// If no logger has been registered, then this function is a no-op.
func Fatal(args ...interface{}) {

View File

@ -34,7 +34,15 @@ const (
passwordFormKey = "password"
)
var ErrInvalidCredentials = errors.New("invalid username or password")
type InvalidCredentialsError struct {
Username string
}
func (e InvalidCredentialsError) Error() string {
// don't leak the username
return "invalid credentials"
}
var ErrUnauthorized = errors.New("unauthorized")
type Store struct {
@ -63,9 +71,12 @@ func (s *Store) Login(w http.ResponseWriter, r *http.Request) error {
// authenticate the user
if !s.config.ValidateCredentials(username, password) {
return ErrInvalidCredentials
return &InvalidCredentialsError{Username: username}
}
// since we only have one user, don't leak the name
logger.Info("User logged in")
newSession.Values[userIDKey] = username
err := newSession.Save(r, w)
@ -90,6 +101,9 @@ func (s *Store) Logout(w http.ResponseWriter, r *http.Request) error {
return err
}
// since we only have one user, don't leak the name
logger.Infof("User logged out")
return nil
}