diff --git a/cmd/dex/config.go b/cmd/dex/config.go index 33c0db4782..b6d34f37e2 100644 --- a/cmd/dex/config.go +++ b/cmd/dex/config.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "log/slog" "net/http" "os" "strings" @@ -11,7 +12,6 @@ import ( "golang.org/x/crypto/bcrypt" "github.com/dexidp/dex/pkg/featureflags" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/server" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent" @@ -236,7 +236,7 @@ type Storage struct { // StorageConfig is a configuration that can create a storage. type StorageConfig interface { - Open(logger log.Logger) (storage.Storage, error) + Open(logger *slog.Logger) (storage.Storage, error) } var ( @@ -386,7 +386,7 @@ type Expiry struct { // Logger holds configuration required to customize logging for dex. type Logger struct { // Level sets logging level severity. - Level string `json:"level"` + Level slog.Level `json:"level"` // Format specifies the format to be used for logging. Format string `json:"format"` diff --git a/cmd/dex/config_test.go b/cmd/dex/config_test.go index e316965039..95a79eb27f 100644 --- a/cmd/dex/config_test.go +++ b/cmd/dex/config_test.go @@ -1,6 +1,7 @@ package main import ( + "log/slog" "os" "testing" @@ -219,7 +220,7 @@ logger: DeviceRequests: "10m", }, Logger: Logger{ - Level: "debug", + Level: slog.LevelDebug, Format: "json", }, } @@ -426,7 +427,7 @@ logger: AuthRequests: "25h", }, Logger: Logger{ - Level: "debug", + Level: slog.LevelDebug, Format: "json", }, } diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go index 9461a6220a..b39354ec0b 100644 --- a/cmd/dex/serve.go +++ b/cmd/dex/serve.go @@ -6,6 +6,7 @@ import ( "crypto/x509" "errors" "fmt" + "log/slog" "net" "net/http" "net/http/pprof" @@ -28,14 +29,12 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/reflection" "github.com/dexidp/dex/api/v2" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/server" "github.com/dexidp/dex/storage" ) @@ -98,22 +97,24 @@ func runServe(options serveOptions) error { return fmt.Errorf("invalid config: %v", err) } - logger.Infof( - "Dex Version: %s, Go Version: %s, Go OS/ARCH: %s %s", - version, - runtime.Version(), - runtime.GOOS, - runtime.GOARCH, + logger.Info( + "Version info", + "dex_version", version, + slog.Group("go", + "version", runtime.Version(), + "os", runtime.GOOS, + "arch", runtime.GOARCH, + ), ) - if c.Logger.Level != "" { - logger.Infof("config using log level: %s", c.Logger.Level) + if c.Logger.Level != slog.LevelInfo { + logger.Info("config using log level", "level", c.Logger.Level) } if err := c.Validate(); err != nil { return err } - logger.Infof("config issuer: %s", c.Issuer) + logger.Info("config issuer", "issuer", c.Issuer) prometheusRegistry := prometheus.NewRegistry() err = prometheusRegistry.Register(collectors.NewGoCollector()) @@ -188,7 +189,7 @@ func runServe(options serveOptions) error { } defer s.Close() - logger.Infof("config storage: %s", c.Storage.Type) + logger.Info("config storage", "storage_type", c.Storage.Type) if len(c.StaticClients) > 0 { for i, client := range c.StaticClients { @@ -213,7 +214,7 @@ func runServe(options serveOptions) error { } c.StaticClients[i].Secret = os.Getenv(client.SecretEnv) } - logger.Infof("config static client: %s", client.Name) + logger.Info("config static client", "client_name", client.Name) } s = storage.WithStaticClients(s, c.StaticClients) } @@ -233,7 +234,7 @@ func runServe(options serveOptions) error { if c.Config == nil { return fmt.Errorf("invalid config: no config field for connector %q", c.ID) } - logger.Infof("config connector: %s", c.ID) + logger.Info("config connector", "connector_id", c.ID) // convert to a storage connector object conn, err := ToStorageConnector(c) @@ -249,22 +250,22 @@ func runServe(options serveOptions) error { Name: "Email", Type: server.LocalConnector, }) - logger.Infof("config connector: local passwords enabled") + logger.Info("config connector: local passwords enabled") } s = storage.WithStaticConnectors(s, storageConnectors) if len(c.OAuth2.ResponseTypes) > 0 { - logger.Infof("config response types accepted: %s", c.OAuth2.ResponseTypes) + logger.Info("config response types accepted", "response_types", c.OAuth2.ResponseTypes) } if c.OAuth2.SkipApprovalScreen { - logger.Infof("config skipping approval screen") + logger.Info("config skipping approval screen") } if c.OAuth2.PasswordConnector != "" { - logger.Infof("config using password grant connector: %s", c.OAuth2.PasswordConnector) + logger.Info("config using password grant connector", "password_connector", c.OAuth2.PasswordConnector) } if len(c.Web.AllowedOrigins) > 0 { - logger.Infof("config allowed origins: %s", c.Web.AllowedOrigins) + logger.Info("config allowed origins", "origins", c.Web.AllowedOrigins) } // explicitly convert to UTC. @@ -294,7 +295,7 @@ func runServe(options serveOptions) error { if err != nil { return fmt.Errorf("invalid config value %q for signing keys expiry: %v", c.Expiry.SigningKeys, err) } - logger.Infof("config signing keys expire after: %v", signingKeys) + logger.Info("config signing keys", "expire_after", signingKeys) serverConfig.RotateKeysAfter = signingKeys } if c.Expiry.IDTokens != "" { @@ -302,7 +303,7 @@ func runServe(options serveOptions) error { if err != nil { return fmt.Errorf("invalid config value %q for id token expiry: %v", c.Expiry.IDTokens, err) } - logger.Infof("config id tokens valid for: %v", idTokens) + logger.Info("config id tokens", "valid_for", idTokens) serverConfig.IDTokensValidFor = idTokens } if c.Expiry.AuthRequests != "" { @@ -310,7 +311,7 @@ func runServe(options serveOptions) error { if err != nil { return fmt.Errorf("invalid config value %q for auth request expiry: %v", c.Expiry.AuthRequests, err) } - logger.Infof("config auth requests valid for: %v", authRequests) + logger.Info("config auth requests", "valid_for", authRequests) serverConfig.AuthRequestsValidFor = authRequests } if c.Expiry.DeviceRequests != "" { @@ -318,7 +319,7 @@ func runServe(options serveOptions) error { if err != nil { return fmt.Errorf("invalid config value %q for device request expiry: %v", c.Expiry.AuthRequests, err) } - logger.Infof("config device requests valid for: %v", deviceRequests) + logger.Info("config device requests", "valid_for", deviceRequests) serverConfig.DeviceRequestsValidFor = deviceRequests } refreshTokenPolicy, err := server.NewRefreshTokenPolicy( @@ -368,7 +369,7 @@ func runServe(options serveOptions) error { if c.Telemetry.HTTP != "" { const name = "telemetry" - logger.Infof("listening (%s) on %s", name, c.Telemetry.HTTP) + logger.Info("listening on", "server", name, "address", c.Telemetry.HTTP) l, err := net.Listen("tcp", c.Telemetry.HTTP) if err != nil { @@ -390,9 +391,9 @@ func runServe(options serveOptions) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - logger.Debugf("starting graceful shutdown (%s)", name) + logger.Debug("starting graceful shutdown", "server", name) if err := server.Shutdown(ctx); err != nil { - logger.Errorf("graceful shutdown (%s): %v", name, err) + logger.Error("graceful shutdown", "server", name, "err", err) } }) } @@ -401,7 +402,7 @@ func runServe(options serveOptions) error { if c.Web.HTTP != "" { const name = "http" - logger.Infof("listening (%s) on %s", name, c.Web.HTTP) + logger.Info("listening on", "server", name, "address", c.Web.HTTP) l, err := net.Listen("tcp", c.Web.HTTP) if err != nil { @@ -419,9 +420,9 @@ func runServe(options serveOptions) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - logger.Debugf("starting graceful shutdown (%s)", name) + logger.Debug("starting graceful shutdown", "server", name) if err := server.Shutdown(ctx); err != nil { - logger.Errorf("graceful shutdown (%s): %v", name, err) + logger.Error("graceful shutdown", "server", name, "err", err) } }) } @@ -430,7 +431,7 @@ func runServe(options serveOptions) error { if c.Web.HTTPS != "" { const name = "https" - logger.Infof("listening (%s) on %s", name, c.Web.HTTPS) + logger.Info("listening on", "server", name, "address", c.Web.HTTPS) l, err := net.Listen("tcp", c.Web.HTTPS) if err != nil { @@ -470,16 +471,16 @@ func runServe(options serveOptions) error { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - logger.Debugf("starting graceful shutdown (%s)", name) + logger.Debug("starting graceful shutdown", "server", name) if err := server.Shutdown(ctx); err != nil { - logger.Errorf("graceful shutdown (%s): %v", name, err) + logger.Error("graceful shutdown", "server", name, "err", err) } }) } // Set up grpc server if c.GRPC.Addr != "" { - logger.Infof("listening (grpc) on %s", c.GRPC.Addr) + logger.Info("listening on", "server", "grpc", "address", c.GRPC.Addr) grpcListener, err := net.Listen("tcp", c.GRPC.Addr) if err != nil { @@ -498,7 +499,7 @@ func runServe(options serveOptions) error { group.Add(func() error { return grpcSrv.Serve(grpcListener) }, func(err error) { - logger.Debugf("starting graceful shutdown (grpc)") + logger.Debug("starting graceful shutdown", "server", "grpc") grpcSrv.GracefulStop() }) } @@ -508,53 +509,29 @@ func runServe(options serveOptions) error { if _, ok := err.(run.SignalError); !ok { return fmt.Errorf("run groups: %w", err) } - logger.Infof("%v, shutdown now", err) + logger.Info("shutdown now", "err", err) } return nil } -var ( - logLevels = []string{"debug", "info", "error"} - logFormats = []string{"json", "text"} -) - -type utcFormatter struct { - f logrus.Formatter -} - -func (f *utcFormatter) Format(e *logrus.Entry) ([]byte, error) { - e.Time = e.Time.UTC() - return f.f.Format(e) -} +var logFormats = []string{"json", "text"} -func newLogger(level string, format string) (log.Logger, error) { - var logLevel logrus.Level - switch strings.ToLower(level) { - case "debug": - logLevel = logrus.DebugLevel - case "", "info": - logLevel = logrus.InfoLevel - case "error": - logLevel = logrus.ErrorLevel - default: - return nil, fmt.Errorf("log level is not one of the supported values (%s): %s", strings.Join(logLevels, ", "), level) - } - - var formatter utcFormatter +func newLogger(level slog.Level, format string) (*slog.Logger, error) { + var handler slog.Handler switch strings.ToLower(format) { case "", "text": - formatter.f = &logrus.TextFormatter{DisableColors: true} + slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + Level: level, + }) case "json": - formatter.f = &logrus.JSONFormatter{} + slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + Level: level, + }) default: return nil, fmt.Errorf("log format is not one of the supported values (%s): %s", strings.Join(logFormats, ", "), format) } - return &logrus.Logger{ - Out: os.Stderr, - Formatter: &formatter, - Level: logLevel, - }, nil + return slog.New(handler), nil } func applyConfigOverrides(options serveOptions, config *Config) { @@ -600,7 +577,7 @@ func pprofHandler(router *http.ServeMux) { // newTLSReloader returns a [tls.Config] with GetCertificate or GetConfigForClient set // to reload certificates from the given paths on SIGHUP or on file creates (atomic update via rename). -func newTLSReloader(logger log.Logger, certFile, keyFile, caFile string, baseConfig *tls.Config) (*tls.Config, error) { +func newTLSReloader(logger *slog.Logger, certFile, keyFile, caFile string, baseConfig *tls.Config) (*tls.Config, error) { // trigger reload on channel sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGHUP) @@ -631,7 +608,7 @@ func newTLSReloader(logger log.Logger, certFile, keyFile, caFile string, baseCon // recommended by fsnotify: watch the dir to handle renames // https://pkg.go.dev/github.com/fsnotify/fsnotify#hdr-Watching_files for dir := range watchDirs { - logger.Debugf("watching dir: %v", dir) + logger.Debug("watching dir", "dir", dir) err := watcher.Add(dir) if err != nil { return nil, fmt.Errorf("watch dir for TLS reloader: %v", err) @@ -654,19 +631,19 @@ func newTLSReloader(logger log.Logger, certFile, keyFile, caFile string, baseCon for { select { case sig := <-sigc: - logger.Debug("reloading cert from signal: %v", sig) + logger.Debug("reloading cert from signal", "signal", sig) case evt := <-watcher.Events: if _, ok := watchFiles[evt.Name]; !ok || !evt.Has(fsnotify.Create) { continue loop } - logger.Debug("reloading cert from fsnotify: %v %v", evt.Name, evt.Op.String()) + logger.Debug("reloading cert from fsnotify", "event", evt.Name, "operation", evt.Op.String()) case err := <-watcher.Errors: - logger.Errorf("TLS reloader watch: %v", err) + logger.Error("TLS reloader watch", "err", err) } loaded, err := loadTLSConfig(certFile, keyFile, caFile, baseConfig) if err != nil { - logger.Errorf("reload TLS config: %v", err) + logger.Error("reload TLS config", "err", err) } ptr.Store(loaded) } diff --git a/connector/atlassiancrowd/atlassiancrowd.go b/connector/atlassiancrowd/atlassiancrowd.go index aa14220341..d36832846e 100644 --- a/connector/atlassiancrowd/atlassiancrowd.go +++ b/connector/atlassiancrowd/atlassiancrowd.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net" "net/http" "strings" @@ -14,7 +15,6 @@ import ( "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" - "github.com/dexidp/dex/pkg/log" ) // Config holds configuration options for Atlassian Crowd connector. @@ -80,16 +80,16 @@ type crowdAuthenticationError struct { } // Open returns a strategy for logging in through Atlassian Crowd -func (c *Config) Open(_ string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.BaseURL == "" { return nil, fmt.Errorf("crowd: no baseURL provided for crowd connector") } - return &crowdConnector{Config: *c, logger: logger}, nil + return &crowdConnector{Config: *c, logger: logger.With(slog.Group("connector", "type", "atlassiancrowd", "id", id))}, nil } type crowdConnector struct { Config - logger log.Logger + logger *slog.Logger } var ( @@ -375,7 +375,7 @@ func (c *crowdConnector) identityFromCrowdUser(user crowdUser) connector.Identit identity.PreferredUsername = user.Email default: if c.PreferredUsernameField != "" { - c.logger.Warnf("preferred_username left empty. Invalid crowd field mapped to preferred_username: %s", c.PreferredUsernameField) + c.logger.Warn("preferred_username left empty. Invalid crowd field mapped to preferred_username", "field", c.PreferredUsernameField) } } @@ -436,12 +436,12 @@ func (c *crowdConnector) validateCrowdResponse(resp *http.Response) ([]byte, err } if resp.StatusCode == http.StatusForbidden && strings.Contains(string(body), "The server understood the request but refuses to authorize it.") { - c.logger.Debugf("crowd response validation failed: %s", string(body)) + c.logger.Debug("crowd response validation failed", "response", string(body)) return nil, fmt.Errorf("dex is forbidden from making requests to the Atlassian Crowd application by URL %q", c.BaseURL) } if resp.StatusCode == http.StatusUnauthorized && string(body) == "Application failed to authenticate" { - c.logger.Debugf("crowd response validation failed: %s", string(body)) + c.logger.Debug("crowd response validation failed", "response", string(body)) return nil, fmt.Errorf("dex failed to authenticate Crowd Application with ID %q", c.ClientID) } return body, nil diff --git a/connector/atlassiancrowd/atlassiancrowd_test.go b/connector/atlassiancrowd/atlassiancrowd_test.go index 36789a3919..b4c35a13cc 100644 --- a/connector/atlassiancrowd/atlassiancrowd_test.go +++ b/connector/atlassiancrowd/atlassiancrowd_test.go @@ -7,12 +7,11 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "net/http/httptest" "reflect" "testing" - - "github.com/sirupsen/logrus" ) func TestUserGroups(t *testing.T) { @@ -151,11 +150,7 @@ type TestServerResponse struct { func newTestCrowdConnector(baseURL string) crowdConnector { connector := crowdConnector{} connector.BaseURL = baseURL - connector.logger = &logrus.Logger{ - Out: io.Discard, - Level: logrus.DebugLevel, - Formatter: &logrus.TextFormatter{DisableColors: true}, - } + connector.logger = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) return connector } diff --git a/connector/authproxy/authproxy.go b/connector/authproxy/authproxy.go index 465c3e3d9e..61353382f5 100644 --- a/connector/authproxy/authproxy.go +++ b/connector/authproxy/authproxy.go @@ -5,12 +5,12 @@ package authproxy import ( "fmt" + "log/slog" "net/http" "net/url" "strings" "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" ) // Config holds the configuration parameters for a connector which returns an @@ -27,7 +27,7 @@ type Config struct { } // Open returns an authentication strategy which requires no user interaction. -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { userIDHeader := c.UserIDHeader if userIDHeader == "" { userIDHeader = "X-Remote-User-Id" @@ -51,7 +51,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) emailHeader: emailHeader, groupHeader: groupHeader, groups: c.Groups, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "authproxy", "id", id)), pathSuffix: "/" + id, }, nil } @@ -64,7 +64,7 @@ type callback struct { emailHeader string groupHeader string groups []string - logger log.Logger + logger *slog.Logger pathSuffix string } diff --git a/connector/authproxy/authproxy_test.go b/connector/authproxy/authproxy_test.go index 5e09872299..fdcf4038cf 100644 --- a/connector/authproxy/authproxy_test.go +++ b/connector/authproxy/authproxy_test.go @@ -2,12 +2,11 @@ package authproxy import ( "io" + "log/slog" "net/http" "reflect" "testing" - "github.com/sirupsen/logrus" - "github.com/dexidp/dex/connector" ) @@ -23,7 +22,7 @@ const ( testUserID = "1234567890" ) -var logger = &logrus.Logger{Out: io.Discard, Formatter: &logrus.TextFormatter{}} +var logger = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) func TestUser(t *testing.T) { config := Config{ diff --git a/connector/bitbucketcloud/bitbucketcloud.go b/connector/bitbucketcloud/bitbucketcloud.go index 27eafb5299..5f802e3414 100644 --- a/connector/bitbucketcloud/bitbucketcloud.go +++ b/connector/bitbucketcloud/bitbucketcloud.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "sync" "time" @@ -16,7 +17,6 @@ import ( "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" - "github.com/dexidp/dex/pkg/log" ) const ( @@ -42,7 +42,7 @@ type Config struct { } // Open returns a strategy for logging in through Bitbucket. -func (c *Config) Open(_ string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { b := bitbucketConnector{ redirectURI: c.RedirectURI, teams: c.Teams, @@ -51,7 +51,7 @@ func (c *Config) Open(_ string, logger log.Logger) (connector.Connector, error) includeTeamGroups: c.IncludeTeamGroups, apiURL: apiURL, legacyAPIURL: legacyAPIURL, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "bitbucketcloud", "id", id)), } return &b, nil @@ -73,7 +73,7 @@ type bitbucketConnector struct { teams []string clientID string clientSecret string - logger log.Logger + logger *slog.Logger apiURL string legacyAPIURL string diff --git a/connector/gitea/gitea.go b/connector/gitea/gitea.go index 6b02099414..62523185d5 100644 --- a/connector/gitea/gitea.go +++ b/connector/gitea/gitea.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "strconv" "sync" @@ -15,7 +16,6 @@ import ( "golang.org/x/oauth2" "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" ) // Config holds configuration options for gitea logins. @@ -51,7 +51,7 @@ type giteaUser struct { } // Open returns a strategy for logging in through Gitea -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.BaseURL == "" { c.BaseURL = "https://gitea.com" } @@ -61,7 +61,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) orgs: c.Orgs, clientID: c.ClientID, clientSecret: c.ClientSecret, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "gitea", "id", id)), loadAllGroups: c.LoadAllGroups, useLoginAsID: c.UseLoginAsID, }, nil @@ -84,7 +84,7 @@ type giteaConnector struct { orgs []Org clientID string clientSecret string - logger log.Logger + logger *slog.Logger httpClient *http.Client // if set to true and no orgs are configured then connector loads all user claims (all orgs and team) loadAllGroups bool diff --git a/connector/github/github.go b/connector/github/github.go index 6cb0db09df..a7818579e2 100644 --- a/connector/github/github.go +++ b/connector/github/github.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "regexp" "strconv" @@ -18,7 +19,6 @@ import ( "github.com/dexidp/dex/connector" groups_pkg "github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/httpclient" - "github.com/dexidp/dex/pkg/log" ) const ( @@ -66,7 +66,7 @@ type Org struct { } // Open returns a strategy for logging in through GitHub. -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.Org != "" { // Return error if both 'org' and 'orgs' fields are used. if len(c.Orgs) > 0 { @@ -82,7 +82,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) clientID: c.ClientID, clientSecret: c.ClientSecret, apiURL: apiURL, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "github", "id", id)), useLoginAsID: c.UseLoginAsID, preferredEmailDomain: c.PreferredEmailDomain, } @@ -142,7 +142,7 @@ type githubConnector struct { orgs []Org clientID string clientSecret string - logger log.Logger + logger *slog.Logger // apiURL defaults to "https://api.github.com" apiURL string // hostName of the GitHub enterprise account. @@ -362,7 +362,7 @@ func (c *githubConnector) groupsForOrgs(ctx context.Context, client *http.Client if len(org.Teams) == 0 { inOrgNoTeams = true } else if teams = groups_pkg.Filter(teams, org.Teams); len(teams) == 0 { - c.logger.Infof("github: user %q in org %q but no teams", userName, org.Name) + c.logger.Info("user in org but no teams", "user", userName, "org", org.Name) } for _, teamName := range teams { @@ -667,7 +667,7 @@ func (c *githubConnector) userInOrg(ctx context.Context, client *http.Client, us switch resp.StatusCode { case http.StatusNoContent: case http.StatusFound, http.StatusNotFound: - c.logger.Infof("github: user %q not in org %q or application not authorized to read org data", userName, orgName) + c.logger.Info("user not in org or application not authorized to read org data", "user", userName, "org", orgName) default: err = fmt.Errorf("github: unexpected return status: %q", resp.Status) } diff --git a/connector/github/github_test.go b/connector/github/github_test.go index af8099e9c7..088cbb238c 100644 --- a/connector/github/github_test.go +++ b/connector/github/github_test.go @@ -6,6 +6,8 @@ import ( "encoding/json" "errors" "fmt" + "io" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -449,6 +451,7 @@ func Test_isPreferredEmailDomain(t *testing.T) { } func Test_Open_PreferredDomainConfig(t *testing.T) { + log := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) tests := []struct { preferredEmailDomain string email string @@ -476,7 +479,7 @@ func Test_Open_PreferredDomainConfig(t *testing.T) { c := Config{ PreferredEmailDomain: test.preferredEmailDomain, } - _, err := c.Open("id", nil) + _, err := c.Open("id", log) expectEquals(t, err, test.expected) }) diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go index 099cd2ef17..fdb2c48204 100644 --- a/connector/gitlab/gitlab.go +++ b/connector/gitlab/gitlab.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "strconv" "time" @@ -15,7 +16,6 @@ import ( "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" - "github.com/dexidp/dex/pkg/log" ) const ( @@ -46,7 +46,7 @@ type gitlabUser struct { } // Open returns a strategy for logging in through GitLab. -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.BaseURL == "" { c.BaseURL = "https://gitlab.com" } @@ -55,7 +55,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) redirectURI: c.RedirectURI, clientID: c.ClientID, clientSecret: c.ClientSecret, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "gitlab", "id", id)), groups: c.Groups, useLoginAsID: c.UseLoginAsID, }, nil @@ -78,7 +78,7 @@ type gitlabConnector struct { groups []string clientID string clientSecret string - logger log.Logger + logger *slog.Logger httpClient *http.Client // if set to true will use the user's handle rather than their numeric id as the ID useLoginAsID bool diff --git a/connector/google/google.go b/connector/google/google.go index c3042970dd..a370b93bbf 100644 --- a/connector/google/google.go +++ b/connector/google/google.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "log/slog" "net/http" "os" "strings" @@ -21,7 +22,6 @@ import ( "github.com/dexidp/dex/connector" pkg_groups "github.com/dexidp/dex/pkg/groups" - "github.com/dexidp/dex/pkg/log" ) const ( @@ -67,9 +67,10 @@ type Config struct { } // Open returns a connector which can be used to login users through Google. -func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) { +func (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) { + logger = logger.With(slog.Group("connector", "type", "google", "id", id)) if c.AdminEmail != "" { - log.Deprecated(logger, `google: use "domainToAdminEmail.*: %s" option instead of "adminEmail: %s".`, c.AdminEmail, c.AdminEmail) + logger.Warn(`use "domainToAdminEmail.*" option instead of "adminEmail"`, "deprecated", true) if c.DomainToAdminEmail == nil { c.DomainToAdminEmail = make(map[string]string) } @@ -152,7 +153,7 @@ type googleConnector struct { oauth2Config *oauth2.Config verifier *oidc.IDTokenVerifier cancel context.CancelFunc - logger log.Logger + logger *slog.Logger hostedDomains []string groups []string serviceAccountFilePath string @@ -340,7 +341,7 @@ func (c *googleConnector) findAdminService(domain string) (*admin.Service, error adminSrv, ok := c.adminSrv[domain] if !ok { adminSrv, ok = c.adminSrv[wildcardDomainToAdminEmail] - c.logger.Debugf("using wildcard (%s) admin email to fetch groups", c.domainToAdminEmail[wildcardDomainToAdminEmail]) + c.logger.Debug("using wildcard admin email to fetch groups", "admin_email", c.domainToAdminEmail[wildcardDomainToAdminEmail]) } if !ok { @@ -377,7 +378,7 @@ func getCredentialsFromFilePath(serviceAccountFilePath string) ([]byte, error) { // If the default credential is empty, it attempts to create a new service with metadata credentials. // If successful, it returns the service and nil error. // If unsuccessful, it returns the error and a nil service. -func getCredentialsFromDefault(ctx context.Context, email string, logger log.Logger) ([]byte, *admin.Service, error) { +func getCredentialsFromDefault(ctx context.Context, email string, logger *slog.Logger) ([]byte, *admin.Service, error) { credential, err := google.FindDefaultCredentials(ctx) if err != nil { return nil, nil, fmt.Errorf("failed to fetch application default credentials: %w", err) @@ -397,9 +398,9 @@ func getCredentialsFromDefault(ctx context.Context, email string, logger log.Log // createServiceWithMetadataServer creates a new service using metadata server. // If an error occurs during the process, it is returned along with a nil service. -func createServiceWithMetadataServer(ctx context.Context, adminEmail string, logger log.Logger) (*admin.Service, error) { +func createServiceWithMetadataServer(ctx context.Context, adminEmail string, logger *slog.Logger) (*admin.Service, error) { serviceAccountEmail, err := metadata.Email("default") - logger.Infof("discovered serviceAccountEmail: %s", serviceAccountEmail) + logger.Info("discovered serviceAccountEmail", "email", serviceAccountEmail) if err != nil { return nil, fmt.Errorf("unable to get service account email from metadata server: %v", err) @@ -423,7 +424,7 @@ func createServiceWithMetadataServer(ctx context.Context, adminEmail string, log // createDirectoryService sets up super user impersonation and creates an admin client for calling // the google admin api. If no serviceAccountFilePath is defined, the application default credential // is used. -func createDirectoryService(serviceAccountFilePath, email string, logger log.Logger) (service *admin.Service, err error) { +func createDirectoryService(serviceAccountFilePath, email string, logger *slog.Logger) (service *admin.Service, err error) { var jsonCredentials []byte ctx := context.Background() diff --git a/connector/google/google_test.go b/connector/google/google_test.go index 2fa2b783ef..bafcadc8ff 100644 --- a/connector/google/google_test.go +++ b/connector/google/google_test.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "io" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -11,7 +13,6 @@ import ( "strings" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" admin "google.golang.org/api/admin/directory/v1" "google.golang.org/api/option" @@ -51,7 +52,7 @@ func testSetup() *httptest.Server { } func newConnector(config *Config) (*googleConnector, error) { - log := logrus.New() + log := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) conn, err := config.Open("id", log) if err != nil { return nil, err diff --git a/connector/keystone/keystone.go b/connector/keystone/keystone.go index 03f473310b..f8dff9e3c9 100644 --- a/connector/keystone/keystone.go +++ b/connector/keystone/keystone.go @@ -7,10 +7,10 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" ) type conn struct { @@ -19,7 +19,7 @@ type conn struct { AdminUsername string AdminPassword string client *http.Client - Logger log.Logger + Logger *slog.Logger } type userKeystone struct { @@ -111,13 +111,13 @@ var ( ) // Open returns an authentication strategy using Keystone. -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { return &conn{ Domain: c.Domain, Host: c.Host, AdminUsername: c.AdminUsername, AdminPassword: c.AdminPassword, - Logger: logger, + Logger: logger.With(slog.Group("connector", "type", "keystone", "id", id)), client: http.DefaultClient, }, nil } @@ -287,7 +287,7 @@ func (p *conn) getUserGroups(ctx context.Context, userID string, token string) ( req = req.WithContext(ctx) resp, err := p.client.Do(req) if err != nil { - p.Logger.Errorf("keystone: error while fetching user %q groups\n", userID) + p.Logger.Error("error while fetching user groups", "user_id", userID, "err", err) return nil, err } diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go index bb434a6cb2..b5729fe876 100644 --- a/connector/ldap/ldap.go +++ b/connector/ldap/ldap.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "encoding/json" "fmt" + "log/slog" "net" "os" "strings" @@ -14,7 +15,6 @@ import ( "github.com/go-ldap/ldap/v3" "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" ) // Config holds the configuration parameters for the LDAP connector. The LDAP @@ -188,12 +188,12 @@ func parseScope(s string) (int, bool) { // Function exists here to allow backward compatibility between old and new // group to user matching implementations. // See "Config.GroupSearch.UserMatchers" comments for the details -func userMatchers(c *Config, logger log.Logger) []UserMatcher { +func userMatchers(c *Config, logger *slog.Logger) []UserMatcher { if len(c.GroupSearch.UserMatchers) > 0 && c.GroupSearch.UserMatchers[0].UserAttr != "" { return c.GroupSearch.UserMatchers } - log.Deprecated(logger, `LDAP: use groupSearch.userMatchers option instead of "userAttr/groupAttr" fields.`) + logger.Warn(`use "groupSearch.userMatchers" option instead of "userAttr/groupAttr" fields`, "deprecated", true) return []UserMatcher{ { UserAttr: c.GroupSearch.UserAttr, @@ -203,7 +203,8 @@ func userMatchers(c *Config, logger log.Logger) []UserMatcher { } // Open returns an authentication strategy using LDAP. -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { + logger = logger.With(slog.Group("connector", "type", "ldap", "id", id)) conn, err := c.OpenConnector(logger) if err != nil { return nil, err @@ -217,7 +218,7 @@ type refreshData struct { } // OpenConnector is the same as Open but returns a type with all implemented connector interfaces. -func (c *Config) OpenConnector(logger log.Logger) (interface { +func (c *Config) OpenConnector(logger *slog.Logger) (interface { connector.Connector connector.PasswordConnector connector.RefreshConnector @@ -226,7 +227,7 @@ func (c *Config) OpenConnector(logger log.Logger) (interface { return c.openConnector(logger) } -func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) { +func (c *Config) openConnector(logger *slog.Logger) (*ldapConnector, error) { requiredFields := []struct { name string val string @@ -300,7 +301,7 @@ type ldapConnector struct { tlsConfig *tls.Config - logger log.Logger + logger *slog.Logger } var ( @@ -359,7 +360,7 @@ func (c *ldapConnector) getAttrs(e ldap.Entry, name string) []string { return []string{e.DN} } - c.logger.Debugf("%q attribute is not fround in entry", name) + c.logger.Debug("attribute is not fround in entry", "attribute", name) return nil } @@ -438,8 +439,8 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E req.Attributes = append(req.Attributes, c.UserSearch.PreferredUsernameAttrAttr) } - c.logger.Infof("performing ldap search %s %s %s", - req.BaseDN, scopeString(req.Scope), req.Filter) + c.logger.Info("performing ldap search", + "base_dn", req.BaseDN, "scope", scopeString(req.Scope), "filter", req.Filter) resp, err := conn.Search(req) if err != nil { return ldap.Entry{}, false, fmt.Errorf("ldap: search with filter %q failed: %v", req.Filter, err) @@ -447,11 +448,11 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E switch n := len(resp.Entries); n { case 0: - c.logger.Errorf("ldap: no results returned for filter: %q", filter) + c.logger.Error("no results returned for filter", "filter", filter) return ldap.Entry{}, false, nil case 1: user = *resp.Entries[0] - c.logger.Infof("username %q mapped to entry %s", username, user.DN) + c.logger.Info("username mapped to entry", "username", username, "user_dn", user.DN) return user, true, nil default: return ldap.Entry{}, false, fmt.Errorf("ldap: filter returned multiple (%d) results: %q", n, filter) @@ -491,11 +492,11 @@ func (c *ldapConnector) Login(ctx context.Context, s connector.Scopes, username, if ldapErr, ok := err.(*ldap.Error); ok { switch ldapErr.ResultCode { case ldap.LDAPResultInvalidCredentials: - c.logger.Errorf("ldap: invalid password for user %q", user.DN) + c.logger.Error("invalid password for user", "user_dn", user.DN) incorrectPass = true return nil case ldap.LDAPResultConstraintViolation: - c.logger.Errorf("ldap: constraint violation for user %q: %s", user.DN, ldapErr.Error()) + c.logger.Error("constraint violation for user", "user_dn", user.DN, "err", ldapErr.Error()) incorrectPass = true return nil } @@ -581,7 +582,7 @@ func (c *ldapConnector) Refresh(ctx context.Context, s connector.Scopes, ident c func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, error) { if c.GroupSearch.BaseDN == "" { - c.logger.Debugf("No groups returned for %q because no groups baseDN has been configured.", c.getAttr(user, c.UserSearch.NameAttr)) + c.logger.Debug("No groups returned because no groups baseDN has been configured.", "base_dn", c.getAttr(user, c.UserSearch.NameAttr)) return nil, nil } @@ -602,8 +603,8 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, gotGroups := false if err := c.do(ctx, func(conn *ldap.Conn) error { - c.logger.Infof("performing ldap search %s %s %s", - req.BaseDN, scopeString(req.Scope), req.Filter) + c.logger.Info("performing ldap search", + "base_dn", req.BaseDN, "scope", scopeString(req.Scope), "filter", req.Filter) resp, err := conn.Search(req) if err != nil { return fmt.Errorf("ldap: search failed: %v", err) @@ -616,7 +617,7 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, } if !gotGroups { // TODO(ericchiang): Is this going to spam the logs? - c.logger.Errorf("ldap: groups search with filter %q returned no groups", filter) + c.logger.Error("groups search returned no groups", "filter", filter) } } } diff --git a/connector/ldap/ldap_test.go b/connector/ldap/ldap_test.go index f00f1ead99..de85b6a256 100644 --- a/connector/ldap/ldap_test.go +++ b/connector/ldap/ldap_test.go @@ -4,11 +4,11 @@ import ( "context" "fmt" "io" + "log/slog" "os" "testing" "github.com/kylelemons/godebug/pretty" - "github.com/sirupsen/logrus" "github.com/dexidp/dex/connector" ) @@ -567,7 +567,7 @@ func runTests(t *testing.T, connMethod connectionMethod, config *Config, tests [ c.BindDN = "cn=admin,dc=example,dc=org" c.BindPW = "admin" - l := &logrus.Logger{Out: io.Discard, Formatter: &logrus.TextFormatter{}} + l := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) conn, err := c.openConnector(l) if err != nil { diff --git a/connector/linkedin/linkedin.go b/connector/linkedin/linkedin.go index f79f1c49d8..f17d17cca1 100644 --- a/connector/linkedin/linkedin.go +++ b/connector/linkedin/linkedin.go @@ -6,13 +6,13 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "strings" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" ) const ( @@ -29,7 +29,7 @@ type Config struct { } // Open returns a strategy for logging in through LinkedIn -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { return &linkedInConnector{ oauth2Config: &oauth2.Config{ ClientID: c.ClientID, @@ -41,7 +41,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) Scopes: []string{"r_liteprofile", "r_emailaddress"}, RedirectURL: c.RedirectURI, }, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "linkedin", "id", id)), }, nil } @@ -51,7 +51,7 @@ type connectorData struct { type linkedInConnector struct { oauth2Config *oauth2.Config - logger log.Logger + logger *slog.Logger } // LinkedIn doesn't provide refresh tokens, so refresh tokens issued by Dex diff --git a/connector/microsoft/microsoft.go b/connector/microsoft/microsoft.go index bbc3d6c6b5..2fcf6a7515 100644 --- a/connector/microsoft/microsoft.go +++ b/connector/microsoft/microsoft.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "strings" "sync" @@ -17,7 +18,6 @@ import ( "github.com/dexidp/dex/connector" groups_pkg "github.com/dexidp/dex/pkg/groups" - "github.com/dexidp/dex/pkg/log" ) // GroupNameFormat represents the format of the group identifier @@ -66,7 +66,7 @@ type Config struct { } // Open returns a strategy for logging in through Microsoft. -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { m := microsoftConnector{ apiURL: strings.TrimSuffix(c.APIURL, "/"), graphURL: strings.TrimSuffix(c.GraphURL, "/"), @@ -78,7 +78,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) groups: c.Groups, groupNameFormat: c.GroupNameFormat, useGroupsAsWhitelist: c.UseGroupsAsWhitelist, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "microsoft", "id", id)), emailToLowercase: c.EmailToLowercase, promptType: c.PromptType, domainHint: c.DomainHint, @@ -133,7 +133,7 @@ type microsoftConnector struct { groupNameFormat GroupNameFormat groups []string useGroupsAsWhitelist bool - logger log.Logger + logger *slog.Logger emailToLowercase bool promptType string domainHint string diff --git a/connector/mock/connectortest.go b/connector/mock/connectortest.go index e97f986574..7e5979a992 100644 --- a/connector/mock/connectortest.go +++ b/connector/mock/connectortest.go @@ -5,16 +5,16 @@ import ( "context" "errors" "fmt" + "log/slog" "net/http" "net/url" "github.com/dexidp/dex/connector" - "github.com/dexidp/dex/pkg/log" ) // NewCallbackConnector returns a mock connector which requires no user interaction. It always returns // the same (fake) identity. -func NewCallbackConnector(logger log.Logger) connector.Connector { +func NewCallbackConnector(logger *slog.Logger) connector.Connector { return &Callback{ Identity: connector.Identity{ UserID: "0-385-28089-0", @@ -39,7 +39,7 @@ var ( type Callback struct { // The returned identity. Identity connector.Identity - Logger log.Logger + Logger *slog.Logger } // LoginURL returns the URL to redirect the user to login with. @@ -74,7 +74,8 @@ func (m *Callback) TokenIdentity(ctx context.Context, subjectTokenType, subjectT type CallbackConfig struct{} // Open returns an authentication strategy which requires no user interaction. -func (c *CallbackConfig) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *CallbackConfig) Open(id string, logger *slog.Logger) (connector.Connector, error) { + logger = logger.With(slog.Group("connector", "type", "callback", "id", id)) return NewCallbackConnector(logger), nil } @@ -86,7 +87,7 @@ type PasswordConfig struct { } // Open returns an authentication strategy which prompts for a predefined username and password. -func (c *PasswordConfig) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *PasswordConfig) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.Username == "" { return nil, errors.New("no username supplied") } @@ -99,7 +100,7 @@ func (c *PasswordConfig) Open(id string, logger log.Logger) (connector.Connector type passwordConnector struct { username string password string - logger log.Logger + logger *slog.Logger } func (p passwordConnector) Close() error { return nil } diff --git a/connector/oauth/oauth.go b/connector/oauth/oauth.go index 2fe39fd467..413a813a08 100644 --- a/connector/oauth/oauth.go +++ b/connector/oauth/oauth.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "strings" @@ -13,7 +14,6 @@ import ( "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/httpclient" - "github.com/dexidp/dex/pkg/log" ) type oauthConnector struct { @@ -31,7 +31,7 @@ type oauthConnector struct { emailVerifiedKey string groupsKey string httpClient *http.Client - logger log.Logger + logger *slog.Logger } type connectorData struct { @@ -58,7 +58,7 @@ type Config struct { } `json:"claimMapping"` } -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { var err error userIDKey := c.UserIDKey @@ -99,7 +99,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) userInfoURL: c.UserInfoURL, scopes: c.Scopes, redirectURI: c.RedirectURI, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "oauth", "id", id)), userIDKey: userIDKey, userNameKey: userNameKey, preferredUsernameKey: preferredUsernameKey, diff --git a/connector/oauth/oauth_test.go b/connector/oauth/oauth_test.go index 62cbd8d59a..d06c0c0840 100644 --- a/connector/oauth/oauth_test.go +++ b/connector/oauth/oauth_test.go @@ -6,6 +6,8 @@ import ( "encoding/json" "errors" "fmt" + "io" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -13,7 +15,6 @@ import ( "testing" "github.com/go-jose/go-jose/v4" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/dexidp/dex/connector" @@ -270,7 +271,7 @@ func newConnector(t *testing.T, serverURL string) *oauthConnector { testConfig.ClaimMapping.EmailKey = "mail" testConfig.ClaimMapping.EmailVerifiedKey = "has_verified_email" - log := logrus.New() + log := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) conn, err := testConfig.Open("id", log) if err != nil { diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go index fd715970b1..6fc16e7944 100644 --- a/connector/oidc/oidc.go +++ b/connector/oidc/oidc.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "net/url" "strings" @@ -17,7 +18,6 @@ import ( "github.com/dexidp/dex/connector" groups_pkg "github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/httpclient" - "github.com/dexidp/dex/pkg/log" ) // Config holds configuration options for OpenID Connect logins. @@ -201,7 +201,7 @@ func knownBrokenAuthHeaderProvider(issuerURL string) bool { // Open returns a connector which can be used to login users through an upstream // OpenID Connect provider. -func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) { +func (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) { if len(c.HostedDomains) > 0 { return nil, fmt.Errorf("support for the Hosted domains option had been deprecated and removed, consider switching to the Google connector") } @@ -220,7 +220,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e return nil, err } if !c.ProviderDiscoveryOverrides.Empty() { - logger.Warnf("overrides for connector %q are set, this can be a vulnerability when not properly configured", id) + logger.Warn("overrides for connector are set, this can be a vulnerability when not properly configured", "connector_id", id) } endpoint := provider.Endpoint() @@ -261,7 +261,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e verifier: provider.Verifier( &oidc.Config{ClientID: clientID}, ), - logger: logger, + logger: logger.With(slog.Group("connector", "type", "oidc", "id", id)), cancel: cancel, httpClient: httpClient, insecureSkipEmailVerified: c.InsecureSkipEmailVerified, @@ -291,7 +291,7 @@ type oidcConnector struct { oauth2Config *oauth2.Config verifier *oidc.IDTokenVerifier cancel context.CancelFunc - logger log.Logger + logger *slog.Logger httpClient *http.Client insecureSkipEmailVerified bool insecureEnableGroups bool diff --git a/connector/oidc/oidc_test.go b/connector/oidc/oidc_test.go index e621a55ffb..07291f7e5b 100644 --- a/connector/oidc/oidc_test.go +++ b/connector/oidc/oidc_test.go @@ -10,6 +10,8 @@ import ( "encoding/json" "errors" "fmt" + "io" + "log/slog" "net/http" "net/http/httptest" "reflect" @@ -18,7 +20,6 @@ import ( "time" "github.com/go-jose/go-jose/v4" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/dexidp/dex/connector" @@ -765,7 +766,7 @@ func newToken(key *jose.JSONWebKey, claims map[string]interface{}) (string, erro } func newConnector(config Config) (*oidcConnector, error) { - logger := logrus.New() + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) conn, err := config.Open("id", logger) if err != nil { return nil, fmt.Errorf("unable to open: %v", err) diff --git a/connector/openshift/openshift.go b/connector/openshift/openshift.go index 99d1b5b2f0..4519a85b6d 100644 --- a/connector/openshift/openshift.go +++ b/connector/openshift/openshift.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "net/http" "strings" @@ -13,7 +14,6 @@ import ( "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/httpclient" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) @@ -44,7 +44,7 @@ type openshiftConnector struct { clientID string clientSecret string cancel context.CancelFunc - logger log.Logger + logger *slog.Logger httpClient *http.Client oauth2Config *oauth2.Config insecureCA bool @@ -62,7 +62,7 @@ type user struct { // Open returns a connector which can be used to login users through an upstream // OpenShift OAuth2 provider. -func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) { +func (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) { var rootCAs []string if c.RootCA != "" { rootCAs = append(rootCAs, c.RootCA) @@ -78,7 +78,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e // OpenWithHTTPClient returns a connector which can be used to login users through an upstream // OpenShift OAuth2 provider. It provides the ability to inject a http.Client. -func (c *Config) OpenWithHTTPClient(id string, logger log.Logger, +func (c *Config) OpenWithHTTPClient(id string, logger *slog.Logger, httpClient *http.Client, ) (conn connector.Connector, err error) { ctx, cancel := context.WithCancel(context.Background()) @@ -96,7 +96,7 @@ func (c *Config) OpenWithHTTPClient(id string, logger log.Logger, clientID: c.ClientID, clientSecret: c.ClientSecret, insecureCA: c.InsecureCA, - logger: logger, + logger: logger.With(slog.Group("connector", "type", "openshift", "id", id)), redirectURI: c.RedirectURI, rootCA: c.RootCA, groups: c.Groups, diff --git a/connector/openshift/openshift_test.go b/connector/openshift/openshift_test.go index 1a2c7a4840..89ec0e25a9 100644 --- a/connector/openshift/openshift_test.go +++ b/connector/openshift/openshift_test.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "io" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -11,7 +13,6 @@ import ( "testing" "time" - "github.com/sirupsen/logrus" "golang.org/x/oauth2" "github.com/dexidp/dex/connector" @@ -37,7 +38,7 @@ func TestOpen(t *testing.T) { InsecureCA: true, } - logger := logrus.New() + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) oconfig, err := c.Open("id", logger) diff --git a/connector/saml/saml.go b/connector/saml/saml.go index fbfb1986a9..1ab8e54411 100644 --- a/connector/saml/saml.go +++ b/connector/saml/saml.go @@ -8,6 +8,7 @@ import ( "encoding/pem" "encoding/xml" "fmt" + "log/slog" "os" "strings" "sync" @@ -21,10 +22,8 @@ import ( "github.com/dexidp/dex/connector" "github.com/dexidp/dex/pkg/groups" - "github.com/dexidp/dex/pkg/log" ) -//nolint const ( bindingRedirect = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" bindingPOST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" @@ -120,11 +119,12 @@ func (c certStore) Certificates() (roots []*x509.Certificate, err error) { // Open validates the config and returns a connector. It does not actually // validate connectivity with the provider. -func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { +func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { + logger = logger.With(slog.Group("connector", "type", "saml", "id", id)) return c.openConnector(logger) } -func (c *Config) openConnector(logger log.Logger) (*provider, error) { +func (c *Config) openConnector(logger *slog.Logger) (*provider, error) { requiredFields := []struct { name, val string }{ @@ -252,7 +252,7 @@ type provider struct { nameIDPolicyFormat string - logger log.Logger + logger *slog.Logger } func (p *provider) POSTData(s connector.Scopes, id string) (action, value string, err error) { @@ -389,7 +389,7 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str // Log the actual attributes we got back from the server. This helps debug // configuration errors on the server side, where the SAML server doesn't // send us the correct attributes. - p.logger.Infof("parsed and verified saml response attributes %s", attributes) + p.logger.Info("parsed and verified saml response attributes", "attributes", attributes) // Grab the email. if ident.Email, _ = attributes.get(p.emailAttr); ident.Email == "" { diff --git a/connector/saml/saml_test.go b/connector/saml/saml_test.go index 68c0cb1a93..f67e3e8bc9 100644 --- a/connector/saml/saml_test.go +++ b/connector/saml/saml_test.go @@ -5,6 +5,8 @@ import ( "encoding/base64" "encoding/pem" "errors" + "io" + "log/slog" "os" "sort" "testing" @@ -12,7 +14,6 @@ import ( "github.com/kylelemons/godebug/pretty" dsig "github.com/russellhaering/goxmldsig" - "github.com/sirupsen/logrus" "github.com/dexidp/dex/connector" ) @@ -420,7 +421,7 @@ func (r responseTest) run(t *testing.T) { t.Fatalf("parse test time: %v", err) } - conn, err := c.openConnector(logrus.New()) + conn, err := c.openConnector(slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))) if err != nil { t.Fatal(err) } @@ -454,7 +455,7 @@ func (r responseTest) run(t *testing.T) { } func TestConfigCAData(t *testing.T) { - logger := logrus.New() + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) validPEM, err := os.ReadFile("testdata/ca.crt") if err != nil { t.Fatal(err) diff --git a/go.mod b/go.mod index 2eaff55385..e342783fcd 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.1 github.com/russellhaering/goxmldsig v1.4.0 - github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 go.etcd.io/etcd/client/pkg/v3 v3.5.14 diff --git a/go.sum b/go.sum index f989eb2911..96eb9070a9 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,6 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -304,7 +302,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/log/deprecated.go b/pkg/log/deprecated.go deleted file mode 100644 index f20e8b4cb8..0000000000 --- a/pkg/log/deprecated.go +++ /dev/null @@ -1,5 +0,0 @@ -package log - -func Deprecated(logger Logger, f string, args ...interface{}) { - logger.Warnf("Deprecated: "+f, args...) -} diff --git a/pkg/log/logger.go b/pkg/log/logger.go deleted file mode 100644 index 4f3cdd3851..0000000000 --- a/pkg/log/logger.go +++ /dev/null @@ -1,18 +0,0 @@ -// Package log provides a logger interface for logger libraries -// so that dex does not depend on any of them directly. -// It also includes a default implementation using Logrus (used by dex previously). -package log - -// Logger serves as an adapter interface for logger libraries -// so that dex does not depend on any of them directly. -type Logger interface { - Debug(args ...interface{}) - Info(args ...interface{}) - Warn(args ...interface{}) - Error(args ...interface{}) - - Debugf(format string, args ...interface{}) - Infof(format string, args ...interface{}) - Warnf(format string, args ...interface{}) - Errorf(format string, args ...interface{}) -} diff --git a/server/api.go b/server/api.go index c0eacb8f80..6a0071efb8 100644 --- a/server/api.go +++ b/server/api.go @@ -4,11 +4,11 @@ import ( "context" "errors" "fmt" + "log/slog" "golang.org/x/crypto/bcrypt" "github.com/dexidp/dex/api/v2" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" ) @@ -29,10 +29,10 @@ const ( ) // NewAPI returns a server which implements the gRPC API interface. -func NewAPI(s storage.Storage, logger log.Logger, version string) api.DexServer { +func NewAPI(s storage.Storage, logger *slog.Logger, version string) api.DexServer { return dexAPI{ s: s, - logger: logger, + logger: logger.With("component", "api"), version: version, } } @@ -41,7 +41,7 @@ type dexAPI struct { api.UnimplementedDexServer s storage.Storage - logger log.Logger + logger *slog.Logger version string } @@ -89,7 +89,7 @@ func (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*ap if err == storage.ErrAlreadyExists { return &api.CreateClientResp{AlreadyExists: true}, nil } - d.logger.Errorf("api: failed to create client: %v", err) + d.logger.Error("failed to create client", "err", err) return nil, fmt.Errorf("create client: %v", err) } @@ -122,7 +122,7 @@ func (d dexAPI) UpdateClient(ctx context.Context, req *api.UpdateClientReq) (*ap if err == storage.ErrNotFound { return &api.UpdateClientResp{NotFound: true}, nil } - d.logger.Errorf("api: failed to update the client: %v", err) + d.logger.Error("failed to update the client", "err", err) return nil, fmt.Errorf("update client: %v", err) } return &api.UpdateClientResp{}, nil @@ -134,7 +134,7 @@ func (d dexAPI) DeleteClient(ctx context.Context, req *api.DeleteClientReq) (*ap if err == storage.ErrNotFound { return &api.DeleteClientResp{NotFound: true}, nil } - d.logger.Errorf("api: failed to delete client: %v", err) + d.logger.Error("failed to delete client", "err", err) return nil, fmt.Errorf("delete client: %v", err) } return &api.DeleteClientResp{}, nil @@ -181,7 +181,7 @@ func (d dexAPI) CreatePassword(ctx context.Context, req *api.CreatePasswordReq) if err == storage.ErrAlreadyExists { return &api.CreatePasswordResp{AlreadyExists: true}, nil } - d.logger.Errorf("api: failed to create password: %v", err) + d.logger.Error("failed to create password", "err", err) return nil, fmt.Errorf("create password: %v", err) } @@ -218,7 +218,7 @@ func (d dexAPI) UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) if err == storage.ErrNotFound { return &api.UpdatePasswordResp{NotFound: true}, nil } - d.logger.Errorf("api: failed to update password: %v", err) + d.logger.Error("failed to update password", "err", err) return nil, fmt.Errorf("update password: %v", err) } @@ -235,7 +235,7 @@ func (d dexAPI) DeletePassword(ctx context.Context, req *api.DeletePasswordReq) if err == storage.ErrNotFound { return &api.DeletePasswordResp{NotFound: true}, nil } - d.logger.Errorf("api: failed to delete password: %v", err) + d.logger.Error("failed to delete password", "err", err) return nil, fmt.Errorf("delete password: %v", err) } return &api.DeletePasswordResp{}, nil @@ -251,7 +251,7 @@ func (d dexAPI) GetVersion(ctx context.Context, req *api.VersionReq) (*api.Versi func (d dexAPI) ListPasswords(ctx context.Context, req *api.ListPasswordReq) (*api.ListPasswordResp, error) { passwordList, err := d.s.ListPasswords() if err != nil { - d.logger.Errorf("api: failed to list passwords: %v", err) + d.logger.Error("failed to list passwords", "err", err) return nil, fmt.Errorf("list passwords: %v", err) } @@ -286,12 +286,12 @@ func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) NotFound: true, }, nil } - d.logger.Errorf("api: there was an error retrieving the password: %v", err) + d.logger.Error("there was an error retrieving the password", "err", err) return nil, fmt.Errorf("verify password: %v", err) } if err := bcrypt.CompareHashAndPassword(password.Hash, []byte(req.Password)); err != nil { - d.logger.Infof("api: password check failed: %v", err) + d.logger.Info("password check failed", "err", err) return &api.VerifyPasswordResp{ Verified: false, }, nil @@ -304,7 +304,7 @@ func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) { id := new(internal.IDTokenSubject) if err := internal.Unmarshal(req.UserId, id); err != nil { - d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err) + d.logger.Error("failed to unmarshal ID Token subject", "err", err) return nil, err } @@ -315,7 +315,7 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api. // An empty list should be returned instead of an error. return &api.ListRefreshResp{}, nil } - d.logger.Errorf("api: failed to list refresh tokens %t here : %v", err == storage.ErrNotFound, err) + d.logger.Error("failed to list refresh tokens here", "err", err) return nil, err } @@ -338,7 +338,7 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api. func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (*api.RevokeRefreshResp, error) { id := new(internal.IDTokenSubject) if err := internal.Unmarshal(req.UserId, id); err != nil { - d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err) + d.logger.Error("failed to unmarshal ID Token subject", "err", err) return nil, err } @@ -349,7 +349,7 @@ func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (* updater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) { refreshRef := old.Refresh[req.ClientId] if refreshRef == nil || refreshRef.ID == "" { - d.logger.Errorf("api: refresh token issued to client %q for user %q not found for deletion", req.ClientId, id.UserId) + d.logger.Error("refresh token issued to client not found for deletion", "client_id", req.ClientId, "user_id", id.UserId) notFound = true return old, storage.ErrNotFound } @@ -366,7 +366,7 @@ func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (* if err == storage.ErrNotFound { return &api.RevokeRefreshResp{NotFound: true}, nil } - d.logger.Errorf("api: failed to update offline session object: %v", err) + d.logger.Error("failed to update offline session object", "err", err) return nil, err } @@ -379,7 +379,7 @@ func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (* // TODO(ericchiang): we don't have any good recourse if this call fails. // Consider garbage collection of refresh tokens with no associated ref. if err := d.s.DeleteRefresh(refreshID); err != nil { - d.logger.Errorf("failed to delete refresh token: %v", err) + d.logger.Error("failed to delete refresh token", "err", err) return nil, err } diff --git a/server/api_test.go b/server/api_test.go index 1ed43168dd..e4150f1f96 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -2,17 +2,16 @@ package server import ( "context" + "io" + "log/slog" "net" - "os" "testing" "time" - "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "github.com/dexidp/dex/api/v2" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/server/internal" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/memory" @@ -30,7 +29,7 @@ type apiClient struct { } // newAPI constructs a gRCP client connected to a backing server. -func newAPI(s storage.Storage, logger log.Logger, t *testing.T) *apiClient { +func newAPI(s storage.Storage, logger *slog.Logger, t *testing.T) *apiClient { l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) @@ -59,11 +58,7 @@ func newAPI(s storage.Storage, logger log.Logger, t *testing.T) *apiClient { // Attempts to create, update and delete a test Password func TestPassword(t *testing.T) { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) s := memory.New(logger) client := newAPI(s, logger, t) @@ -172,11 +167,7 @@ func TestPassword(t *testing.T) { // Ensures checkCost returns expected values func TestCheckCost(t *testing.T) { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) s := memory.New(logger) client := newAPI(s, logger, t) @@ -229,11 +220,7 @@ func TestCheckCost(t *testing.T) { // Attempts to list and revoke an existing refresh token. func TestRefreshToken(t *testing.T) { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) s := memory.New(logger) client := newAPI(s, logger, t) @@ -342,11 +329,7 @@ func TestRefreshToken(t *testing.T) { } func TestUpdateClient(t *testing.T) { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) s := memory.New(logger) client := newAPI(s, logger, t) diff --git a/server/deviceflowhandlers.go b/server/deviceflowhandlers.go index 5683e9441a..6f8aae0306 100644 --- a/server/deviceflowhandlers.go +++ b/server/deviceflowhandlers.go @@ -13,7 +13,6 @@ import ( "golang.org/x/net/html" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" ) @@ -49,7 +48,7 @@ func (s *Server) handleDeviceExchange(w http.ResponseWriter, r *http.Request) { invalidAttempt = false } if err := s.templates.device(r, w, s.getDeviceVerificationURI(), userCode, invalidAttempt); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("server template error", "err", err) s.renderError(r, w, http.StatusNotFound, "Page not found") } default: @@ -65,7 +64,7 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) { case http.MethodPost: err := r.ParseForm() if err != nil { - s.logger.Errorf("Could not parse Device Request body: %v", err) + s.logger.Error("could not parse Device Request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusNotFound) return } @@ -86,7 +85,7 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) { return } - s.logger.Infof("Received device request for client %v with scopes %v", clientID, scopes) + s.logger.Info("received device request", "client_id", clientID, "scoped", scopes) // Make device code deviceCode := storage.NewDeviceCode() @@ -108,7 +107,7 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) { } if err := s.storage.CreateDeviceRequest(ctx, deviceReq); err != nil { - s.logger.Errorf("Failed to store device request; %v", err) + s.logger.Error("failed to store device request", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError) return } @@ -127,14 +126,14 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) { } if err := s.storage.CreateDeviceToken(ctx, deviceToken); err != nil { - s.logger.Errorf("Failed to store device token %v", err) + s.logger.Error("failed to store device token", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError) return } u, err := url.Parse(s.issuerURL.String()) if err != nil { - s.logger.Errorf("Could not parse issuer URL %v", err) + s.logger.Error("could not parse issuer URL", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError) return } @@ -175,14 +174,14 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) { } func (s *Server) handleDeviceTokenDeprecated(w http.ResponseWriter, r *http.Request) { - log.Deprecated(s.logger, `The /device/token endpoint was called. It will be removed, use /token instead.`) + s.logger.Warn(`the /device/token endpoint was called. It will be removed, use /token instead.`, "deprecated", true) w.Header().Set("Content-Type", "application/json") switch r.Method { case http.MethodPost: err := r.ParseForm() if err != nil { - s.logger.Warnf("Could not parse Device Token Request body: %v", err) + s.logger.Warn("could not parse Device Token Request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) return } @@ -212,7 +211,7 @@ func (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) { deviceToken, err := s.storage.GetDeviceToken(deviceCode) if err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get device code: %v", err) + s.logger.Error("failed to get device code", "err", err) } s.tokenErrHelper(w, errInvalidRequest, "Invalid Device code.", http.StatusBadRequest) return @@ -242,7 +241,7 @@ func (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) { } // Update device token last request time in storage if err := s.storage.UpdateDeviceToken(deviceCode, updater); err != nil { - s.logger.Errorf("failed to update device token: %v", err) + s.logger.Error("failed to update device token", "err", err) s.renderError(r, w, http.StatusInternalServerError, "") return } @@ -259,7 +258,7 @@ func (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) { case providedCodeVerifier != "" && codeChallengeFromStorage != "": calculatedCodeChallenge, err := s.calculateCodeChallenge(providedCodeVerifier, deviceToken.PKCE.CodeChallengeMethod) if err != nil { - s.logger.Error(err) + s.logger.Error("failed to calculate code challenge", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -304,7 +303,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { if err != nil || s.now().After(authCode.Expiry) { errCode := http.StatusBadRequest if err != nil && err != storage.ErrNotFound { - s.logger.Errorf("failed to get auth code: %v", err) + s.logger.Error("failed to get auth code", "err", err) errCode = http.StatusInternalServerError } s.renderError(r, w, errCode, "Invalid or expired auth code.") @@ -316,7 +315,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { if err != nil || s.now().After(deviceReq.Expiry) { errCode := http.StatusBadRequest if err != nil && err != storage.ErrNotFound { - s.logger.Errorf("failed to get device code: %v", err) + s.logger.Error("failed to get device code", "err", err) errCode = http.StatusInternalServerError } s.renderError(r, w, errCode, "Invalid or expired user code.") @@ -326,7 +325,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { client, err := s.storage.GetClient(deviceReq.ClientID) if err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get client: %v", err) + s.logger.Error("failed to get client", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) } else { s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) @@ -340,7 +339,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { resp, err := s.exchangeAuthCode(ctx, w, authCode, client) if err != nil { - s.logger.Errorf("Could not exchange auth code for client %q: %v", deviceReq.ClientID, err) + s.logger.Error("could not exchange auth code for clien", "client_id", deviceReq.ClientID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Failed to exchange auth code.") return } @@ -350,7 +349,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { if err != nil || s.now().After(old.Expiry) { errCode := http.StatusBadRequest if err != nil && err != storage.ErrNotFound { - s.logger.Errorf("failed to get device token: %v", err) + s.logger.Error("failed to get device token", "err", err) errCode = http.StatusInternalServerError } s.renderError(r, w, errCode, "Invalid or expired device code.") @@ -363,7 +362,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { } respStr, err := json.MarshalIndent(resp, "", " ") if err != nil { - s.logger.Errorf("failed to marshal device token response: %v", err) + s.logger.Error("failed to marshal device token response", "err", err) s.renderError(r, w, http.StatusInternalServerError, "") return old, err } @@ -375,13 +374,13 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) { // Update refresh token in the storage, store the token and mark as complete if err := s.storage.UpdateDeviceToken(deviceReq.DeviceCode, updater); err != nil { - s.logger.Errorf("failed to update device token: %v", err) + s.logger.Error("failed to update device token", "err", err) s.renderError(r, w, http.StatusBadRequest, "") return } if err := s.templates.deviceSuccess(r, w, client.Name); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("Server template error", "err", err) s.renderError(r, w, http.StatusNotFound, "Page not found") } @@ -396,7 +395,7 @@ func (s *Server) verifyUserCode(w http.ResponseWriter, r *http.Request) { case http.MethodPost: err := r.ParseForm() if err != nil { - s.logger.Warnf("Could not parse user code verification request body : %v", err) + s.logger.Warn("could not parse user code verification request body", "err", err) s.renderError(r, w, http.StatusBadRequest, "") return } @@ -413,10 +412,10 @@ func (s *Server) verifyUserCode(w http.ResponseWriter, r *http.Request) { deviceRequest, err := s.storage.GetDeviceRequest(userCode) if err != nil || s.now().After(deviceRequest.Expiry) { if err != nil && err != storage.ErrNotFound { - s.logger.Errorf("failed to get device request: %v", err) + s.logger.Error("failed to get device request", "err", err) } if err := s.templates.device(r, w, s.getDeviceVerificationURI(), userCode, true); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("Server template error", "err", err) s.renderError(r, w, http.StatusNotFound, "Page not found") } return diff --git a/server/handlers.go b/server/handlers.go index ccd534d991..42f3ebe5d5 100644 --- a/server/handlers.go +++ b/server/handlers.go @@ -35,13 +35,13 @@ func (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) { // TODO(ericchiang): Cache this. keys, err := s.storage.GetKeys() if err != nil { - s.logger.Errorf("failed to get keys: %v", err) + s.logger.Error("failed to get keys", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } if keys.SigningKeyPub == nil { - s.logger.Errorf("No public keys found.") + s.logger.Error("no public keys found.") s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } @@ -56,7 +56,7 @@ func (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) { data, err := json.MarshalIndent(jwks, "", " ") if err != nil { - s.logger.Errorf("failed to marshal discovery data: %v", err) + s.logger.Error("failed to marshal discovery data", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } @@ -132,7 +132,7 @@ func (s *Server) discoveryHandler() (http.HandlerFunc, error) { func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) { // Extract the arguments if err := r.ParseForm(); err != nil { - s.logger.Errorf("Failed to parse arguments: %v", err) + s.logger.Error("failed to parse arguments", "err", err) s.renderError(r, w, http.StatusBadRequest, err.Error()) return @@ -142,7 +142,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) { connectors, err := s.storage.ListConnectors() if err != nil { - s.logger.Errorf("Failed to get list of connectors: %v", err) + s.logger.Error("failed to get list of connectors", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Failed to retrieve connector list.") return } @@ -185,7 +185,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) { } if err := s.templates.login(r, w, connectorInfos); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("server template error", "err", err) } } @@ -193,7 +193,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { ctx := r.Context() authReq, err := s.parseAuthorizationRequest(r) if err != nil { - s.logger.Errorf("Failed to parse authorization request: %v", err) + s.logger.Error("failed to parse authorization request", "err", err) switch authErr := err.(type) { case *redirectedAuthErr: @@ -209,22 +209,22 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { connID, err := url.PathUnescape(mux.Vars(r)["connector"]) if err != nil { - s.logger.Errorf("Failed to parse connector: %v", err) + s.logger.Error("failed to parse connector", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist") return } conn, err := s.getConnector(connID) if err != nil { - s.logger.Errorf("Failed to get connector: %v", err) + s.logger.Error("Failed to get connector", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist") return } // Set the connector being used for the login. if authReq.ConnectorID != "" && authReq.ConnectorID != connID { - s.logger.Errorf("Mismatched connector ID in auth request: %s vs %s", - authReq.ConnectorID, connID) + s.logger.Error("mismatched connector ID in auth request", + "auth_request_connector_id", authReq.ConnectorID, "connector_id", connID) s.renderError(r, w, http.StatusBadRequest, "Bad connector ID") return } @@ -234,7 +234,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { // Actually create the auth request authReq.Expiry = s.now().Add(s.authRequestsValidFor) if err := s.storage.CreateAuthRequest(ctx, *authReq); err != nil { - s.logger.Errorf("Failed to create authorization request: %v", err) + s.logger.Error("failed to create authorization request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Failed to connect to the database.") return } @@ -260,7 +260,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { // TODO(ericchiang): Is this appropriate or should we also be using a nonce? callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReq.ID) if err != nil { - s.logger.Errorf("Connector %q returned error when creating callback: %v", connID, err) + s.logger.Error("connector returned error when creating callback", "connector_id", connID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } @@ -278,7 +278,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) { case connector.SAMLConnector: action, value, err := conn.POSTData(scopes, authReq.ID) if err != nil { - s.logger.Errorf("Creating SAML data: %v", err) + s.logger.Error("creating SAML data", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Connector Login Error") return } @@ -321,36 +321,36 @@ func (s *Server) handlePasswordLogin(w http.ResponseWriter, r *http.Request) { authReq, err := s.storage.GetAuthRequest(authID) if err != nil { if err == storage.ErrNotFound { - s.logger.Errorf("Invalid 'state' parameter provided: %v", err) + s.logger.Error("invalid 'state' parameter provided", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") return } - s.logger.Errorf("Failed to get auth request: %v", err) + s.logger.Error("failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } connID, err := url.PathUnescape(mux.Vars(r)["connector"]) if err != nil { - s.logger.Errorf("Failed to parse connector: %v", err) + s.logger.Error("failed to parse connector", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist") return } else if connID != "" && connID != authReq.ConnectorID { - s.logger.Errorf("Connector mismatch: authentication started with id %q, but password login for id %q was triggered", authReq.ConnectorID, connID) + s.logger.Error("connector mismatch: password login triggered for different connector from authentication start", "start_connector_id", authReq.ConnectorID, "password_connector_id", connID) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } conn, err := s.getConnector(authReq.ConnectorID) if err != nil { - s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err) + s.logger.Error("failed to get connector", "connector_id", authReq.ConnectorID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } pwConn, ok := conn.Connector.(connector.PasswordConnector) if !ok { - s.logger.Errorf("Expected password connector in handlePasswordLogin(), but got %v", pwConn) + s.logger.Error("expected password connector in handlePasswordLogin()", "password_connector", pwConn) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } @@ -358,7 +358,7 @@ func (s *Server) handlePasswordLogin(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: if err := s.templates.password(r, w, r.URL.String(), "", usernamePrompt(pwConn), false, backLink); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("server template error", "err", err) } case http.MethodPost: username := r.FormValue("login") @@ -367,20 +367,20 @@ func (s *Server) handlePasswordLogin(w http.ResponseWriter, r *http.Request) { identity, ok, err := pwConn.Login(ctx, scopes, username, password) if err != nil { - s.logger.Errorf("Failed to login user: %v", err) + s.logger.Error("failed to login user", "err", err) s.renderError(r, w, http.StatusInternalServerError, fmt.Sprintf("Login error: %v", err)) return } if !ok { if err := s.templates.password(r, w, r.URL.String(), username, usernamePrompt(pwConn), true, backLink); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("server template error", "err", err) } - s.logger.Errorf("Failed login attempt for user: %q. Invalid credentials.", username) + s.logger.Error("failed login attempt: Invalid credentials.", "user", username) return } redirectURL, canSkipApproval, err := s.finalizeLogin(ctx, identity, authReq, conn.Connector) if err != nil { - s.logger.Errorf("Failed to finalize login: %v", err) + s.logger.Error("failed to finalize login", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } @@ -388,7 +388,7 @@ func (s *Server) handlePasswordLogin(w http.ResponseWriter, r *http.Request) { if canSkipApproval { authReq, err = s.storage.GetAuthRequest(authReq.ID) if err != nil { - s.logger.Errorf("Failed to get finalized auth request: %v", err) + s.logger.Error("failed to get finalized auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } @@ -424,29 +424,29 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) authReq, err := s.storage.GetAuthRequest(authID) if err != nil { if err == storage.ErrNotFound { - s.logger.Errorf("Invalid 'state' parameter provided: %v", err) + s.logger.Error("invalid 'state' parameter provided", "err", err) s.renderError(r, w, http.StatusBadRequest, "Requested resource does not exist.") return } - s.logger.Errorf("Failed to get auth request: %v", err) + s.logger.Error("failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } connID, err := url.PathUnescape(mux.Vars(r)["connector"]) if err != nil { - s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err) + s.logger.Error("failed to get connector", "connector_id", authReq.ConnectorID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } else if connID != "" && connID != authReq.ConnectorID { - s.logger.Errorf("Connector mismatch: authentication started with id %q, but callback for id %q was triggered", authReq.ConnectorID, connID) + s.logger.Error("connector mismatch: callback triggered for different connector than authentication start", "authentication_start_connector_id", authReq.ConnectorID, "connector_id", connID) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } conn, err := s.getConnector(authReq.ConnectorID) if err != nil { - s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err) + s.logger.Error("failed to get connector", "connector_id", authReq.ConnectorID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Requested resource does not exist.") return } @@ -455,14 +455,14 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) switch conn := conn.Connector.(type) { case connector.CallbackConnector: if r.Method != http.MethodGet { - s.logger.Errorf("SAML request mapped to OAuth2 connector") + s.logger.Error("SAML request mapped to OAuth2 connector") s.renderError(r, w, http.StatusBadRequest, "Invalid request") return } identity, err = conn.HandleCallback(parseScopes(authReq.Scopes), r) case connector.SAMLConnector: if r.Method != http.MethodPost { - s.logger.Errorf("OAuth2 request mapped to SAML connector") + s.logger.Error("OAuth2 request mapped to SAML connector") s.renderError(r, w, http.StatusBadRequest, "Invalid request") return } @@ -473,14 +473,14 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) } if err != nil { - s.logger.Errorf("Failed to authenticate: %v", err) + s.logger.Error("failed to authenticate", "err", err) s.renderError(r, w, http.StatusInternalServerError, fmt.Sprintf("Failed to authenticate: %v", err)) return } redirectURL, canSkipApproval, err := s.finalizeLogin(ctx, identity, authReq, conn.Connector) if err != nil { - s.logger.Errorf("Failed to finalize login: %v", err) + s.logger.Error("failed to finalize login", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } @@ -488,7 +488,7 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) if canSkipApproval { authReq, err = s.storage.GetAuthRequest(authReq.ID) if err != nil { - s.logger.Errorf("Failed to get finalized auth request: %v", err) + s.logger.Error("failed to get finalized auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Login error.") return } @@ -526,8 +526,9 @@ func (s *Server) finalizeLogin(ctx context.Context, identity connector.Identity, email += " (unverified)" } - s.logger.Infof("login successful: connector %q, username=%q, preferred_username=%q, email=%q, groups=%q", - authReq.ConnectorID, claims.Username, claims.PreferredUsername, email, claims.Groups) + s.logger.Info("login successful", + "connector_id", authReq.ConnectorID, "username", claims.Username, + "preferred_username", claims.PreferredUsername, "email", email, "groups", claims.Groups) // we can skip the redirect to /approval and go ahead and send code if it's not required if s.skipApproval && !authReq.ForceApprovalPrompt { @@ -561,7 +562,7 @@ func (s *Server) finalizeLogin(ctx context.Context, identity connector.Identity, session, err := s.storage.GetOfflineSessions(identity.UserID, authReq.ConnectorID) if err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get offline session: %v", err) + s.logger.Error("failed to get offline session", "err", err) return "", false, err } offlineSessions := storage.OfflineSessions{ @@ -574,7 +575,7 @@ func (s *Server) finalizeLogin(ctx context.Context, identity connector.Identity, // Create a new OfflineSession object for the user and add a reference object for // the newly received refreshtoken. if err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil { - s.logger.Errorf("failed to create offline session: %v", err) + s.logger.Error("failed to create offline session", "err", err) return "", false, err } @@ -588,7 +589,7 @@ func (s *Server) finalizeLogin(ctx context.Context, identity connector.Identity, } return old, nil }); err != nil { - s.logger.Errorf("failed to update offline session: %v", err) + s.logger.Error("failed to update offline session", "err", err) return "", false, err } @@ -609,12 +610,12 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { authReq, err := s.storage.GetAuthRequest(r.FormValue("req")) if err != nil { - s.logger.Errorf("Failed to get auth request: %v", err) + s.logger.Error("failed to get auth request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Database error.") return } if !authReq.LoggedIn { - s.logger.Errorf("Auth request does not have an identity for approval") + s.logger.Error("auth request does not have an identity for approval") s.renderError(r, w, http.StatusInternalServerError, "Login process not yet finalized.") return } @@ -633,12 +634,12 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) { case http.MethodGet: client, err := s.storage.GetClient(authReq.ClientID) if err != nil { - s.logger.Errorf("Failed to get client %q: %v", authReq.ClientID, err) + s.logger.Error("Failed to get client", "client_id", authReq.ClientID, "err", err) s.renderError(r, w, http.StatusInternalServerError, "Failed to retrieve client.") return } if err := s.templates.approval(r, w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("server template error", "err", err) } case http.MethodPost: if r.FormValue("approval") != "approve" { @@ -658,7 +659,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe if err := s.storage.DeleteAuthRequest(authReq.ID); err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("Failed to delete authorization request: %v", err) + s.logger.Error("Failed to delete authorization request", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") } else { s.renderError(r, w, http.StatusBadRequest, "User session error.") @@ -704,7 +705,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe PKCE: authReq.PKCE, } if err := s.storage.CreateAuthCode(ctx, code); err != nil { - s.logger.Errorf("Failed to create auth code: %v", err) + s.logger.Error("Failed to create auth code", "err", err) s.renderError(r, w, http.StatusInternalServerError, "Internal server error.") return } @@ -713,7 +714,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe // rejected earlier. If we got here we're using the code flow. if authReq.RedirectURI == redirectURIOOB { if err := s.templates.oob(r, w, code.ID); err != nil { - s.logger.Errorf("Server template error: %v", err) + s.logger.Error("server template error", "err", err) } return } @@ -725,14 +726,14 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe accessToken, _, err = s.newAccessToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, authReq.ConnectorID) if err != nil { - s.logger.Errorf("failed to create new access token: %v", err) + s.logger.Error("failed to create new access token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } idToken, idTokenExpiry, err = s.newIDToken(authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, accessToken, code.ID, authReq.ConnectorID) if err != nil { - s.logger.Errorf("failed to create ID token: %v", err) + s.logger.Error("failed to create ID token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -807,7 +808,7 @@ func (s *Server) withClientFromStorage(w http.ResponseWriter, r *http.Request, h client, err := s.storage.GetClient(clientID) if err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get client: %v", err) + s.logger.Error("failed to get client", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) } else { s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) @@ -817,9 +818,9 @@ func (s *Server) withClientFromStorage(w http.ResponseWriter, r *http.Request, h if subtle.ConstantTimeCompare([]byte(client.Secret), []byte(clientSecret)) != 1 { if clientSecret == "" { - s.logger.Infof("missing client_secret on token request for client: %s", client.ID) + s.logger.Info("missing client_secret on token request", "client_id", client.ID) } else { - s.logger.Infof("invalid client_secret on token request for client: %s", client.ID) + s.logger.Info("invalid client_secret on token request", "client_id", client.ID) } s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized) return @@ -837,14 +838,14 @@ func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { - s.logger.Errorf("Could not parse request body: %v", err) + s.logger.Error("could not parse request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) return } grantType := r.PostFormValue("grant_type") if !contains(s.supportedGrantTypes, grantType) { - s.logger.Errorf("unsupported grant type: %v", grantType) + s.logger.Error("unsupported grant type", "grant_type", grantType) s.tokenErrHelper(w, errUnsupportedGrantType, "", http.StatusBadRequest) return } @@ -890,7 +891,7 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s authCode, err := s.storage.GetAuthCode(code) if err != nil || s.now().After(authCode.Expiry) || authCode.ClientID != client.ID { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get auth code: %v", err) + s.logger.Error("failed to get auth code", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) } else { s.tokenErrHelper(w, errInvalidGrant, "Invalid or expired code parameter.", http.StatusBadRequest) @@ -906,7 +907,7 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s case providedCodeVerifier != "" && codeChallengeFromStorage != "": calculatedCodeChallenge, err := s.calculateCodeChallenge(providedCodeVerifier, authCode.PKCE.CodeChallengeMethod) if err != nil { - s.logger.Error(err) + s.logger.Error("failed to calculate code challenge", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -940,20 +941,20 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, authCode storage.AuthCode, client storage.Client) (*accessTokenResponse, error) { accessToken, _, err := s.newAccessToken(client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, authCode.ConnectorID) if err != nil { - s.logger.Errorf("failed to create new access token: %v", err) + s.logger.Error("failed to create new access token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } idToken, expiry, err := s.newIDToken(client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, accessToken, authCode.ID, authCode.ConnectorID) if err != nil { - s.logger.Errorf("failed to create ID token: %v", err) + s.logger.Error("failed to create ID token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } if err := s.storage.DeleteAuthCode(authCode.ID); err != nil { - s.logger.Errorf("failed to delete auth code: %v", err) + s.logger.Error("failed to delete auth code", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } @@ -964,7 +965,7 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au // Connectors like `saml` do not implement RefreshConnector. conn, err := s.getConnector(authCode.ConnectorID) if err != nil { - s.logger.Errorf("connector with ID %q not found: %v", authCode.ConnectorID, err) + s.logger.Error("connector not found", "connector_id", authCode.ConnectorID, "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return false } @@ -1000,13 +1001,13 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au Token: refresh.Token, } if refreshToken, err = internal.Marshal(token); err != nil { - s.logger.Errorf("failed to marshal refresh token: %v", err) + s.logger.Error("failed to marshal refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } if err := s.storage.CreateRefresh(ctx, refresh); err != nil { - s.logger.Errorf("failed to create refresh token: %v", err) + s.logger.Error("failed to create refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return nil, err } @@ -1019,7 +1020,7 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au if deleteToken { // Delete newly created refresh token from storage. if err := s.storage.DeleteRefresh(refresh.ID); err != nil { - s.logger.Errorf("failed to delete refresh token: %v", err) + s.logger.Error("failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -1036,7 +1037,7 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au // Try to retrieve an existing OfflineSession object for the corresponding user. if session, err := s.storage.GetOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID); err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get offline session: %v", err) + s.logger.Error("failed to get offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err @@ -1051,7 +1052,7 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au // Create a new OfflineSession object for the user and add a reference object for // the newly received refreshtoken. if err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil { - s.logger.Errorf("failed to create offline session: %v", err) + s.logger.Error("failed to create offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err @@ -1060,7 +1061,7 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok { // Delete old refresh token from storage. if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil && err != storage.ErrNotFound { - s.logger.Errorf("failed to delete refresh token: %v", err) + s.logger.Error("failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err @@ -1072,7 +1073,7 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au old.Refresh[tokenRef.ClientID] = &tokenRef return old, nil }); err != nil { - s.logger.Errorf("failed to update offline session: %v", err) + s.logger.Error("failed to update offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return nil, err @@ -1184,7 +1185,7 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli password := q.Get("password") identity, ok, err := passwordConnector.Login(ctx, parseScopes(scopes), username, password) if err != nil { - s.logger.Errorf("Failed to login user: %v", err) + s.logger.Error("failed to login user", "err", err) s.tokenErrHelper(w, errInvalidRequest, "Could not login user", http.StatusBadRequest) return } @@ -1205,14 +1206,14 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli accessToken, _, err := s.newAccessToken(client.ID, claims, scopes, nonce, connID) if err != nil { - s.logger.Errorf("password grant failed to create new access token: %v", err) + s.logger.Error("password grant failed to create new access token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, nonce, accessToken, "", connID) if err != nil { - s.logger.Errorf("password grant failed to create new ID token: %v", err) + s.logger.Error("password grant failed to create new ID token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -1252,13 +1253,13 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli Token: refresh.Token, } if refreshToken, err = internal.Marshal(token); err != nil { - s.logger.Errorf("failed to marshal refresh token: %v", err) + s.logger.Error("failed to marshal refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } if err := s.storage.CreateRefresh(ctx, refresh); err != nil { - s.logger.Errorf("failed to create refresh token: %v", err) + s.logger.Error("failed to create refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -1271,7 +1272,7 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli if deleteToken { // Delete newly created refresh token from storage. if err := s.storage.DeleteRefresh(refresh.ID); err != nil { - s.logger.Errorf("failed to delete refresh token: %v", err) + s.logger.Error("failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -1288,7 +1289,7 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli // Try to retrieve an existing OfflineSession object for the corresponding user. if session, err := s.storage.GetOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID); err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get offline session: %v", err) + s.logger.Error("failed to get offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return @@ -1304,7 +1305,7 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli // Create a new OfflineSession object for the user and add a reference object for // the newly received refreshtoken. if err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil { - s.logger.Errorf("failed to create offline session: %v", err) + s.logger.Error("failed to create offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return @@ -1314,9 +1315,9 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli // Delete old refresh token from storage. if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil { if err == storage.ErrNotFound { - s.logger.Warnf("database inconsistent, refresh token missing: %v", oldTokenRef.ID) + s.logger.Warn("database inconsistent, refresh token missing", "token_id", oldTokenRef.ID) } else { - s.logger.Errorf("failed to delete refresh token: %v", err) + s.logger.Error("failed to delete refresh token", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return @@ -1330,7 +1331,7 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli old.ConnectorData = identity.ConnectorData return old, nil }); err != nil { - s.logger.Errorf("failed to update offline session: %v", err) + s.logger.Error("failed to update offline session", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) deleteToken = true return @@ -1346,7 +1347,7 @@ func (s *Server) handleTokenExchange(w http.ResponseWriter, r *http.Request, cli ctx := r.Context() if err := r.ParseForm(); err != nil { - s.logger.Errorf("could not parse request body: %v", err) + s.logger.Error("could not parse request body", "err", err) s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest) return } @@ -1375,19 +1376,19 @@ func (s *Server) handleTokenExchange(w http.ResponseWriter, r *http.Request, cli conn, err := s.getConnector(connID) if err != nil { - s.logger.Errorf("failed to get connector: %v", err) + s.logger.Error("failed to get connector", "err", err) s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) return } teConn, ok := conn.Connector.(connector.TokenIdentityConnector) if !ok { - s.logger.Errorf("connector doesn't implement token exchange: %v", connID) + s.logger.Error("connector doesn't implement token exchange", "connector_id", connID) s.tokenErrHelper(w, errInvalidRequest, "Requested connector does not exist.", http.StatusBadRequest) return } identity, err := teConn.TokenIdentity(ctx, subjectTokenType, subjectToken) if err != nil { - s.logger.Errorf("failed to verify subject token: %v", err) + s.logger.Error("failed to verify subject token", "err", err) s.tokenErrHelper(w, errAccessDenied, "", http.StatusUnauthorized) return } @@ -1415,7 +1416,7 @@ func (s *Server) handleTokenExchange(w http.ResponseWriter, r *http.Request, cli return } if err != nil { - s.logger.Errorf("token exchange failed to create new %v token: %v", requestedTokenType, err) + s.logger.Error("token exchange failed to create new token", "requested_token_type", requestedTokenType, "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -1451,7 +1452,7 @@ func (s *Server) toAccessTokenResponse(idToken, accessToken, refreshToken string func (s *Server) writeAccessToken(w http.ResponseWriter, resp *accessTokenResponse) { data, err := json.Marshal(resp) if err != nil { - s.logger.Errorf("failed to marshal access token response: %v", err) + s.logger.Error("failed to marshal access token response", "err", err) s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) return } @@ -1466,13 +1467,13 @@ func (s *Server) writeAccessToken(w http.ResponseWriter, resp *accessTokenRespon func (s *Server) renderError(r *http.Request, w http.ResponseWriter, status int, description string) { if err := s.templates.err(r, w, status, description); err != nil { - s.logger.Errorf("server template error: %v", err) + s.logger.Error("server template error", "err", err) } } func (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) { if err := tokenErr(w, typ, description, statusCode); err != nil { - s.logger.Errorf("token error response: %v", err) + s.logger.Error("token error response", "err", err) } } diff --git a/server/introspectionhandler.go b/server/introspectionhandler.go index f0d1f807ca..8c6e4419f3 100644 --- a/server/introspectionhandler.go +++ b/server/introspectionhandler.go @@ -179,14 +179,14 @@ func (s *Server) getTokenFromRequest(r *http.Request) (string, TokenTypeEnum, er token := r.PostForm.Get("token") tokenType, err := s.guessTokenType(r.Context(), token) if err != nil { - s.logger.Error(err) + s.logger.Error("failed to guess token type", "err", err) return "", 0, newIntrospectInternalServerError() } requestTokenType := r.PostForm.Get("token_type_hint") if requestTokenType != "" { if tokenType.String() != requestTokenType { - s.logger.Warnf("Token type hint doesn't match token type: %s != %s", requestTokenType, tokenType) + s.logger.Warn("token type hint doesn't match token type", "request_token_type", requestTokenType, "token_type", tokenType) } } @@ -211,13 +211,13 @@ func (s *Server) introspectRefreshToken(_ context.Context, token string) (*Intro return nil, newIntrospectInactiveTokenError() } - s.logger.Errorf("failed to get refresh token: %v", err) + s.logger.Error("failed to get refresh token", "err", err) return nil, newIntrospectInternalServerError() } subjectString, sErr := genSubject(rCtx.storageToken.Claims.UserID, rCtx.storageToken.ConnectorID) if sErr != nil { - s.logger.Errorf("failed to marshal offline session ID: %v", err) + s.logger.Error("failed to marshal offline session ID", "err", err) return nil, newIntrospectInternalServerError() } @@ -253,19 +253,19 @@ func (s *Server) introspectAccessToken(ctx context.Context, token string) (*Intr var claims IntrospectionExtra if err := idToken.Claims(&claims); err != nil { - s.logger.Errorf("Error while fetching token claims: %s", err.Error()) + s.logger.Error("error while fetching token claims", "err", err.Error()) return nil, newIntrospectInternalServerError() } clientID, err := getClientID(idToken.Audience, claims.AuthorizingParty) if err != nil { - s.logger.Error("Error while fetching client_id from token: %s", err.Error()) + s.logger.Error("error while fetching client_id from token:", "err", err.Error()) return nil, newIntrospectInternalServerError() } client, err := s.storage.GetClient(clientID) if err != nil { - s.logger.Error("Error while fetching client from storage: %s", err.Error()) + s.logger.Error("error while fetching client from storage", "err", err.Error()) return nil, newIntrospectInternalServerError() } @@ -299,7 +299,7 @@ func (s *Server) handleIntrospect(w http.ResponseWriter, r *http.Request) { introspect, err = s.introspectRefreshToken(ctx, token) default: // Token type is neither handled token types. - s.logger.Errorf("Unknown token type: %s", tokenType) + s.logger.Error("unknown token type", "token_type", tokenType) introspectInactiveErr(w) return } @@ -309,7 +309,7 @@ func (s *Server) handleIntrospect(w http.ResponseWriter, r *http.Request) { if intErr, ok := err.(*introspectionError); ok { s.introspectErrHelper(w, intErr.typ, intErr.desc, intErr.code) } else { - s.logger.Errorf("An unknown error occurred: %s", err.Error()) + s.logger.Error("an unknown error occurred", "err", err.Error()) s.introspectErrHelper(w, errServerError, "An unknown error occurred", http.StatusInternalServerError) } @@ -332,7 +332,7 @@ func (s *Server) introspectErrHelper(w http.ResponseWriter, typ string, descript } if err := tokenErr(w, typ, description, statusCode); err != nil { - s.logger.Errorf("introspect error response: %v", err) + s.logger.Error("introspect error response", "err", err) } } diff --git a/server/oauth2.go b/server/oauth2.go index 3589e493ea..3d9cfc8fe7 100644 --- a/server/oauth2.go +++ b/server/oauth2.go @@ -353,7 +353,7 @@ func genSubject(userID string, connID string) (string, error) { func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []string, nonce, accessToken, code, connID string) (idToken string, expiry time.Time, err error) { keys, err := s.storage.GetKeys() if err != nil { - s.logger.Errorf("Failed to get keys: %v", err) + s.logger.Error("failed to get keys", "err", err) return "", expiry, err } @@ -371,7 +371,7 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str subjectString, err := genSubject(claims.UserID, connID) if err != nil { - s.logger.Errorf("failed to marshal offline session ID: %v", err) + s.logger.Error("failed to marshal offline session ID", "err", err) return "", expiry, fmt.Errorf("failed to marshal offline session ID: %v", err) } @@ -386,7 +386,7 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str if accessToken != "" { atHash, err := accessTokenHash(signingAlg, accessToken) if err != nil { - s.logger.Errorf("error computing at_hash: %v", err) + s.logger.Error("error computing at_hash", "err", err) return "", expiry, fmt.Errorf("error computing at_hash: %v", err) } tok.AccessTokenHash = atHash @@ -395,7 +395,7 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str if code != "" { cHash, err := accessTokenHash(signingAlg, code) if err != nil { - s.logger.Errorf("error computing c_hash: %v", err) + s.logger.Error("error computing c_hash", "err", err) return "", expiry, fmt.Errorf("error computing c_hash: #{err}") } tok.CodeHash = cHash @@ -482,7 +482,7 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques if err == storage.ErrNotFound { return nil, newDisplayedErr(http.StatusNotFound, "Invalid client_id (%q).", clientID) } - s.logger.Errorf("Failed to get client: %v", err) + s.logger.Error("failed to get client", "err", err) return nil, newDisplayedErr(http.StatusInternalServerError, "Database error.") } @@ -501,7 +501,7 @@ func (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthReques if connectorID != "" { connectors, err := s.storage.ListConnectors() if err != nil { - s.logger.Errorf("Failed to list connectors: %v", err) + s.logger.Error("failed to list connectors", "err", err) return nil, newRedirectedErr(errServerError, "Unable to retrieve connectors") } if !validateConnectorID(connectors, connectorID) { @@ -637,7 +637,7 @@ func (s *Server) validateCrossClientTrust(clientID, peerID string) (trusted bool peer, err := s.storage.GetClient(peerID) if err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("Failed to get client: %v", err) + s.logger.Error("failed to get client", "err", err) return false, err } return false, nil diff --git a/server/refreshhandlers.go b/server/refreshhandlers.go index cb53802b87..01a0f435b6 100644 --- a/server/refreshhandlers.go +++ b/server/refreshhandlers.go @@ -87,7 +87,7 @@ func (s *Server) getRefreshTokenFromStorage(clientID *string, token *internal.Re refresh, err := s.storage.GetRefresh(token.RefreshId) if err != nil { if err != storage.ErrNotFound { - s.logger.Errorf("failed to get refresh token: %v", err) + s.logger.Error("failed to get refresh token", "err", err) return nil, newInternalServerError() } return nil, invalidErr @@ -95,7 +95,7 @@ func (s *Server) getRefreshTokenFromStorage(clientID *string, token *internal.Re // Only check ClientID if it was provided; if clientID != nil && (refresh.ClientID != *clientID) { - s.logger.Errorf("client %s trying to claim token for client %s", clientID, refresh.ClientID) + s.logger.Error("trying to claim token for different client", "client_id", clientID, "refresh_client_id", refresh.ClientID) // According to https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 Dex should respond with an // invalid grant error if token has already been claimed by another client. return nil, &refreshError{msg: errInvalidGrant, desc: invalidErr.desc, code: http.StatusBadRequest} @@ -108,18 +108,18 @@ func (s *Server) getRefreshTokenFromStorage(clientID *string, token *internal.Re case refresh.ObsoleteToken != token.Token: fallthrough case refresh.ObsoleteToken == "": - s.logger.Errorf("refresh token with id %s claimed twice", refresh.ID) + s.logger.Error("refresh token claimed twice", "token_id", refresh.ID) return nil, invalidErr } } if s.refreshTokenPolicy.CompletelyExpired(refresh.CreatedAt) { - s.logger.Errorf("refresh token with id %s expired", refresh.ID) + s.logger.Error("refresh token expired", "token_id", refresh.ID) return nil, expiredErr } if s.refreshTokenPolicy.ExpiredBecauseUnused(refresh.LastUsed) { - s.logger.Errorf("refresh token with id %s expired due to inactivity", refresh.ID) + s.logger.Error("refresh token expired due to inactivity", "token_id", refresh.ID) return nil, expiredErr } @@ -128,7 +128,7 @@ func (s *Server) getRefreshTokenFromStorage(clientID *string, token *internal.Re // Get Connector refreshCtx.connector, err = s.getConnector(refresh.ConnectorID) if err != nil { - s.logger.Errorf("connector with ID %q not found: %v", refresh.ConnectorID, err) + s.logger.Error("connector not found", "connector_id", refresh.ConnectorID, "err", err) return nil, newInternalServerError() } @@ -137,7 +137,7 @@ func (s *Server) getRefreshTokenFromStorage(clientID *string, token *internal.Re switch { case err != nil: if err != storage.ErrNotFound { - s.logger.Errorf("failed to get offline session: %v", err) + s.logger.Error("failed to get offline session", "err", err) return nil, newInternalServerError() } case len(refresh.ConnectorData) > 0: @@ -191,11 +191,11 @@ func (s *Server) refreshWithConnector(ctx context.Context, rCtx *refreshContext, if refreshConn, ok := rCtx.connector.Connector.(connector.RefreshConnector); ok { // Set connector data to the one received from an offline session ident.ConnectorData = rCtx.connectorData - s.logger.Debugf("connector data before refresh: %s", ident.ConnectorData) + s.logger.Debug("connector data before refresh", "connector_data", ident.ConnectorData) newIdent, err := refreshConn.Refresh(ctx, parseScopes(rCtx.scopes), ident) if err != nil { - s.logger.Errorf("failed to refresh identity: %v", err) + s.logger.Error("failed to refresh identity", "err", err) return ident, newInternalServerError() } @@ -216,7 +216,7 @@ func (s *Server) updateOfflineSession(refresh *storage.RefreshToken, ident conne old.ConnectorData = ident.ConnectorData } - s.logger.Debugf("saved connector data: %s %s", ident.UserID, ident.ConnectorData) + s.logger.Debug("saved connector data", "user_id", ident.UserID, "connector_data", ident.ConnectorData) return old, nil } @@ -225,7 +225,7 @@ func (s *Server) updateOfflineSession(refresh *storage.RefreshToken, ident conne // in offline session for the user. err := s.storage.UpdateOfflineSessions(refresh.Claims.UserID, refresh.ConnectorID, offlineSessionUpdater) if err != nil { - s.logger.Errorf("failed to update offline session: %v", err) + s.logger.Error("failed to update offline session", "err", err) return newInternalServerError() } @@ -316,7 +316,7 @@ func (s *Server) updateRefreshToken(ctx context.Context, rCtx *refreshContext) ( // Update refresh token in the storage. err := s.storage.UpdateRefreshToken(rCtx.storageToken.ID, refreshTokenUpdater) if err != nil { - s.logger.Errorf("failed to update refresh token: %v", err) + s.logger.Error("failed to update refresh token", "err", err) return nil, ident, newInternalServerError() } @@ -366,21 +366,21 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie accessToken, _, err := s.newAccessToken(client.ID, claims, rCtx.scopes, rCtx.storageToken.Nonce, rCtx.storageToken.ConnectorID) if err != nil { - s.logger.Errorf("failed to create new access token: %v", err) + s.logger.Error("failed to create new access token", "err", err) s.refreshTokenErrHelper(w, newInternalServerError()) return } idToken, expiry, err := s.newIDToken(client.ID, claims, rCtx.scopes, rCtx.storageToken.Nonce, accessToken, "", rCtx.storageToken.ConnectorID) if err != nil { - s.logger.Errorf("failed to create ID token: %v", err) + s.logger.Error("failed to create ID token", "err", err) s.refreshTokenErrHelper(w, newInternalServerError()) return } rawNewToken, err := internal.Marshal(newToken) if err != nil { - s.logger.Errorf("failed to marshal refresh token: %v", err) + s.logger.Error("failed to marshal refresh token", "err", err) s.refreshTokenErrHelper(w, newInternalServerError()) return } diff --git a/server/rotation.go b/server/rotation.go index 77a9926ee3..dfd776d677 100644 --- a/server/rotation.go +++ b/server/rotation.go @@ -8,11 +8,11 @@ import ( "errors" "fmt" "io" + "log/slog" "time" "github.com/go-jose/go-jose/v4" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" ) @@ -61,7 +61,7 @@ type keyRotator struct { strategy rotationStrategy now func() time.Time - logger log.Logger + logger *slog.Logger } // startKeyRotation begins key rotation in a new goroutine, closing once the context is canceled. @@ -74,9 +74,9 @@ func (s *Server) startKeyRotation(ctx context.Context, strategy rotationStrategy // Try to rotate immediately so properly configured storages will have keys. if err := rotator.rotate(); err != nil { if err == errAlreadyRotated { - s.logger.Infof("Key rotation not needed: %v", err) + s.logger.Info("key rotation not needed", "err", err) } else { - s.logger.Errorf("failed to rotate keys: %v", err) + s.logger.Error("failed to rotate keys", "err", err) } } @@ -87,7 +87,7 @@ func (s *Server) startKeyRotation(ctx context.Context, strategy rotationStrategy return case <-time.After(time.Second * 30): if err := rotator.rotate(); err != nil { - s.logger.Errorf("failed to rotate keys: %v", err) + s.logger.Error("failed to rotate keys", "err", err) } } } @@ -102,7 +102,7 @@ func (k keyRotator) rotate() error { if k.now().Before(keys.NextRotation) { return nil } - k.logger.Infof("keys expired, rotating") + k.logger.Info("keys expired, rotating") // Generate the key outside of a storage transaction. key, err := k.strategy.key() @@ -174,7 +174,7 @@ func (k keyRotator) rotate() error { if err != nil { return err } - k.logger.Infof("keys rotated, next rotation: %s", nextRotation) + k.logger.Info("keys rotated", "next_rotation", nextRotation) return nil } @@ -187,10 +187,10 @@ type RefreshTokenPolicy struct { now func() time.Time - logger log.Logger + logger *slog.Logger } -func NewRefreshTokenPolicy(logger log.Logger, rotation bool, validIfNotUsedFor, absoluteLifetime, reuseInterval string) (*RefreshTokenPolicy, error) { +func NewRefreshTokenPolicy(logger *slog.Logger, rotation bool, validIfNotUsedFor, absoluteLifetime, reuseInterval string) (*RefreshTokenPolicy, error) { r := RefreshTokenPolicy{now: time.Now, logger: logger} var err error @@ -199,7 +199,7 @@ func NewRefreshTokenPolicy(logger log.Logger, rotation bool, validIfNotUsedFor, if err != nil { return nil, fmt.Errorf("invalid config value %q for refresh token valid if not used for: %v", validIfNotUsedFor, err) } - logger.Infof("config refresh tokens valid if not used for: %v", validIfNotUsedFor) + logger.Info("config refresh tokens", "valid_if_not_used_for", validIfNotUsedFor) } if absoluteLifetime != "" { @@ -207,7 +207,7 @@ func NewRefreshTokenPolicy(logger log.Logger, rotation bool, validIfNotUsedFor, if err != nil { return nil, fmt.Errorf("invalid config value %q for refresh tokens absolute lifetime: %v", absoluteLifetime, err) } - logger.Infof("config refresh tokens absolute lifetime: %v", absoluteLifetime) + logger.Info("config refresh tokens", "absolute_lifetime", absoluteLifetime) } if reuseInterval != "" { @@ -215,11 +215,11 @@ func NewRefreshTokenPolicy(logger log.Logger, rotation bool, validIfNotUsedFor, if err != nil { return nil, fmt.Errorf("invalid config value %q for refresh tokens reuse interval: %v", reuseInterval, err) } - logger.Infof("config refresh tokens reuse interval: %v", reuseInterval) + logger.Info("config refresh tokens", "reuse_interval", reuseInterval) } r.rotateRefreshTokens = !rotation - logger.Infof("config refresh tokens rotation enabled: %v", r.rotateRefreshTokens) + logger.Info("config refresh tokens rotation", "enabled", r.rotateRefreshTokens) return &r, nil } diff --git a/server/rotation_test.go b/server/rotation_test.go index e279bf543e..1d0d2f100a 100644 --- a/server/rotation_test.go +++ b/server/rotation_test.go @@ -1,12 +1,12 @@ package server import ( - "os" + "io" + "log/slog" "sort" "testing" "time" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage" @@ -68,11 +68,7 @@ func TestKeyRotator(t *testing.T) { // Only the last 5 verification keys are expected to be kept around. maxVerificationKeys := 5 - l := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + l := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) r := &keyRotator{ Storage: memory.New(l), @@ -104,11 +100,7 @@ func TestKeyRotator(t *testing.T) { func TestRefreshTokenPolicy(t *testing.T) { lastTime := time.Now() - l := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + l := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) r, err := NewRefreshTokenPolicy(l, true, "1m", "1m", "1m") require.NoError(t, err) diff --git a/server/server.go b/server/server.go index dddbb137e9..5c1a97b896 100644 --- a/server/server.go +++ b/server/server.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io/fs" + "log/slog" "net/http" "net/url" "os" @@ -42,7 +43,6 @@ import ( "github.com/dexidp/dex/connector/oidc" "github.com/dexidp/dex/connector/openshift" "github.com/dexidp/dex/connector/saml" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/web" ) @@ -108,7 +108,7 @@ type Config struct { Web WebConfig - Logger log.Logger + Logger *slog.Logger PrometheusRegistry *prometheus.Registry @@ -189,7 +189,7 @@ type Server struct { refreshTokenPolicy *RefreshTokenPolicy - logger log.Logger + logger *slog.Logger } // NewServer constructs a server from the provided config. @@ -556,10 +556,11 @@ func (s *Server) startGarbageCollection(ctx context.Context, frequency time.Dura return case <-time.After(frequency): if r, err := s.storage.GarbageCollect(now()); err != nil { - s.logger.Errorf("garbage collection failed: %v", err) + s.logger.ErrorContext(ctx, "garbage collection failed", "err", err) } else if !r.IsEmpty() { - s.logger.Infof("garbage collection run, delete auth requests=%d, auth codes=%d, device requests=%d, device tokens=%d", - r.AuthRequests, r.AuthCodes, r.DeviceRequests, r.DeviceTokens) + s.logger.InfoContext(ctx, "garbage collection run, delete auth", + "requests", r.AuthRequests, "auth_codes", r.AuthCodes, + "device_requests", r.DeviceRequests, "device_tokens", r.DeviceTokens) } } } @@ -568,7 +569,7 @@ func (s *Server) startGarbageCollection(ctx context.Context, frequency time.Dura // ConnectorConfig is a configuration that can open a connector. type ConnectorConfig interface { - Open(id string, logger log.Logger) (connector.Connector, error) + Open(id string, logger *slog.Logger) (connector.Connector, error) } // ConnectorsConfig variable provides an easy way to return a config struct @@ -596,7 +597,7 @@ var ConnectorsConfig = map[string]func() ConnectorConfig{ } // openConnector will parse the connector config and open the connector. -func openConnector(logger log.Logger, conn storage.Connector) (connector.Connector, error) { +func openConnector(logger *slog.Logger, conn storage.Connector) (connector.Connector, error) { var c connector.Connector f, ok := ConnectorsConfig[conn.Type] diff --git a/server/server_test.go b/server/server_test.go index 25d1909dc2..8936c90a07 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -9,11 +9,11 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/http/httptest" "net/http/httputil" "net/url" - "os" "path" "reflect" "sort" @@ -26,7 +26,6 @@ import ( "github.com/go-jose/go-jose/v4" "github.com/kylelemons/godebug/pretty" "github.com/prometheus/client_golang/prometheus" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" "golang.org/x/oauth2" @@ -77,11 +76,7 @@ FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo= -----END RSA PRIVATE KEY-----`) -var logger = &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, -} +var logger = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Config)) (*httptest.Server, *Server) { var server *Server diff --git a/storage/ent/mysql.go b/storage/ent/mysql.go index 4a9407f95c..008f7bad33 100644 --- a/storage/ent/mysql.go +++ b/storage/ent/mysql.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "database/sql" "fmt" + "log/slog" "net" "os" "strconv" @@ -15,7 +16,6 @@ import ( entSQL "entgo.io/ent/dialect/sql" "github.com/go-sql-driver/mysql" // Register mysql driver. - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/client" "github.com/dexidp/dex/storage/ent/db" @@ -39,7 +39,7 @@ type MySQL struct { } // Open always returns a new in sqlite3 storage. -func (m *MySQL) Open(logger log.Logger) (storage.Storage, error) { +func (m *MySQL) Open(logger *slog.Logger) (storage.Storage, error) { logger.Debug("experimental ent-based storage driver is enabled") drv, err := m.driver() if err != nil { diff --git a/storage/ent/mysql_test.go b/storage/ent/mysql_test.go index 6c2dfa1ddf..f3e198aa72 100644 --- a/storage/ent/mysql_test.go +++ b/storage/ent/mysql_test.go @@ -1,11 +1,12 @@ package ent import ( + "io" + "log/slog" "os" "strconv" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage" @@ -39,11 +40,7 @@ func mysqlTestConfig(host string, port uint64) *MySQL { } func newMySQLStorage(host string, port uint64) storage.Storage { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) cfg := mysqlTestConfig(host, port) s, err := cfg.Open(logger) diff --git a/storage/ent/postgres.go b/storage/ent/postgres.go index ac091e702c..dad81df445 100644 --- a/storage/ent/postgres.go +++ b/storage/ent/postgres.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "database/sql" "fmt" + "log/slog" "net" "regexp" "strconv" @@ -14,13 +15,11 @@ import ( entSQL "entgo.io/ent/dialect/sql" _ "github.com/lib/pq" // Register postgres driver. - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/client" "github.com/dexidp/dex/storage/ent/db" ) -//nolint const ( // postgres SSL modes pgSSLDisable = "disable" @@ -37,7 +36,7 @@ type Postgres struct { } // Open always returns a new in sqlite3 storage. -func (p *Postgres) Open(logger log.Logger) (storage.Storage, error) { +func (p *Postgres) Open(logger *slog.Logger) (storage.Storage, error) { logger.Debug("experimental ent-based storage driver is enabled") drv, err := p.driver() if err != nil { diff --git a/storage/ent/postgres_test.go b/storage/ent/postgres_test.go index c8e3a54df2..baf0172bb0 100644 --- a/storage/ent/postgres_test.go +++ b/storage/ent/postgres_test.go @@ -1,11 +1,12 @@ package ent import ( + "io" + "log/slog" "os" "strconv" "testing" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage" @@ -36,11 +37,7 @@ func postgresTestConfig(host string, port uint64) *Postgres { } func newPostgresStorage(host string, port uint64) storage.Storage { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) cfg := postgresTestConfig(host, port) s, err := cfg.Open(logger) diff --git a/storage/ent/sqlite.go b/storage/ent/sqlite.go index c0b442f400..8c5287ef50 100644 --- a/storage/ent/sqlite.go +++ b/storage/ent/sqlite.go @@ -3,12 +3,12 @@ package ent import ( "context" "crypto/sha256" + "log/slog" "strings" "entgo.io/ent/dialect/sql" _ "github.com/mattn/go-sqlite3" // Register sqlite driver. - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/ent/client" "github.com/dexidp/dex/storage/ent/db" @@ -20,7 +20,7 @@ type SQLite3 struct { } // Open always returns a new in sqlite3 storage. -func (s *SQLite3) Open(logger log.Logger) (storage.Storage, error) { +func (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) { logger.Debug("experimental ent-based storage driver is enabled") // Implicitly set foreign_keys pragma to "on" because it is required by ent diff --git a/storage/ent/sqlite_test.go b/storage/ent/sqlite_test.go index 301d769b4a..d88097c225 100644 --- a/storage/ent/sqlite_test.go +++ b/storage/ent/sqlite_test.go @@ -1,21 +1,16 @@ package ent import ( - "os" + "io" + "log/slog" "testing" - "github.com/sirupsen/logrus" - "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) func newSQLiteStorage() storage.Storage { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) cfg := SQLite3{File: ":memory:"} s, err := cfg.Open(logger) diff --git a/storage/etcd/config.go b/storage/etcd/config.go index 7f1a7b4fb7..a8aee39aca 100644 --- a/storage/etcd/config.go +++ b/storage/etcd/config.go @@ -1,13 +1,13 @@ package etcd import ( + "log/slog" "time" "go.etcd.io/etcd/client/pkg/v3/transport" clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3/namespace" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" ) @@ -34,11 +34,11 @@ type Etcd struct { } // Open creates a new storage implementation backed by Etcd -func (p *Etcd) Open(logger log.Logger) (storage.Storage, error) { +func (p *Etcd) Open(logger *slog.Logger) (storage.Storage, error) { return p.open(logger) } -func (p *Etcd) open(logger log.Logger) (*conn, error) { +func (p *Etcd) open(logger *slog.Logger) (*conn, error) { cfg := clientv3.Config{ Endpoints: p.Endpoints, DialTimeout: defaultDialTimeout, diff --git a/storage/etcd/etcd.go b/storage/etcd/etcd.go index e4b24b4a4a..f65701ff1f 100644 --- a/storage/etcd/etcd.go +++ b/storage/etcd/etcd.go @@ -4,12 +4,12 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "strings" "time" clientv3 "go.etcd.io/etcd/client/v3" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" ) @@ -33,7 +33,7 @@ var _ storage.Storage = (*conn)(nil) type conn struct { db *clientv3.Client - logger log.Logger + logger *slog.Logger } func (c *conn) Close() error { @@ -52,7 +52,7 @@ func (c *conn) GarbageCollect(now time.Time) (result storage.GCResult, err error for _, authRequest := range authRequests { if now.After(authRequest.Expiry) { if err := c.deleteKey(ctx, keyID(authRequestPrefix, authRequest.ID)); err != nil { - c.logger.Errorf("failed to delete auth request: %v", err) + c.logger.Error("failed to delete auth request", "err", err) delErr = fmt.Errorf("failed to delete auth request: %v", err) } result.AuthRequests++ @@ -70,7 +70,7 @@ func (c *conn) GarbageCollect(now time.Time) (result storage.GCResult, err error for _, authCode := range authCodes { if now.After(authCode.Expiry) { if err := c.deleteKey(ctx, keyID(authCodePrefix, authCode.ID)); err != nil { - c.logger.Errorf("failed to delete auth code %v", err) + c.logger.Error("failed to delete auth code", "err", err) delErr = fmt.Errorf("failed to delete auth code: %v", err) } result.AuthCodes++ @@ -85,7 +85,7 @@ func (c *conn) GarbageCollect(now time.Time) (result storage.GCResult, err error for _, deviceRequest := range deviceRequests { if now.After(deviceRequest.Expiry) { if err := c.deleteKey(ctx, keyID(deviceRequestPrefix, deviceRequest.UserCode)); err != nil { - c.logger.Errorf("failed to delete device request %v", err) + c.logger.Error("failed to delete device request", "err", err) delErr = fmt.Errorf("failed to delete device request: %v", err) } result.DeviceRequests++ @@ -100,7 +100,7 @@ func (c *conn) GarbageCollect(now time.Time) (result storage.GCResult, err error for _, deviceToken := range deviceTokens { if now.After(deviceToken.Expiry) { if err := c.deleteKey(ctx, keyID(deviceTokenPrefix, deviceToken.DeviceCode)); err != nil { - c.logger.Errorf("failed to delete device token %v", err) + c.logger.Error("failed to delete device token", "err", err) delErr = fmt.Errorf("failed to delete device token: %v", err) } result.DeviceTokens++ diff --git a/storage/etcd/etcd_test.go b/storage/etcd/etcd_test.go index 8a9af5c9d5..5a568e8c3f 100644 --- a/storage/etcd/etcd_test.go +++ b/storage/etcd/etcd_test.go @@ -3,13 +3,14 @@ package etcd import ( "context" "fmt" + "io" + "log/slog" "os" "runtime" "strings" "testing" "time" - "github.com/sirupsen/logrus" clientv3 "go.etcd.io/etcd/client/v3" "github.com/dexidp/dex/storage" @@ -55,11 +56,7 @@ func cleanDB(c *conn) error { return nil } -var logger = &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, -} +var logger = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) func TestEtcd(t *testing.T) { testEtcdEnv := "DEX_ETCD_ENDPOINTS" diff --git a/storage/kubernetes/client.go b/storage/kubernetes/client.go index af0a2338fa..1a1653b345 100644 --- a/storage/kubernetes/client.go +++ b/storage/kubernetes/client.go @@ -13,6 +13,7 @@ import ( "hash" "hash/fnv" "io" + "log/slog" "net" "net/http" "net/url" @@ -27,7 +28,6 @@ import ( "github.com/ghodss/yaml" "golang.org/x/net/http2" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) @@ -36,7 +36,7 @@ type client struct { client *http.Client baseURL string namespace string - logger log.Logger + logger *slog.Logger // Hash function to map IDs (which could span a large range) to Kubernetes names. // While this is not currently upgradable, it could be in the future. @@ -268,7 +268,7 @@ func (cli *client) detectKubernetesVersion() error { clusterVersion, err := semver.NewVersion(version.GitVersion) if err != nil { - cli.logger.Warnf("cannot detect Kubernetes version (%s): %v", clusterVersion, err) + cli.logger.Warn("cannot detect Kubernetes version", "version", clusterVersion, "err", err) return nil } @@ -358,7 +358,7 @@ func defaultTLSConfig() *tls.Config { } } -func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger log.Logger, inCluster bool) (*client, error) { +func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger *slog.Logger, inCluster bool) (*client, error) { tlsConfig := defaultTLSConfig() data := func(b string, file string) ([]byte, error) { if b != "" { @@ -418,7 +418,7 @@ func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, l apiVersion := "dex.coreos.com/v1" - logger.Infof("kubernetes client apiVersion = %s", apiVersion) + logger.Info("kubernetes client", "api_version", apiVersion) return &client{ client: &http.Client{ Transport: t, diff --git a/storage/kubernetes/client_test.go b/storage/kubernetes/client_test.go index cfd04857b6..c8fc8db11b 100644 --- a/storage/kubernetes/client_test.go +++ b/storage/kubernetes/client_test.go @@ -3,6 +3,8 @@ package kubernetes import ( "hash" "hash/fnv" + "io" + "log/slog" "net/http" "os" "path/filepath" @@ -10,7 +12,6 @@ import ( "testing" "time" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/dexidp/dex/storage/kubernetes/k8sapi" @@ -52,11 +53,7 @@ func TestOfflineTokenName(t *testing.T) { } func TestInClusterTransport(t *testing.T) { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) user := k8sapi.AuthInfo{Token: "abc"} cli, err := newClient( diff --git a/storage/kubernetes/lock.go b/storage/kubernetes/lock.go index 12075e81db..c67380dcc0 100644 --- a/storage/kubernetes/lock.go +++ b/storage/kubernetes/lock.go @@ -53,14 +53,14 @@ func (l *refreshTokenLock) Unlock(id string) { r, err := l.cli.getRefreshToken(id) if err != nil { - l.cli.logger.Debugf("failed to get resource to release lock for refresh token %s: %v", id, err) + l.cli.logger.Debug("failed to get resource to release lock for refresh token", "token_id", id, "err", err) return } r.Annotations = nil err = l.cli.put(resourceRefreshToken, r.ObjectMeta.Name, r) if err != nil { - l.cli.logger.Debugf("failed to release lock for refresh token %s: %v", id, err) + l.cli.logger.Debug("failed to release lock for refresh token", "token_id", id, "err", err) } } @@ -114,7 +114,7 @@ func (l *refreshTokenLock) setLockAnnotation(id string) (bool, error) { return false, nil } - l.cli.logger.Debugf("break lock annotation error: %v", err) + l.cli.logger.Debug("break lock annotation", "error", err) if isKubernetesAPIConflictError(err) { l.waitingState = true // after breaking error waiting for the lock to be released diff --git a/storage/kubernetes/storage.go b/storage/kubernetes/storage.go index c08362b8eb..8b6d5c9c2e 100644 --- a/storage/kubernetes/storage.go +++ b/storage/kubernetes/storage.go @@ -4,12 +4,12 @@ import ( "context" "errors" "fmt" + "log/slog" "math/rand" "net/http" "strings" "time" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) @@ -53,7 +53,7 @@ type Config struct { } // Open returns a storage using Kubernetes third party resource. -func (c *Config) Open(logger log.Logger) (storage.Storage, error) { +func (c *Config) Open(logger *slog.Logger) (storage.Storage, error) { cli, err := c.open(logger, false) if err != nil { return nil, err @@ -66,7 +66,7 @@ func (c *Config) Open(logger log.Logger) (storage.Storage, error) { // // waitForResources controls if errors creating the resources cause this method to return // immediately (used during testing), or if the client will asynchronously retry. -func (c *Config) open(logger log.Logger, waitForResources bool) (*client, error) { +func (c *Config) open(logger *slog.Logger, waitForResources bool) (*client, error) { if c.InCluster && (c.KubeConfigFile != "") { return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigFile'") } @@ -155,12 +155,12 @@ func (cli *client) registerCustomResources() (ok bool) { r := definitions[i] var i interface{} - cli.logger.Infof("checking if custom resource %s has already been created...", r.ObjectMeta.Name) + cli.logger.Info("checking if custom resource has already been created...", "object", r.ObjectMeta.Name) if err := cli.list(r.Spec.Names.Plural, &i); err == nil { - cli.logger.Infof("The custom resource %s already available, skipping create", r.ObjectMeta.Name) + cli.logger.Info("the custom resource already available, skipping create", "object", r.ObjectMeta.Name) continue } else { - cli.logger.Infof("failed to list custom resource %s, attempting to create: %v", r.ObjectMeta.Name, err) + cli.logger.Info("failed to list custom resource, attempting to create", "object", r.ObjectMeta.Name, "err", err) } err = cli.postResource(cli.crdAPIVersion, "", "customresourcedefinitions", r) @@ -169,17 +169,17 @@ func (cli *client) registerCustomResources() (ok bool) { if err != nil { switch err { case storage.ErrAlreadyExists: - cli.logger.Infof("custom resource already created %s", resourceName) + cli.logger.Info("custom resource already created", "object", resourceName) case storage.ErrNotFound: - cli.logger.Errorf("custom resources not found, please enable the respective API group") + cli.logger.Error("custom resources not found, please enable the respective API group") ok = false default: - cli.logger.Errorf("creating custom resource %s: %v", resourceName, err) + cli.logger.Error("creating custom resource", "object", resourceName, "err", err) ok = false } continue } - cli.logger.Errorf("create custom resource %s", resourceName) + cli.logger.Error("create custom resource", "object", resourceName) } return ok } @@ -197,7 +197,7 @@ func (cli *client) waitForCRDs(ctx context.Context) error { break } - cli.logger.Errorf("checking CRD: %v", err) + cli.logger.ErrorContext(ctx, "checking CRD", "err", err) select { case <-ctx.Done(): @@ -556,7 +556,7 @@ func (cli *client) UpdateKeys(updater func(old storage.Keys) (storage.Keys, erro err = cli.post(resourceKeys, newKeys) if err != nil && errors.Is(err, storage.ErrAlreadyExists) { // We need to tolerate conflicts here in case of HA mode. - cli.logger.Debugf("Keys creation failed: %v. It is possible that keys have already been created by another dex instance.", err) + cli.logger.Debug("Keys creation failed. It is possible that keys have already been created by another dex instance.", "err", err) return errors.New("keys already created by another server instance") } @@ -569,7 +569,7 @@ func (cli *client) UpdateKeys(updater func(old storage.Keys) (storage.Keys, erro if isKubernetesAPIConflictError(err) { // We need to tolerate conflicts here in case of HA mode. // Dex instances run keys rotation at the same time because they use SigningKey.nextRotation CR field as a trigger. - cli.logger.Debugf("Keys rotation failed: %v. It is possible that keys have already been rotated by another dex instance.", err) + cli.logger.Debug("Keys rotation failed. It is possible that keys have already been rotated by another dex instance.", "err", err) return errors.New("keys already rotated by another server instance") } @@ -622,7 +622,7 @@ func (cli *client) GarbageCollect(now time.Time) (result storage.GCResult, err e for _, authRequest := range authRequests.AuthRequests { if now.After(authRequest.Expiry) { if err := cli.delete(resourceAuthRequest, authRequest.ObjectMeta.Name); err != nil { - cli.logger.Errorf("failed to delete auth request: %v", err) + cli.logger.Error("failed to delete auth request", "err", err) delErr = fmt.Errorf("failed to delete auth request: %v", err) } result.AuthRequests++ @@ -640,7 +640,7 @@ func (cli *client) GarbageCollect(now time.Time) (result storage.GCResult, err e for _, authCode := range authCodes.AuthCodes { if now.After(authCode.Expiry) { if err := cli.delete(resourceAuthCode, authCode.ObjectMeta.Name); err != nil { - cli.logger.Errorf("failed to delete auth code %v", err) + cli.logger.Error("failed to delete auth code", "err", err) delErr = fmt.Errorf("failed to delete auth code: %v", err) } result.AuthCodes++ @@ -655,7 +655,7 @@ func (cli *client) GarbageCollect(now time.Time) (result storage.GCResult, err e for _, deviceRequest := range deviceRequests.DeviceRequests { if now.After(deviceRequest.Expiry) { if err := cli.delete(resourceDeviceRequest, deviceRequest.ObjectMeta.Name); err != nil { - cli.logger.Errorf("failed to delete device request: %v", err) + cli.logger.Error("failed to delete device request", "err", err) delErr = fmt.Errorf("failed to delete device request: %v", err) } result.DeviceRequests++ @@ -670,7 +670,7 @@ func (cli *client) GarbageCollect(now time.Time) (result storage.GCResult, err e for _, deviceToken := range deviceTokens.DeviceTokens { if now.After(deviceToken.Expiry) { if err := cli.delete(resourceDeviceToken, deviceToken.ObjectMeta.Name); err != nil { - cli.logger.Errorf("failed to delete device token: %v", err) + cli.logger.Error("failed to delete device token", "err", err) delErr = fmt.Errorf("failed to delete device token: %v", err) } result.DeviceTokens++ diff --git a/storage/kubernetes/storage_test.go b/storage/kubernetes/storage_test.go index b4b42688e2..d8bfd1f689 100644 --- a/storage/kubernetes/storage_test.go +++ b/storage/kubernetes/storage_test.go @@ -5,6 +5,8 @@ import ( "crypto/tls" "errors" "fmt" + "io" + "log/slog" "net/http" "net/http/httptest" "os" @@ -13,7 +15,6 @@ import ( "testing" "time" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -57,11 +58,7 @@ func (s *StorageTestSuite) SetupTest() { KubeConfigFile: kubeconfigPath, } - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) kubeClient, err := config.open(logger, true) s.Require().NoError(err) @@ -253,11 +250,7 @@ func newStatusCodesResponseTestClient(getResponseCode, actionResponseCode int) * return &client{ client: &http.Client{Transport: tr}, baseURL: s.URL, - logger: &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - }, + logger: slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})), } } @@ -314,11 +307,7 @@ func TestRefreshTokenLock(t *testing.T) { KubeConfigFile: kubeconfigPath, } - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) kubeClient, err := config.open(logger, true) require.NoError(t, err) diff --git a/storage/memory/memory.go b/storage/memory/memory.go index 8e080c9faa..4399c61df1 100644 --- a/storage/memory/memory.go +++ b/storage/memory/memory.go @@ -3,18 +3,18 @@ package memory import ( "context" + "log/slog" "strings" "sync" "time" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" ) var _ storage.Storage = (*memStorage)(nil) // New returns an in memory storage. -func New(logger log.Logger) storage.Storage { +func New(logger *slog.Logger) storage.Storage { return &memStorage{ clients: make(map[string]storage.Client), authCodes: make(map[string]storage.AuthCode), @@ -36,7 +36,7 @@ type Config struct { // The in memory implementation has no config. } // Open always returns a new in memory storage. -func (c *Config) Open(logger log.Logger) (storage.Storage, error) { +func (c *Config) Open(logger *slog.Logger) (storage.Storage, error) { return New(logger), nil } @@ -55,7 +55,7 @@ type memStorage struct { keys storage.Keys - logger log.Logger + logger *slog.Logger } type offlineSessionID struct { diff --git a/storage/memory/memory_test.go b/storage/memory/memory_test.go index 84a8826ef2..75a17ac62c 100644 --- a/storage/memory/memory_test.go +++ b/storage/memory/memory_test.go @@ -1,21 +1,16 @@ package memory import ( - "os" + "io" + "log/slog" "testing" - "github.com/sirupsen/logrus" - "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) func TestStorage(t *testing.T) { - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) newStorage := func() storage.Storage { return New(logger) diff --git a/storage/memory/static_test.go b/storage/memory/static_test.go index 4be23a1e6a..b913874231 100644 --- a/storage/memory/static_test.go +++ b/storage/memory/static_test.go @@ -3,22 +3,17 @@ package memory import ( "context" "fmt" - "os" + "io" + "log/slog" "strings" "testing" - "github.com/sirupsen/logrus" - "github.com/dexidp/dex/storage" ) func TestStaticClients(t *testing.T) { ctx := context.Background() - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) backing := New(logger) c1 := storage.Client{ID: "foo", Secret: "foo_secret"} @@ -102,11 +97,7 @@ func TestStaticClients(t *testing.T) { func TestStaticPasswords(t *testing.T) { ctx := context.Background() - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) backing := New(logger) p1 := storage.Password{Email: "foo@example.com", Username: "foo_secret"} @@ -215,11 +206,7 @@ func TestStaticPasswords(t *testing.T) { func TestStaticConnectors(t *testing.T) { ctx := context.Background() - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) backing := New(logger) config1 := []byte(`{"issuer": "https://accounts.google.com"}`) diff --git a/storage/sql/config.go b/storage/sql/config.go index 8b78242563..5379aeb6b2 100644 --- a/storage/sql/config.go +++ b/storage/sql/config.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "database/sql" "fmt" + "log/slog" "net" "os" "regexp" @@ -15,7 +16,6 @@ import ( "github.com/go-sql-driver/mysql" "github.com/lib/pq" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" ) @@ -31,7 +31,6 @@ const ( mysqlErrUnknownSysVar = 1193 ) -//nolint const ( // postgres SSL modes pgSSLDisable = "disable" @@ -40,7 +39,6 @@ const ( pgSSLVerifyFull = "verify-full" ) -//nolint const ( // MySQL SSL modes mysqlSSLTrue = "true" @@ -84,7 +82,7 @@ type Postgres struct { } // Open creates a new storage implementation backed by Postgres. -func (p *Postgres) Open(logger log.Logger) (storage.Storage, error) { +func (p *Postgres) Open(logger *slog.Logger) (storage.Storage, error) { conn, err := p.open(logger) if err != nil { return nil, err @@ -164,7 +162,7 @@ func (p *Postgres) createDataSourceName() string { return strings.Join(parameters, " ") } -func (p *Postgres) open(logger log.Logger) (*conn, error) { +func (p *Postgres) open(logger *slog.Logger) (*conn, error) { dataSourceName := p.createDataSourceName() db, err := sql.Open("postgres", dataSourceName) @@ -216,7 +214,7 @@ type MySQL struct { } // Open creates a new storage implementation backed by MySQL. -func (s *MySQL) Open(logger log.Logger) (storage.Storage, error) { +func (s *MySQL) Open(logger *slog.Logger) (storage.Storage, error) { conn, err := s.open(logger) if err != nil { return nil, err @@ -224,7 +222,7 @@ func (s *MySQL) Open(logger log.Logger) (storage.Storage, error) { return conn, nil } -func (s *MySQL) open(logger log.Logger) (*conn, error) { +func (s *MySQL) open(logger *slog.Logger) (*conn, error) { cfg := mysql.Config{ User: s.User, Passwd: s.Password, diff --git a/storage/sql/config_test.go b/storage/sql/config_test.go index 1178728c1a..b1037e64e9 100644 --- a/storage/sql/config_test.go +++ b/storage/sql/config_test.go @@ -2,15 +2,14 @@ package sql import ( "fmt" + "io" + "log/slog" "os" "runtime" "strconv" "testing" "time" - "github.com/sirupsen/logrus" - - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/conformance" ) @@ -48,14 +47,10 @@ func cleanDB(c *conn) error { return nil } -var logger = &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, -} +var logger = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) type opener interface { - open(logger log.Logger) (*conn, error) + open(logger *slog.Logger) (*conn, error) } func testDB(t *testing.T, o opener, withTransactions bool) { diff --git a/storage/sql/migrate_test.go b/storage/sql/migrate_test.go index 4b77eb2fbe..bf9f0ddb4d 100644 --- a/storage/sql/migrate_test.go +++ b/storage/sql/migrate_test.go @@ -5,11 +5,11 @@ package sql import ( "database/sql" - "os" + "io" + "log/slog" "testing" sqlite3 "github.com/mattn/go-sqlite3" - "github.com/sirupsen/logrus" ) func TestMigrate(t *testing.T) { @@ -19,11 +19,7 @@ func TestMigrate(t *testing.T) { } defer db.Close() - logger := &logrus.Logger{ - Out: os.Stderr, - Formatter: &logrus.TextFormatter{DisableColors: true}, - Level: logrus.DebugLevel, - } + logger := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})) errCheck := func(err error) bool { sqlErr, ok := err.(sqlite3.Error) diff --git a/storage/sql/sql.go b/storage/sql/sql.go index 0a29216936..d671021fca 100644 --- a/storage/sql/sql.go +++ b/storage/sql/sql.go @@ -3,14 +3,13 @@ package sql import ( "database/sql" + "log/slog" "regexp" "time" // import third party drivers _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" - - "github.com/dexidp/dex/pkg/log" ) // flavor represents a specific SQL implementation, and is used to translate query strings @@ -131,7 +130,7 @@ func (c *conn) translateArgs(args []interface{}) []interface{} { type conn struct { db *sql.DB flavor *flavor - logger log.Logger + logger *slog.Logger alreadyExistsCheck func(err error) bool } diff --git a/storage/sql/sqlite.go b/storage/sql/sqlite.go index 43df671a7c..2d29e607dc 100644 --- a/storage/sql/sqlite.go +++ b/storage/sql/sqlite.go @@ -6,10 +6,10 @@ package sql import ( "database/sql" "fmt" + "log/slog" sqlite3 "github.com/mattn/go-sqlite3" - "github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/storage" ) @@ -20,7 +20,7 @@ type SQLite3 struct { } // Open creates a new storage implementation backed by SQLite3 -func (s *SQLite3) Open(logger log.Logger) (storage.Storage, error) { +func (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) { conn, err := s.open(logger) if err != nil { return nil, err @@ -28,7 +28,7 @@ func (s *SQLite3) Open(logger log.Logger) (storage.Storage, error) { return conn, nil } -func (s *SQLite3) open(logger log.Logger) (*conn, error) { +func (s *SQLite3) open(logger *slog.Logger) (*conn, error) { db, err := sql.Open("sqlite3", s.File) if err != nil { return nil, err diff --git a/storage/static.go b/storage/static.go index e8902b9b59..ca04937acf 100644 --- a/storage/static.go +++ b/storage/static.go @@ -3,9 +3,8 @@ package storage import ( "context" "errors" + "log/slog" "strings" - - "github.com/dexidp/dex/pkg/log" ) // Tests for this code are in the "memory" package, since this package doesn't @@ -90,17 +89,17 @@ type staticPasswordsStorage struct { // A map of passwords that is indexed by lower-case email ids passwordsByEmail map[string]Password - logger log.Logger + logger *slog.Logger } // WithStaticPasswords returns a storage with a read-only set of passwords. -func WithStaticPasswords(s Storage, staticPasswords []Password, logger log.Logger) Storage { +func WithStaticPasswords(s Storage, staticPasswords []Password, logger *slog.Logger) Storage { passwordsByEmail := make(map[string]Password, len(staticPasswords)) for _, p := range staticPasswords { // Enable case insensitive email comparison. lowerEmail := strings.ToLower(p.Email) if _, ok := passwordsByEmail[lowerEmail]; ok { - logger.Errorf("Attempting to create StaticPasswords with the same email id: %s", p.Email) + logger.Error("attempting to create StaticPasswords with the same email id", "email", p.Email) } passwordsByEmail[lowerEmail] = p }