DEV Community

SANKET PATIL
SANKET PATIL

Posted on

Building a Production-Ready Logger Service in Angular πŸš€

When I started working on Angular app, I noticed one common thing: console.log everywhere. Debug messages scattered across components, warnings mixed with production logs, and the occasional sensitive info sneaking into the console.

With guidance from my peers, I learned about a much better approach-a centralized Logger Service. In this post, I’ll walk you through a production-ready Logger Service implementation for Angular that makes logging clean, powerful, and safe.


The Problem πŸ€”

We’ve all been there-debugging with console.log() sprinkled across the codebase.
The issues?

  • Cluttered production logs
  • Manual cleanup before deployment
  • Inconsistent formatting
  • No central control
  • Potential exposure of sensitive info

The Solution ✨

A Logger Service that provides:

  • βœ… Environment-aware logging (disabled in production)
  • βœ… Multiple log levels: DEBUG, INFO, WARN, ERROR
  • βœ… Consistent formatting with timestamps
  • βœ… Advanced features (grouping, timing, styled logs, tables)
  • βœ… Type safety with full TypeScript support

Implementation πŸ’»

Core Logger Service

export enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3
}

@Injectable({ providedIn: 'root' })
export class LoggerService {
  private isEnabled: boolean = !environment.production;
  private logLevel: LogLevel = LogLevel.DEBUG;

  constructor() {
    if (environment.hasOwnProperty('enableLogging')) {
      this.isEnabled = (environment as any).enableLogging;
    }

    if (environment.hasOwnProperty('logLevel')) {
      this.logLevel = (environment as any).logLevel;
    }
  }

  debug(message: any, ...params: any[]): void {
    this.log(LogLevel.DEBUG, message, ...params);
  }

  info(message: any, ...params: any[]): void {
    this.log(LogLevel.INFO, message, ...params);
  }

  warn(message: any, ...params: any[]): void {
    this.log(LogLevel.WARN, message, ...params);
  }

  error(message: any, ...params: any[]): void {
    console.error(message, ...params);
  }

  private log(level: LogLevel, message: any, ...params: any[]): void {
    if (!this.isEnabled || level < this.logLevel) return;

    const timestamp = new Date().toISOString();
    const prefix = `[${timestamp}] [${LogLevel[level]}]`;

    switch (level) {
      case LogLevel.DEBUG: console.log(prefix, message, ...params); break;
      case LogLevel.INFO: console.info(prefix, message, ...params); break;
      case LogLevel.WARN: console.warn(prefix, message, ...params); break;
      case LogLevel.ERROR: console.error(prefix, message, ...params); break;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Key highlights:

  • Disabled automatically in production
  • Errors always logged regardless of environment
  • ISO timestamps for precise log tracking

Advanced Features 🎯

1. Grouping Related Logs

group(name: string, collapsed = false): void {
  if (this.isEnabled) {
    collapsed ? console.groupCollapsed(name) : console.group(name);
  }
}

groupEnd(): void {
  if (this.isEnabled) console.groupEnd();
}
Enter fullscreen mode Exit fullscreen mode

2. Performance Timing

time(name: string): void {
  if (this.isEnabled && this.logLevel <= LogLevel.DEBUG) console.time(name);
}

timeEnd(name: string): void {
  if (this.isEnabled && this.logLevel <= LogLevel.DEBUG) console.timeEnd(name);
}
Enter fullscreen mode Exit fullscreen mode

3. Table Display

table(data: any): void {
  if (this.isEnabled && this.logLevel <= LogLevel.DEBUG) console.table(data);
}
Enter fullscreen mode Exit fullscreen mode

4. Styled Logs

styled(msg: string, styles = 'color: #2196F3; font-weight: bold;'): void {
  if (this.isEnabled && this.logLevel <= LogLevel.INFO) {
    console.log(`%c${msg}`, styles);
  }
}
Enter fullscreen mode Exit fullscreen mode

Migration Guide πŸ“

Before:

console.log('Debug info');
console.warn('Warning message');
console.error('Error occurred');
Enter fullscreen mode Exit fullscreen mode

After:

this.logger.debug('Debug info');
this.logger.warn('Warning message');
this.logger.error('Error occurred');
Enter fullscreen mode Exit fullscreen mode

Dynamic Configuration βš™οΈ

Enable/disable logging at runtime:

constructor(private logger: LoggerService) {
  if (window.location.href.includes('debug=true')) {
    this.logger.setEnabled(true);
    this.logger.setLogLevel(LogLevel.DEBUG);
  }
}
Enter fullscreen mode Exit fullscreen mode

Benefits 🎁

  1. Cleaner production logs
  2. Rich formatting and timestamps
  3. Toggle logging with a single flag
  4. Zero overhead when disabled
  5. Type safety with TypeScript
  6. Consistent across the app
  7. No accidental leakage of sensitive info

Best Practices πŸ“š

  • Use logger.error() for errors (always logged)
  • Use debug() for diagnostics, info() for general messages, warn() for potential issues
  • Group related logs for readability
  • Time performance-critical operations
  • Replace all console. with logger.

Conclusion 🎯

A Logger Service is one of those simple but powerful upgrades that every Angular app should have.
It makes logs consistent, keeps production clean, and gives developers richer debugging tools.

Since adding it to our project, debugging has become easier and our production logs are finally clean.
No more scattered console.logs 🚫


πŸ’¬ What’s your logging strategy? Share your approach in the comments!

Top comments (0)