veza/apps/web/src/utils/logger.ts

139 lines
3.9 KiB
TypeScript

/**
* FIX #18, #19, #22, #25: Logger structuré pour le frontend
* - Support de la corrélation avec request_id
* - Logs structurés en JSON en production (FIX #25: Standardisé)
* - Format texte en développement pour lisibilité
* - Filtrage selon l'environnement
*/
interface LogContext {
request_id?: string;
user_id?: string;
component?: string;
action?: string;
[key: string]: unknown;
}
interface Logger {
debug: (message: string, context?: LogContext, ...args: unknown[]) => void;
info: (message: string, context?: LogContext, ...args: unknown[]) => void;
warn: (message: string, context?: LogContext, ...args: unknown[]) => void;
error: (message: string, context?: LogContext, ...args: unknown[]) => void;
}
const isDev = import.meta.env.DEV;
const isProd = import.meta.env.PROD;
// FIX #21, #24: Configuration du niveau de log via variable d'environnement
// FIX #24: Standardiser sur LOG_LEVEL (avec fallback sur VITE_LOG_LEVEL pour compatibilité)
const logLevel = (
import.meta.env.VITE_LOG_LEVEL ||
import.meta.env.LOG_LEVEL ||
(isDev ? 'DEBUG' : 'WARN')
).toUpperCase();
// Contexte global pour la corrélation
let globalContext: LogContext = {};
/**
* FIX #22: Définir le contexte global (request_id, user_id, etc.)
*/
export function setLogContext(context: LogContext): void {
globalContext = { ...globalContext, ...context };
}
/**
* FIX #22: Obtenir le contexte global
*/
export function getLogContext(): LogContext {
return { ...globalContext };
}
/**
* FIX #22: Effacer le contexte global
*/
export function clearLogContext(): void {
globalContext = {};
}
/**
* Formater un log structuré
*/
function formatLog(
level: string,
message: string,
context?: LogContext,
...args: unknown[]
): void {
const logContext = { ...globalContext, ...context };
const timestamp = new Date().toISOString();
// FIX #25: Standardiser sur JSON en production pour faciliter l'agrégation
if (isProd) {
// En production : JSON structuré (standardisé pour agrégation)
const logEntry = {
timestamp,
level,
message,
...logContext,
...(args.length > 0 && { data: args }),
};
console.log(JSON.stringify(logEntry));
} else {
// En développement : format lisible
const contextStr = Object.keys(logContext).length > 0
? ` ${JSON.stringify(logContext)}`
: '';
const argsStr = args.length > 0 ? ` ${args.map(a => JSON.stringify(a)).join(' ')}` : '';
console.log(`[${level}] ${message}${contextStr}${argsStr}`);
}
}
/**
* FIX #21: Vérifier si un niveau de log doit être affiché
*/
function shouldLog(level: string): boolean {
const levelOrder = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
const currentLevelIndex = levelOrder.indexOf(logLevel);
const messageLevelIndex = levelOrder.indexOf(level);
// Si le niveau n'est pas trouvé, autoriser par défaut (sécurité)
if (currentLevelIndex === -1 || messageLevelIndex === -1) {
return true;
}
// Logger si le niveau du message est >= au niveau configuré
return messageLevelIndex >= currentLevelIndex;
}
/**
* Logger structuré qui supporte la corrélation
* FIX #21: Respecte le niveau de log configuré via VITE_LOG_LEVEL
*/
export const logger: Logger = {
debug: (message: string, context?: LogContext, ...args: unknown[]) => {
if (shouldLog('DEBUG')) {
formatLog('DEBUG', message, context, ...args);
}
},
info: (message: string, context?: LogContext, ...args: unknown[]) => {
if (shouldLog('INFO')) {
formatLog('INFO', message, context, ...args);
}
},
warn: (message: string, context?: LogContext, ...args: unknown[]) => {
if (shouldLog('WARN')) {
formatLog('WARN', message, context, ...args);
}
},
error: (message: string, context?: LogContext, ...args: unknown[]) => {
if (shouldLog('ERROR')) {
formatLog('ERROR', message, context, ...args);
}
},
};
/**
* Export par défaut pour faciliter l'import
*/
export default logger;