refactor: Clean up logging system with LOG_LEVEL filtering
- Add LOG_LEVEL env variable support (debug, info, warn, error) - Default to 'info' level for production-ready logs - Integrate Morgan HTTP logging with custom logger - Remove console.logs and replace with custom logger - Remove sensitive password debug logs from email service - Remove noisy warn logs from email sync and event notifier - Add gray color for timestamps to improve readability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { validateBody } from './middlewares/global/validateBody.js';
|
|||||||
import { notFound } from './middlewares/global/notFound.js';
|
import { notFound } from './middlewares/global/notFound.js';
|
||||||
import { errorHandler } from './middlewares/global/errorHandler.js';
|
import { errorHandler } from './middlewares/global/errorHandler.js';
|
||||||
import { apiRateLimiter } from './middlewares/security/rateLimiter.js';
|
import { apiRateLimiter } from './middlewares/security/rateLimiter.js';
|
||||||
|
import { logger } from './utils/logger.js';
|
||||||
|
|
||||||
// Import routes
|
// Import routes
|
||||||
import authRoutes from './routes/auth.routes.js';
|
import authRoutes from './routes/auth.routes.js';
|
||||||
@@ -29,8 +30,9 @@ import eventRoutes from './routes/event.routes.js';
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// Security middleware
|
// HTTP request logging via Morgan -> custom logger
|
||||||
app.use(morgan('dev'));
|
const morganStream = { write: (message) => logger.http(message.trim()) };
|
||||||
|
app.use(morgan(':method :url :status :response-time ms', { stream: morganStream }));
|
||||||
app.use(
|
app.use(
|
||||||
helmet({
|
helmet({
|
||||||
contentSecurityPolicy: {
|
contentSecurityPolicy: {
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ const pool = new Pool({
|
|||||||
connectionTimeoutMillis: 2000,
|
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) => {
|
pool.on('error', (err) => {
|
||||||
console.error('Neočakávaná chyba databázy:', err);
|
console.error('[FATAL] Database connection error:', err.message);
|
||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -332,7 +332,6 @@ export const sendSingleEventNotification = async (eventId, adminUserId) => {
|
|||||||
const userEmail = await getUserEmail(userId);
|
const userEmail = await getUserEmail(userId);
|
||||||
|
|
||||||
if (!userEmail) {
|
if (!userEmail) {
|
||||||
logger.warn(`Používateľ ${username} nemá nastavený primárny email - preskakujem`);
|
|
||||||
stats.skipped++;
|
stats.skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -343,7 +342,7 @@ export const sendSingleEventNotification = async (eventId, adminUserId) => {
|
|||||||
const textBody = generateEventNotificationText({ firstName, username, event });
|
const textBody = generateEventNotificationText({ firstName, username, event });
|
||||||
|
|
||||||
// Send email
|
// 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);
|
const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody);
|
||||||
|
|
||||||
@@ -427,7 +426,6 @@ export const sendEventNotifications = async () => {
|
|||||||
const userEmail = await getUserEmail(userId);
|
const userEmail = await getUserEmail(userId);
|
||||||
|
|
||||||
if (!userEmail) {
|
if (!userEmail) {
|
||||||
logger.warn(`Používateľ ${username} nemá nastavený primárny email - preskakujem`);
|
|
||||||
stats.skipped++;
|
stats.skipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -438,7 +436,7 @@ export const sendEventNotifications = async () => {
|
|||||||
const textBody = generateEventNotificationText({ firstName, username, event });
|
const textBody = generateEventNotificationText({ firstName, username, event });
|
||||||
|
|
||||||
// Send email
|
// 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);
|
const success = await sendNotificationEmail(jmapConfig, userEmail, subject, htmlBody, textBody);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import app from './app.js';
|
import app from './app.js';
|
||||||
import { startAllCronJobs } from './cron/index.js';
|
import { startAllCronJobs } from './cron/index.js';
|
||||||
|
import { logger } from './utils/logger.js';
|
||||||
|
|
||||||
const port = process.env.PORT || 5000;
|
const port = process.env.PORT || 5000;
|
||||||
app.listen(port, () => {
|
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
|
// Start cron jobs after server is running
|
||||||
startAllCronJobs();
|
startAllCronJobs();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { db } from '../../config/database.js';
|
import { db } from '../../config/database.js';
|
||||||
import { companyUsers, projectUsers, todoUsers } from '../../db/schema.js';
|
import { companyUsers, projectUsers, todoUsers } from '../../db/schema.js';
|
||||||
import { eq, and } from 'drizzle-orm';
|
import { eq, and } from 'drizzle-orm';
|
||||||
|
import { logger } from '../../utils/logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Univerzálny middleware pre kontrolu prístupu k resources
|
* Univerzálny middleware pre kontrolu prístupu k resources
|
||||||
@@ -104,7 +105,7 @@ export const checkResourceAccess = (resourceType, paramName) => {
|
|||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Resource access check error:', error);
|
logger.error('Resource access check error', error);
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
|
|||||||
@@ -409,19 +409,8 @@ export const removeUserFromEmailAccount = async (accountId, userId) => {
|
|||||||
* Get email account with decrypted password (for JMAP operations)
|
* Get email account with decrypted password (for JMAP operations)
|
||||||
*/
|
*/
|
||||||
export const getEmailAccountWithCredentials = async (accountId, userId) => {
|
export const getEmailAccountWithCredentials = async (accountId, userId) => {
|
||||||
logger.debug('getEmailAccountWithCredentials called', { accountId, userId });
|
|
||||||
|
|
||||||
const account = await getEmailAccountById(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);
|
const decryptedPassword = decryptPassword(account.emailPassword);
|
||||||
logger.debug('Password decrypted', { passwordLength: decryptedPassword?.length });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: account.id,
|
id: account.id,
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ export const syncEmailsFromSender = async (
|
|||||||
|
|
||||||
// Skip emails without from or to (malformed data)
|
// Skip emails without from or to (malformed data)
|
||||||
if (!fromEmail && !toEmail) {
|
if (!fromEmail && !toEmail) {
|
||||||
logger.warn(`Preskakujem email ${messageId} - chýba pole from aj to`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +159,6 @@ export const syncEmailsFromSender = async (
|
|||||||
toEmail?.toLowerCase() === senderEmail.toLowerCase();
|
toEmail?.toLowerCase() === senderEmail.toLowerCase();
|
||||||
|
|
||||||
if (!belongsToContact) {
|
if (!belongsToContact) {
|
||||||
logger.warn(`Preskakujem email ${messageId} - nepatrí kontaktu ${senderEmail} (od: ${fromEmail}, pre: ${toEmail})`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { db } from '../config/database.js';
|
|||||||
import { timesheets, users } from '../db/schema.js';
|
import { timesheets, users } from '../db/schema.js';
|
||||||
import { and, desc, eq } from 'drizzle-orm';
|
import { and, desc, eq } from 'drizzle-orm';
|
||||||
import { BadRequestError, ForbiddenError, NotFoundError } from '../utils/errors.js';
|
import { BadRequestError, ForbiddenError, NotFoundError } from '../utils/errors.js';
|
||||||
|
import { logger } from '../utils/logger.js';
|
||||||
|
|
||||||
const ALLOWED_MIME_TYPES = [
|
const ALLOWED_MIME_TYPES = [
|
||||||
'application/pdf',
|
'application/pdf',
|
||||||
@@ -71,8 +72,7 @@ const safeUnlink = async (filePath) => {
|
|||||||
try {
|
try {
|
||||||
await fs.unlink(filePath);
|
await fs.unlink(filePath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Keep server responsive even if cleanup fails
|
logger.error('Failed to delete file', error);
|
||||||
console.error('Nepodarilo sa zmazať súbor:', error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ export const getTodoById = async (todoId) => {
|
|||||||
*/
|
*/
|
||||||
export const createTodo = async (userId, data) => {
|
export const createTodo = async (userId, data) => {
|
||||||
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
const { title, description, projectId, companyId, assignedUserIds, status, priority, dueDate } = data;
|
||||||
console.log('Service createTodo - assignedUserIds:', assignedUserIds);
|
|
||||||
|
|
||||||
// Verify project exists if provided
|
// Verify project exists if provided
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
@@ -205,10 +204,7 @@ export const createTodo = async (userId, data) => {
|
|||||||
assignedBy: userId,
|
assignedBy: userId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('Inserting todo_users:', todoUserInserts);
|
|
||||||
await db.insert(todoUsers).values(todoUserInserts);
|
await db.insert(todoUsers).values(todoUserInserts);
|
||||||
} else {
|
|
||||||
console.log('No users to assign - assignedUserIds:', assignedUserIds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newTodo;
|
return newTodo;
|
||||||
|
|||||||
@@ -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 = {
|
const colors = {
|
||||||
reset: '\x1b[0m',
|
reset: '\x1b[0m',
|
||||||
red: '\x1b[31m',
|
red: '\x1b[31m',
|
||||||
@@ -10,37 +25,51 @@ const colors = {
|
|||||||
blue: '\x1b[34m',
|
blue: '\x1b[34m',
|
||||||
magenta: '\x1b[35m',
|
magenta: '\x1b[35m',
|
||||||
cyan: '\x1b[36m',
|
cyan: '\x1b[36m',
|
||||||
|
gray: '\x1b[90m',
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTimestamp = () => {
|
const getTimestamp = () => {
|
||||||
return new Date().toISOString();
|
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 = {
|
export const logger = {
|
||||||
info: (message, ...args) => {
|
info: (message, ...args) => {
|
||||||
|
if (!shouldLog('info')) return;
|
||||||
console.log(
|
console.log(
|
||||||
`${colors.blue}[INFO]${colors.reset} ${getTimestamp()} - ${message}`,
|
`${colors.blue}[INFO]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`,
|
||||||
...args
|
...args
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
success: (message, ...args) => {
|
success: (message, ...args) => {
|
||||||
|
if (!shouldLog('info')) return;
|
||||||
console.log(
|
console.log(
|
||||||
`${colors.green}[SUCCESS]${colors.reset} ${getTimestamp()} - ${message}`,
|
`${colors.green}[SUCCESS]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`,
|
||||||
...args
|
...args
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
warn: (message, ...args) => {
|
warn: (message, ...args) => {
|
||||||
|
if (!shouldLog('warn')) return;
|
||||||
console.warn(
|
console.warn(
|
||||||
`${colors.yellow}[WARN]${colors.reset} ${getTimestamp()} - ${message}`,
|
`${colors.yellow}[WARN]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`,
|
||||||
...args
|
...args
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
error: (message, error, ...args) => {
|
error: (message, error, ...args) => {
|
||||||
|
if (!shouldLog('error')) return;
|
||||||
console.error(
|
console.error(
|
||||||
`${colors.red}[ERROR]${colors.reset} ${getTimestamp()} - ${message}`,
|
`${colors.red}[ERROR]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`,
|
||||||
...args
|
...args
|
||||||
);
|
);
|
||||||
if (error && process.env.NODE_ENV === 'development') {
|
if (error && process.env.NODE_ENV === 'development') {
|
||||||
@@ -49,18 +78,26 @@ export const logger = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
debug: (message, ...args) => {
|
debug: (message, ...args) => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (!shouldLog('debug')) return;
|
||||||
console.log(
|
|
||||||
`${colors.cyan}[DEBUG]${colors.reset} ${getTimestamp()} - ${message}`,
|
|
||||||
...args
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
audit: (message, ...args) => {
|
|
||||||
console.log(
|
console.log(
|
||||||
`${colors.magenta}[AUDIT]${colors.reset} ${getTimestamp()} - ${message}`,
|
`${colors.cyan}[DEBUG]${colors.reset} ${colors.gray}${getTimestamp()}${colors.reset} ${message}`,
|
||||||
...args
|
...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}`
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user