Skip to content

logger.ts - Centralized Frontend Logging Service

Purpose

The logger.ts file provides a centralized, configurable logging system for the ReViewPoint frontend application. It offers structured logging with multiple log levels, environment-aware configuration, and intelligent argument normalization for consistent log output across the entire application.

Key Components

Log Level System

export type LogLevel = "error" | "warn" | "info" | "debug" | "trace";

const LOG_LEVELS: LogLevel[] = ["error", "warn", "info", "debug", "trace"];

Log Level Hierarchy

// Severity hierarchy (highest to lowest):
error    Critical failures, exceptions, system errors
warn     Warnings, deprecated usage, potential issues
info     General information, application flow
debug    Detailed debugging information
trace    Granular execution tracking, stack traces

Core Features

Environment-Aware Log Level Configuration

function getLogLevel(): LogLevel {
  // Priority order for log level determination:

  // 1. Browser window global (runtime configuration)
  if (typeof window !== "undefined" && window.LOG_LEVEL) {
    return window.LOG_LEVEL as LogLevel;
  }

  // 2. Global environment variable
  if (typeof globalThis !== "undefined" && globalThis.LOG_LEVEL) {
    return globalThis.LOG_LEVEL as LogLevel;
  }

  // 3. Process environment variable (Vite/Node.js)
  if (typeof process !== "undefined" && process.env?.LOG_LEVEL) {
    return process.env.LOG_LEVEL as LogLevel;
  }

  // 4. Test environment default
  if (typeof process !== "undefined" && process.env?.NODE_ENV === "test") {
    return "error"; // Minimize test output
  }

  // 5. Production default
  return "warn"; // Conservative default
}

Intelligent Log Filtering

function shouldLog(level: LogLevel): boolean {
  const currentLevel = getLogLevel();
  return LOG_LEVELS.indexOf(level) <= LOG_LEVELS.indexOf(currentLevel);
}

// Example filtering behavior:
// Current level: "warn"
// - logger.error() → ✅ Logged (error ≤ warn)
// - logger.warn()  → ✅ Logged (warn ≤ warn)
// - logger.info()  → ❌ Filtered (info > warn)
// - logger.debug() → ❌ Filtered (debug > warn)

Advanced Argument Normalization

function normalizeErrorArg(arg: unknown): unknown {
  // Error objects - preserve stack traces
  if (arg instanceof Error) return arg;

  // Objects and arrays - JSON serialization
  if (typeof arg === "object" && arg !== null) {
    try {
      return JSON.stringify(arg);
    } catch {
      return String(arg); // Fallback for circular references
    }
  }

  // Primitives - string conversion
  return String(arg);
}

Logger Interface

Complete Logging API

const logger = {
  error: (...args: unknown[]) => {
    if (shouldLog("error"))
      console.error("[ERROR]", ...args.map(normalizeErrorArg));
  },

  warn: (...args: unknown[]) => {
    if (shouldLog("warn"))
      console.warn("[WARN]", ...args.map(normalizeErrorArg));
  },

  info: (...args: unknown[]) => {
    if (shouldLog("info"))
      console.info("[INFO]", ...args.map(normalizeErrorArg));
  },

  debug: (...args: unknown[]) => {
    if (shouldLog("debug"))
      console.debug("[DEBUG]", ...args.map(normalizeErrorArg));
  },

  trace: (...args: unknown[]) => {
    if (shouldLog("trace"))
      console.trace("[TRACE]", ...args.map(normalizeErrorArg));
  },
};

Configuration Strategies

Development Environment Setup

// Method 1: Runtime configuration (dynamic)
window.LOG_LEVEL = "debug";

// Method 2: Environment variable (build-time)
// In .env.local:
LOG_LEVEL = debug;

// Method 3: Vite configuration
// In vite.config.ts:
export default defineConfig({
  define: {
    "process.env.LOG_LEVEL": JSON.stringify("debug"),
  },
});

Production Environment Setup

// Conservative production logging
// In .env.production:
LOG_LEVEL = warn;

// Or runtime configuration for specific debugging:
if (window.location.search.includes("debug=true")) {
  window.LOG_LEVEL = "debug";
}

Test Environment Configuration

// Automatic test environment detection
if (process.env.NODE_ENV === "test") {
  return "error"; // Minimize test console noise
}

// Override in specific test files:
beforeAll(() => {
  window.LOG_LEVEL = "debug"; // Enable detailed logging for debugging
});

Usage Patterns

Basic Logging

import logger from "@/logger";

// Simple message logging
logger.info("User authentication successful");
logger.warn("API response took longer than expected");
logger.error("Failed to load user data");

// Multiple arguments
logger.debug("API call", { endpoint: "/users", method: "GET" });
logger.error("Request failed", error, { context: "user-profile" });

Structured Object Logging

// Complex object logging with automatic serialization
const userState = {
  id: "123",
  name: "John Doe",
  preferences: { theme: "dark", notifications: true },
};

logger.info("User state updated:", userState);
// Output: [INFO] User state updated: {"id":"123","name":"John Doe","preferences":{"theme":"dark","notifications":true}}

// Error object with preserved stack trace
try {
  riskyOperation();
} catch (error) {
  logger.error("Operation failed:", error);
  // Output: [ERROR] Operation failed: Error: Something went wrong
  //   at riskyOperation (file.js:10:5)
  //   at ...
}

API Integration Logging

import logger from "@/logger";

class ApiClient {
  async request(endpoint: string, options: RequestOptions) {
    logger.debug("API request started:", { endpoint, method: options.method });

    try {
      const response = await fetch(endpoint, options);

      if (!response.ok) {
        logger.warn("API request failed:", {
          endpoint,
          status: response.status,
          statusText: response.statusText,
        });
      } else {
        logger.info("API request successful:", { endpoint });
      }

      return response;
    } catch (error) {
      logger.error("API request error:", error, { endpoint });
      throw error;
    }
  }
}

Component Lifecycle Logging

import logger from '@/logger';
import { useEffect } from 'react';

function UserProfile({ userId }: { userId: string }) {
  useEffect(() => {
    logger.debug('UserProfile component mounted:', { userId });

    return () => {
      logger.debug('UserProfile component unmounted:', { userId });
    };
  }, [userId]);

  const handleUserUpdate = (userData: UserData) => {
    logger.info('User data updated:', { userId, fields: Object.keys(userData) });
  };

  return <div>...</div>;
}

Advanced Logging Patterns

Conditional Debug Logging

import logger from "@/logger";

// Performance-sensitive logging
function expensiveOperation(data: LargeDataSet) {
  // Only serialize large objects if debug logging is enabled
  if (logger.debug.length > 0) {
    // Check if debug would actually log
    logger.debug("Processing large dataset:", {
      size: data.length,
      sample: data.slice(0, 3), // Log sample instead of full dataset
    });
  }

  return processData(data);
}

Context-Aware Logging

import logger from "@/logger";

class ContextualLogger {
  constructor(private context: string) {}

  info(message: string, ...args: unknown[]) {
    logger.info(`[${this.context}] ${message}`, ...args);
  }

  error(message: string, ...args: unknown[]) {
    logger.error(`[${this.context}] ${message}`, ...args);
  }
}

// Usage in different modules
const authLogger = new ContextualLogger("AUTH");
const apiLogger = new ContextualLogger("API");

authLogger.info("User login attempt"); // [INFO] [AUTH] User login attempt
apiLogger.error("Request timeout"); // [ERROR] [API] Request timeout

Feature Flag Integration

import logger from "@/logger";
import { isFeatureEnabled } from "@/lib/config/featureFlags";

function featureLogger(feature: string) {
  return {
    info: (message: string, ...args: unknown[]) => {
      if (isFeatureEnabled("enableDetailedLogging")) {
        logger.info(`[${feature}] ${message}`, ...args);
      }
    },
  };
}

const uploadLogger = featureLogger("UPLOAD");
uploadLogger.info("File upload started"); // Only logs if feature enabled

Development and Debugging

Browser Console Integration

// Expose logger globally for debugging (development only)
if (process.env.NODE_ENV === "development") {
  window.logger = logger;

  // Enable runtime log level changes
  window.setLogLevel = (level: LogLevel) => {
    window.LOG_LEVEL = level;
    logger.info(`Log level changed to: ${level}`);
  };
}

// Browser console usage:
// window.setLogLevel('debug')
// window.logger.debug('Manual debug message')

Log Analytics and Monitoring

import logger from "@/logger";

// Enhanced logger with analytics
class AnalyticsLogger {
  private originalLogger = logger;

  error(message: string, ...args: unknown[]) {
    // Send to error monitoring service
    if (window.Sentry) {
      window.Sentry.captureException(new Error(message));
    }

    // Also send to analytics
    if (window.plausible) {
      window.plausible("Frontend Error", {
        props: { message: message.substring(0, 100) },
      });
    }

    return this.originalLogger.error(message, ...args);
  }
}

Performance Logging

import logger from "@/logger";

function performanceLogger<T>(operation: string, fn: () => T): T {
  const start = performance.now();
  logger.debug(`Starting operation: ${operation}`);

  try {
    const result = fn();
    const duration = performance.now() - start;

    logger.info(`Operation completed: ${operation}`, {
      duration: `${duration.toFixed(2)}ms`,
    });

    return result;
  } catch (error) {
    const duration = performance.now() - start;
    logger.error(`Operation failed: ${operation}`, error, {
      duration: `${duration.toFixed(2)}ms`,
    });
    throw error;
  }
}

// Usage
const userData = performanceLogger("fetch-user-data", () => {
  return fetchUserFromAPI(userId);
});

Log Level Best Practices

Error Level Guidelines

// ❌ Overuse of error level
logger.error("User clicked button"); // This is not an error

// ✅ Appropriate error usage
logger.error("Authentication failed:", error);
logger.error("Failed to save user data:", error);
logger.error("Critical service unavailable:", serviceError);

Debug Level Guidelines

// ✅ Good debug usage
logger.debug("Function parameters:", { userId, options });
logger.debug("State before update:", currentState);
logger.debug("API response received:", response.headers);

// ❌ Sensitive information in logs
logger.debug("User password:", password); // Security risk!
logger.debug("API key:", apiKey); // Security risk!

Production Logging Strategy

// Production-appropriate logging levels:

// ERROR: Actual failures that impact functionality
logger.error("Payment processing failed:", error);

// WARN: Potential issues that don't break functionality
logger.warn("API response slow:", { duration: 5000 });

// INFO: Important business events
logger.info("User registration completed:", { userId });

// DEBUG/TRACE: Only in development
if (process.env.NODE_ENV === "development") {
  logger.debug("Component render cycle:", componentName);
  logger.trace("Detailed execution path:", executionSteps);
}

Dependencies

Environment Dependencies

  • Browser Environment: window object for runtime configuration
  • Node.js Environment: process.env for build-time configuration
  • Vite Build System: Environment variable processing

No External Dependencies

The logger is implemented using only browser/Node.js built-ins:

  • console API for output
  • Native JavaScript for configuration and normalization
  • TypeScript types for type safety

Development Notes

Log Level Testing

// Test different log levels
describe("Logger", () => {
  beforeEach(() => {
    // Reset log level for each test
    delete window.LOG_LEVEL;
  });

  it("should filter logs based on level", () => {
    window.LOG_LEVEL = "warn";

    const consoleSpy = jest.spyOn(console, "info");
    logger.info("test message");

    expect(consoleSpy).not.toHaveBeenCalled(); // info > warn, should be filtered
  });
});

Security Considerations

  • Sensitive Data: Never log passwords, API keys, or personal information
  • Production Logs: Use conservative log levels to avoid information leakage
  • Error Context: Include helpful context without exposing internal system details
  • Log Sanitization: Consider sanitizing user inputs before logging