[Sub-issue #80-D]: Frontend Logger Module #84

Closed
opened 2026-02-03 02:18:58 +00:00 by egullickson · 0 comments
Owner

Parent Issue

Refs #80 - Unified Debug Logging System

Scope

Create centralized frontend logger that respects VITE_LOG_LEVEL and includes request correlation.

Files to Create/Modify

  • Create: frontend/src/utils/logger.ts
  • Modify: frontend/vite.config.ts - Ensure VITE_LOG_LEVEL is available

Implementation Details

logger.ts

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 = validLevels.includes(rawLevel) ? rawLevel : 'info';

if (rawLevel !== LOG_LEVEL) {
  console.warn(`Invalid VITE_LOG_LEVEL "${import.meta.env.VITE_LOG_LEVEL}", falling back to "info"`);
}

const shouldLog = (level: LogLevel): boolean => 
  LEVELS[level] >= LEVELS[LOG_LEVEL];

// Sanitize sensitive data
const sanitize = (meta?: object): object | undefined => {
  if (!meta) return undefined;
  const sanitized = { ...meta };
  const sensitiveKeys = ['token', 'password', 'secret', 'authorization', 'apiKey'];
  for (const key of Object.keys(sanitized)) {
    if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
      (sanitized as any)[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;

API Client Integration

Update API client to:

  • Generate/extract requestId for correlation
  • Include requestId in logger calls

Acceptance Criteria

  • VITE_LOG_LEVEL controls frontend log verbosity
  • Invalid VITE_LOG_LEVEL shows warning and falls back to 'info'
  • Sensitive data (tokens, passwords) are sanitized in logs
  • Logger fails silently (no app crashes)
  • API calls logged with requestId for correlation
  • Works on mobile viewport (320px)
  • Works on desktop viewport (1920px)

Dependencies

Depends on #80-A (config generator)

Milestone

Milestone 3: Frontend Logging

## Parent Issue Refs #80 - Unified Debug Logging System ## Scope Create centralized frontend logger that respects VITE_LOG_LEVEL and includes request correlation. ## Files to Create/Modify - Create: `frontend/src/utils/logger.ts` - Modify: `frontend/vite.config.ts` - Ensure VITE_LOG_LEVEL is available ## Implementation Details ### logger.ts ```typescript 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 = validLevels.includes(rawLevel) ? rawLevel : 'info'; if (rawLevel !== LOG_LEVEL) { console.warn(`Invalid VITE_LOG_LEVEL "${import.meta.env.VITE_LOG_LEVEL}", falling back to "info"`); } const shouldLog = (level: LogLevel): boolean => LEVELS[level] >= LEVELS[LOG_LEVEL]; // Sanitize sensitive data const sanitize = (meta?: object): object | undefined => { if (!meta) return undefined; const sanitized = { ...meta }; const sensitiveKeys = ['token', 'password', 'secret', 'authorization', 'apiKey']; for (const key of Object.keys(sanitized)) { if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) { (sanitized as any)[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; ``` ### API Client Integration Update API client to: - Generate/extract requestId for correlation - Include requestId in logger calls ## Acceptance Criteria - [ ] VITE_LOG_LEVEL controls frontend log verbosity - [ ] Invalid VITE_LOG_LEVEL shows warning and falls back to 'info' - [ ] Sensitive data (tokens, passwords) are sanitized in logs - [ ] Logger fails silently (no app crashes) - [ ] API calls logged with requestId for correlation - [ ] Works on mobile viewport (320px) - [ ] Works on desktop viewport (1920px) ## Dependencies Depends on #80-A (config generator) ## Milestone Milestone 3: Frontend Logging
egullickson added the
status
backlog
type
feature
labels 2026-02-03 02:19:40 +00:00
egullickson added
status
in-progress
and removed
status
backlog
labels 2026-02-05 02:01:03 +00:00
egullickson added
status
review
and removed
status
in-progress
labels 2026-02-05 02:06:03 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#84