mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
feat(server): logging & error-visibility hardening
Make server failures debuggable without leaking secrets: - configurable stdout log level (config.yaml logging.level + OWNCORD_LOGGING_LEVEL) - preserve the DB cause in ErrInternal wraps; log auth-DB failures distinctly from bad tokens; log the previously-silent expired-session cleanup goroutine - route HTTP handler panics through slog (was chi stderr-only, invisible to the admin log stream) - stackutil: argument-free panic stacks so key/token bytes never reach the admin ring buffer / SSE; slog.LogValuer redaction on VoiceConfig/GitHubConfig/ GIFConfig/Config and db.User/db.Session - logctx: req_id/trace_id correlation on ...Context log calls Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
-4
@@ -6,7 +6,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -16,6 +15,7 @@ import (
|
||||
"github.com/owncord/server/permissions"
|
||||
"github.com/owncord/server/plugin"
|
||||
"github.com/owncord/server/service"
|
||||
"github.com/owncord/server/stackutil"
|
||||
"github.com/owncord/server/syncutil"
|
||||
)
|
||||
|
||||
@@ -276,12 +276,10 @@ func (h *Hub) Run() {
|
||||
}
|
||||
panicCount++
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
n := runtime.Stack(buf, false)
|
||||
slog.Error("hub: panic recovered",
|
||||
"panic", r,
|
||||
"panic_count", panicCount,
|
||||
"stack", string(buf[:n]))
|
||||
"stack", stackutil.Capture())
|
||||
|
||||
if panicCount >= 3 {
|
||||
slog.Error("hub: too many panics in 60s, stopping")
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
|
||||
"github.com/owncord/server/stackutil"
|
||||
)
|
||||
|
||||
// handlerV2Entry pairs a V2 handler with its domain-specific dependency struct.
|
||||
@@ -47,15 +48,13 @@ func (r *HandlerRegistry) DispatchV2(ctx context.Context, cmd Command, info Clie
|
||||
}
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
buf := make([]byte, 4096)
|
||||
n := runtime.Stack(buf, false)
|
||||
// TODO: stack trace may contain sensitive function arguments
|
||||
// (e.g. encrypted keys). Consider scrubbing or limiting frames.
|
||||
// stackutil.Capture omits argument values, which for E2EE
|
||||
// handlers can include encrypted key material.
|
||||
slog.Error("DispatchV2 panic recovered",
|
||||
"type", cmd.Type(),
|
||||
"user_id", info.UserID,
|
||||
"panic", rec,
|
||||
"stack", string(buf[:n]),
|
||||
"stack", stackutil.Capture(),
|
||||
)
|
||||
result = Result{Error: ClientError{Code: ErrCodeInternal, Message: "internal error"}}
|
||||
ok = true
|
||||
|
||||
@@ -503,6 +503,11 @@ func authenticateConn(parent context.Context, conn *websocket.Conn, database *db
|
||||
sess, err := database.GetSessionByTokenHash(ctx, hash)
|
||||
if err != nil || sess == nil {
|
||||
_ = conn.Write(ctx, websocket.MessageText, buildAuthError("invalid token"))
|
||||
if err != nil {
|
||||
// DB outage, not a bad token — carry the cause so the caller's log
|
||||
// distinguishes it from an ordinary invalid-token rejection.
|
||||
return nil, "", 0, fmt.Errorf("auth: session lookup failed: %w", err)
|
||||
}
|
||||
return nil, "", 0, fmt.Errorf("auth: invalid session")
|
||||
}
|
||||
|
||||
@@ -514,6 +519,9 @@ func authenticateConn(parent context.Context, conn *websocket.Conn, database *db
|
||||
user, err := database.GetUserByID(ctx, sess.UserID)
|
||||
if err != nil || user == nil {
|
||||
_ = conn.Write(ctx, websocket.MessageText, buildAuthError("user not found"))
|
||||
if err != nil {
|
||||
return nil, "", 0, fmt.Errorf("auth: user lookup failed: %w", err)
|
||||
}
|
||||
return nil, "", 0, fmt.Errorf("auth: user not found")
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -226,10 +227,18 @@ func (h *Hub) sendToUserIfInVoiceChannel(voiceChannelID, targetUserID int64, msg
|
||||
|
||||
target, ok := h.clients[targetUserID]
|
||||
if !ok {
|
||||
return // target not connected — silently drop
|
||||
// Undeliverable key offer can leave the peer unable to decrypt — the
|
||||
// payload is dropped, but log (IDs only, never the encrypted key) so
|
||||
// the failure is diagnosable rather than silent.
|
||||
slog.Debug("e2ee: key offer dropped, target not connected",
|
||||
"target_user_id", targetUserID, "voice_channel_id", voiceChannelID)
|
||||
return
|
||||
}
|
||||
if target.getVoiceChID() != voiceChannelID {
|
||||
return // target not in expected voice channel — silently drop
|
||||
slog.Debug("e2ee: key offer dropped, target not in expected voice channel",
|
||||
"target_user_id", targetUserID, "voice_channel_id", voiceChannelID,
|
||||
"target_voice_channel_id", target.getVoiceChID())
|
||||
return
|
||||
}
|
||||
target.sendMsg(msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user