diff --git a/src/app.js b/src/app.js index 110a722..13cea49 100644 --- a/src/app.js +++ b/src/app.js @@ -10,6 +10,7 @@ import { validateBody } from './middlewares/global/validateBody.js'; import { notFound } from './middlewares/global/notFound.js'; import { errorHandler } from './middlewares/global/errorHandler.js'; import { apiRateLimiter } from './middlewares/security/rateLimiter.js'; +import { logger } from './utils/logger.js'; // Import routes import authRoutes from './routes/auth.routes.js'; @@ -29,8 +30,9 @@ import eventRoutes from './routes/event.routes.js'; const app = express(); -// Security middleware -app.use(morgan('dev')); +// HTTP request logging via Morgan -> custom logger +const morganStream = { write: (message) => logger.http(message.trim()) }; +app.use(morgan(':method :url :status :response-time ms', { stream: morganStream })); app.use( helmet({ contentSecurityPolicy: { diff --git a/src/config/database.js b/src/config/database.js index 476adcb..fc2b497 100644 --- a/src/config/database.js +++ b/src/config/database.js @@ -15,9 +15,9 @@ const pool = new Pool({ connectionTimeoutMillis: 2000, }); -// Note: Connection logging handled in index.js to avoid circular dependencies +// Critical database errors - use console.error directly to avoid circular imports pool.on('error', (err) => { - console.error('Neočakávaná chyba databázy:', err); + console.error('[FATAL] Database connection error:', err.message); process.exit(-1); }); diff --git a/src/cron/calendar/event-notifier.js b/src/cron/calendar/event-notifier.js index 171506f..691c7ac 100644 --- a/src/cron/calendar/event-notifier.js +++ b/src/cron/calendar/event-notifier.js @@ -332,7 +332,6 @@ export const sendSingleEventNotification = async (eventId, adminUserId) => { const userEmail = await getUserEmail(userId); if (!userEmail) { - logger.warn(`Používateľ ${username} nemá nastavený primárny email - preskakujem`); stats.skipped++; continue; } @@ -343,7 +342,7 @@ export const sendSingleEventNotification = async (eventId, adminUserId) => { const textBody = generateEventNotificationText({ firstName, username, event }); // Send email - logger.info(`Odosielam notifikáciu pre ${username} (${userEmail})`); + logger.info(`Odosielam notifikáciu pre ${username}`); const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody); @@ -427,7 +426,6 @@ export const sendEventNotifications = async () => { const userEmail = await getUserEmail(userId); if (!userEmail) { - logger.warn(`Používateľ ${username} nemá nastavený primárny email - preskakujem`); stats.skipped++; continue; } @@ -438,7 +436,7 @@ export const sendEventNotifications = async () => { const textBody = generateEventNotificationText({ firstName, username, event }); // Send email - logger.info(`Odosielam notifikáciu pre ${username} (${userEmail}) - udalosť: ${event.title}`); + logger.info(`Odosielam notifikáciu pre ${username} - udalosť: ${event.title}`); const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody); diff --git a/src/index.js b/src/index.js index 54b32ec..b908e6b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,10 @@ import app from './app.js'; import { startAllCronJobs } from './cron/index.js'; +import { logger } from './utils/logger.js'; const port = process.env.PORT || 5000; app.listen(port, () => { - console.log(`🚀 Server running on http://localhost:${port}`); + logger.info(`Server running on http://localhost:${port}`); // Start cron jobs after server is running startAllCronJobs(); diff --git a/src/middlewares/auth/resourceAccessMiddleware.js b/src/middlewares/auth/resourceAccessMiddleware.js index a3a1cd2..7949002 100644 --- a/src/middlewares/auth/resourceAccessMiddleware.js +++ b/src/middlewares/auth/resourceAccessMiddleware.js @@ -1,6 +1,7 @@ import { db } from '../../config/database.js'; import { companyUsers, projectUsers, todoUsers } from '../../db/schema.js'; import { eq, and } from 'drizzle-orm'; +import { logger } from '../../utils/logger.js'; /** * Univerzálny middleware pre kontrolu prístupu k resources @@ -104,7 +105,7 @@ export const checkResourceAccess = (resourceType, paramName) => { next(); } catch (error) { - console.error('Resource access check error:', error); + logger.error('Resource access check error', error); return res.status(500).json({ success: false, error: { diff --git a/src/services/email-account.service.js b/src/services/email-account.service.js index 3b853b3..6d82ba6 100644 --- a/src/services/email-account.service.js +++ b/src/services/email-account.service.js @@ -409,19 +409,8 @@ export const removeUserFromEmailAccount = async (accountId, userId) => { * Get email account with decrypted password (for JMAP operations) */ export const getEmailAccountWithCredentials = async (accountId, userId) => { - logger.debug('getEmailAccountWithCredentials called', { accountId, userId }); - const account = await getEmailAccountById(accountId, userId); - - logger.debug('Account retrieved', { - id: account.id, - email: account.email, - hasPassword: !!account.emailPassword, - passwordLength: account.emailPassword?.length - }); - const decryptedPassword = decryptPassword(account.emailPassword); - logger.debug('Password decrypted', { passwordLength: decryptedPassword?.length }); return { id: account.id, diff --git a/src/services/jmap/sync.js b/src/services/jmap/sync.js index af00796..16c881a 100644 --- a/src/services/jmap/sync.js +++ b/src/services/jmap/sync.js @@ -147,7 +147,6 @@ export const syncEmailsFromSender = async ( // Skip emails without from or to (malformed data) if (!fromEmail && !toEmail) { - logger.warn(`Preskakujem email ${messageId} - chýba pole from aj to`); continue; } @@ -160,7 +159,6 @@ export const syncEmailsFromSender = async ( toEmail?.toLowerCase() === senderEmail.toLowerCase(); if (!belongsToContact) { - logger.warn(`Preskakujem email ${messageId} - nepatrí kontaktu ${senderEmail} (od: ${fromEmail}, pre: ${toEmail})`); continue; } diff --git a/src/services/timesheet.service.js b/src/services/timesheet.service.js index 8908a2d..98a1033 100644 --- a/src/services/timesheet.service.js +++ b/src/services/timesheet.service.js @@ -4,6 +4,7 @@ import { db } from '../config/database.js'; import { timesheets, users } from '../db/schema.js'; import { and, desc, eq } from 'drizzle-orm'; import { BadRequestError, ForbiddenError, NotFoundError } from '../utils/errors.js'; +import { logger } from '../utils/logger.js'; const ALLOWED_MIME_TYPES = [ 'application/pdf', @@ -71,8 +72,7 @@ const safeUnlink = async (filePath) => { try { await fs.unlink(filePath); } catch (error) { - // Keep server responsive even if cleanup fails - console.error('Nepodarilo sa zmazať súbor:', error); + logger.error('Failed to delete file', error); } }; diff --git a/src/services/todo.service.js b/src/services/todo.service.js index 9d8eae4..a85fa30 100644 --- a/src/services/todo.service.js +++ b/src/services/todo.service.js @@ -142,7 +142,6 @@ export const getTodoById = async (todoId) => { */ export const createTodo = async (userId, data) => { const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data; - console.log('Service createTodo - assignedUserIds:', assignedUserIds); // Verify project exists if provided if (projectId) { @@ -205,10 +204,7 @@ export const createTodo = async (userId, data) => { assignedBy: userId, })); - console.log('Inserting todo_users:', todoUserInserts); await db.insert(todoUsers).values(todoUserInserts); - } else { - console.log('No users to assign - assignedUserIds:', assignedUserIds); } return newTodo; diff --git a/src/utils/logger.js b/src/utils/logger.js index 2ace87c..93dbc7f 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -1,7 +1,22 @@ /** - * Simple logger utility + * Logger utility with LOG_LEVEL filtering + * + * LOG_LEVEL options (from lowest to highest): + * - debug: Show all logs + * - info: Show info, success, warn, error, audit + * - warn: Show warn, error, audit + * - error: Show only errors and audit + * + * Default: 'info' (production-ready default) */ +const LOG_LEVELS = { + debug: 0, + info: 1, + warn: 2, + error: 3, +}; + const colors = { reset: '\x1b[0m', red: '\x1b[31m', @@ -10,37 +25,51 @@ const colors = { blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', + gray: '\x1b[90m', }; const getTimestamp = () => { return new Date().toISOString(); }; +const getCurrentLevel = () => { + const level = process.env.LOG_LEVEL?.toLowerCase() || 'info'; + return LOG_LEVELS[level] ?? LOG_LEVELS.info; +}; + +const shouldLog = (level) => { + return LOG_LEVELS[level] <= getCurrentLevel(); +}; + export const logger = { info: (message, ...args) => { + if (!shouldLog('info')) return; console.log( - `${colors.blue}[INFO]${colors.reset} ${getTimestamp()} - ${message}`, + `${colors.blue}[INFO]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`, ...args ); }, success: (message, ...args) => { + if (!shouldLog('info')) return; console.log( - `${colors.green}[SUCCESS]${colors.reset} ${getTimestamp()} - ${message}`, + `${colors.green}[SUCCESS]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`, ...args ); }, warn: (message, ...args) => { + if (!shouldLog('warn')) return; console.warn( - `${colors.yellow}[WARN]${colors.reset} ${getTimestamp()} - ${message}`, + `${colors.yellow}[WARN]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`, ...args ); }, error: (message, error, ...args) => { + if (!shouldLog('error')) return; console.error( - `${colors.red}[ERROR]${colors.reset} ${getTimestamp()} - ${message}`, + `${colors.red}[ERROR]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`, ...args ); if (error && process.env.NODE_ENV === 'development') { @@ -49,18 +78,26 @@ export const logger = { }, debug: (message, ...args) => { - if (process.env.NODE_ENV === 'development') { - console.log( - `${colors.cyan}[DEBUG]${colors.reset} ${getTimestamp()} - ${message}`, - ...args - ); - } - }, - - audit: (message, ...args) => { + if (!shouldLog('debug')) return; console.log( - `${colors.magenta}[AUDIT]${colors.reset} ${getTimestamp()} - ${message}`, + `${colors.cyan}[DEBUG]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`, ...args ); }, + + audit: (message, ...args) => { + // Audit logs always show regardless of level + console.log( + `${colors.magenta}[AUDIT]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`, + ...args + ); + }, + + // HTTP request logger (for Morgan integration) + http: (message) => { + if (!shouldLog('info')) return; + console.log( + `${colors.gray}[HTTP]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}` + ); + }, };