From f2e36b09f2902398bb4bdfbf0f4e0d0a55a673e8 Mon Sep 17 00:00:00 2001 From: ae Date: Sun, 4 May 2025 14:07:23 +0300 Subject: [PATCH] feat: consistent internal error logging --- server/internal/service/auth.go | 20 +++++++++++++++++++- server/internal/service/notes.go | 7 ++++++- server/internal/service/service.go | 6 ++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/server/internal/service/auth.go b/server/internal/service/auth.go index 33cb4ae..312d520 100644 --- a/server/internal/service/auth.go +++ b/server/internal/service/auth.go @@ -175,6 +175,7 @@ func (rs authResource) Create(w http.ResponseWriter, r *http.Request) { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost) if err != nil { + log.Error().Err(err).Msg("Failed to create user") respondError(w, http.StatusInternalServerError, "Failed to create user") return } @@ -187,11 +188,14 @@ func (rs authResource) Create(w http.ResponseWriter, r *http.Request) { if isDuplicateEntry(err) { respondError(w, http.StatusConflict, "Username is already in use") } else { + log.Error().Err(err).Msg("Failed to create user") respondError(w, http.StatusInternalServerError, "Failed to create user") } return } + log.Info().Msgf("User account '%s' created", user.Username) + respondJSON(w, http.StatusCreated, map[string]string{ "id": user.ID.String(), "username": user.Username, @@ -235,6 +239,7 @@ func (rs authResource) Login(w http.ResponseWriter, r *http.Request) { // the database for further token rotations. tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin) if err != nil { + log.Error().Err(err).Msg("Failed to generate tokens") respondError(w, http.StatusInternalServerError, "Failed to generate tokens") return } @@ -310,6 +315,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { + log.Error().Err(err).Msg("Failed to update password") respondError(w, http.StatusInternalServerError, "Failed to update password") return } @@ -319,6 +325,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) { PasswordHash: string(hashedPassword), }) if err != nil { + log.Error().Err(err).Msg("Failed to update password") respondError(w, http.StatusInternalServerError, "Failed to update password") return } @@ -331,6 +338,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) { // Generate a new pair (access & refresh tokens) tokenPair, err := rs.GenerateTokenPair(r.Context(), user.ID, user.IsAdmin) if err != nil { + log.Error().Err(err).Msg("Failed to generate tokens") respondError(w, http.StatusInternalServerError, "Failed to generate tokens") return } @@ -377,6 +385,7 @@ func (rs authResource) OwnerDelete(w http.ResponseWriter, r *http.Request) { err := rs.Users.DeleteUser(r.Context(), user.ID) if err != nil { + log.Error().Err(err).Msg("Failed to delete user") respondError(w, http.StatusInternalServerError, "Failed to delete user") return } @@ -385,9 +394,11 @@ func (rs authResource) OwnerDelete(w http.ResponseWriter, r *http.Request) { rs.setAuthCookies(w, nil, true) if err := rs.Users.RevokeAllUserRefreshTokens(r.Context(), user.ID); err != nil { - log.Error().Msgf("Failed to revoke refresh tokens: %s", err) + log.Warn().Err(err).Msg("Failed to revoke refresh tokens") } + log.Info().Msgf("User account '%s' deleted by the owner", user.Username) + w.WriteHeader(http.StatusNoContent) } @@ -400,6 +411,7 @@ func (rs authResource) List(w http.ResponseWriter, r *http.Request) { Offset: offset, }) if err != nil { + log.Error().Err(err).Msg("Failed to retrieve users") respondError(w, http.StatusInternalServerError, "Failed to retrieve users") return } @@ -431,6 +443,7 @@ func (rs authResource) AdminDelete(w http.ResponseWriter, r *http.Request) { } if err := rs.Users.DeleteUser(r.Context(), targetID); err != nil { + log.Error().Err(err).Msg("Failed to delete user") respondError(w, http.StatusInternalServerError, "Failed to delete user") return } @@ -439,6 +452,8 @@ func (rs authResource) AdminDelete(w http.ResponseWriter, r *http.Request) { log.Error().Msgf("Failed to revoke refresh tokens: %s", err) } + log.Info().Msgf("User account '%s' deleted by an admin", targetID) + w.WriteHeader(http.StatusNoContent) } @@ -529,6 +544,7 @@ func (rs authResource) RefreshAccessToken(w http.ResponseWriter, r *http.Request // Revoke the given (single use) refresh token if err := rs.RevokeRefreshToken(r.Context(), refreshToken); err != nil { + log.Error().Err(err).Msg("Failed to revoke token") respondError(w, http.StatusInternalServerError, "Failed to revoke token") return } @@ -542,6 +558,7 @@ func (rs authResource) RefreshAccessToken(w http.ResponseWriter, r *http.Request // Generate a new pair (access & refresh tokens) tokenPair, err := rs.GenerateTokenPair(r.Context(), userID, claims.Admin) if err != nil { + log.Error().Err(err).Msg("Failed to generate tokens") respondError(w, http.StatusInternalServerError, "Failed to generate tokens") return } @@ -575,6 +592,7 @@ func (rs authResource) Logout(w http.ResponseWriter, r *http.Request) { rs.setAuthCookies(w, nil, true) if err := rs.Tokens.RevokeAllUserRefreshTokens(r.Context(), userID); err != nil { + log.Error().Err(err).Msg("Failed to logout") respondError(w, http.StatusInternalServerError, "Failed to logout") return } diff --git a/server/internal/service/notes.go b/server/internal/service/notes.go index 4b58223..cd4d490 100644 --- a/server/internal/service/notes.go +++ b/server/internal/service/notes.go @@ -100,6 +100,7 @@ func (rs *notesResource) Create(w http.ResponseWriter, r *http.Request) { // Metadata object (parent) note, err := rs.Notes.CreateNote(r.Context(), userID) if err != nil { + log.Error().Err(err).Msg("Failed to create note") respondError(w, http.StatusInternalServerError, "Failed to create note") return } @@ -112,6 +113,7 @@ func (rs *notesResource) Create(w http.ResponseWriter, r *http.Request) { ContentHash: sha1ContentHash(initVersionTitle, initVersionContent), }) if err != nil { + log.Error().Err(err).Msg("Failed to create initial version") respondError(w, http.StatusInternalServerError, "Failed to create initial version") return } @@ -146,6 +148,7 @@ func (rs *notesResource) ListMetadata(w http.ResponseWriter, r *http.Request) { Offset: offset, }) if err != nil { + log.Error().Err(err).Msg("Failed to retrieve notes") respondError(w, http.StatusInternalServerError, "Failed to retrieve notes") return } @@ -197,6 +200,7 @@ func (rs *notesResource) Delete(w http.ResponseWriter, r *http.Request) { UserID: userID, // NOTE: using `fullNote.userID` here'd be insecure }) if err != nil { + log.Error().Err(err).Msg("Failed to delete note") respondError(w, http.StatusInternalServerError, "Failed to delete note") return } @@ -220,6 +224,7 @@ func (rs *notesResource) GetVersionHistory(w http.ResponseWriter, r *http.Reques Offset: offset, }) if err != nil { + log.Error().Err(err).Msg("Failed to get version history") respondError(w, http.StatusInternalServerError, "Failed to get version history") return } @@ -303,7 +308,7 @@ func (rs *notesResource) CreateVersion(w http.ResponseWriter, r *http.Request) { ContentHash: sha1ContentHash(*req.Title, *req.Content), }) if err != nil { - log.Error().Err(err).Msg("Failed to create new note version") + log.Error().Err(err).Msg("Failed to create note version") respondError(w, http.StatusInternalServerError, "Failed to create note version") return } diff --git a/server/internal/service/service.go b/server/internal/service/service.go index ebd3670..aeb9265 100644 --- a/server/internal/service/service.go +++ b/server/internal/service/service.go @@ -67,7 +67,7 @@ func Run(conn *pgx.Conn, q *data.Queries, config SvcConfig) error { r.Use(middleware.AllowContentType("application/json")) // Cleanup workers - scheduleTokenCleanup(context.Background(), q) + scheduleArtifactCleanup(context.Background(), q) // Routes grouped by functionality (we must prefix the API routes with `/api` // as the domain will be the same for the front and back ends) @@ -88,10 +88,12 @@ func Run(conn *pgx.Conn, q *data.Queries, config SvcConfig) error { // Start worker that automatically cleans up the `notes` (cascading to `note_versions`) and // `refresh_tokens` tables from expired (or revoked) entries. The tasks run once during // initialization and then once an hour until the backend is shutdown. -func scheduleTokenCleanup(ctx context.Context, q *data.Queries) { +func scheduleArtifactCleanup(ctx context.Context, q *data.Queries) { cleanupNotes(ctx, q) cleanupRefreshTokens(ctx, q) + log.Info().Msg("Scheduled database artifact cleanup to run once an hour") + ticker := time.NewTicker(1 * time.Hour) go func() { for range ticker.C {