detached 2960188

feat: Implement structured logging with Pino across server routes

- Add Pino logger with configurable log levels
- Create centralized logging utility with context-aware logging
- Replace console.log/debug/error with structured logging
- Enhance log messages with additional context and metadata
- Configure log levels dynamically based on environment
commit
2960188b03ac2e37d64fa384070a6d5969e95f57
author
Kit · · 2025-02-15 09:05 UTC
parent
afa6c9d
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
19 files · +507 -146
M package.json +2 -0
@@ -54,6 +54,8 @@
5454 "@libsql/client": "^0.14.0",
5555 "dotenv": "^16.4.7",
5656 "drizzle-orm": "^0.39.3",
57+ "pino": "^9.6.0",
58+ "pino-pretty": "^13.0.0",
5759 "uuid": "^11.0.5",
5860 "xp.css": "^0.2.6"
5961 }
@@ -54,6 +54,8 @@
54 "@libsql/client": "^0.14.0",54 "@libsql/client": "^0.14.0",
55 "dotenv": "^16.4.7",55 "dotenv": "^16.4.7",
56 "drizzle-orm": "^0.39.3",56 "drizzle-orm": "^0.39.3",
57+ "pino": "^9.6.0",
58+ "pino-pretty": "^13.0.0",
57 "uuid": "^11.0.5",59 "uuid": "^11.0.5",
58 "xp.css": "^0.2.6"60 "xp.css": "^0.2.6"
59 }61 }
M pnpm-lock.yaml +184 -0 generated file: collapsed
M src/hooks.server.ts +16 -13
@@ -6,33 +6,36 @@ import db from "$lib/db/db.server";
66 import { handleRateLimit } from "$lib/api/rate-limiter";
77 import { users } from "$lib/db/schema";
88 import { initializeMessageCache } from "$lib/cache/initialize-cache";
9+import { createLogger } from "$lib/utils/logger.server";
10+
11+const log = createLogger('hooks-server');
912
1013 async function setAllUsersOffline() {
1114 try {
12- console.log("Setting all users to offline status...");
15+ log.info("Setting all users to offline status...");
1316 await db.update(users)
1417 .set({
1518 status: "offline"
1619 });
17- console.log("Successfully set all users to offline status");
20+ log.info("Successfully set all users to offline status");
1821 } catch (error) {
19- console.error("Failed to set users offline:", error);
22+ log.error("Failed to set users offline", { error });
2023 }
2124 }
2225
2326 // Initialize database when the server starts
24-console.debug("Initializing database on server start...");
27+log.debug("Initializing database on server start...");
2528 initializeDatabase()
2629 .then(async () => {
2730 await setAllUsersOffline();
2831 await initializeMessageCache();
2932 })
3033 .catch(error => {
31- console.error('Failed to initialize database:', error);
34+ log.error('Failed to initialize database', { error });
3235 });
3336
3437 export const handle: Handle = async ({ event, resolve }) => {
35- console.debug("Handling request:", event.url.pathname);
38+ log.debug("Handling request", { path: event.url.pathname });
3639
3740 // Identify public chat and room requests
3841 const isPublicChatRequest =
@@ -69,7 +72,7 @@ export const handle: Handle = async ({ event, resolve }) => {
6972
7073 // Handle public chat/room requests
7174 if (isPublicChatRequest || isPublicRoomRequest) {
72- console.debug("Public request accessed:", event.url.pathname);
75+ log.debug("Public request accessed", { path: event.url.pathname });
7376 event.locals.user = null;
7477 event.locals.session = null; // Public access doesn't require a session
7578 const response = await resolve(event);
@@ -78,11 +81,11 @@ export const handle: Handle = async ({ event, resolve }) => {
7881
7982 // Handle public routes: if token exists, validate and propagate
8083 if (publicRoutes.includes(event.url.pathname)) {
81- console.debug("Public route accessed:", event.url.pathname);
84+ log.debug("Public route accessed", { path: event.url.pathname });
8285 if (token) {
8386 const { session, user } = await validateSessionToken(token);
8487 if (session !== null) {
85- console.debug("Valid session found on public route for user:", user?.nickname);
88+ log.debug("Valid session found on public route", { user: user?.nickname });
8689 setSessionTokenCookie(event, token, session.expiresAt);
8790 event.locals.session = session;
8891 event.locals.user = user;
@@ -94,7 +97,7 @@ export const handle: Handle = async ({ event, resolve }) => {
9497
9598 // Protected routes: ensure authentication
9699 if (token === null) {
97- console.debug("No session token found");
100+ log.debug("No session token found");
98101 event.locals.user = null;
99102 event.locals.session = null;
100103 const response = new Response("Unauthorized", { status: 401 });
@@ -103,12 +106,12 @@ export const handle: Handle = async ({ event, resolve }) => {
103106
104107 const { session, user } = await validateSessionToken(token);
105108 if (session !== null) {
106- console.debug("Valid session found for user:", user?.nickname);
109+ log.debug("Valid session found", { user: user?.nickname });
107110 setSessionTokenCookie(event, token, session.expiresAt);
108111 event.locals.session = session;
109112 event.locals.user = user;
110113 } else {
111- console.debug("Invalid session token");
114+ log.debug("Invalid session token");
112115 deleteSessionTokenCookie(event);
113116 event.locals.session = null;
114117 event.locals.user = null;
@@ -118,7 +121,7 @@ export const handle: Handle = async ({ event, resolve }) => {
118121
119122 // After authentication, ensure SSE endpoint does not trigger rate limiting
120123 if (event.url.pathname.startsWith('/api/sse') && !event.locals.user) {
121- console.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");
124+ log.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");
122125 return addDebugHeaders(new Response("Unauthorized", { status: 401 }));
123126 }
124127
@@ -6,33 +6,36 @@ import db from "$lib/db/db.server";
6 import { handleRateLimit } from "$lib/api/rate-limiter";6 import { handleRateLimit } from "$lib/api/rate-limiter";
7 import { users } from "$lib/db/schema";7 import { users } from "$lib/db/schema";
8 import { initializeMessageCache } from "$lib/cache/initialize-cache";8 import { initializeMessageCache } from "$lib/cache/initialize-cache";
9+import { createLogger } from "$lib/utils/logger.server";
10+
11+const log = createLogger('hooks-server');
9 12
10 async function setAllUsersOffline() {13 async function setAllUsersOffline() {
11 try {14 try {
12- console.log("Setting all users to offline status...");15+ log.info("Setting all users to offline status...");
13 await db.update(users)16 await db.update(users)
14 .set({ 17 .set({
15 status: "offline"18 status: "offline"
16 });19 });
17- console.log("Successfully set all users to offline status");20+ log.info("Successfully set all users to offline status");
18 } catch (error) {21 } catch (error) {
19- console.error("Failed to set users offline:", error);22+ log.error("Failed to set users offline", { error });
20 }23 }
21 }24 }
22 25
23 // Initialize database when the server starts26 // Initialize database when the server starts
24-console.debug("Initializing database on server start...");27+log.debug("Initializing database on server start...");
25 initializeDatabase()28 initializeDatabase()
26 .then(async () => {29 .then(async () => {
27 await setAllUsersOffline();30 await setAllUsersOffline();
28 await initializeMessageCache();31 await initializeMessageCache();
29 })32 })
30 .catch(error => {33 .catch(error => {
31- console.error('Failed to initialize database:', error);34+ log.error('Failed to initialize database', { error });
32 });35 });
33 36
34 export const handle: Handle = async ({ event, resolve }) => {37 export const handle: Handle = async ({ event, resolve }) => {
35- console.debug("Handling request:", event.url.pathname);38+ log.debug("Handling request", { path: event.url.pathname });
36 39
37 // Identify public chat and room requests40 // Identify public chat and room requests
38 const isPublicChatRequest = 41 const isPublicChatRequest =
@@ -69,7 +72,7 @@ export const handle: Handle = async ({ event, resolve }) => {
69 72
70 // Handle public chat/room requests73 // Handle public chat/room requests
71 if (isPublicChatRequest || isPublicRoomRequest) {74 if (isPublicChatRequest || isPublicRoomRequest) {
72- console.debug("Public request accessed:", event.url.pathname);75+ log.debug("Public request accessed", { path: event.url.pathname });
73 event.locals.user = null;76 event.locals.user = null;
74 event.locals.session = null; // Public access doesn't require a session77 event.locals.session = null; // Public access doesn't require a session
75 const response = await resolve(event);78 const response = await resolve(event);
@@ -78,11 +81,11 @@ export const handle: Handle = async ({ event, resolve }) => {
78 81
79 // Handle public routes: if token exists, validate and propagate82 // Handle public routes: if token exists, validate and propagate
80 if (publicRoutes.includes(event.url.pathname)) {83 if (publicRoutes.includes(event.url.pathname)) {
81- console.debug("Public route accessed:", event.url.pathname);84+ log.debug("Public route accessed", { path: event.url.pathname });
82 if (token) {85 if (token) {
83 const { session, user } = await validateSessionToken(token);86 const { session, user } = await validateSessionToken(token);
84 if (session !== null) {87 if (session !== null) {
85- console.debug("Valid session found on public route for user:", user?.nickname);88+ log.debug("Valid session found on public route", { user: user?.nickname });
86 setSessionTokenCookie(event, token, session.expiresAt);89 setSessionTokenCookie(event, token, session.expiresAt);
87 event.locals.session = session;90 event.locals.session = session;
88 event.locals.user = user;91 event.locals.user = user;
@@ -94,7 +97,7 @@ export const handle: Handle = async ({ event, resolve }) => {
94 97
95 // Protected routes: ensure authentication98 // Protected routes: ensure authentication
96 if (token === null) {99 if (token === null) {
97- console.debug("No session token found");100+ log.debug("No session token found");
98 event.locals.user = null;101 event.locals.user = null;
99 event.locals.session = null;102 event.locals.session = null;
100 const response = new Response("Unauthorized", { status: 401 });103 const response = new Response("Unauthorized", { status: 401 });
@@ -103,12 +106,12 @@ export const handle: Handle = async ({ event, resolve }) => {
103 106
104 const { session, user } = await validateSessionToken(token);107 const { session, user } = await validateSessionToken(token);
105 if (session !== null) {108 if (session !== null) {
106- console.debug("Valid session found for user:", user?.nickname);109+ log.debug("Valid session found", { user: user?.nickname });
107 setSessionTokenCookie(event, token, session.expiresAt);110 setSessionTokenCookie(event, token, session.expiresAt);
108 event.locals.session = session;111 event.locals.session = session;
109 event.locals.user = user;112 event.locals.user = user;
110 } else {113 } else {
111- console.debug("Invalid session token");114+ log.debug("Invalid session token");
112 deleteSessionTokenCookie(event);115 deleteSessionTokenCookie(event);
113 event.locals.session = null;116 event.locals.session = null;
114 event.locals.user = null;117 event.locals.user = null;
@@ -118,7 +121,7 @@ export const handle: Handle = async ({ event, resolve }) => {
118 121
119 // After authentication, ensure SSE endpoint does not trigger rate limiting122 // After authentication, ensure SSE endpoint does not trigger rate limiting
120 if (event.url.pathname.startsWith('/api/sse') && !event.locals.user) {123 if (event.url.pathname.startsWith('/api/sse') && !event.locals.user) {
121- console.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");124+ log.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");
122 return addDebugHeaders(new Response("Unauthorized", { status: 401 }));125 return addDebugHeaders(new Response("Unauthorized", { status: 401 }));
123 }126 }
124 127
M src/lib/api/rate-limiter.ts +5 -2
@@ -1,4 +1,7 @@
11 import type { RequestEvent } from '@sveltejs/kit';
2+import { createLogger } from '$lib/utils/logger.server';
3+
4+const log = createLogger('rate-limiter');
25
36 const isDev = import.meta.env.DEV;
47
@@ -86,7 +89,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
8689
8790 // Check if we're over the limit
8891 if (validRequests.length >= points) {
89- console.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);
92+ log.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);
9093 const oldestRequest = validRequests[0];
9194 const retryAfter = durationMs - (now - oldestRequest.timestamp);
9295 return { limited: true, retryAfter };
@@ -104,7 +107,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
104107 const requests = ipRequests.get(ip) || [];
105108 const validRequests = requests.filter(req => now - req.timestamp < durationMs);
106109
107- console.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);
110+ log.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);
108111
109112 if (validRequests.length >= points) {
110113 const oldestRequest = validRequests[0];
@@ -1,4 +1,7 @@
1 import type { RequestEvent } from '@sveltejs/kit';1 import type { RequestEvent } from '@sveltejs/kit';
2+import { createLogger } from '$lib/utils/logger.server';
3+
4+const log = createLogger('rate-limiter');
2 5
3 const isDev = import.meta.env.DEV;6 const isDev = import.meta.env.DEV;
4 7
@@ -86,7 +89,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
86 89
87 // Check if we're over the limit90 // Check if we're over the limit
88 if (validRequests.length >= points) {91 if (validRequests.length >= points) {
89- console.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);92+ log.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);
90 const oldestRequest = validRequests[0];93 const oldestRequest = validRequests[0];
91 const retryAfter = durationMs - (now - oldestRequest.timestamp);94 const retryAfter = durationMs - (now - oldestRequest.timestamp);
92 return { limited: true, retryAfter };95 return { limited: true, retryAfter };
@@ -104,7 +107,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
104 const requests = ipRequests.get(ip) || [];107 const requests = ipRequests.get(ip) || [];
105 const validRequests = requests.filter(req => now - req.timestamp < durationMs);108 const validRequests = requests.filter(req => now - req.timestamp < durationMs);
106 109
107- console.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);110+ log.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);
108 111
109 if (validRequests.length >= points) {112 if (validRequests.length >= points) {
110 const oldestRequest = validRequests[0];113 const oldestRequest = validRequests[0];
M src/lib/api/session.cookie.ts +10 -6
@@ -1,11 +1,15 @@
11 import type { Cookies } from "@sveltejs/kit";
2+import { createLogger } from "$lib/utils/logger.server";
3+
4+const log = createLogger('session-cookie');
25
36 // Helper function to determine cookie domain
47 function getCookieDomain(): string | undefined {
58 if (process.env.NODE_ENV === 'production') {
69 // Check for Cloudflare Pages domain
710 if (process.env.CF_PAGES_URL) {
8- console.debug("Cloudflare Pages URL:", process.env.CF_PAGES_URL);
11+ const url = process.env.CF_PAGES_URL;
12+ log.debug("Cloudflare Pages URL:", { url });
913 // For preview deployments, use host-only cookie
1014 if (process.env.CF_PAGES_BRANCH !== 'main') {
1115 return undefined;
@@ -20,10 +24,10 @@ function getCookieDomain(): string | undefined {
2024 }
2125
2226 export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token: string, expiresAt: number): void {
23- console.debug("Setting session cookie with expiry:", new Date(expiresAt));
27+ log.debug("Setting session cookie with expiry:", new Date(expiresAt));
2428
2529 const domain = getCookieDomain();
26- console.debug("Setting cookie for domain:", domain);
30+ log.debug("Setting cookie for domain:", { domain });
2731
2832 cookies.set("session", token, {
2933 httpOnly: true,
@@ -36,10 +40,10 @@ export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token:
3640 }
3741
3842 export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): void {
39- console.debug("Deleting session cookie");
43+ log.debug("Deleting session cookie");
4044
4145 const domain = getCookieDomain();
42- console.debug("Deleting cookie for domain:", domain);
46+ log.debug("Deleting cookie for domain:", { domain });
4347
4448 cookies.set("session", "", {
4549 httpOnly: true,
@@ -52,6 +56,6 @@ export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): voi
5256 }
5357
5458 export function getSessionTokenCookie({ cookies }: { cookies: Cookies }): string | undefined {
55- console.debug("Getting session cookie");
59+ log.debug("Getting session cookie");
5660 return cookies.get("session");
5761 } ⏎̸
@@ -1,11 +1,15 @@
1 import type { Cookies } from "@sveltejs/kit";1 import type { Cookies } from "@sveltejs/kit";
2+import { createLogger } from "$lib/utils/logger.server";
3+
4+const log = createLogger('session-cookie');
2 5
3 // Helper function to determine cookie domain6 // Helper function to determine cookie domain
4 function getCookieDomain(): string | undefined {7 function getCookieDomain(): string | undefined {
5 if (process.env.NODE_ENV === 'production') {8 if (process.env.NODE_ENV === 'production') {
6 // Check for Cloudflare Pages domain9 // Check for Cloudflare Pages domain
7 if (process.env.CF_PAGES_URL) {10 if (process.env.CF_PAGES_URL) {
8- console.debug("Cloudflare Pages URL:", process.env.CF_PAGES_URL);11+ const url = process.env.CF_PAGES_URL;
12+ log.debug("Cloudflare Pages URL:", { url });
9 // For preview deployments, use host-only cookie13 // For preview deployments, use host-only cookie
10 if (process.env.CF_PAGES_BRANCH !== 'main') {14 if (process.env.CF_PAGES_BRANCH !== 'main') {
11 return undefined;15 return undefined;
@@ -20,10 +24,10 @@ function getCookieDomain(): string | undefined {
20 }24 }
21 25
22 export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token: string, expiresAt: number): void {26 export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token: string, expiresAt: number): void {
23- console.debug("Setting session cookie with expiry:", new Date(expiresAt));27+ log.debug("Setting session cookie with expiry:", new Date(expiresAt));
24 28
25 const domain = getCookieDomain();29 const domain = getCookieDomain();
26- console.debug("Setting cookie for domain:", domain);30+ log.debug("Setting cookie for domain:", { domain });
27 31
28 cookies.set("session", token, {32 cookies.set("session", token, {
29 httpOnly: true,33 httpOnly: true,
@@ -36,10 +40,10 @@ export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token:
36 }40 }
37 41
38 export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): void {42 export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): void {
39- console.debug("Deleting session cookie");43+ log.debug("Deleting session cookie");
40 44
41 const domain = getCookieDomain();45 const domain = getCookieDomain();
42- console.debug("Deleting cookie for domain:", domain);46+ log.debug("Deleting cookie for domain:", { domain });
43 47
44 cookies.set("session", "", {48 cookies.set("session", "", {
45 httpOnly: true,49 httpOnly: true,
@@ -52,6 +56,6 @@ export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): voi
52 }56 }
53 57
54 export function getSessionTokenCookie({ cookies }: { cookies: Cookies }): string | undefined {58 export function getSessionTokenCookie({ cookies }: { cookies: Cookies }): string | undefined {
55- console.debug("Getting session cookie");59+ log.debug("Getting session cookie");
56 return cookies.get("session");60 return cookies.get("session");
57 } ⏎̸61 } ⏎̸
M src/lib/api/session.server.ts +15 -12
@@ -2,6 +2,9 @@ import db from '../db/db.server';
22 import { sessions, users } from '../db/schema';
33 import { eq } from 'drizzle-orm';
44 import type { Session, User } from '../types/chat';
5+import { createLogger } from '../utils/logger.server';
6+
7+const log = createLogger('session-server');
58
69 // Helper: Compute SHA-256 hash of a message and encode it as a hex string.
710 export async function sha256(message: string): Promise<string> {
@@ -41,14 +44,14 @@ export function generateSessionToken(): string {
4144 const randomBytesArray = new Uint8Array(20);
4245 crypto.getRandomValues(randomBytesArray);
4346 const token = base32Encode(randomBytesArray);
44- console.debug("Generated session token:", token);
47+ log.debug("Generated session token", { token });
4548 return token;
4649 }
4750
4851 // Create a session record once a valid token is available.
4952 // The session ID is the SHA-256 hash of the token.
5053 export async function createSession(token: string, userId: string): Promise<Session> {
51- console.debug("Creating session for user:", userId);
54+ log.debug("Creating session", { userId });
5255 const sessionId = await sha256(token);
5356 const createdAt = Date.now();
5457 const expiresAt = createdAt + 24 * 60 * 60 * 1000; // 24 hours expiry
@@ -58,7 +61,7 @@ export async function createSession(token: string, userId: string): Promise<Sess
5861 createdAt,
5962 expiresAt,
6063 });
61- console.debug("Session created with id:", sessionId);
64+ log.debug("Session created", { sessionId });
6265 return { id: sessionId, userId, createdAt, expiresAt };
6366 }
6467
@@ -73,41 +76,41 @@ export type SessionValidationResult =
7376
7477 // Validate a session token by converting it to its SHA‑256 hash, checking expiration, and fetching the user.
7578 export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
76- console.debug("Validating session token:", token);
79+ log.debug("Validating session token");
7780 const sessionId = await sha256(token);
7881 const session = await db.query.sessions.findFirst({
7982 where: eq(sessions.id, sessionId)
8083 });
8184 if (!session) {
82- console.debug("Session not found for id:", sessionId);
85+ log.debug("Session not found", { sessionId });
8386 return { session: null, user: null };
8487 }
8588 const now = Date.now();
8689 if (session.expiresAt < now) {
87- console.debug("Session expired for id:", sessionId);
90+ log.debug("Session expired", { sessionId });
8891 return { session: null, user: null };
8992 }
9093 const user = await db.query.users.findFirst({
9194 where: eq(users.id, session.userId)
9295 });
9396 if (!user) {
94- console.debug("User not found for session id:", sessionId);
97+ log.debug("User not found for session", { sessionId });
9598 return { session: null, user: null };
9699 }
97- console.debug("Session validated for id:", sessionId);
100+ log.debug("Session validated", { sessionId, userId: user.id });
98101 return { session, user };
99102 }
100103
101104 // Invalidate a specific session by deleting it from the database.
102105 export async function invalidateSession(sessionId: string): Promise<void> {
103- console.debug("Invalidating session with id:", sessionId);
106+ log.debug("Invalidating session", { sessionId });
104107 await db.delete(sessions).where(eq(sessions.id, sessionId));
105- console.debug("Session invalidated:", sessionId);
108+ log.debug("Session invalidated", { sessionId });
106109 }
107110
108111 // Invalidate all sessions for a given user.
109112 export async function invalidateAllSessions(userId: string): Promise<void> {
110- console.debug("Invalidating all sessions for user:", userId);
113+ log.debug("Invalidating all sessions for user", { userId });
111114 await db.delete(sessions).where(eq(sessions.userId, userId));
112- console.debug("All sessions invalidated for user:", userId);
115+ log.debug("All sessions invalidated", { userId });
113116 } ⏎̸
@@ -2,6 +2,9 @@ import db from '../db/db.server';
2 import { sessions, users } from '../db/schema';2 import { sessions, users } from '../db/schema';
3 import { eq } from 'drizzle-orm';3 import { eq } from 'drizzle-orm';
4 import type { Session, User } from '../types/chat';4 import type { Session, User } from '../types/chat';
5+import { createLogger } from '../utils/logger.server';
6+
7+const log = createLogger('session-server');
5 8
6 // Helper: Compute SHA-256 hash of a message and encode it as a hex string.9 // Helper: Compute SHA-256 hash of a message and encode it as a hex string.
7 export async function sha256(message: string): Promise<string> {10 export async function sha256(message: string): Promise<string> {
@@ -41,14 +44,14 @@ export function generateSessionToken(): string {
41 const randomBytesArray = new Uint8Array(20);44 const randomBytesArray = new Uint8Array(20);
42 crypto.getRandomValues(randomBytesArray);45 crypto.getRandomValues(randomBytesArray);
43 const token = base32Encode(randomBytesArray);46 const token = base32Encode(randomBytesArray);
44- console.debug("Generated session token:", token);47+ log.debug("Generated session token", { token });
45 return token;48 return token;
46 }49 }
47 50
48 // Create a session record once a valid token is available.51 // Create a session record once a valid token is available.
49 // The session ID is the SHA-256 hash of the token.52 // The session ID is the SHA-256 hash of the token.
50 export async function createSession(token: string, userId: string): Promise<Session> {53 export async function createSession(token: string, userId: string): Promise<Session> {
51- console.debug("Creating session for user:", userId);54+ log.debug("Creating session", { userId });
52 const sessionId = await sha256(token);55 const sessionId = await sha256(token);
53 const createdAt = Date.now();56 const createdAt = Date.now();
54 const expiresAt = createdAt + 24 * 60 * 60 * 1000; // 24 hours expiry57 const expiresAt = createdAt + 24 * 60 * 60 * 1000; // 24 hours expiry
@@ -58,7 +61,7 @@ export async function createSession(token: string, userId: string): Promise<Sess
58 createdAt,61 createdAt,
59 expiresAt,62 expiresAt,
60 });63 });
61- console.debug("Session created with id:", sessionId);64+ log.debug("Session created", { sessionId });
62 return { id: sessionId, userId, createdAt, expiresAt };65 return { id: sessionId, userId, createdAt, expiresAt };
63 }66 }
64 67
@@ -73,41 +76,41 @@ export type SessionValidationResult =
73 76
74 // Validate a session token by converting it to its SHA‑256 hash, checking expiration, and fetching the user.77 // Validate a session token by converting it to its SHA‑256 hash, checking expiration, and fetching the user.
75 export async function validateSessionToken(token: string): Promise<SessionValidationResult> {78 export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
76- console.debug("Validating session token:", token);79+ log.debug("Validating session token");
77 const sessionId = await sha256(token);80 const sessionId = await sha256(token);
78 const session = await db.query.sessions.findFirst({81 const session = await db.query.sessions.findFirst({
79 where: eq(sessions.id, sessionId)82 where: eq(sessions.id, sessionId)
80 });83 });
81 if (!session) {84 if (!session) {
82- console.debug("Session not found for id:", sessionId);85+ log.debug("Session not found", { sessionId });
83 return { session: null, user: null };86 return { session: null, user: null };
84 }87 }
85 const now = Date.now();88 const now = Date.now();
86 if (session.expiresAt < now) {89 if (session.expiresAt < now) {
87- console.debug("Session expired for id:", sessionId);90+ log.debug("Session expired", { sessionId });
88 return { session: null, user: null };91 return { session: null, user: null };
89 }92 }
90 const user = await db.query.users.findFirst({93 const user = await db.query.users.findFirst({
91 where: eq(users.id, session.userId)94 where: eq(users.id, session.userId)
92 });95 });
93 if (!user) {96 if (!user) {
94- console.debug("User not found for session id:", sessionId);97+ log.debug("User not found for session", { sessionId });
95 return { session: null, user: null };98 return { session: null, user: null };
96 }99 }
97- console.debug("Session validated for id:", sessionId);100+ log.debug("Session validated", { sessionId, userId: user.id });
98 return { session, user };101 return { session, user };
99 }102 }
100 103
101 // Invalidate a specific session by deleting it from the database.104 // Invalidate a specific session by deleting it from the database.
102 export async function invalidateSession(sessionId: string): Promise<void> {105 export async function invalidateSession(sessionId: string): Promise<void> {
103- console.debug("Invalidating session with id:", sessionId);106+ log.debug("Invalidating session", { sessionId });
104 await db.delete(sessions).where(eq(sessions.id, sessionId));107 await db.delete(sessions).where(eq(sessions.id, sessionId));
105- console.debug("Session invalidated:", sessionId);108+ log.debug("Session invalidated", { sessionId });
106 }109 }
107 110
108 // Invalidate all sessions for a given user.111 // Invalidate all sessions for a given user.
109 export async function invalidateAllSessions(userId: string): Promise<void> {112 export async function invalidateAllSessions(userId: string): Promise<void> {
110- console.debug("Invalidating all sessions for user:", userId);113+ log.debug("Invalidating all sessions for user", { userId });
111 await db.delete(sessions).where(eq(sessions.userId, userId));114 await db.delete(sessions).where(eq(sessions.userId, userId));
112- console.debug("All sessions invalidated for user:", userId);115+ log.debug("All sessions invalidated", { userId });
113 } ⏎̸116 } ⏎̸
M src/lib/cache/initialize-cache.ts +7 -4
@@ -2,16 +2,19 @@ import { messageCache } from '$lib/cache/message-cache';
22 import db from '$lib/db/db.server';
33 import { messages } from '$lib/db/schema';
44 import { desc } from 'drizzle-orm';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('cache-initialize');
58
69 let isCacheInitialized = false;
710
811 export async function initializeMessageCache(): Promise<void> {
912 if (isCacheInitialized) {
10- console.debug('[Cache] Message cache already initialized.');
13+ log.debug('[Cache] Message cache already initialized.');
1114 return;
1215 }
1316 try {
14- console.debug('[Cache] Initializing message cache with pre-population.');
17+ log.debug('[Cache] Initializing message cache with pre-population.');
1518 const fetchedMessages = await db.select()
1619 .from(messages)
1720 .orderBy(desc(messages.timestamp))
@@ -20,11 +23,11 @@ export async function initializeMessageCache(): Promise<void> {
2023 await messageCache.initialize(fetchedMessages.reverse());
2124 isCacheInitialized = true;
2225 const stats = messageCache.getCacheStats();
23- console.debug('[Cache] Message cache initialized successfully:', {
26+ log.debug('[Cache] Message cache initialized successfully:', {
2427 roomCount: stats.roomCount,
2528 totalMessages: stats.totalMessages
2629 });
2730 } catch (error) {
28- console.error('[Cache] Failed to initialize message cache:', error);
31+ log.error('[Cache] Failed to initialize message cache:', { error: error instanceof Error ? error.message : 'Unknown error' });
2932 }
3033 } ⏎̸
@@ -2,16 +2,19 @@ import { messageCache } from '$lib/cache/message-cache';
2 import db from '$lib/db/db.server';2 import db from '$lib/db/db.server';
3 import { messages } from '$lib/db/schema';3 import { messages } from '$lib/db/schema';
4 import { desc } from 'drizzle-orm';4 import { desc } from 'drizzle-orm';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('cache-initialize');
5 8
6 let isCacheInitialized = false;9 let isCacheInitialized = false;
7 10
8 export async function initializeMessageCache(): Promise<void> {11 export async function initializeMessageCache(): Promise<void> {
9 if (isCacheInitialized) {12 if (isCacheInitialized) {
10- console.debug('[Cache] Message cache already initialized.');13+ log.debug('[Cache] Message cache already initialized.');
11 return;14 return;
12 }15 }
13 try {16 try {
14- console.debug('[Cache] Initializing message cache with pre-population.');17+ log.debug('[Cache] Initializing message cache with pre-population.');
15 const fetchedMessages = await db.select()18 const fetchedMessages = await db.select()
16 .from(messages)19 .from(messages)
17 .orderBy(desc(messages.timestamp))20 .orderBy(desc(messages.timestamp))
@@ -20,11 +23,11 @@ export async function initializeMessageCache(): Promise<void> {
20 await messageCache.initialize(fetchedMessages.reverse());23 await messageCache.initialize(fetchedMessages.reverse());
21 isCacheInitialized = true;24 isCacheInitialized = true;
22 const stats = messageCache.getCacheStats();25 const stats = messageCache.getCacheStats();
23- console.debug('[Cache] Message cache initialized successfully:', {26+ log.debug('[Cache] Message cache initialized successfully:', {
24 roomCount: stats.roomCount,27 roomCount: stats.roomCount,
25 totalMessages: stats.totalMessages28 totalMessages: stats.totalMessages
26 });29 });
27 } catch (error) {30 } catch (error) {
28- console.error('[Cache] Failed to initialize message cache:', error);31+ log.error('[Cache] Failed to initialize message cache:', { error: error instanceof Error ? error.message : 'Unknown error' });
29 }32 }
30 } ⏎̸33 } ⏎̸
M src/lib/db/db.server.ts +8 -5
@@ -2,30 +2,33 @@ import { drizzle } from 'drizzle-orm/libsql';
22 import { createClient } from '@libsql/client';
33 import { TURSO_AUTH_TOKEN, TURSO_DATABASE_URL } from '$env/static/private';
44 import schema, { ensureDefaultChatRoom } from './schema';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('db-server');
58
69 const client = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN });
710 const db = drizzle(client, { schema });
811
912 // Initialize database with required data
1013 export const initializeDatabase = async () => {
11- console.debug('Initializing database...');
14+ log.debug('Initializing database...');
1215 try {
1316 await ensureDefaultChatRoom(db);
14- console.debug('Database initialization complete');
17+ log.debug('Database initialization complete');
1518 } catch (error) {
16- console.error('Error initializing database:', error);
19+ log.error('Error initializing database:', { error: error instanceof Error ? error.message : 'Unknown error' });
1720 throw error;
1821 }
1922 };
2023
2124 // Call initialization when the module is loaded
2225 initializeDatabase().catch(error => {
23- console.error('Failed to initialize database:', error);
26+ log.error('Failed to initialize database:', { error: error instanceof Error ? error.message : 'Unknown error' });
2427 });
2528
2629 export const testConnection = async () => {
2730 const result = await db.query.users.findMany();
28- console.log(result);
31+ log.debug('Test connection result:', result);
2932 };
3033
3134 export default db;⏎̸
@@ -2,30 +2,33 @@ import { drizzle } from 'drizzle-orm/libsql';
2 import { createClient } from '@libsql/client';2 import { createClient } from '@libsql/client';
3 import { TURSO_AUTH_TOKEN, TURSO_DATABASE_URL } from '$env/static/private';3 import { TURSO_AUTH_TOKEN, TURSO_DATABASE_URL } from '$env/static/private';
4 import schema, { ensureDefaultChatRoom } from './schema';4 import schema, { ensureDefaultChatRoom } from './schema';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('db-server');
5 8
6 const client = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN });9 const client = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN });
7 const db = drizzle(client, { schema });10 const db = drizzle(client, { schema });
8 11
9 // Initialize database with required data12 // Initialize database with required data
10 export const initializeDatabase = async () => {13 export const initializeDatabase = async () => {
11- console.debug('Initializing database...');14+ log.debug('Initializing database...');
12 try {15 try {
13 await ensureDefaultChatRoom(db);16 await ensureDefaultChatRoom(db);
14- console.debug('Database initialization complete');17+ log.debug('Database initialization complete');
15 } catch (error) {18 } catch (error) {
16- console.error('Error initializing database:', error);19+ log.error('Error initializing database:', { error: error instanceof Error ? error.message : 'Unknown error' });
17 throw error;20 throw error;
18 }21 }
19 };22 };
20 23
21 // Call initialization when the module is loaded24 // Call initialization when the module is loaded
22 initializeDatabase().catch(error => {25 initializeDatabase().catch(error => {
23- console.error('Failed to initialize database:', error);26+ log.error('Failed to initialize database:', { error: error instanceof Error ? error.message : 'Unknown error' });
24 });27 });
25 28
26 export const testConnection = async () => {29 export const testConnection = async () => {
27 const result = await db.query.users.findMany();30 const result = await db.query.users.findMany();
28- console.log(result);31+ log.debug('Test connection result:', result);
29 };32 };
30 33
31 export default db;⏎̸34 export default db;⏎̸
M src/lib/db/schema.ts +9 -6
@@ -4,6 +4,9 @@ import type { User, ChatRoom, Message, UserStatus, MessageType, Session } from "
44 import { v4 as uuidv4 } from "uuid";
55 import { eq } from "drizzle-orm";
66 import type { LibSQLDatabase } from "drizzle-orm/libsql";
7+import { createLogger } from "$lib/utils/logger.server";
8+
9+const log = createLogger('schema');
710
811 // Default chat room ID - using a UUID to avoid collisions
912 export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';
@@ -120,7 +123,7 @@ const schema = {
120123
121124 // Function to ensure default chat room exists
122125 export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
123- console.debug('Ensuring default chat room exists...');
126+ log.debug('Ensuring default chat room exists...');
124127 try {
125128 // Use a transaction to handle concurrent creation attempts
126129 await db.transaction(async (tx) => {
@@ -130,25 +133,25 @@ export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
130133 .get();
131134
132135 if (!defaultRoom) {
133- console.debug('Creating default chat room...');
136+ log.debug('Creating default chat room...');
134137 await tx.insert(chatRooms).values({
135138 id: DEFAULT_CHAT_ROOM_ID,
136139 name: 'General',
137140 type: 'group',
138141 createdAt: Date.now()
139142 });
140- console.debug('Default chat room created successfully');
143+ log.debug('Default chat room created successfully');
141144 } else {
142- console.debug('Default chat room already exists');
145+ log.debug('Default chat room already exists');
143146 }
144147 });
145148 } catch (error: unknown) {
146149 // If the error is a unique constraint violation, another process probably created the room
147150 if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
148- console.debug('Default chat room was created by another process');
151+ log.debug('Default chat room was created by another process');
149152 return;
150153 }
151- console.error('Error ensuring default chat room:', error);
154+ log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
152155 throw error;
153156 }
154157 }
@@ -4,6 +4,9 @@ import type { User, ChatRoom, Message, UserStatus, MessageType, Session } from "
4 import { v4 as uuidv4 } from "uuid";4 import { v4 as uuidv4 } from "uuid";
5 import { eq } from "drizzle-orm";5 import { eq } from "drizzle-orm";
6 import type { LibSQLDatabase } from "drizzle-orm/libsql";6 import type { LibSQLDatabase } from "drizzle-orm/libsql";
7+import { createLogger } from "$lib/utils/logger.server";
8+
9+const log = createLogger('schema');
7 10
8 // Default chat room ID - using a UUID to avoid collisions11 // Default chat room ID - using a UUID to avoid collisions
9 export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';12 export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';
@@ -120,7 +123,7 @@ const schema = {
120 123
121 // Function to ensure default chat room exists124 // Function to ensure default chat room exists
122 export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {125 export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
123- console.debug('Ensuring default chat room exists...');126+ log.debug('Ensuring default chat room exists...');
124 try {127 try {
125 // Use a transaction to handle concurrent creation attempts128 // Use a transaction to handle concurrent creation attempts
126 await db.transaction(async (tx) => {129 await db.transaction(async (tx) => {
@@ -130,25 +133,25 @@ export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
130 .get();133 .get();
131 134
132 if (!defaultRoom) {135 if (!defaultRoom) {
133- console.debug('Creating default chat room...');136+ log.debug('Creating default chat room...');
134 await tx.insert(chatRooms).values({137 await tx.insert(chatRooms).values({
135 id: DEFAULT_CHAT_ROOM_ID,138 id: DEFAULT_CHAT_ROOM_ID,
136 name: 'General',139 name: 'General',
137 type: 'group',140 type: 'group',
138 createdAt: Date.now()141 createdAt: Date.now()
139 });142 });
140- console.debug('Default chat room created successfully');143+ log.debug('Default chat room created successfully');
141 } else {144 } else {
142- console.debug('Default chat room already exists');145+ log.debug('Default chat room already exists');
143 }146 }
144 });147 });
145 } catch (error: unknown) {148 } catch (error: unknown) {
146 // If the error is a unique constraint violation, another process probably created the room149 // If the error is a unique constraint violation, another process probably created the room
147 if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {150 if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
148- console.debug('Default chat room was created by another process');151+ log.debug('Default chat room was created by another process');
149 return;152 return;
150 }153 }
151- console.error('Error ensuring default chat room:', error);154+ log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
152 throw error;155 throw error;
153 }156 }
154 }157 }
A src/lib/utils/logger.server.ts +55 -0
@@ -0,0 +1,55 @@
1+import pino from 'pino';
2+
3+const logLevel = process.env.NODE_ENV === 'development' ? 'debug' : 'info';
4+
5+class LoggerService {
6+ private static instance: LoggerService;
7+ private logger: pino.Logger;
8+ private moduleLoggers: Map<string, Logger>;
9+
10+ private constructor() {
11+ this.logger = pino({
12+ transport: {
13+ target: 'pino-pretty',
14+ options: {
15+ colorize: true,
16+ translateTime: 'HH:MM:ss',
17+ ignore: 'pid,hostname',
18+ },
19+ },
20+ level: logLevel,
21+ });
22+ this.moduleLoggers = new Map();
23+ }
24+
25+ public static getInstance(): LoggerService {
26+ if (!LoggerService.instance) {
27+ LoggerService.instance = new LoggerService();
28+ }
29+ return LoggerService.instance;
30+ }
31+
32+ public getLogger(module: string): Logger {
33+ if (!this.moduleLoggers.has(module)) {
34+ this.moduleLoggers.set(module, {
35+ debug: (msg: string, obj = {}) => this.logger.debug({ module, ...obj }, msg),
36+ info: (msg: string, obj = {}) => this.logger.info({ module, ...obj }, msg),
37+ warn: (msg: string, obj = {}) => this.logger.warn({ module, ...obj }, msg),
38+ error: (msg: string, obj = {}) => this.logger.error({ module, ...obj }, msg)
39+ });
40+ }
41+ return this.moduleLoggers.get(module)!;
42+ }
43+}
44+
45+export interface Logger {
46+ debug: (msg: string, obj?: object) => void;
47+ info: (msg: string, obj?: object) => void;
48+ warn: (msg: string, obj?: object) => void;
49+ error: (msg: string, obj?: object) => void;
50+}
51+
52+// Export a function to get a logger for a specific module
53+export const createLogger = (module: string): Logger => {
54+ return LoggerService.getInstance().getLogger(module);
55+}; ⏎̸
@@ -0,0 +1,55 @@
1+import pino from 'pino';
2+
3+const logLevel = process.env.NODE_ENV === 'development' ? 'debug' : 'info';
4+
5+class LoggerService {
6+ private static instance: LoggerService;
7+ private logger: pino.Logger;
8+ private moduleLoggers: Map<string, Logger>;
9+
10+ private constructor() {
11+ this.logger = pino({
12+ transport: {
13+ target: 'pino-pretty',
14+ options: {
15+ colorize: true,
16+ translateTime: 'HH:MM:ss',
17+ ignore: 'pid,hostname',
18+ },
19+ },
20+ level: logLevel,
21+ });
22+ this.moduleLoggers = new Map();
23+ }
24+
25+ public static getInstance(): LoggerService {
26+ if (!LoggerService.instance) {
27+ LoggerService.instance = new LoggerService();
28+ }
29+ return LoggerService.instance;
30+ }
31+
32+ public getLogger(module: string): Logger {
33+ if (!this.moduleLoggers.has(module)) {
34+ this.moduleLoggers.set(module, {
35+ debug: (msg: string, obj = {}) => this.logger.debug({ module, ...obj }, msg),
36+ info: (msg: string, obj = {}) => this.logger.info({ module, ...obj }, msg),
37+ warn: (msg: string, obj = {}) => this.logger.warn({ module, ...obj }, msg),
38+ error: (msg: string, obj = {}) => this.logger.error({ module, ...obj }, msg)
39+ });
40+ }
41+ return this.moduleLoggers.get(module)!;
42+ }
43+}
44+
45+export interface Logger {
46+ debug: (msg: string, obj?: object) => void;
47+ info: (msg: string, obj?: object) => void;
48+ warn: (msg: string, obj?: object) => void;
49+ error: (msg: string, obj?: object) => void;
50+}
51+
52+// Export a function to get a logger for a specific module
53+export const createLogger = (module: string): Logger => {
54+ return LoggerService.getInstance().getLogger(module);
55+}; ⏎̸
M src/routes/api/chat/messages/+server.ts +68 -46
@@ -15,6 +15,9 @@ import { chatRooms, DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
1515 import { users } from '$lib/db/schema';
1616 import { ensureDefaultChatRoom } from '$lib/db/schema';
1717 import { messageCache } from '$lib/cache/message-cache';
18+import { createLogger } from '$lib/utils/logger.server';
19+
20+const log = createLogger('chat-server');
1821
1922 // Rate limiting configuration
2023 const INITIAL_COOLDOWN = 1000; // 1 second
@@ -54,7 +57,7 @@ async function initializeCache() {
5457 if (isCacheInitialized) return;
5558
5659 try {
57- console.log('[Chat] Initializing message cache');
60+ log.info('Initializing message cache');
5861 // Fetch messages from all rooms, limited to last 100 per room
5962 const fetchedMessages = await db.select()
6063 .from(messages)
@@ -66,12 +69,12 @@ async function initializeCache() {
6669
6770 // Log cache stats after initialization
6871 const stats = messageCache.getCacheStats();
69- console.log('[Chat] Message cache initialized successfully:', {
72+ log.info('Message cache initialized successfully', {
7073 roomCount: stats.roomCount,
7174 totalMessages: stats.totalMessages
7275 });
7376 } catch (error) {
74- console.error('[Chat] Failed to initialize message cache:', error);
77+ log.error('Failed to initialize message cache', { error });
7578 // Don't set isCacheInitialized to true on error
7679 }
7780 }
@@ -79,47 +82,44 @@ async function initializeCache() {
7982 // GET endpoint: fetch messages from cache or DB
8083 export async function GET({ request, locals }) {
8184 const url = new URL(request.url);
82- const isPublic = url.searchParams.get('public') === 'true';
83- const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
8485 const beforeTimestamp = url.searchParams.get('before');
85-
86- if (!isPublic && !locals.user) {
87- throw error(401, 'Authentication required');
88- }
86+ const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
87+ let fetchedMessages: Message[] = [];
8988
9089 try {
9190 // Ensure default chat room exists
9291 await ensureDefaultChatRoom(db);
93-
94- let fetchedMessages: Message[];
95- if (isPublic) {
96- if (!isCacheInitialized) {
97- await initializeCache();
98- }
99-
100- if (beforeTimestamp) {
101- // For public users requesting older messages, fetch from DB with limit
102- console.log('[Chat] Fetching public messages from DB before timestamp:', beforeTimestamp);
103- const query = db.select()
104- .from(messages)
105- .where(and(
106- eq(messages.chatRoomId, roomId),
107- lt(messages.timestamp, parseInt(beforeTimestamp))
108- ))
109- .orderBy(desc(messages.timestamp))
110- .limit(10);
111- fetchedMessages = (await query).reverse();
112- } else {
113- console.log('[Chat] Fetching public messages from cache', { roomId });
114- fetchedMessages = messageCache.getMessages(roomId, 10);
92+
93+ // Initialize cache if needed
94+ if (!isCacheInitialized) {
95+ await initializeCache();
96+ }
97+
98+ // Try to get messages from cache first
99+ if (!beforeTimestamp) {
100+ log.debug('Attempting to fetch messages from cache', { roomId });
101+ const cachedMessages = messageCache.getMessages(roomId);
102+ if (cachedMessages && cachedMessages.length > 0) {
103+ log.debug('Messages found in cache', {
104+ roomId,
105+ messageCount: cachedMessages.length
106+ });
107+ fetchedMessages = cachedMessages;
115108 }
116- } else {
109+ }
110+
111+ // If no cached messages or pagination request, fetch from DB
112+ if (fetchedMessages.length === 0) {
117113 if (!locals.user) {
114+ log.warn('Authentication required');
118115 throw error(401, 'Authentication required');
119116 }
120117
121118 if (beforeTimestamp) {
122- console.log('[Chat] Fetching messages from DB before timestamp:', beforeTimestamp);
119+ log.debug('Fetching messages from DB before timestamp', {
120+ roomId,
121+ beforeTimestamp
122+ });
123123 const query = db.select()
124124 .from(messages)
125125 .where(and(
@@ -130,7 +130,7 @@ export async function GET({ request, locals }) {
130130 .limit(100);
131131 fetchedMessages = (await query).reverse();
132132 } else {
133- console.log('[Chat] Fetching messages from DB for instant chat', { roomId });
133+ log.debug('Fetching messages from DB for instant chat', { roomId });
134134 const query = db.select()
135135 .from(messages)
136136 .where(eq(messages.chatRoomId, roomId))
@@ -153,7 +153,10 @@ export async function GET({ request, locals }) {
153153 }
154154 );
155155 } catch (error) {
156- console.log('[Chat] Error fetching messages:', error);
156+ log.error('Error fetching messages', {
157+ error: error instanceof Error ? error.message : 'Unknown error',
158+ roomId
159+ });
157160 const errorResponse: GetMessagesResponse = {
158161 success: false,
159162 error: 'Failed to fetch messages'
@@ -171,16 +174,20 @@ export async function GET({ request, locals }) {
171174 // POST endpoint: receive a new message, save it in the DB, and broadcast via SSE
172175 export async function POST({ request, locals }: { request: Request, locals: App.Locals }) {
173176 if (!locals.user) {
177+ log.warn('Authentication required');
174178 throw error(401, 'Authentication required');
175179 }
176180
177- console.log('[Chat] New message received');
181+ log.debug('New message received');
178182 try {
179183 // Check rate limiting
180184 const { canSend, retryAfter } = updateUserCooldown(locals.user.id);
181185 if (!canSend) {
182186 const maskedUserId = `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`;
183- console.log('[Chat] Rate limited:', { userId: maskedUserId, retryAfter });
187+ log.warn('Rate limited', {
188+ userId: maskedUserId,
189+ retryAfter
190+ });
184191 const errorResponse: SendMessageResponse = {
185192 success: false,
186193 error: 'Please wait before sending another message',
@@ -201,7 +208,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
201208
202209 const data = await request.json() as SendMessageRequest;
203210 if (!data.content || !data.userId) {
204- console.log('[Chat] Invalid payload received');
211+ log.warn('Invalid payload received', {
212+ hasContent: Boolean(data.content),
213+ hasUserId: Boolean(data.userId)
214+ });
205215 const errorResponse: SendMessageResponse = {
206216 success: false,
207217 error: 'Invalid message payload'
@@ -210,6 +220,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
210220 }
211221
212222 if (data.userId !== locals.user.id) {
223+ log.warn('Unauthorized message attempt', {
224+ requestedUserId: data.userId,
225+ actualUserId: `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`
226+ });
213227 throw error(403, 'Cannot post messages as another user');
214228 }
215229
@@ -223,7 +237,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
223237 .get();
224238
225239 if (!chatRoom) {
226- console.log('[Chat] Chat room not found:', chatRoomId);
240+ log.warn('Chat room not found', { chatRoomId });
227241 const errorResponse: SendMessageResponse = {
228242 success: false,
229243 error: 'Chat room not found'
@@ -239,7 +253,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
239253
240254 if (!user) {
241255 const maskedUserId = `${data.userId.slice(0, 4)}...${data.userId.slice(-4)}`;
242- console.log('[Chat] User not found:', maskedUserId);
256+ log.warn('User not found', { maskedUserId });
243257 const errorResponse: SendMessageResponse = {
244258 success: false,
245259 error: 'User not found'
@@ -249,7 +263,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
249263
250264 const newMessage: Message = {
251265 id: uuidv4(),
252- chatRoomId: data.chatRoomId || DEFAULT_CHAT_ROOM_ID,
266+ chatRoomId,
253267 senderId: data.userId,
254268 content: data.content,
255269 type: data.type || 'chat',
@@ -258,7 +272,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
258272
259273 // Save to DB
260274 await db.insert(messages).values(newMessage);
261- console.log('[Chat] Message saved in DB', {
275+ log.debug('Message saved in DB', {
262276 messageId: newMessage.id,
263277 chatRoomId: newMessage.chatRoomId,
264278 type: newMessage.type,
@@ -267,7 +281,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
267281
268282 // Update message cache
269283 messageCache.addMessage(newMessage);
270- console.debug('[Chat] Message added to cache', {
284+ log.debug('Message added to cache', {
271285 messageId: newMessage.id,
272286 chatRoomId: newMessage.chatRoomId
273287 });
@@ -275,6 +289,12 @@ export async function POST({ request, locals }: { request: Request, locals: App.
275289 // Broadcast the new message via the unified SSE emitter
276290 sseEmitter.emit('sse', { type: 'chatMessage', data: newMessage });
277291
292+ log.debug('Message processed successfully', {
293+ messageId: newMessage.id,
294+ userId: `${newMessage.senderId.slice(0, 4)}...${newMessage.senderId.slice(-4)}`,
295+ roomId: newMessage.chatRoomId
296+ });
297+
278298 const successResponse: SendMessageResponse = {
279299 success: true,
280300 message: newMessage
@@ -287,8 +307,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
287307 headers: { 'Content-Type': 'application/json' }
288308 }
289309 );
290- } catch {
291- console.log('[Chat] Error processing message');
310+ } catch (error) {
311+ log.error('Error processing message', {
312+ error: error instanceof Error ? error.message : 'Unknown error'
313+ });
292314 const errorResponse: SendMessageResponse = {
293315 success: false,
294316 error: 'Failed to save message'
@@ -301,4 +323,4 @@ export async function POST({ request, locals }: { request: Request, locals: App.
301323 }
302324 );
303325 }
304-} ⏎̸
326+}⏎̸
@@ -15,6 +15,9 @@ import { chatRooms, DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
15 import { users } from '$lib/db/schema';15 import { users } from '$lib/db/schema';
16 import { ensureDefaultChatRoom } from '$lib/db/schema';16 import { ensureDefaultChatRoom } from '$lib/db/schema';
17 import { messageCache } from '$lib/cache/message-cache';17 import { messageCache } from '$lib/cache/message-cache';
18+import { createLogger } from '$lib/utils/logger.server';
19+
20+const log = createLogger('chat-server');
18 21
19 // Rate limiting configuration22 // Rate limiting configuration
20 const INITIAL_COOLDOWN = 1000; // 1 second23 const INITIAL_COOLDOWN = 1000; // 1 second
@@ -54,7 +57,7 @@ async function initializeCache() {
54 if (isCacheInitialized) return;57 if (isCacheInitialized) return;
55 58
56 try {59 try {
57- console.log('[Chat] Initializing message cache');60+ log.info('Initializing message cache');
58 // Fetch messages from all rooms, limited to last 100 per room61 // Fetch messages from all rooms, limited to last 100 per room
59 const fetchedMessages = await db.select()62 const fetchedMessages = await db.select()
60 .from(messages)63 .from(messages)
@@ -66,12 +69,12 @@ async function initializeCache() {
66 69
67 // Log cache stats after initialization70 // Log cache stats after initialization
68 const stats = messageCache.getCacheStats();71 const stats = messageCache.getCacheStats();
69- console.log('[Chat] Message cache initialized successfully:', {72+ log.info('Message cache initialized successfully', {
70 roomCount: stats.roomCount,73 roomCount: stats.roomCount,
71 totalMessages: stats.totalMessages74 totalMessages: stats.totalMessages
72 });75 });
73 } catch (error) {76 } catch (error) {
74- console.error('[Chat] Failed to initialize message cache:', error);77+ log.error('Failed to initialize message cache', { error });
75 // Don't set isCacheInitialized to true on error78 // Don't set isCacheInitialized to true on error
76 }79 }
77 }80 }
@@ -79,47 +82,44 @@ async function initializeCache() {
79 // GET endpoint: fetch messages from cache or DB82 // GET endpoint: fetch messages from cache or DB
80 export async function GET({ request, locals }) {83 export async function GET({ request, locals }) {
81 const url = new URL(request.url);84 const url = new URL(request.url);
82- const isPublic = url.searchParams.get('public') === 'true';
83- const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
84 const beforeTimestamp = url.searchParams.get('before');85 const beforeTimestamp = url.searchParams.get('before');
85- 86+ const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
86- if (!isPublic && !locals.user) {87+ let fetchedMessages: Message[] = [];
87- throw error(401, 'Authentication required');
88- }
89 88
90 try {89 try {
91 // Ensure default chat room exists90 // Ensure default chat room exists
92 await ensureDefaultChatRoom(db);91 await ensureDefaultChatRoom(db);
93- 92+
94- let fetchedMessages: Message[];93+ // Initialize cache if needed
95- if (isPublic) {94+ if (!isCacheInitialized) {
96- if (!isCacheInitialized) {95+ await initializeCache();
97- await initializeCache();96+ }
98- }97+
99- 98+ // Try to get messages from cache first
100- if (beforeTimestamp) {99+ if (!beforeTimestamp) {
101- // For public users requesting older messages, fetch from DB with limit100+ log.debug('Attempting to fetch messages from cache', { roomId });
102- console.log('[Chat] Fetching public messages from DB before timestamp:', beforeTimestamp);101+ const cachedMessages = messageCache.getMessages(roomId);
103- const query = db.select()102+ if (cachedMessages && cachedMessages.length > 0) {
104- .from(messages)103+ log.debug('Messages found in cache', {
105- .where(and(104+ roomId,
106- eq(messages.chatRoomId, roomId),105+ messageCount: cachedMessages.length
107- lt(messages.timestamp, parseInt(beforeTimestamp))106+ });
108- ))107+ fetchedMessages = cachedMessages;
109- .orderBy(desc(messages.timestamp))
110- .limit(10);
111- fetchedMessages = (await query).reverse();
112- } else {
113- console.log('[Chat] Fetching public messages from cache', { roomId });
114- fetchedMessages = messageCache.getMessages(roomId, 10);
115 }108 }
116- } else {109+ }
110+
111+ // If no cached messages or pagination request, fetch from DB
112+ if (fetchedMessages.length === 0) {
117 if (!locals.user) {113 if (!locals.user) {
114+ log.warn('Authentication required');
118 throw error(401, 'Authentication required');115 throw error(401, 'Authentication required');
119 }116 }
120 117
121 if (beforeTimestamp) {118 if (beforeTimestamp) {
122- console.log('[Chat] Fetching messages from DB before timestamp:', beforeTimestamp);119+ log.debug('Fetching messages from DB before timestamp', {
120+ roomId,
121+ beforeTimestamp
122+ });
123 const query = db.select()123 const query = db.select()
124 .from(messages)124 .from(messages)
125 .where(and(125 .where(and(
@@ -130,7 +130,7 @@ export async function GET({ request, locals }) {
130 .limit(100);130 .limit(100);
131 fetchedMessages = (await query).reverse();131 fetchedMessages = (await query).reverse();
132 } else {132 } else {
133- console.log('[Chat] Fetching messages from DB for instant chat', { roomId });133+ log.debug('Fetching messages from DB for instant chat', { roomId });
134 const query = db.select()134 const query = db.select()
135 .from(messages)135 .from(messages)
136 .where(eq(messages.chatRoomId, roomId))136 .where(eq(messages.chatRoomId, roomId))
@@ -153,7 +153,10 @@ export async function GET({ request, locals }) {
153 }153 }
154 );154 );
155 } catch (error) {155 } catch (error) {
156- console.log('[Chat] Error fetching messages:', error);156+ log.error('Error fetching messages', {
157+ error: error instanceof Error ? error.message : 'Unknown error',
158+ roomId
159+ });
157 const errorResponse: GetMessagesResponse = {160 const errorResponse: GetMessagesResponse = {
158 success: false,161 success: false,
159 error: 'Failed to fetch messages'162 error: 'Failed to fetch messages'
@@ -171,16 +174,20 @@ export async function GET({ request, locals }) {
171 // POST endpoint: receive a new message, save it in the DB, and broadcast via SSE174 // POST endpoint: receive a new message, save it in the DB, and broadcast via SSE
172 export async function POST({ request, locals }: { request: Request, locals: App.Locals }) {175 export async function POST({ request, locals }: { request: Request, locals: App.Locals }) {
173 if (!locals.user) {176 if (!locals.user) {
177+ log.warn('Authentication required');
174 throw error(401, 'Authentication required');178 throw error(401, 'Authentication required');
175 }179 }
176 180
177- console.log('[Chat] New message received');181+ log.debug('New message received');
178 try {182 try {
179 // Check rate limiting183 // Check rate limiting
180 const { canSend, retryAfter } = updateUserCooldown(locals.user.id);184 const { canSend, retryAfter } = updateUserCooldown(locals.user.id);
181 if (!canSend) {185 if (!canSend) {
182 const maskedUserId = `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`;186 const maskedUserId = `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`;
183- console.log('[Chat] Rate limited:', { userId: maskedUserId, retryAfter });187+ log.warn('Rate limited', {
188+ userId: maskedUserId,
189+ retryAfter
190+ });
184 const errorResponse: SendMessageResponse = {191 const errorResponse: SendMessageResponse = {
185 success: false,192 success: false,
186 error: 'Please wait before sending another message',193 error: 'Please wait before sending another message',
@@ -201,7 +208,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
201 208
202 const data = await request.json() as SendMessageRequest;209 const data = await request.json() as SendMessageRequest;
203 if (!data.content || !data.userId) {210 if (!data.content || !data.userId) {
204- console.log('[Chat] Invalid payload received');211+ log.warn('Invalid payload received', {
212+ hasContent: Boolean(data.content),
213+ hasUserId: Boolean(data.userId)
214+ });
205 const errorResponse: SendMessageResponse = {215 const errorResponse: SendMessageResponse = {
206 success: false,216 success: false,
207 error: 'Invalid message payload'217 error: 'Invalid message payload'
@@ -210,6 +220,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
210 }220 }
211 221
212 if (data.userId !== locals.user.id) {222 if (data.userId !== locals.user.id) {
223+ log.warn('Unauthorized message attempt', {
224+ requestedUserId: data.userId,
225+ actualUserId: `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`
226+ });
213 throw error(403, 'Cannot post messages as another user');227 throw error(403, 'Cannot post messages as another user');
214 }228 }
215 229
@@ -223,7 +237,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
223 .get();237 .get();
224 238
225 if (!chatRoom) {239 if (!chatRoom) {
226- console.log('[Chat] Chat room not found:', chatRoomId);240+ log.warn('Chat room not found', { chatRoomId });
227 const errorResponse: SendMessageResponse = {241 const errorResponse: SendMessageResponse = {
228 success: false,242 success: false,
229 error: 'Chat room not found'243 error: 'Chat room not found'
@@ -239,7 +253,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
239 253
240 if (!user) {254 if (!user) {
241 const maskedUserId = `${data.userId.slice(0, 4)}...${data.userId.slice(-4)}`;255 const maskedUserId = `${data.userId.slice(0, 4)}...${data.userId.slice(-4)}`;
242- console.log('[Chat] User not found:', maskedUserId);256+ log.warn('User not found', { maskedUserId });
243 const errorResponse: SendMessageResponse = {257 const errorResponse: SendMessageResponse = {
244 success: false,258 success: false,
245 error: 'User not found'259 error: 'User not found'
@@ -249,7 +263,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
249 263
250 const newMessage: Message = {264 const newMessage: Message = {
251 id: uuidv4(),265 id: uuidv4(),
252- chatRoomId: data.chatRoomId || DEFAULT_CHAT_ROOM_ID,266+ chatRoomId,
253 senderId: data.userId,267 senderId: data.userId,
254 content: data.content,268 content: data.content,
255 type: data.type || 'chat',269 type: data.type || 'chat',
@@ -258,7 +272,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
258 272
259 // Save to DB273 // Save to DB
260 await db.insert(messages).values(newMessage);274 await db.insert(messages).values(newMessage);
261- console.log('[Chat] Message saved in DB', { 275+ log.debug('Message saved in DB', {
262 messageId: newMessage.id,276 messageId: newMessage.id,
263 chatRoomId: newMessage.chatRoomId,277 chatRoomId: newMessage.chatRoomId,
264 type: newMessage.type,278 type: newMessage.type,
@@ -267,7 +281,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
267 281
268 // Update message cache282 // Update message cache
269 messageCache.addMessage(newMessage);283 messageCache.addMessage(newMessage);
270- console.debug('[Chat] Message added to cache', {284+ log.debug('Message added to cache', {
271 messageId: newMessage.id,285 messageId: newMessage.id,
272 chatRoomId: newMessage.chatRoomId286 chatRoomId: newMessage.chatRoomId
273 });287 });
@@ -275,6 +289,12 @@ export async function POST({ request, locals }: { request: Request, locals: App.
275 // Broadcast the new message via the unified SSE emitter289 // Broadcast the new message via the unified SSE emitter
276 sseEmitter.emit('sse', { type: 'chatMessage', data: newMessage });290 sseEmitter.emit('sse', { type: 'chatMessage', data: newMessage });
277 291
292+ log.debug('Message processed successfully', {
293+ messageId: newMessage.id,
294+ userId: `${newMessage.senderId.slice(0, 4)}...${newMessage.senderId.slice(-4)}`,
295+ roomId: newMessage.chatRoomId
296+ });
297+
278 const successResponse: SendMessageResponse = {298 const successResponse: SendMessageResponse = {
279 success: true,299 success: true,
280 message: newMessage300 message: newMessage
@@ -287,8 +307,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
287 headers: { 'Content-Type': 'application/json' } 307 headers: { 'Content-Type': 'application/json' }
288 }308 }
289 );309 );
290- } catch {310+ } catch (error) {
291- console.log('[Chat] Error processing message');311+ log.error('Error processing message', {
312+ error: error instanceof Error ? error.message : 'Unknown error'
313+ });
292 const errorResponse: SendMessageResponse = {314 const errorResponse: SendMessageResponse = {
293 success: false,315 success: false,
294 error: 'Failed to save message'316 error: 'Failed to save message'
@@ -301,4 +323,4 @@ export async function POST({ request, locals }: { request: Request, locals: App.
301 }323 }
302 );324 );
303 }325 }
304-} ⏎̸326+}⏎̸
M src/routes/api/rooms/[roomId]/+server.ts +10 -4
@@ -4,7 +4,9 @@ import { messages, users } from '$lib/db/schema';
44 import type { PublicRoomResponse } from '$lib/types/payloads';
55 import { error } from '@sveltejs/kit';
66 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
78
9+const log = createLogger('rooms-server');
810
911 export async function GET({ params, url, locals }): Promise<Response> {
1012 const { roomId } = params;
@@ -12,11 +14,12 @@ export async function GET({ params, url, locals }): Promise<Response> {
1214
1315 // Check authentication for non-public requests
1416 if (!isPublic && !locals.user) {
17+ log.warn('Authentication required');
1518 throw error(401, 'Authentication required');
1619 }
1720
1821 try {
19- console.log('[Rooms] Fetching room data:', {
22+ log.debug('Fetching room data', {
2023 roomId,
2124 isPublic,
2225 requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'
@@ -47,7 +50,7 @@ export async function GET({ params, url, locals }): Promise<Response> {
4750 buddyList: sanitizedUsers
4851 };
4952
50- console.log('[Rooms] Successfully fetched room data:', {
53+ log.debug('Successfully fetched room data', {
5154 roomId,
5255 userCount: sanitizedUsers.length,
5356 isPublic
@@ -57,8 +60,11 @@ export async function GET({ params, url, locals }): Promise<Response> {
5760 status: 200,
5861 headers: { 'Content-Type': 'application/json' }
5962 });
60- } catch {
61- console.log('[Rooms] Error fetching room data:', { roomId });
63+ } catch (error) {
64+ log.error('Error fetching room data', {
65+ roomId,
66+ error: error instanceof Error ? error.message : 'Unknown error'
67+ });
6268 const errorResponse: PublicRoomResponse = {
6369 success: false,
6470 error: 'Failed to fetch room data'
@@ -4,7 +4,9 @@ import { messages, users } from '$lib/db/schema';
4 import type { PublicRoomResponse } from '$lib/types/payloads';4 import type { PublicRoomResponse } from '$lib/types/payloads';
5 import { error } from '@sveltejs/kit';5 import { error } from '@sveltejs/kit';
6 import { createSafeUser } from '$lib/types/chat';6 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
7 8
9+const log = createLogger('rooms-server');
8 10
9 export async function GET({ params, url, locals }): Promise<Response> {11 export async function GET({ params, url, locals }): Promise<Response> {
10 const { roomId } = params;12 const { roomId } = params;
@@ -12,11 +14,12 @@ export async function GET({ params, url, locals }): Promise<Response> {
12 14
13 // Check authentication for non-public requests15 // Check authentication for non-public requests
14 if (!isPublic && !locals.user) {16 if (!isPublic && !locals.user) {
17+ log.warn('Authentication required');
15 throw error(401, 'Authentication required');18 throw error(401, 'Authentication required');
16 }19 }
17 20
18 try {21 try {
19- console.log('[Rooms] Fetching room data:', { 22+ log.debug('Fetching room data', {
20 roomId,23 roomId,
21 isPublic,24 isPublic,
22 requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'25 requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'
@@ -47,7 +50,7 @@ export async function GET({ params, url, locals }): Promise<Response> {
47 buddyList: sanitizedUsers50 buddyList: sanitizedUsers
48 };51 };
49 52
50- console.log('[Rooms] Successfully fetched room data:', { 53+ log.debug('Successfully fetched room data', {
51 roomId,54 roomId,
52 userCount: sanitizedUsers.length,55 userCount: sanitizedUsers.length,
53 isPublic56 isPublic
@@ -57,8 +60,11 @@ export async function GET({ params, url, locals }): Promise<Response> {
57 status: 200,60 status: 200,
58 headers: { 'Content-Type': 'application/json' }61 headers: { 'Content-Type': 'application/json' }
59 });62 });
60- } catch {63+ } catch (error) {
61- console.log('[Rooms] Error fetching room data:', { roomId });64+ log.error('Error fetching room data', {
65+ roomId,
66+ error: error instanceof Error ? error.message : 'Unknown error'
67+ });
62 const errorResponse: PublicRoomResponse = {68 const errorResponse: PublicRoomResponse = {
63 success: false,69 success: false,
64 error: 'Failed to fetch room data'70 error: 'Failed to fetch room data'
M src/routes/api/session/login/+server.ts +28 -8
@@ -7,6 +7,9 @@ import { verifyPassword } from '$lib/utils/password';
77 import { generateSessionToken, createSession } from '$lib/api/session.server';
88 import { setSessionTokenCookie } from '$lib/api/session.cookie';
99 import { createSafeUser } from '$lib/types/chat';
10+import { createLogger } from '$lib/utils/logger.server';
11+
12+const log = createLogger('login-server');
1013
1114 // In-memory map to track failed login attempts per IP
1215 const loginAttempts = new Map<string, { count: number, lastAttempt: number }>();
@@ -25,9 +28,10 @@ setInterval(() => {
2528 }, 60 * 60 * 1000);
2629
2730 export const POST: RequestHandler = async ({ request, cookies }) => {
28- console.log("[Login] New login attempt received");
31+ log.debug('New login attempt received');
2932
3033 if (request.method !== 'POST') {
34+ log.warn('Invalid method used', { method: request.method });
3135 return new Response(JSON.stringify({ error: 'Method Not Allowed' } as LoginResponseError), { status: 405 });
3236 }
3337
@@ -40,7 +44,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
4044 // If the user has failed three or more times, apply exponential backoff
4145 if (attemptData.count >= MAX_ATTEMPTS) {
4246 const delay = Math.pow(2, attemptData.count - MAX_ATTEMPTS + 1) * 1000; // delay in milliseconds
43- console.log(`[Login] Rate limit exceeded for IP ${maskedIp} - applying ${delay}ms delay`);
47+ log.warn('Rate limit exceeded', { maskedIp, delay });
4448 await sleep(delay);
4549 }
4650
@@ -48,14 +52,17 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
4852 try {
4953 body = await request.json();
5054 } catch {
51- console.log("[Login] Invalid JSON payload received");
55+ log.warn('Invalid JSON payload received');
5256 return new Response(JSON.stringify({ error: 'Invalid JSON' } as LoginResponseError), { status: 400 });
5357 }
5458
5559 const { username, password } = body as { username: string, password: string };
5660
5761 if (typeof username !== 'string' || typeof password !== 'string') {
58- console.log("Missing or invalid input fields", { username, password });
62+ log.warn('Missing or invalid input fields', {
63+ hasUsername: typeof username === 'string',
64+ hasPassword: typeof password === 'string'
65+ });
5966 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as LoginResponseError), { status: 400 });
6067 }
6168
@@ -68,7 +75,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
6875 attemptData.count++;
6976 attemptData.lastAttempt = now;
7077 loginAttempts.set(ip, attemptData);
71- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);
78+ log.warn('Login failed - user not found', {
79+ maskedIp,
80+ attemptCount: attemptData.count,
81+ username: username.trim()
82+ });
7283 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
7384 return new Response(JSON.stringify({
7485 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
@@ -82,14 +93,18 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
8293 attemptData.count++;
8394 attemptData.lastAttempt = now;
8495 loginAttempts.set(ip, attemptData);
85- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);
96+ log.warn('Login failed - invalid password', {
97+ maskedIp,
98+ attemptCount: attemptData.count,
99+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`
100+ });
86101 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
87102 return new Response(JSON.stringify({
88103 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
89104 } as LoginResponseError), { status: 401 });
90105 }
91- } catch {
92- console.log("[Login] Error verifying password");
106+ } catch (error) {
107+ log.error('Error verifying password', { error });
93108 return new Response(JSON.stringify({ error: 'Internal Server Error' } as LoginResponseError), { status: 500 });
94109 }
95110
@@ -111,6 +126,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
111126 // Set session cookie
112127 setSessionTokenCookie({ cookies }, token, session.expiresAt);
113128
129+ log.info('Login successful', {
130+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`,
131+ expiresAt: new Date(session.expiresAt).toISOString()
132+ });
133+
114134 return new Response(JSON.stringify({
115135 success: true,
116136 user: createSafeUser(user)
@@ -7,6 +7,9 @@ import { verifyPassword } from '$lib/utils/password';
7 import { generateSessionToken, createSession } from '$lib/api/session.server';7 import { generateSessionToken, createSession } from '$lib/api/session.server';
8 import { setSessionTokenCookie } from '$lib/api/session.cookie';8 import { setSessionTokenCookie } from '$lib/api/session.cookie';
9 import { createSafeUser } from '$lib/types/chat';9 import { createSafeUser } from '$lib/types/chat';
10+import { createLogger } from '$lib/utils/logger.server';
11+
12+const log = createLogger('login-server');
10 13
11 // In-memory map to track failed login attempts per IP14 // In-memory map to track failed login attempts per IP
12 const loginAttempts = new Map<string, { count: number, lastAttempt: number }>();15 const loginAttempts = new Map<string, { count: number, lastAttempt: number }>();
@@ -25,9 +28,10 @@ setInterval(() => {
25 }, 60 * 60 * 1000);28 }, 60 * 60 * 1000);
26 29
27 export const POST: RequestHandler = async ({ request, cookies }) => {30 export const POST: RequestHandler = async ({ request, cookies }) => {
28- console.log("[Login] New login attempt received");31+ log.debug('New login attempt received');
29 32
30 if (request.method !== 'POST') {33 if (request.method !== 'POST') {
34+ log.warn('Invalid method used', { method: request.method });
31 return new Response(JSON.stringify({ error: 'Method Not Allowed' } as LoginResponseError), { status: 405 });35 return new Response(JSON.stringify({ error: 'Method Not Allowed' } as LoginResponseError), { status: 405 });
32 }36 }
33 37
@@ -40,7 +44,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
40 // If the user has failed three or more times, apply exponential backoff44 // If the user has failed three or more times, apply exponential backoff
41 if (attemptData.count >= MAX_ATTEMPTS) {45 if (attemptData.count >= MAX_ATTEMPTS) {
42 const delay = Math.pow(2, attemptData.count - MAX_ATTEMPTS + 1) * 1000; // delay in milliseconds46 const delay = Math.pow(2, attemptData.count - MAX_ATTEMPTS + 1) * 1000; // delay in milliseconds
43- console.log(`[Login] Rate limit exceeded for IP ${maskedIp} - applying ${delay}ms delay`);47+ log.warn('Rate limit exceeded', { maskedIp, delay });
44 await sleep(delay);48 await sleep(delay);
45 }49 }
46 50
@@ -48,14 +52,17 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
48 try {52 try {
49 body = await request.json();53 body = await request.json();
50 } catch {54 } catch {
51- console.log("[Login] Invalid JSON payload received");55+ log.warn('Invalid JSON payload received');
52 return new Response(JSON.stringify({ error: 'Invalid JSON' } as LoginResponseError), { status: 400 });56 return new Response(JSON.stringify({ error: 'Invalid JSON' } as LoginResponseError), { status: 400 });
53 }57 }
54 58
55 const { username, password } = body as { username: string, password: string };59 const { username, password } = body as { username: string, password: string };
56 60
57 if (typeof username !== 'string' || typeof password !== 'string') {61 if (typeof username !== 'string' || typeof password !== 'string') {
58- console.log("Missing or invalid input fields", { username, password });62+ log.warn('Missing or invalid input fields', {
63+ hasUsername: typeof username === 'string',
64+ hasPassword: typeof password === 'string'
65+ });
59 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as LoginResponseError), { status: 400 });66 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as LoginResponseError), { status: 400 });
60 }67 }
61 68
@@ -68,7 +75,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
68 attemptData.count++;75 attemptData.count++;
69 attemptData.lastAttempt = now;76 attemptData.lastAttempt = now;
70 loginAttempts.set(ip, attemptData);77 loginAttempts.set(ip, attemptData);
71- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);78+ log.warn('Login failed - user not found', {
79+ maskedIp,
80+ attemptCount: attemptData.count,
81+ username: username.trim()
82+ });
72 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);83 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
73 return new Response(JSON.stringify({ 84 return new Response(JSON.stringify({
74 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}` 85 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
@@ -82,14 +93,18 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
82 attemptData.count++;93 attemptData.count++;
83 attemptData.lastAttempt = now;94 attemptData.lastAttempt = now;
84 loginAttempts.set(ip, attemptData);95 loginAttempts.set(ip, attemptData);
85- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);96+ log.warn('Login failed - invalid password', {
97+ maskedIp,
98+ attemptCount: attemptData.count,
99+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`
100+ });
86 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);101 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
87 return new Response(JSON.stringify({ 102 return new Response(JSON.stringify({
88 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}` 103 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
89 } as LoginResponseError), { status: 401 });104 } as LoginResponseError), { status: 401 });
90 }105 }
91- } catch {106+ } catch (error) {
92- console.log("[Login] Error verifying password");107+ log.error('Error verifying password', { error });
93 return new Response(JSON.stringify({ error: 'Internal Server Error' } as LoginResponseError), { status: 500 });108 return new Response(JSON.stringify({ error: 'Internal Server Error' } as LoginResponseError), { status: 500 });
94 }109 }
95 110
@@ -111,6 +126,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
111 // Set session cookie126 // Set session cookie
112 setSessionTokenCookie({ cookies }, token, session.expiresAt);127 setSessionTokenCookie({ cookies }, token, session.expiresAt);
113 128
129+ log.info('Login successful', {
130+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`,
131+ expiresAt: new Date(session.expiresAt).toISOString()
132+ });
133+
114 return new Response(JSON.stringify({ 134 return new Response(JSON.stringify({
115 success: true,135 success: true,
116 user: createSafeUser(user)136 user: createSafeUser(user)
M src/routes/api/session/logout/+server.ts +6 -1
@@ -1,6 +1,9 @@
11 import { json } from '@sveltejs/kit';
22 import { invalidateSession } from '$lib/api/session.server';
33 import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id
4+import { createLogger } from '$lib/utils/logger.server';
5+
6+const log = createLogger('logout-server');
47
58 export async function POST({ cookies }) {
69 const token = cookies.get('session');
@@ -9,7 +12,9 @@ export async function POST({ cookies }) {
912 const sessionId = await sha256(token);
1013 await invalidateSession(sessionId);
1114 cookies.delete('session', { path: '/' });
12- console.debug("Logged out session with id:", sessionId);
15+ log.info('Session invalidated', { sessionId: `${sessionId.slice(0, 4)}...${sessionId.slice(-4)}` });
16+ } else {
17+ log.debug('No session token found in cookies');
1318 }
1419
1520 return json({ success: true });
@@ -1,6 +1,9 @@
1 import { json } from '@sveltejs/kit';1 import { json } from '@sveltejs/kit';
2 import { invalidateSession } from '$lib/api/session.server';2 import { invalidateSession } from '$lib/api/session.server';
3 import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id3 import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id
4+import { createLogger } from '$lib/utils/logger.server';
5+
6+const log = createLogger('logout-server');
4 7
5 export async function POST({ cookies }) {8 export async function POST({ cookies }) {
6 const token = cookies.get('session');9 const token = cookies.get('session');
@@ -9,7 +12,9 @@ export async function POST({ cookies }) {
9 const sessionId = await sha256(token);12 const sessionId = await sha256(token);
10 await invalidateSession(sessionId);13 await invalidateSession(sessionId);
11 cookies.delete('session', { path: '/' });14 cookies.delete('session', { path: '/' });
12- console.debug("Logged out session with id:", sessionId);15+ log.info('Session invalidated', { sessionId: `${sessionId.slice(0, 4)}...${sessionId.slice(-4)}` });
16+ } else {
17+ log.debug('No session token found in cookies');
13 }18 }
14 19
15 return json({ success: true });20 return json({ success: true });
M src/routes/api/session/validate/+server.ts +12 -2
@@ -1,17 +1,27 @@
11 import { json } from '@sveltejs/kit';
22 import { validateSessionToken } from '$lib/api/session.server';
3+import { createLogger } from '$lib/utils/logger.server';
4+
5+const log = createLogger('validate-server');
36
47 export async function GET({ cookies }) {
58 const token = cookies.get('session');
69
710 if (!token) {
8- console.debug("No session token found in cookies.");
11+ log.debug('No session token found in cookies');
912 return json({ session: null, user: null }, { status: 401 });
1013 }
1114
1215 const result = await validateSessionToken(token);
1316
14- console.debug("Validate endpoint result:", result);
17+ if (result.session && result.user) {
18+ log.debug('Session validated', {
19+ userId: `${result.user.id.slice(0, 4)}...${result.user.id.slice(-4)}`,
20+ sessionId: `${result.session.id.slice(0, 4)}...${result.session.id.slice(-4)}`
21+ });
22+ } else {
23+ log.debug('Invalid session token');
24+ }
1525
1626 return json(result);
1727 } ⏎̸
@@ -1,17 +1,27 @@
1 import { json } from '@sveltejs/kit';1 import { json } from '@sveltejs/kit';
2 import { validateSessionToken } from '$lib/api/session.server';2 import { validateSessionToken } from '$lib/api/session.server';
3+import { createLogger } from '$lib/utils/logger.server';
4+
5+const log = createLogger('validate-server');
3 6
4 export async function GET({ cookies }) {7 export async function GET({ cookies }) {
5 const token = cookies.get('session');8 const token = cookies.get('session');
6 9
7 if (!token) {10 if (!token) {
8- console.debug("No session token found in cookies.");11+ log.debug('No session token found in cookies');
9 return json({ session: null, user: null }, { status: 401 });12 return json({ session: null, user: null }, { status: 401 });
10 }13 }
11 14
12 const result = await validateSessionToken(token);15 const result = await validateSessionToken(token);
13 16
14- console.debug("Validate endpoint result:", result);17+ if (result.session && result.user) {
18+ log.debug('Session validated', {
19+ userId: `${result.user.id.slice(0, 4)}...${result.user.id.slice(-4)}`,
20+ sessionId: `${result.session.id.slice(0, 4)}...${result.session.id.slice(-4)}`
21+ });
22+ } else {
23+ log.debug('Invalid session token');
24+ }
15 25
16 return json(result);26 return json(result);
17 } ⏎̸27 } ⏎̸
M src/routes/api/sse/+server.ts +26 -26
@@ -10,6 +10,9 @@ import db from '$lib/db/db.server';
1010 import { users } from '$lib/db/schema';
1111 import { eq, and, lt } from 'drizzle-orm';
1212 import { createSafeUser } from '$lib/types/chat';
13+import { createLogger } from '$lib/utils/logger.server';
14+
15+const log = createLogger('sse-server');
1316
1417 console.log('[SSE] Server initialized');
1518
@@ -35,7 +38,7 @@ function startTimeoutCheck() {
3538
3639 timeoutInterval = setInterval(async () => {
3740 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;
38- console.log('[SSE] Checking for timed out users', {
41+ log.debug('Checking for timed out users', {
3942 checkTime: new Date().toISOString(),
4043 timeoutThreshold: new Date(timeoutThreshold).toISOString()
4144 });
@@ -60,13 +63,13 @@ function startTimeoutCheck() {
6063 });
6164
6265 if (result.length > 0) {
63- console.log('[SSE] Found timed out users:', result.map(user => ({
64- nickname: user.nickname,
65- lastSeen: new Date(user.lastSeen || 0).toISOString()
66- })));
66+ log.info('User status updated to offline', {
67+ usersUpdated: result.length,
68+ timeoutThreshold: new Date(timeoutThreshold).toISOString()
69+ });
6770 }
6871 } catch (error) {
69- console.error('[SSE] Error during timeout check:', error);
72+ log.error('Error during timeout check', { error });
7073 }
7174 }, CHECK_INTERVAL_MS);
7275 }
@@ -81,7 +84,7 @@ function startBuddyListUpdateInterval() {
8184 const now = Date.now();
8285 // Only fetch and broadcast if 10 seconds have passed since last update
8386 if (now - lastBuddyListUpdate >= BUDDY_LIST_BROADCAST_INTERVAL) {
84- console.log('[SSE] Broadcasting buddy list update...');
87+ log.debug('Broadcasting buddy list update...');
8588 // Query the complete buddy list from the database.
8689 const buddyList = await db.select().from(users);
8790 // Use createSafeUser to properly sanitize user data
@@ -98,7 +101,7 @@ function startBuddyListUpdateInterval() {
98101 lastBuddyListUpdate = now;
99102 }
100103 } catch (error) {
101- console.error('[SSE] Error fetching buddy list for update:', error);
104+ log.error('Error fetching buddy list for update', { error });
102105 }
103106 }, 1000); // Check every second but only broadcast every 10 seconds
104107 }
@@ -108,9 +111,9 @@ if (!globalThis.__sseIntervalsStarted) {
108111 startTimeoutCheck();
109112 startBuddyListUpdateInterval();
110113 globalThis.__sseIntervalsStarted = true;
111- console.log('[SSE] Started global intervals for timeout and buddy list updates.');
114+ log.info('Started global intervals for timeout and buddy list updates');
112115 } else {
113- console.log('[SSE] Global intervals already started, skipping initialization.');
116+ log.debug('Global intervals already started, skipping initialization');
114117 }
115118
116119 // Ensure the interval is cleared if the module is reloaded
@@ -134,17 +137,14 @@ if (typeof process !== 'undefined') {
134137 });
135138 }
136139
137-export const GET: RequestHandler = async ({ request, locals, cookies }) => {
138- console.log('[SSE] Connection attempt:', {
139- hasLocals: !!locals,
140- hasUser: !!locals.user,
141- userId: locals.user?.id,
142- hasSessionCookie: !!cookies.get('session'),
143- headers: Object.fromEntries(request.headers.entries())
140+export const GET: RequestHandler = async ({ request, locals, }) => {
141+ log.debug('Processing SSE request', {
142+ url: request.url,
143+ userId: locals.user?.id
144144 });
145145
146146 if (!locals.user) {
147- console.log('[SSE] Authentication failed - no user in locals');
147+ log.warn('Authentication failed - no user in locals');
148148 return new Response('Unauthorized', {
149149 status: 401,
150150 headers: {
@@ -168,7 +168,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
168168 })
169169 .where(eq(users.id, userId));
170170
171- console.log('[SSE] User status updated to online:', {
171+ log.info('User status updated to online', {
172172 userId: `${userId.slice(0, 4)}...${userId.slice(-4)}`,
173173 timestamp: new Date(now).toISOString()
174174 });
@@ -182,7 +182,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
182182 }
183183 });
184184 } catch (error) {
185- console.error('[SSE] Error updating user status:', error);
185+ log.error('Error updating user status', { error });
186186 }
187187
188188 const stream = new ReadableStream({
@@ -193,10 +193,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
193193 `data: ${JSON.stringify(event.data)}\n\n`;
194194 controller.enqueue(encoder.encode(payload));
195195 if (!['userStatusUpdate', 'chatMessage'].includes(event.type)) {
196- console.log('[SSE] Event sent:', { type: event.type });
196+ log.info('Event sent', { type: event.type });
197197 }
198- } catch {
199- console.log('[SSE] Error sending event');
198+ } catch (error) {
199+ log.error('Error sending event', { error });
200200 }
201201 };
202202
@@ -208,10 +208,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
208208 request.signal.addEventListener('abort', () => {
209209 if (onSSE) {
210210 sseEmitter.removeListener('sse', onSSE);
211- console.log('[SSE] Removed listener due to abort signal.');
211+ log.info('Removed listener due to abort signal');
212212 }
213213 });
214- console.log('[SSE] Added new listener');
214+ log.info('Added new listener');
215215
216216 controller.enqueue(encoder.encode('data: Connected\n\n'));
217217
@@ -223,7 +223,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
223223 if (keepAliveInterval) clearInterval(keepAliveInterval);
224224 if (onSSE) sseEmitter.removeListener('sse', onSSE);
225225 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
226- console.log('[SSE] Connection closed:', { userId: maskedUserId, reason });
226+ log.info('Connection closed', { userId: maskedUserId, reason });
227227
228228 const now = Date.now();
229229 await db.update(users)
@@ -10,6 +10,9 @@ import db from '$lib/db/db.server';
10 import { users } from '$lib/db/schema';10 import { users } from '$lib/db/schema';
11 import { eq, and, lt } from 'drizzle-orm';11 import { eq, and, lt } from 'drizzle-orm';
12 import { createSafeUser } from '$lib/types/chat';12 import { createSafeUser } from '$lib/types/chat';
13+import { createLogger } from '$lib/utils/logger.server';
14+
15+const log = createLogger('sse-server');
13 16
14 console.log('[SSE] Server initialized');17 console.log('[SSE] Server initialized');
15 18
@@ -35,7 +38,7 @@ function startTimeoutCheck() {
35 38
36 timeoutInterval = setInterval(async () => {39 timeoutInterval = setInterval(async () => {
37 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;40 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;
38- console.log('[SSE] Checking for timed out users', { 41+ log.debug('Checking for timed out users', {
39 checkTime: new Date().toISOString(),42 checkTime: new Date().toISOString(),
40 timeoutThreshold: new Date(timeoutThreshold).toISOString()43 timeoutThreshold: new Date(timeoutThreshold).toISOString()
41 });44 });
@@ -60,13 +63,13 @@ function startTimeoutCheck() {
60 });63 });
61 64
62 if (result.length > 0) {65 if (result.length > 0) {
63- console.log('[SSE] Found timed out users:', result.map(user => ({66+ log.info('User status updated to offline', {
64- nickname: user.nickname,67+ usersUpdated: result.length,
65- lastSeen: new Date(user.lastSeen || 0).toISOString()68+ timeoutThreshold: new Date(timeoutThreshold).toISOString()
66- })));69+ });
67 }70 }
68 } catch (error) {71 } catch (error) {
69- console.error('[SSE] Error during timeout check:', error);72+ log.error('Error during timeout check', { error });
70 }73 }
71 }, CHECK_INTERVAL_MS);74 }, CHECK_INTERVAL_MS);
72 }75 }
@@ -81,7 +84,7 @@ function startBuddyListUpdateInterval() {
81 const now = Date.now();84 const now = Date.now();
82 // Only fetch and broadcast if 10 seconds have passed since last update85 // Only fetch and broadcast if 10 seconds have passed since last update
83 if (now - lastBuddyListUpdate >= BUDDY_LIST_BROADCAST_INTERVAL) {86 if (now - lastBuddyListUpdate >= BUDDY_LIST_BROADCAST_INTERVAL) {
84- console.log('[SSE] Broadcasting buddy list update...');87+ log.debug('Broadcasting buddy list update...');
85 // Query the complete buddy list from the database.88 // Query the complete buddy list from the database.
86 const buddyList = await db.select().from(users);89 const buddyList = await db.select().from(users);
87 // Use createSafeUser to properly sanitize user data90 // Use createSafeUser to properly sanitize user data
@@ -98,7 +101,7 @@ function startBuddyListUpdateInterval() {
98 lastBuddyListUpdate = now;101 lastBuddyListUpdate = now;
99 }102 }
100 } catch (error) {103 } catch (error) {
101- console.error('[SSE] Error fetching buddy list for update:', error);104+ log.error('Error fetching buddy list for update', { error });
102 }105 }
103 }, 1000); // Check every second but only broadcast every 10 seconds106 }, 1000); // Check every second but only broadcast every 10 seconds
104 }107 }
@@ -108,9 +111,9 @@ if (!globalThis.__sseIntervalsStarted) {
108 startTimeoutCheck();111 startTimeoutCheck();
109 startBuddyListUpdateInterval();112 startBuddyListUpdateInterval();
110 globalThis.__sseIntervalsStarted = true;113 globalThis.__sseIntervalsStarted = true;
111- console.log('[SSE] Started global intervals for timeout and buddy list updates.');114+ log.info('Started global intervals for timeout and buddy list updates');
112 } else {115 } else {
113- console.log('[SSE] Global intervals already started, skipping initialization.');116+ log.debug('Global intervals already started, skipping initialization');
114 }117 }
115 118
116 // Ensure the interval is cleared if the module is reloaded119 // Ensure the interval is cleared if the module is reloaded
@@ -134,17 +137,14 @@ if (typeof process !== 'undefined') {
134 });137 });
135 }138 }
136 139
137-export const GET: RequestHandler = async ({ request, locals, cookies }) => {140+export const GET: RequestHandler = async ({ request, locals, }) => {
138- console.log('[SSE] Connection attempt:', {141+ log.debug('Processing SSE request', {
139- hasLocals: !!locals,142+ url: request.url,
140- hasUser: !!locals.user,143+ userId: locals.user?.id
141- userId: locals.user?.id,
142- hasSessionCookie: !!cookies.get('session'),
143- headers: Object.fromEntries(request.headers.entries())
144 });144 });
145 145
146 if (!locals.user) {146 if (!locals.user) {
147- console.log('[SSE] Authentication failed - no user in locals');147+ log.warn('Authentication failed - no user in locals');
148 return new Response('Unauthorized', { 148 return new Response('Unauthorized', {
149 status: 401,149 status: 401,
150 headers: {150 headers: {
@@ -168,7 +168,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
168 })168 })
169 .where(eq(users.id, userId));169 .where(eq(users.id, userId));
170 170
171- console.log('[SSE] User status updated to online:', {171+ log.info('User status updated to online', {
172 userId: `${userId.slice(0, 4)}...${userId.slice(-4)}`,172 userId: `${userId.slice(0, 4)}...${userId.slice(-4)}`,
173 timestamp: new Date(now).toISOString()173 timestamp: new Date(now).toISOString()
174 });174 });
@@ -182,7 +182,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
182 }182 }
183 });183 });
184 } catch (error) {184 } catch (error) {
185- console.error('[SSE] Error updating user status:', error);185+ log.error('Error updating user status', { error });
186 }186 }
187 187
188 const stream = new ReadableStream({188 const stream = new ReadableStream({
@@ -193,10 +193,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
193 `data: ${JSON.stringify(event.data)}\n\n`;193 `data: ${JSON.stringify(event.data)}\n\n`;
194 controller.enqueue(encoder.encode(payload));194 controller.enqueue(encoder.encode(payload));
195 if (!['userStatusUpdate', 'chatMessage'].includes(event.type)) {195 if (!['userStatusUpdate', 'chatMessage'].includes(event.type)) {
196- console.log('[SSE] Event sent:', { type: event.type });196+ log.info('Event sent', { type: event.type });
197 }197 }
198- } catch {198+ } catch (error) {
199- console.log('[SSE] Error sending event');199+ log.error('Error sending event', { error });
200 }200 }
201 };201 };
202 202
@@ -208,10 +208,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
208 request.signal.addEventListener('abort', () => {208 request.signal.addEventListener('abort', () => {
209 if (onSSE) {209 if (onSSE) {
210 sseEmitter.removeListener('sse', onSSE);210 sseEmitter.removeListener('sse', onSSE);
211- console.log('[SSE] Removed listener due to abort signal.');211+ log.info('Removed listener due to abort signal');
212 }212 }
213 });213 });
214- console.log('[SSE] Added new listener');214+ log.info('Added new listener');
215 215
216 controller.enqueue(encoder.encode('data: Connected\n\n'));216 controller.enqueue(encoder.encode('data: Connected\n\n'));
217 217
@@ -223,7 +223,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
223 if (keepAliveInterval) clearInterval(keepAliveInterval);223 if (keepAliveInterval) clearInterval(keepAliveInterval);
224 if (onSSE) sseEmitter.removeListener('sse', onSSE);224 if (onSSE) sseEmitter.removeListener('sse', onSSE);
225 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;225 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
226- console.log('[SSE] Connection closed:', { userId: maskedUserId, reason });226+ log.info('Connection closed', { userId: maskedUserId, reason });
227 227
228 const now = Date.now();228 const now = Date.now();
229 await db.update(users)229 await db.update(users)
M src/routes/api/status/+server.ts +11 -3
@@ -5,21 +5,26 @@ import { eq } from 'drizzle-orm';
55 import { validateSessionToken, generateSessionToken, createSession } from '$lib/api/session.server';
66 import { setSessionTokenCookie } from '$lib/api/session.cookie';
77 import { createSafeUser } from '$lib/types/chat';
8+import { createLogger } from '$lib/utils/logger.server';
9+
10+const log = createLogger('status-server');
811
912 export async function POST({ request, cookies, locals }) {
1013 if (!locals.user) {
14+ log.warn('Authentication required');
1115 throw error(401, 'Authentication required');
1216 }
1317
1418 const { status } = await request.json();
1519 const validStatuses = ['online', 'away', 'busy', 'offline'];
1620 if (!validStatuses.includes(status)) {
21+ log.warn('Invalid status received', { status });
1722 throw error(400, 'Invalid status');
1823 }
1924
2025 const userId = locals.user.id;
2126 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
22- console.log('[Status] Updating user status:', { userId: maskedUserId, status });
27+ log.debug('Updating user status', { userId: maskedUserId, status });
2328
2429 const now = Date.now();
2530
@@ -33,7 +38,7 @@ export async function POST({ request, cookies, locals }) {
3338 .where(eq(users.id, userId))
3439 .returning()
3540 .get();
36- console.log('[Status] Database updated successfully');
41+ log.debug('Database updated successfully', { userId: maskedUserId, status });
3742
3843 // Renew session if status is online and session is near expiry
3944 if (status === 'online') {
@@ -47,7 +52,10 @@ export async function POST({ request, cookies, locals }) {
4752 const newToken = generateSessionToken();
4853 const newSession = await createSession(newToken, userId);
4954 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);
50- console.log('[Status] Session renewed:', { userId: maskedUserId, expiresAt: new Date(newSession.expiresAt).toISOString() });
55+ log.info('Session renewed', {
56+ userId: maskedUserId,
57+ expiresAt: new Date(newSession.expiresAt).toISOString()
58+ });
5159 }
5260 }
5361 }
@@ -5,21 +5,26 @@ import { eq } from 'drizzle-orm';
5 import { validateSessionToken, generateSessionToken, createSession } from '$lib/api/session.server';5 import { validateSessionToken, generateSessionToken, createSession } from '$lib/api/session.server';
6 import { setSessionTokenCookie } from '$lib/api/session.cookie';6 import { setSessionTokenCookie } from '$lib/api/session.cookie';
7 import { createSafeUser } from '$lib/types/chat';7 import { createSafeUser } from '$lib/types/chat';
8+import { createLogger } from '$lib/utils/logger.server';
9+
10+const log = createLogger('status-server');
8 11
9 export async function POST({ request, cookies, locals }) {12 export async function POST({ request, cookies, locals }) {
10 if (!locals.user) {13 if (!locals.user) {
14+ log.warn('Authentication required');
11 throw error(401, 'Authentication required');15 throw error(401, 'Authentication required');
12 }16 }
13 17
14 const { status } = await request.json();18 const { status } = await request.json();
15 const validStatuses = ['online', 'away', 'busy', 'offline'];19 const validStatuses = ['online', 'away', 'busy', 'offline'];
16 if (!validStatuses.includes(status)) {20 if (!validStatuses.includes(status)) {
21+ log.warn('Invalid status received', { status });
17 throw error(400, 'Invalid status');22 throw error(400, 'Invalid status');
18 }23 }
19 24
20 const userId = locals.user.id;25 const userId = locals.user.id;
21 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;26 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
22- console.log('[Status] Updating user status:', { userId: maskedUserId, status });27+ log.debug('Updating user status', { userId: maskedUserId, status });
23 28
24 const now = Date.now();29 const now = Date.now();
25 30
@@ -33,7 +38,7 @@ export async function POST({ request, cookies, locals }) {
33 .where(eq(users.id, userId))38 .where(eq(users.id, userId))
34 .returning()39 .returning()
35 .get();40 .get();
36- console.log('[Status] Database updated successfully');41+ log.debug('Database updated successfully', { userId: maskedUserId, status });
37 42
38 // Renew session if status is online and session is near expiry43 // Renew session if status is online and session is near expiry
39 if (status === 'online') {44 if (status === 'online') {
@@ -47,7 +52,10 @@ export async function POST({ request, cookies, locals }) {
47 const newToken = generateSessionToken();52 const newToken = generateSessionToken();
48 const newSession = await createSession(newToken, userId);53 const newSession = await createSession(newToken, userId);
49 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);54 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);
50- console.log('[Status] Session renewed:', { userId: maskedUserId, expiresAt: new Date(newSession.expiresAt).toISOString() });55+ log.info('Session renewed', {
56+ userId: maskedUserId,
57+ expiresAt: new Date(newSession.expiresAt).toISOString()
58+ });
51 }59 }
52 }60 }
53 }61 }
M src/routes/api/users/[userId]/+server.ts +12 -5
@@ -4,10 +4,13 @@ import { users } from '$lib/db/schema';
44 import { eq } from 'drizzle-orm';
55 import type { RequestHandler } from './$types';
66 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
8+
9+const log = createLogger('users-server');
710
811 export const GET: RequestHandler = async ({ params }) => {
912 const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;
10- console.log('[Users] Fetching user data:', { userId: maskedUserId });
13+ log.debug('Fetching user data', { userId: maskedUserId });
1114
1215 try {
1316 const user = await db.select()
@@ -16,23 +19,27 @@ export const GET: RequestHandler = async ({ params }) => {
1619 .get();
1720
1821 if (!user) {
19- console.log('[Users] User not found:', { userId: maskedUserId });
22+ log.warn('User not found', { userId: maskedUserId });
2023 throw error(404, 'User not found');
2124 }
2225
2326 const safeUser = createSafeUser(user);
2427
25- console.log('[Users] User data retrieved:', {
28+ log.debug('User data retrieved', {
2629 userId: maskedUserId,
2730 status: safeUser.status,
2831 lastSeen: safeUser.lastSeen ? new Date(safeUser.lastSeen).toISOString() : null
2932 });
33+
3034 return json({
3135 success: true,
3236 user: safeUser
3337 });
34- } catch {
35- console.log('[Users] Error fetching user data:', { userId: maskedUserId });
38+ } catch (err) {
39+ log.error('Error fetching user data', {
40+ userId: maskedUserId,
41+ error: err instanceof Error ? err.message : 'Unknown error'
42+ });
3643 throw error(500, 'Failed to fetch user information');
3744 }
3845 };⏎̸
@@ -4,10 +4,13 @@ import { users } from '$lib/db/schema';
4 import { eq } from 'drizzle-orm';4 import { eq } from 'drizzle-orm';
5 import type { RequestHandler } from './$types';5 import type { RequestHandler } from './$types';
6 import { createSafeUser } from '$lib/types/chat';6 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
8+
9+const log = createLogger('users-server');
7 10
8 export const GET: RequestHandler = async ({ params }) => {11 export const GET: RequestHandler = async ({ params }) => {
9 const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;12 const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;
10- console.log('[Users] Fetching user data:', { userId: maskedUserId });13+ log.debug('Fetching user data', { userId: maskedUserId });
11 14
12 try {15 try {
13 const user = await db.select()16 const user = await db.select()
@@ -16,23 +19,27 @@ export const GET: RequestHandler = async ({ params }) => {
16 .get();19 .get();
17 20
18 if (!user) {21 if (!user) {
19- console.log('[Users] User not found:', { userId: maskedUserId });22+ log.warn('User not found', { userId: maskedUserId });
20 throw error(404, 'User not found');23 throw error(404, 'User not found');
21 }24 }
22 25
23 const safeUser = createSafeUser(user);26 const safeUser = createSafeUser(user);
24 27
25- console.log('[Users] User data retrieved:', { 28+ log.debug('User data retrieved', {
26 userId: maskedUserId,29 userId: maskedUserId,
27 status: safeUser.status,30 status: safeUser.status,
28 lastSeen: safeUser.lastSeen ? new Date(safeUser.lastSeen).toISOString() : null31 lastSeen: safeUser.lastSeen ? new Date(safeUser.lastSeen).toISOString() : null
29 });32 });
33+
30 return json({34 return json({
31 success: true,35 success: true,
32 user: safeUser36 user: safeUser
33 });37 });
34- } catch {38+ } catch (err) {
35- console.log('[Users] Error fetching user data:', { userId: maskedUserId });39+ log.error('Error fetching user data', {
40+ userId: maskedUserId,
41+ error: err instanceof Error ? err.message : 'Unknown error'
42+ });
36 throw error(500, 'Failed to fetch user information');43 throw error(500, 'Failed to fetch user information');
37 }44 }
38 };⏎̸45 };⏎̸
M vite.config.ts +23 -3
@@ -2,6 +2,19 @@ import { defineConfig } from 'vitest/config';
22 import { loadEnv } from 'vite';
33 import { sveltekit } from '@sveltejs/kit/vite';
44
5+/**
6+ * Logging configuration
7+ * This project uses two logging systems:
8+ * 1. Console logging (development/client-side) - controlled by console stripping
9+ * 2. Pino logging (server-side) - controlled by LOG_LEVEL
10+ *
11+ * LOG_LEVEL affects both systems:
12+ * - error: only error logs
13+ * - warn: warn and error logs
14+ * - info: info, warn, and error logs (default in production)
15+ * - debug: all logs (default in development)
16+ */
17+
518 // Define which console methods to strip based on LOG_LEVEL
619 const getConsoleFuncsToStrip = (logLevel: string) => {
720 switch (logLevel?.toLowerCase()) {
@@ -18,9 +31,11 @@ const getConsoleFuncsToStrip = (logLevel: string) => {
1831 };
1932
2033 export default defineConfig(({ mode }) => {
21- // Load env file based on `mode` value. mode can be 'development', 'production', etc.
34+ // Load env file based on `mode` value
2235 const env = loadEnv(mode, process.cwd(), '');
23- console.log(`Building with LOG_LEVEL=${env.LOG_LEVEL || 'info'} in ${mode} mode`);
36+ const logLevel = env.LOG_LEVEL || (mode === 'development' ? 'debug' : 'info');
37+
38+ console.log(`Building with LOG_LEVEL=${logLevel} in ${mode} mode`);
2439
2540 return {
2641 plugins: [sveltekit()],
@@ -31,9 +46,14 @@ export default defineConfig(({ mode }) => {
3146 minify: 'terser',
3247 terserOptions: {
3348 compress: {
34- pure_funcs: getConsoleFuncsToStrip(env.LOG_LEVEL || 'info')
49+ // Strip console methods based on LOG_LEVEL
50+ pure_funcs: getConsoleFuncsToStrip(logLevel)
3551 }
3652 },
53+ },
54+ // Make LOG_LEVEL available to client-side code
55+ define: {
56+ 'process.env.LOG_LEVEL': JSON.stringify(logLevel)
3757 }
3858 };
3959 });
@@ -2,6 +2,19 @@ import { defineConfig } from 'vitest/config';
2 import { loadEnv } from 'vite';2 import { loadEnv } from 'vite';
3 import { sveltekit } from '@sveltejs/kit/vite';3 import { sveltekit } from '@sveltejs/kit/vite';
4 4
5+/**
6+ * Logging configuration
7+ * This project uses two logging systems:
8+ * 1. Console logging (development/client-side) - controlled by console stripping
9+ * 2. Pino logging (server-side) - controlled by LOG_LEVEL
10+ *
11+ * LOG_LEVEL affects both systems:
12+ * - error: only error logs
13+ * - warn: warn and error logs
14+ * - info: info, warn, and error logs (default in production)
15+ * - debug: all logs (default in development)
16+ */
17+
5 // Define which console methods to strip based on LOG_LEVEL18 // Define which console methods to strip based on LOG_LEVEL
6 const getConsoleFuncsToStrip = (logLevel: string) => {19 const getConsoleFuncsToStrip = (logLevel: string) => {
7 switch (logLevel?.toLowerCase()) {20 switch (logLevel?.toLowerCase()) {
@@ -18,9 +31,11 @@ const getConsoleFuncsToStrip = (logLevel: string) => {
18 };31 };
19 32
20 export default defineConfig(({ mode }) => {33 export default defineConfig(({ mode }) => {
21- // Load env file based on `mode` value. mode can be 'development', 'production', etc.34+ // Load env file based on `mode` value
22 const env = loadEnv(mode, process.cwd(), '');35 const env = loadEnv(mode, process.cwd(), '');
23- console.log(`Building with LOG_LEVEL=${env.LOG_LEVEL || 'info'} in ${mode} mode`);36+ const logLevel = env.LOG_LEVEL || (mode === 'development' ? 'debug' : 'info');
37+
38+ console.log(`Building with LOG_LEVEL=${logLevel} in ${mode} mode`);
24 39
25 return {40 return {
26 plugins: [sveltekit()],41 plugins: [sveltekit()],
@@ -31,9 +46,14 @@ export default defineConfig(({ mode }) => {
31 minify: 'terser',46 minify: 'terser',
32 terserOptions: {47 terserOptions: {
33 compress: {48 compress: {
34- pure_funcs: getConsoleFuncsToStrip(env.LOG_LEVEL || 'info')49+ // Strip console methods based on LOG_LEVEL
50+ pure_funcs: getConsoleFuncsToStrip(logLevel)
35 }51 }
36 },52 },
53+ },
54+ // Make LOG_LEVEL available to client-side code
55+ define: {
56+ 'process.env.LOG_LEVEL': JSON.stringify(logLevel)
37 }57 }
38 };58 };
39 });59 });

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close