feat: add pino logger for serverside

This commit is contained in:
Levente Orban
2025-10-27 09:33:00 +01:00
parent 935042dd06
commit 2a96d3762c
11 changed files with 367 additions and 24 deletions

View File

@@ -2,6 +2,7 @@
import type { Handle } from '@sveltejs/kit';
import { generateUserId } from '$lib/generateUserId.js';
import { ensureDatabaseConnection } from '$lib/database/healthCheck';
import { logger } from '$lib/logger';
// Global flag to track if database health check has been performed
let dbHealthCheckPerformed = false;
@@ -18,7 +19,7 @@ export const handle: Handle = async ({ event, resolve }) => {
});
dbHealthCheckPerformed = true;
} catch (error) {
console.error('Database health check failed:', error);
logger.error({ error }, 'Database health check failed');
// The ensureDatabaseConnection function will exit the process
// if the database is unavailable, so this catch is just for safety
process.exit(1);
@@ -33,11 +34,10 @@ export const handle: Handle = async ({ event, resolve }) => {
const PATH = '/';
if (!cactoideUserId) {
console.debug(`There is no cactoideUserId cookie, generating new one...`);
logger.debug({ userId }, 'No cactoideUserId cookie found, generating new one');
event.cookies.set('cactoideUserId', userId, { path: PATH, maxAge: MAX_AGE });
} else {
console.debug(`cactoideUserId: ${cactoideUserId}`);
console.debug(`cactoideUserId cookie found, using existing one...`);
logger.debug({ cactoideUserId }, 'cactoideUserId cookie found, using existing one');
}
return resolve(event);

View File

@@ -1,5 +1,6 @@
import postgres from 'postgres';
import { env } from '$env/dynamic/private';
import { logger } from '$lib/logger';
interface HealthCheckOptions {
maxRetries?: number;
@@ -29,7 +30,7 @@ export async function checkDatabaseHealth(
} = options;
if (!env.DATABASE_URL) {
console.error('DATABASE_URL environment variable is not set');
logger.error('DATABASE_URL environment variable is not set');
return {
success: false,
error: 'DATABASE_URL environment variable is not set',
@@ -39,10 +40,10 @@ export async function checkDatabaseHealth(
let lastError: Error | null = null;
console.log(`Starting database health check (max retries: ${maxRetries})`);
logger.info({ maxRetries }, 'Starting database health check');
for (let attempt = 1; attempt <= maxRetries; attempt++) {
console.log(`Attempt ${attempt}/${maxRetries} - Testing database connection`);
logger.debug({ attempt, maxRetries }, 'Testing database connection');
try {
// Create a new connection for the health check
@@ -57,7 +58,7 @@ export async function checkDatabaseHealth(
await client`SELECT 1 as health_check`;
await client.end();
console.log(`Database connection successful on attempt ${attempt}.`);
logger.info({ attempt }, 'Database connection successful');
return {
success: true,
@@ -66,12 +67,12 @@ export async function checkDatabaseHealth(
} catch (error) {
lastError = error as Error;
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error(`Connection failed on attempt ${attempt}: ${errorMessage}`);
logger.warn({ attempt, error: errorMessage }, 'Database connection failed');
// Don't wait after the last attempt
if (attempt < maxRetries) {
const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
console.log(`Waiting ${delay}ms before retry...`);
logger.debug({ delay }, 'Waiting before retry');
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
@@ -79,7 +80,10 @@ export async function checkDatabaseHealth(
const finalError = lastError?.message || 'Unknown database connection error';
console.error(`All ${maxRetries} attempts failed. Final error: ${finalError}`);
logger.error(
{ attempts: maxRetries, error: finalError },
'All database connection attempts failed'
);
return {
success: false,
@@ -95,10 +99,10 @@ export async function ensureDatabaseConnection(options?: HealthCheckOptions): Pr
const result = await checkDatabaseHealth(options);
if (!result.success) {
console.error('Database connection failed after all retry attempts');
console.error(`Error: ${result.error}`);
console.error(`Attempts made: ${result.attempts}`);
console.error('Exiting application...');
logger.fatal(
{ error: result.error, attempts: result.attempts },
'Database connection failed after all retry attempts. Exiting application'
);
process.exit(1);
}
}

29
src/lib/logger.ts Normal file
View File

@@ -0,0 +1,29 @@
import pino from 'pino';
import { LOG_PRETTY, LOG_LEVEL } from '$env/static/private';
const USE_PRETTY_LOGS = LOG_PRETTY === 'true';
const transport = USE_PRETTY_LOGS
? {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname'
}
}
: undefined;
// Create the logger instance
export const logger = pino({
level: LOG_LEVEL,
transport
});
// Export a helper to create child loggers with context
export function createChildLogger(bindings: Record<string, unknown>) {
return logger.child(bindings);
}
// Export types
export type Logger = typeof logger;

View File

@@ -2,7 +2,11 @@ import { database } from '$lib/database/db';
import { events, inviteTokens } from '$lib/database/schema';
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
<<<<<<< HEAD
import { generateInviteToken, calculateTokenExpiration } from '$lib/inviteTokenHelpers.js';
=======
import { logger } from '$lib/logger';
>>>>>>> 222c2ee (feat: add pino logger for serverside)
// Generate a random URL-friendly ID
function generateEventId(): string {
@@ -116,7 +120,7 @@ export const actions: Actions = {
userId: userId!
})
.catch((error) => {
console.error('Unexpected error', error);
logger.error({ error, eventId, userId }, 'Unexpected error creating event');
throw error;
});

View File

@@ -2,6 +2,7 @@ import { database } from '$lib/database/db';
import { desc, inArray } from 'drizzle-orm';
import type { PageServerLoad } from './$types';
import { events } from '$lib/database/schema';
import { logger } from '$lib/logger';
export const load: PageServerLoad = async () => {
try {
@@ -33,7 +34,7 @@ export const load: PageServerLoad = async () => {
events: transformedEvents
};
} catch (error) {
console.error('Error loading public events:', error);
logger.error({ error }, 'Error loading public events');
// Return empty array on error to prevent page crash
return {

View File

@@ -3,6 +3,7 @@ import { events } from '$lib/database/schema';
import { fail } from '@sveltejs/kit';
import { eq, desc } from 'drizzle-orm';
import type { Actions } from './$types';
import { logger } from '$lib/logger';
export const load = async ({ cookies }) => {
const userId = cookies.get('cactoideUserId');
@@ -36,7 +37,7 @@ export const load = async ({ cookies }) => {
return { events: transformedEvents };
} catch (error) {
console.error('Error loading user events:', error);
logger.error({ error, userId }, 'Error loading user events');
return { events: [] };
}
};
@@ -68,7 +69,7 @@ export const actions: Actions = {
return { success: true };
} catch (error) {
console.error('Error deleting event:', error);
logger.error({ error, eventId, userId }, 'Error deleting event');
return fail(500, { error: 'Failed to delete event' });
}
}

View File

@@ -3,6 +3,7 @@ import { events, rsvps } from '$lib/database/schema';
import { eq, asc } from 'drizzle-orm';
import { error, fail } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { logger } from '$lib/logger';
export const load: PageServerLoad = async ({ params, cookies }) => {
const eventId = params.id;
@@ -70,7 +71,7 @@ export const load: PageServerLoad = async ({ params, cookies }) => {
} catch (err) {
if (err instanceof Response) throw err; // This is the 404 error
console.error('Error loading event:', err);
logger.error({ error: err, eventId }, 'Error loading event');
throw error(500, 'Failed to load event');
}
};
@@ -145,7 +146,7 @@ export const actions: Actions = {
return { success: true, type: 'add' };
} catch (err) {
console.error('Error adding RSVP:', err);
logger.error({ error: err, eventId, userId, name }, 'Error adding RSVP');
return fail(500, { error: 'Failed to add RSVP' });
}
},
@@ -163,7 +164,7 @@ export const actions: Actions = {
await database.delete(rsvps).where(eq(rsvps.id, rsvpId));
return { success: true, type: 'remove' };
} catch (err) {
console.error('Error removing RSVP:', err);
logger.error({ error: err, rsvpId }, 'Error removing RSVP');
return fail(500, { error: 'Failed to remove RSVP' });
}
}

View File

@@ -3,6 +3,7 @@ import { events, inviteTokens } from '$lib/database/schema';
import { eq, and } from 'drizzle-orm';
import { fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
import { logger } from '$lib/logger';
export const load: PageServerLoad = async ({ params, cookies }) => {
const eventId = params.id;
@@ -165,7 +166,7 @@ export const actions: Actions = {
})
.where(and(eq(events.id, eventId), eq(events.userId, userId)))
.catch((error) => {
console.error('Unexpected error updating event', error);
logger.error({ error, eventId, userId }, 'Unexpected error updating event');
throw error;
});