feat: Frontend Logger Module (#84) #92

Merged
egullickson merged 1 commits from issue-84-frontend-logger-module into main 2026-02-05 02:13:13 +00:00
2 changed files with 95 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
/**
* @ai-summary Centralized frontend logger with level filtering and sanitization
* @ai-context Use this instead of console.log for consistent logging
*/
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
const LEVELS: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
};
const validLevels: LogLevel[] = ['debug', 'info', 'warn', 'error'];
const rawLevel = (
import.meta.env.VITE_LOG_LEVEL || 'info'
).toLowerCase() as LogLevel;
const LOG_LEVEL: LogLevel = validLevels.includes(rawLevel) ? rawLevel : 'info';
// Warn once on startup if invalid level
if (import.meta.env.VITE_LOG_LEVEL && rawLevel !== LOG_LEVEL) {
console.warn(
`[Logger] Invalid VITE_LOG_LEVEL "${import.meta.env.VITE_LOG_LEVEL}", using "info"`
);
}
const shouldLog = (level: LogLevel): boolean => LEVELS[level] >= LEVELS[LOG_LEVEL];
// Sanitize sensitive data from logs
const sensitiveKeys = [
'token',
'password',
'secret',
'authorization',
'apikey',
'credential',
];
const sanitize = (meta?: object): object | undefined => {
if (!meta) return undefined;
const sanitized = { ...meta };
for (const key of Object.keys(sanitized)) {
if (sensitiveKeys.some((s) => key.toLowerCase().includes(s))) {
(sanitized as Record<string, unknown>)[key] = '[REDACTED]';
}
}
return sanitized;
};
export const logger = {
debug: (msg: string, meta?: object): void => {
if (shouldLog('debug')) {
try {
console.debug(`[DEBUG] ${msg}`, sanitize(meta) ?? '');
} catch {
/* Fail silently */
}
}
},
info: (msg: string, meta?: object): void => {
if (shouldLog('info')) {
try {
console.log(`[INFO] ${msg}`, sanitize(meta) ?? '');
} catch {
/* Fail silently */
}
}
},
warn: (msg: string, meta?: object): void => {
if (shouldLog('warn')) {
try {
console.warn(`[WARN] ${msg}`, sanitize(meta) ?? '');
} catch {
/* Fail silently */
}
}
},
error: (msg: string, meta?: object): void => {
if (shouldLog('error')) {
try {
console.error(`[ERROR] ${msg}`, sanitize(meta) ?? '');
} catch {
/* Fail silently */
}
}
},
};
export default logger;

View File

@@ -5,6 +5,7 @@ interface ImportMetaEnv {
readonly VITE_AUTH0_DOMAIN: string
readonly VITE_AUTH0_CLIENT_ID: string
readonly VITE_AUTH0_AUDIENCE: string
readonly VITE_LOG_LEVEL?: string
}
interface ImportMeta {