feat(logging): add structured logging across server and SDK

Replace raw fmt/log calls with structured slog logger (Go) and
console-based logger (TypeScript). Add request logging middleware.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing 2026-03-26 10:57:11 +08:00
parent 6da87dd725
commit 8983a9fefa
34 changed files with 510 additions and 113 deletions

View file

@ -2,11 +2,13 @@ package handler
import (
"encoding/json"
"log/slog"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/multica-ai/multica/server/internal/logger"
db "github.com/multica-ai/multica/server/pkg/db/generated"
"github.com/multica-ai/multica/server/pkg/protocol"
)
@ -158,6 +160,7 @@ func (h *Handler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
return
}
slog.Info("workspace created", append(logger.RequestAttrs(r), "workspace_id", uuidToString(ws.ID), "name", ws.Name, "slug", ws.Slug)...)
writeJSON(w, http.StatusCreated, workspaceToResponse(ws))
}
@ -204,10 +207,12 @@ func (h *Handler) UpdateWorkspace(w http.ResponseWriter, r *http.Request) {
ws, err := h.Queries.UpdateWorkspace(r.Context(), params)
if err != nil {
slog.Warn("update workspace failed", append(logger.RequestAttrs(r), "error", err, "workspace_id", id)...)
writeError(w, http.StatusInternalServerError, "failed to update workspace: "+err.Error())
return
}
slog.Info("workspace updated", append(logger.RequestAttrs(r), "workspace_id", id)...)
userID := requestUserID(r)
h.publish(protocol.EventWorkspaceUpdated, id, "member", userID, map[string]any{"workspace": workspaceToResponse(ws)})
@ -363,10 +368,12 @@ func (h *Handler) CreateMember(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusConflict, "user is already a member")
return
}
slog.Warn("create member failed", append(logger.RequestAttrs(r), "error", err, "workspace_id", workspaceID, "email", email)...)
writeError(w, http.StatusInternalServerError, "failed to create member")
return
}
slog.Info("member added", append(logger.RequestAttrs(r), "member_id", uuidToString(member.ID), "workspace_id", workspaceID, "email", email, "role", role)...)
userID := requestUserID(r)
h.publish(protocol.EventMemberAdded, workspaceID, "member", userID, map[string]any{"member": memberWithUserResponse(member, user)})
@ -479,10 +486,12 @@ func (h *Handler) DeleteMember(w http.ResponseWriter, r *http.Request) {
}
if err := h.Queries.DeleteMember(r.Context(), target.ID); err != nil {
slog.Warn("delete member failed", append(logger.RequestAttrs(r), "error", err, "member_id", memberID, "workspace_id", workspaceID)...)
writeError(w, http.StatusInternalServerError, "failed to delete member")
return
}
slog.Info("member removed", append(logger.RequestAttrs(r), "member_id", uuidToString(target.ID), "workspace_id", workspaceID, "user_id", uuidToString(target.UserID))...)
userID := requestUserID(r)
h.publish(protocol.EventMemberRemoved, workspaceID, "member", userID, map[string]any{
"member_id": uuidToString(target.ID),
@ -513,10 +522,12 @@ func (h *Handler) LeaveWorkspace(w http.ResponseWriter, r *http.Request) {
}
if err := h.Queries.DeleteMember(r.Context(), member.ID); err != nil {
slog.Warn("leave workspace failed", append(logger.RequestAttrs(r), "error", err, "workspace_id", workspaceID)...)
writeError(w, http.StatusInternalServerError, "failed to leave workspace")
return
}
slog.Info("member removed", append(logger.RequestAttrs(r), "member_id", uuidToString(member.ID), "workspace_id", workspaceID, "user_id", uuidToString(member.UserID))...)
userID := requestUserID(r)
h.publish(protocol.EventMemberRemoved, workspaceID, "member", userID, map[string]any{
"member_id": uuidToString(member.ID),
@ -534,10 +545,12 @@ func (h *Handler) DeleteWorkspace(w http.ResponseWriter, r *http.Request) {
}
if err := h.Queries.DeleteWorkspace(r.Context(), parseUUID(workspaceID)); err != nil {
slog.Warn("delete workspace failed", append(logger.RequestAttrs(r), "error", err, "workspace_id", workspaceID)...)
writeError(w, http.StatusInternalServerError, "failed to delete workspace")
return
}
slog.Info("workspace deleted", append(logger.RequestAttrs(r), "workspace_id", workspaceID)...)
h.publish(protocol.EventWorkspaceDeleted, workspaceID, "member", requestUserID(r), map[string]any{
"workspace_id": workspaceID,
})