From a78013d53a3afe86c9285f06f8b6ac2979f9853e Mon Sep 17 00:00:00 2001 From: Jelle Vink Date: Mon, 18 May 2026 19:09:51 -0700 Subject: [PATCH] Transition from zerolog to log/slog and bump go.skymeyer.dev/pkg --- backend/auth.go | 30 +++++++------- backend/config.go | 22 +++++----- backend/handlers.go | 8 ++-- backend/logger.go | 99 ++++++++++++++++++++++++++++++--------------- backend/server.go | 18 +++++---- backend/store.go | 57 +++++++++++++------------- cmd/root.go | 17 ++++---- cmd/server.go | 16 ++++---- go.mod | 5 +-- go.sum | 11 +---- 10 files changed, 153 insertions(+), 130 deletions(-) diff --git a/backend/auth.go b/backend/auth.go index acfa178..b54ae7d 100644 --- a/backend/auth.go +++ b/backend/auth.go @@ -15,7 +15,7 @@ import ( "context" "github.com/coreos/go-oidc/v3/oidc" - "github.com/rs/zerolog/log" + "log/slog" "golang.org/x/oauth2" "golang.org/x/oauth2/google" ) @@ -121,27 +121,27 @@ func AuthLoginHandler(w http.ResponseWriter, r *http.Request) { func AuthCallbackHandler(w http.ResponseWriter, r *http.Request) { oauthState, err := r.Cookie("ots_oauth_state") if err != nil { - log.Warn().Err(err).Msg("OAuth state cookie missing") + slog.Warn("OAuth state cookie missing", "error", err) http.Redirect(w, r, "/?error=oauth_state_missing", http.StatusTemporaryRedirect) return } oauthVerifier, err := r.Cookie("ots_oauth_verifier") if err != nil { - log.Warn().Err(err).Msg("OAuth verifier cookie missing") + slog.Warn("OAuth verifier cookie missing", "error", err) http.Redirect(w, r, "/?error=oauth_verifier_missing", http.StatusTemporaryRedirect) return } oauthNonce, err := r.Cookie("ots_oauth_nonce") if err != nil { - log.Warn().Err(err).Msg("OAuth nonce cookie missing") + slog.Warn("OAuth nonce cookie missing", "error", err) http.Redirect(w, r, "/?error=oauth_nonce_missing", http.StatusTemporaryRedirect) return } if r.FormValue("state") != oauthState.Value { - log.Warn().Msg("Invalid OAuth state") + slog.Warn("Invalid OAuth state") http.Redirect(w, r, "/?error=invalid_oauth_state", http.StatusTemporaryRedirect) return } @@ -179,22 +179,22 @@ func AuthCallbackHandler(w http.ResponseWriter, r *http.Request) { token, err := getOAuthConfig().Exchange(r.Context(), code, oauth2.SetAuthURLParam("code_verifier", oauthVerifier.Value)) if err != nil { - log.Error().Err(err).Msg("OAuth exchange failed") + slog.Error("OAuth exchange failed", "error", err) http.Error(w, "auth failed", http.StatusInternalServerError) return } rawIDToken, ok := token.Extra("id_token").(string) if !ok { - log.Error().Msg("No id_token found in token response") + slog.Error("No id_token found in token response") http.Error(w, "auth failed", http.StatusInternalServerError) return } - log.Debug().Str("id_token", rawIDToken).Msg("id token received") + slog.Debug("id token received", "id_token", rawIDToken) provider, err := getOIDCProvider(r.Context()) if err != nil { - log.Error().Err(err).Msg("Failed initializing OIDC provider") + slog.Error("Failed initializing OIDC provider", "error", err) http.Error(w, "internal error", http.StatusInternalServerError) return } @@ -202,26 +202,26 @@ func AuthCallbackHandler(w http.ResponseWriter, r *http.Request) { verifier := provider.Verifier(&oidc.Config{ClientID: AppConfig.GoogleClientID}) idToken, err := verifier.Verify(r.Context(), rawIDToken) if err != nil { - log.Error().Err(err).Msg("Failed to verify ID token") + slog.Error("Failed to verify ID token", "error", err) http.Error(w, "auth failed", http.StatusInternalServerError) return } if idToken.Nonce != oauthNonce.Value { - log.Warn().Msg("Invalid nonce in ID token") + slog.Warn("Invalid nonce in ID token") http.Error(w, "auth failed", http.StatusForbidden) return } if err := idToken.VerifyAccessToken(token.AccessToken); err != nil { - log.Error().Err(err).Msg("Failed to verify access token hash") + slog.Error("Failed to verify access token hash", "error", err) http.Error(w, "auth failed", http.StatusInternalServerError) return } var ui UserInfo if err := idToken.Claims(&ui); err != nil { - log.Error().Err(err).Msg("Failed parsing id_token claims") + slog.Error("Failed parsing id_token claims", "error", err) http.Error(w, "auth failed", http.StatusInternalServerError) return } @@ -248,7 +248,7 @@ func handleUserLogin(w http.ResponseWriter, r *http.Request, u *UserInfo) { val, err := signSession(sess) if err != nil { - log.Error().Err(err).Msg("Failed signing session") + slog.Error("Failed signing session", "error", err) http.Error(w, "internal error", http.StatusInternalServerError) return } @@ -263,7 +263,7 @@ func handleUserLogin(w http.ResponseWriter, r *http.Request, u *UserInfo) { SameSite: http.SameSiteLaxMode, }) - log.Info().Str("user", u.ID).Msg("User logged in") + slog.Info("User logged in", "user", u.ID) http.Redirect(w, r, "/", http.StatusSeeOther) } diff --git a/backend/config.go b/backend/config.go index 68646c5..0c49243 100644 --- a/backend/config.go +++ b/backend/config.go @@ -4,7 +4,7 @@ import ( "strings" "time" - "github.com/rs/zerolog/log" + "log/slog" "github.com/thomaspoignant/go-feature-flag/ffcontext" ) @@ -71,7 +71,7 @@ func (c *Config) IsAllowed(ui *UserInfo) bool { // Check allowed domains for _, ad := range c.AllowedDomains { if strings.EqualFold(ad, ui.Domain()) { - log.Debug().Str("user", ui.ID).Str("domain", ui.Domain()).Msg("user allowed via domain") + slog.Debug("user allowed via domain", "user", ui.ID, "domain", ui.Domain()) return true } } @@ -79,13 +79,13 @@ func (c *Config) IsAllowed(ui *UserInfo) bool { // Check allowed emails for _, ae := range c.AllowedEmails { if strings.EqualFold(ae, ui.Email) { - log.Debug().Str("user", ui.ID).Msg("user allowed via email") + slog.Debug("user allowed via email", "user", ui.ID) return true } } // If both slices are empty, effectively everyone is blocked. - log.Warn().Str("user", ui.ID).Msg("unauthorized login attempt") + slog.Warn("unauthorized login attempt", "user", ui.ID) return false } @@ -103,34 +103,34 @@ func (c *Config) FeatureFlagAuthz(ui *UserInfo) bool { // Check if user is blocked blocked, err := ffs.Client().BoolVariation(c.FFBlockEmails, ctx, false) if err != nil { - log.Error().Err(err).Msg("fflags: failed to get blocked users") + slog.Error("fflags: failed to get blocked users", "error", err) } if blocked { - log.Warn().Str("user", ui.ID).Msg("fflags: blocked user login attempt") + slog.Warn("fflags: blocked user login attempt", "user", ui.ID) return false } // Authorize domains allowed, err = ffs.Client().BoolVariation(c.FFAuthzDomains, ctx, false) if err != nil { - log.Error().Err(err).Msg("fflags: failed to get allowed domains") + slog.Error("fflags: failed to get allowed domains", "error", err) } if allowed { - log.Debug().Str("user", ui.ID).Str("domain", ui.Domain()).Msg("fflags: user allowed via domain") + slog.Debug("fflags: user allowed via domain", "user", ui.ID, "domain", ui.Domain()) return true } // Authorize individual users allowed, err = ffs.Client().BoolVariation(c.FFAuthzEmails, ctx, false) if err != nil { - log.Error().Err(err).Msg("fflags: failed to get allowed users") + slog.Error("fflags: failed to get allowed users", "error", err) } if allowed { - log.Debug().Str("user", ui.ID).Msg("fflags: user allowed via email") + slog.Debug("fflags: user allowed via email", "user", ui.ID) return true } - log.Warn().Str("user", ui.ID).Msg("fflags: unauthorized login attempt") + slog.Warn("fflags: unauthorized login attempt", "user", ui.ID) return false } diff --git a/backend/handlers.go b/backend/handlers.go index 2b3fe44..b768461 100644 --- a/backend/handlers.go +++ b/backend/handlers.go @@ -7,7 +7,7 @@ import ( "net/http" "github.com/go-chi/chi/v5" - "github.com/rs/zerolog/log" + "log/slog" ) type SecretRequest struct { @@ -29,7 +29,7 @@ func AuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session, err := GetSession(r) if err != nil { - log.Warn().Err(err).Msg("Unauthorized access attempt") + slog.Warn("Unauthorized access attempt", "error", err) http.Error(w, "unauthorized", http.StatusUnauthorized) return } @@ -55,14 +55,14 @@ func CreateSecretHandler(w http.ResponseWriter, r *http.Request) { } if len(req.Secret) > AppConfig.MaxSecretLength { - log.Warn().Int("length", len(req.Secret)).Msg("Secret maximum length exceeded") + slog.Warn("Secret maximum length exceeded", "length", len(req.Secret)) http.Error(w, fmt.Sprintf("secret exceeded maximum length of %d", AppConfig.MaxSecretLength), http.StatusBadRequest) return } id, err := GlobalStore.StoreSecret(r.Context(), req.Secret, req.TTLHours) if err != nil { - log.Error().Err(err).Msg("Failed storing secret") + slog.Error("Failed storing secret", "error", err) http.Error(w, "internal server error", http.StatusInternalServerError) return } diff --git a/backend/logger.go b/backend/logger.go index 5ea5e3d..00801be 100644 --- a/backend/logger.go +++ b/backend/logger.go @@ -1,44 +1,77 @@ package backend import ( - "io" + "log/slog" "net/http" "os" + "strings" "time" "github.com/go-chi/chi/v5/middleware" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" ) -// InitLogger initializes the global zerolog instance with GCP-friendly configurations. +// InitLogger initializes the global slog instance with GCP-friendly configurations. func InitLogger(levelStr string, dev bool) { - // GCP expects the severity field instead of level - zerolog.LevelFieldName = "severity" - // GCP expects timestamp field to be timestamp - zerolog.TimestampFieldName = "timestamp" - // Parse configured log level securely - level, err := zerolog.ParseLevel(levelStr) - if err != nil { - level = zerolog.InfoLevel + var level slog.Level + switch strings.ToLower(levelStr) { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warn", "warning": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + level = slog.LevelInfo } - zerolog.SetGlobalLevel(level) - - // In a real GCP environment without a TTY, outputting to os.Stdout directly in JSON format is preferred. - // Since chi operates on a request basis, we'll setup the global logger. - var writer io.Writer + var handler slog.Handler if dev { - writer = zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: "15:04:05"} + // Local console-friendly format + handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.TimeKey { + a.Value = slog.StringValue(a.Value.Time().Format("15:04:05")) + } + return a + }, + }) } else { - writer = os.Stdout + // GCP Cloud Run JSON-friendly format + handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: level, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.LevelKey { + a.Key = "severity" + lvl := a.Value.Any().(slog.Level) + switch lvl { + case slog.LevelDebug: + a.Value = slog.StringValue("DEBUG") + case slog.LevelInfo: + a.Value = slog.StringValue("INFO") + case slog.LevelWarn: + a.Value = slog.StringValue("WARNING") + case slog.LevelError: + a.Value = slog.StringValue("ERROR") + default: + a.Value = slog.StringValue(lvl.String()) + } + } else if a.Key == slog.TimeKey { + a.Key = "timestamp" + } + return a + }, + }) } - log.Logger = zerolog.New(writer).With().Timestamp().Logger() + + slog.SetDefault(slog.New(handler)) } -// LoggerMiddleware is a custom chi-middleware that bridges chi requests to zerolog -func LoggerMiddleware(logger *zerolog.Logger) func(next http.Handler) http.Handler { +// LoggerMiddleware is a custom chi-middleware that bridges chi requests to slog +func LoggerMiddleware(logger *slog.Logger) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() @@ -52,21 +85,21 @@ func LoggerMiddleware(logger *zerolog.Logger) func(next http.Handler) http.Handl latency := time.Since(start) // Log using GCP friendly format - event := logger.Info() + var level slog.Level = slog.LevelInfo if ww.Status() >= 500 { - event = logger.Error() + level = slog.LevelError } else if ww.Status() >= 400 { - event = logger.Warn() + level = slog.LevelWarn } - event. - Str("method", r.Method). - Str("path", r.URL.Path). - Str("remote_ip", r.RemoteAddr). - Str("user_agent", r.UserAgent()). - Int("status", ww.Status()). - Dur("latency", latency). - Msg("HTTP Request") + logger.Log(r.Context(), level, "HTTP Request", + "method", r.Method, + "path", r.URL.Path, + "remote_ip", r.RemoteAddr, + "user_agent", r.UserAgent(), + "status", ww.Status(), + "latency", latency, + ) }() // Call next handler diff --git a/backend/server.go b/backend/server.go index 3433715..8952e5a 100644 --- a/backend/server.go +++ b/backend/server.go @@ -9,7 +9,8 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/httprate" - "github.com/rs/zerolog/log" + "log/slog" + "os" "github.com/skymeyer/onetime-secret/frontend" "github.com/thomaspoignant/go-feature-flag/ffcontext" ) @@ -23,7 +24,7 @@ func StartServer() error { r.Use(middleware.RealIP) r.Use(IPBlockMiddleware) if AppConfig.Dev { - r.Use(LoggerMiddleware(&log.Logger)) + r.Use(LoggerMiddleware(slog.Default())) } r.Use(middleware.Recoverer) @@ -50,7 +51,7 @@ func StartServer() error { AppConfig.RateLimitWin, httprate.WithKeyByIP(), // Note middleware.RealIP ensure the remote address is correct httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) { - log.Warn().Str("ip", r.RemoteAddr).Str("path", r.URL.Path).Msg("Rate limit exceeded") + slog.Warn("Rate limit exceeded", "ip", r.RemoteAddr, "path", r.URL.Path) http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests) }), )) @@ -65,7 +66,7 @@ func StartServer() error { FileServer(r, "/", http.FS(frontend.FS)) bindAddr := fmt.Sprintf(":%d", AppConfig.Port) - log.Info().Str("bind", bindAddr).Msg("Server starting") + slog.Info("Server starting", "bind", bindAddr) return http.ListenAndServe(bindAddr, r) } @@ -74,7 +75,8 @@ func StartServer() error { // static files from a http.FileSystem. It additionally handles SPA routing fallback. func FileServer(r chi.Router, path string, root http.FileSystem) { if strings.ContainsAny(path, "{}*") { - log.Fatal().Msg("FileServer does not permit any URL parameters") + slog.Error("FileServer does not permit any URL parameters") + os.Exit(1) } fs := http.StripPrefix(path, http.FileServer(root)) @@ -136,7 +138,7 @@ func isIPBlocked(ip string) bool { ctx := ffcontext.NewEvaluationContext(ip) blocked, err := ffs.Client().BoolVariationDetails(AppConfig.FFBlockedIPs, ctx, false) if err != nil { - log.Error().Err(err).Msg("fflags: failed to get blocked ips") + slog.Error("fflags: failed to get blocked ips", "error", err) return false } if blocked.Value { @@ -146,7 +148,7 @@ func isIPBlocked(ip string) bool { } else { reason = "unknown" } - log.Warn().Str("ip", ip).Str("reason", reason).Msg("fflags: blocked ip") + slog.Warn("fflags: blocked ip", "ip", ip, "reason", reason) return true } return false @@ -156,7 +158,7 @@ func isIPBlocked(ip string) bool { if len(AppConfig.BlockedIPs) > 0 { for _, blockedIP := range AppConfig.BlockedIPs { if ip == blockedIP || strings.HasPrefix(ip, blockedIP) { - log.Warn().Str("ip", ip).Msg("blocked ip") + slog.Warn("blocked ip", "ip", ip) return true } } diff --git a/backend/store.go b/backend/store.go index a4b5b44..06e8731 100644 --- a/backend/store.go +++ b/backend/store.go @@ -11,9 +11,7 @@ import ( "time" "cloud.google.com/go/storage" - - "github.com/rs/zerolog/log" - + "log/slog" "go.skymeyer.dev/pkg/crypto" ) @@ -84,16 +82,16 @@ func (s *SecretStore) StoreUser(ctx context.Context, ui UserInfo) error { data, err := json.Marshal(ui) if err != nil { - log.Error().Err(err).Str("user", ui.ID).Msg("failed to marshal user info") + slog.Error("failed to marshal user info", "error", err, "user", ui.ID) return fmt.Errorf("failed to marshal user info: %w", err) } w.ContentType = "application/json" if _, err := io.Copy(w, bytes.NewBuffer(data)); err != nil { - log.Error().Err(err).Str("user", ui.ID).Msg("failed to write user info") + slog.Error("failed to write user info", "error", err, "user", ui.ID) return fmt.Errorf("failed to write user info: %w", err) } - log.Debug().Str("user", ui.ID).Interface("user_info", ui).Msg("user info stored successfully") + slog.Debug("user info stored successfully", "user", ui.ID, "user_info", ui) return nil } @@ -102,17 +100,16 @@ func (s *SecretStore) StoreUser(ctx context.Context, ui UserInfo) error { func (s *SecretStore) StoreSecret(ctx context.Context, value string, ttlHours int) (string, error) { id, err := GenerateID() if err != nil { - log.Error().Err(err).Msg("failed to generate id") + slog.Error("failed to generate id", "error", err) return "", fmt.Errorf("failed to generate id: %w", err) } if ttlHours <= 0 { - log.Debug().Int("ttlHours", ttlHours).Msg("ttlHours is zero or negative, using default") + slog.Debug("ttlHours is zero or negative, using default", "ttlHours", ttlHours) ttlHours = AppConfig.DefaultTTLHours } if ttlHours > AppConfig.MaxTTLHours { - log.Debug().Int("ttlHours", ttlHours).Int("maxTTLHours", AppConfig.MaxTTLHours). - Msg("ttlHours is greater than maxTTLHours, using maxTTLHours") + slog.Debug("ttlHours is greater than maxTTLHours, using maxTTLHours", "ttlHours", ttlHours, "maxTTLHours", AppConfig.MaxTTLHours) ttlHours = AppConfig.MaxTTLHours } @@ -133,7 +130,7 @@ func (s *SecretStore) StoreSecret(ctx context.Context, value string, ttlHours in // Convert to JSON jsonBytes, err := json.Marshal(secret) if err != nil { - log.Error().Err(err).Str("user", user).Msg("failed to marshal secret") + slog.Error("failed to marshal secret", "error", err, "user", user) return "", fmt.Errorf("failed to marshal secret: %w", err) } @@ -143,7 +140,7 @@ func (s *SecretStore) StoreSecret(ctx context.Context, value string, ttlHours in }) sealed, err := crypto.Seal(ctx, jsonBytes) if err != nil { - log.Error().Err(err).Str("user", user).Msg("failed to seal secret") + slog.Error("failed to seal secret", "error", err, "user", user) return "", fmt.Errorf("failed to seal secret: %w", err) } @@ -170,21 +167,21 @@ func (s *SecretStore) StoreSecret(ctx context.Context, value string, ttlHours in // Write the file content sealedBytes, err := sealed.Bytes() if err != nil { - log.Error().Err(err).Str("user", user).Msg("failed to marshal sealed") + slog.Error("failed to marshal sealed", "error", err, "user", user) return "", fmt.Errorf("failed to marshal sealed: %w", err) } if _, err := io.Copy(w, bytes.NewReader(sealedBytes)); err != nil { - log.Error().Err(err).Str("user", user).Msg("io.Copy failed") + slog.Error("io.Copy failed", "error", err, "user", user) return "", fmt.Errorf("io.Copy: %w", err) } // Close the writer to finalize the upload if err := w.Close(); err != nil { - log.Error().Err(err).Str("user", user).Msg("w.Close failed") + slog.Error("w.Close failed", "error", err, "user", user) return "", fmt.Errorf("w.Close: %w", err) } - log.Info().Str("id", id).Str("user", user).Int("ttlHours", ttlHours).Msg("secret stored successfully") + slog.Info("secret stored successfully", "id", id, "user", user, "ttlHours", ttlHours) return id, nil } @@ -198,27 +195,27 @@ func (s *SecretStore) GetMetadata(ctx context.Context, id string) (*SecretEntry, if err == storage.ErrObjectNotExist { return nil, false } - log.Info().Err(err).Str("id", id).Msg("gcs object no longer exists") + slog.Info("gcs object no longer exists", "error", err, "id", id) return nil, false } // Object exists, return its custom metadata createdAt, err := time.Parse(time.RFC3339, attrs.Metadata[METADATA_CREATED_AT]) if err != nil { - log.Error().Err(err).Str("id", id).Msg("time.Parse created_at failed") + slog.Error("time.Parse created_at failed", "error", err, "id", id) return nil, false } expiresAt, err := time.Parse(time.RFC3339, attrs.Metadata[METADATA_EXPIRES_AT]) if err != nil { - log.Error().Err(err).Str("id", id).Msg("time.Parse expires_at failed") + slog.Error("time.Parse expires_at failed", "error", err, "id", id) return nil, false } // If expired, we pretend it doesn't exist. GCS Bucket Lifecycle will delete it later. if time.Now().After(expiresAt) { - log.Debug().Str("id", id).Msg("secret exists but is expired") + slog.Debug("secret exists but is expired", "id", id) if err := s.destroySecret(ctx, &SecretEntry{ID: id}); err != nil { - log.Error().Err(err).Str("id", id).Msg("destroy expired secret failed") + slog.Error("destroy expired secret failed", "error", err, "id", id) } return nil, false } @@ -232,7 +229,7 @@ func (s *SecretStore) GetMetadata(ctx context.Context, id string) (*SecretEntry, secret.Owner = owner } - log.Info().Str("id", id).Str("owner", secret.Owner).Msg("secret metadata retrieved") + slog.Info("secret metadata retrieved", "id", id, "owner", secret.Owner) return secret, true } @@ -242,14 +239,14 @@ func (s *SecretStore) RevealSecret(ctx context.Context, id string) (string, bool // Get secret metadata secret, ok := s.GetMetadata(ctx, id) if !ok { - log.Error().Str("id", id).Msg("secret does not exist or is expired") + slog.Error("secret does not exist or is expired", "id", id) return "", false } // Create a reader for the object reader, err := s.gcs.Bucket(s.secretBucket).Object(id).NewReader(ctx) if err != nil { - log.Error().Err(err).Str("id", id).Msg("reader creation failed") + slog.Error("reader creation failed", "error", err, "id", id) return "", false } defer reader.Close() @@ -257,14 +254,14 @@ func (s *SecretStore) RevealSecret(ctx context.Context, id string) (string, bool // Read the bytes from the file bytes, err := io.ReadAll(reader) if err != nil { - log.Error().Err(err).Str("id", id).Msg("io.ReadAll failed") + slog.Error("io.ReadAll failed", "error", err, "id", id) return "", false } // Decrypt the secret sealed, err := crypto.UnmarshalSealed(bytes) if err != nil { - log.Error().Err(err).Str("id", id).Msg("crypto.UnmarshalSealed failed") + slog.Error("crypto.UnmarshalSealed failed", "error", err, "id", id) return "", false } @@ -274,18 +271,18 @@ func (s *SecretStore) RevealSecret(ctx context.Context, id string) (string, bool }) decrypted, err := crypto.Unseal(ctx, sealed) if err != nil { - log.Error().Err(err).Str("id", id).Str("owner", secret.Owner).Msg("crypto.Unseal failed") + slog.Error("crypto.Unseal failed", "error", err, "id", id, "owner", secret.Owner) return "", false } if err := json.Unmarshal(decrypted, &secret); err != nil { - log.Error().Err(err).Str("id", id).Str("owner", secret.Owner).Msg("json.Unmarshal failed") + slog.Error("json.Unmarshal failed", "error", err, "id", id, "owner", secret.Owner) return "", false } // Burn the secret if err := s.destroySecret(ctx, secret); err != nil { - log.Error().Err(err).Str("id", secret.ID).Str("owner", secret.Owner).Msg("destroy revealed secret failed") + slog.Error("destroy revealed secret failed", "error", err, "id", secret.ID, "owner", secret.Owner) return "", false } @@ -296,6 +293,6 @@ func (s *SecretStore) destroySecret(ctx context.Context, secret *SecretEntry) er if err := s.gcs.Bucket(s.secretBucket).Object(secret.ID).Delete(ctx); err != nil { return err } - log.Info().Str("id", secret.ID).Str("owner", secret.Owner).Msg("secret destroyed") + slog.Info("secret destroyed", "id", secret.ID, "owner", secret.Owner) return nil } diff --git a/cmd/root.go b/cmd/root.go index 1e5b102..10f5c6b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,7 +6,7 @@ import ( "time" semver "github.com/hashicorp/go-version" - "github.com/rs/zerolog/log" + "log/slog" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -102,7 +102,7 @@ func initConfig() { viper.AutomaticEnv() if err := viper.ReadInConfig(); err == nil { - log.Info().Str("config", viper.ConfigFileUsed()).Msg("Using config file") + slog.Info("Using config file", "config", viper.ConfigFileUsed()) } } @@ -130,12 +130,13 @@ func getVersion() *semver.Version { sv, err := semver.NewVersion(v) if err != nil { - log.Warn().Err(err). - Str("version", version). - Str("commit", commit). - Str("timestamp", timestamp). - Str("input", v). - Msg("Failed to parse version") + slog.Warn("Failed to parse version", + "error", err, + "version", version, + "commit", commit, + "timestamp", timestamp, + "input", v, + ) versionSemver = semver.Must(semver.NewVersion("v0.0.0+unknown")) } else { versionSemver = sv diff --git a/cmd/server.go b/cmd/server.go index 7d9d919..0acb562 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -4,9 +4,10 @@ import ( "crypto/rand" "encoding/hex" "fmt" + "log/slog" + "os" "time" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -54,18 +55,18 @@ var serverCmd = &cobra.Command{ SessionSecret: viper.GetString("session-secret"), } - // Initialize Logger backend.InitLogger(backend.AppConfig.LogLevel, backend.AppConfig.Dev) - log.Info().Str("version", getVersion().String()).Msg("Logger initialized successfully") + slog.Info("Logger initialized successfully", "version", getVersion().String()) // Security: Fallback to a random session secret if none provided in dev if backend.AppConfig.SessionSecret == "" { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { - log.Fatal().Err(err).Msg("Failed to generate session secret") + slog.Error("Failed to generate session secret", "error", err) + os.Exit(1) } backend.AppConfig.SessionSecret = hex.EncodeToString(b) - log.Warn().Msg("No session secret provided, generated a random one") + slog.Warn("No session secret provided, generated a random one") } // Initialize secret store @@ -86,8 +87,7 @@ var serverCmd = &cobra.Command{ backend.AppConfig.SecretBucket, backend.AppConfig.UserBucket); err != nil { return err } - log.Info().Str("secret-bucket", backend.AppConfig.SecretBucket).Str("kek", kek).Str("dek", dek). - Msg("Secret store initialized") + slog.Info("Secret store initialized", "secret-bucket", backend.AppConfig.SecretBucket, "kek", kek, "dek", dek) // Initialize feature flags if enabled if backend.AppConfig.FFFile != "" && backend.AppConfig.FFAuthzDomains != "" && backend.AppConfig.FFAuthzEmails != "" && backend.AppConfig.FFBlockEmails != "" { @@ -95,7 +95,7 @@ var serverCmd = &cobra.Command{ if err := backend.InitFFManager(backend.AppConfig.FFFile, refresh); err != nil { return err } - log.Info().Dur("refresh", refresh).Str("file", backend.AppConfig.FFFile).Msg("Feature flags initialized") + slog.Info("Feature flags initialized", "refresh", refresh, "file", backend.AppConfig.FFFile) } // Start Server diff --git a/go.mod b/go.mod index 9c8873a..84aeacb 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,10 @@ require ( github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/httprate v0.15.0 github.com/hashicorp/go-version v1.2.0 - github.com/rs/zerolog v1.35.0 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/thomaspoignant/go-feature-flag v1.52.0 - go.skymeyer.dev/pkg v1.0.2 + go.skymeyer.dev/pkg v1.0.3 golang.org/x/oauth2 v0.36.0 ) @@ -55,8 +54,6 @@ require ( github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/nikunjy/rules v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect diff --git a/go.sum b/go.sum index 9d68962..0632a6c 100644 --- a/go.sum +++ b/go.sum @@ -175,10 +175,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/nikunjy/rules v1.5.0 h1:KJDSLOsFhwt7kcXUyZqwkgrQg5YoUwj+TVu6ItCQShw= github.com/nikunjy/rules v1.5.0/go.mod h1:TlZtZdBChrkqi8Lr2AXocme8Z7EsbxtFdDoKeI6neBQ= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -194,8 +190,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= -github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= @@ -254,8 +248,8 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.skymeyer.dev/pkg v1.0.2 h1:lyehe6W2XDgYe3/lt3q3HoJkSkYxB9HP3DbBW2sjQS8= -go.skymeyer.dev/pkg v1.0.2/go.mod h1:hqCcbEXRvwU/7UXCyuksCPridNBsQPAOdNm4lTd902o= +go.skymeyer.dev/pkg v1.0.3 h1:D79bLcdaDWx2Vp+rcc+rqgLDDIptd2PIvM7+cCHBqKQ= +go.skymeyer.dev/pkg v1.0.3/go.mod h1:ylLWISOCzFsIBWhur+1KnQYW8u5upvNAHjLRTWiTgkk= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= gocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY= @@ -270,7 +264,6 @@ golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=