TL;DR: Logging in Angular goes far beyond console.log(). With a custom TypeScript decorator you can automatically log method calls, a LoggerService brings structure, and with Sentry or LogRocket you send logs straight to the cloud. Here's everything you need for professional logging.In software development, logging plays a crucial role in monitoring program execution and quickly identifying errors. Especially in large applications, effective logging is indispensable. This article introduces a logging decorator for Angular that allows you to conveniently log method calls and their return values.
🔧 The Code in Detail
The provided code uses TypeScript and the Angular development environment to create a flexible logging system. It consists of several parts, each fulfilling specific tasks:
Importing the Dev Mode Function
import { isDevMode } from '@angular/core';
This line imports the isDevMode function from the Angular core module to determine whether the application is running in development mode.
Constants for the Log Format
import { isDevMode } from '@angular/core';
export const logFormat = 'font-size:11px;display:block;border-left:5px solid {{color}};padding-left:5px;';
const startColor = 'green';
const endColor = 'red';
export function Log(whitelist?: Array<string>, blacklist?: Array<string>, logType: logType = { type: 'both' }): ClassDecorator {
return function (cls: any) {
const properties = whitelist || Object.getOwnPropertyNames(cls.prototype);
properties.forEach(propName => {
if (shouldLog(propName, whitelist, blacklist)) {
log(cls, propName, logType);
}
});
};
}
function shouldLog(propName: string, whitelist?: Array<string>, blacklist?: Array<string>): boolean {
const isBlacklisted = blacklist?.some(f => f.toLowerCase() === propName.toLowerCase());
return !isBlacklisted && (!whitelist || whitelist.includes(propName));
}
function log(cls: any, fnName: string, logType: logType) {
if (!isDevMode()) return;
const original = cls.prototype[fnName];
if (typeof original === 'function') {
cls.prototype[fnName] = function (...args) {
if (logType.type === 'in' || logType.type === 'both') {
console.log(`%c${cls.name}.${fnName}():`, logFormat.replace('{{color}}', startColor), ...args);
}
const ret = original.apply(this, args);
if (logType.type === 'out' || logType.type === 'both') {
console.log(`%c${cls.name}.${fnName}()`, logFormat.replace('{{color}}', endColor), ret);
}
return ret;
};
}
}
export interface logLine {
logging: boolean;
}
export interface logType {
type: 'in' | 'out' | 'both';
}
🏗️ Using the Decorator in a Component
Now it gets exciting: You can apply the @Log() decorator directly to an Angular component. This automatically logs all method calls – without any manual console.log() in every method.
import { Component, OnInit } from '@angular/core';
import { Log } from './log.decorator';
@Log()
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
})
export class DashboardComponent implements OnInit {
ngOnInit(): void {
this.loadData();
}
loadData(): void {
// Your logic here...
console.log('Loading data');
}
calculateSum(a: number, b: number): number {
return a + b;
}
}
As soon as you open the component in development mode, you'll automatically see in the console:
- Green bar: Method call with passed arguments
- Red bar: Return value of the method
You can also use the decorator with a whitelist or blacklist to only log specific methods:
// Only log loadData()
@Log(['loadData'])
// Log everything except ngOnInit()
@Log(undefined, ['ngOnInit'])
// Only log input parameters, no return values
@Log(undefined, undefined, { type: 'in' })
📋 Overview of the Most Important Console Methods
Before we dive deeper, here's an overview of the most important console methods that help you in everyday work:
console.log(), console.warn(), console.error()
The classics – for different log levels:
console.log('📝 Info: User logged in');
console.warn('⚠️ Warning: API response slow');
console.error('❌ Error: Network unreachable');
console.table()
Perfect for arrays and objects – displays data as a table:
const users = [
{ name: 'Anna', role: 'Admin' },
{ name: 'Max', role: 'User' },
{ name: 'Lisa', role: 'Editor' },
];
console.table(users);
console.group() and console.groupEnd()
Group related logs for better overview:
console.group('🔄 HTTP Request');
console.log('URL: /api/users');
console.log('Method: GET');
console.log('Status: 200');
console.groupEnd();
console.time() and console.timeEnd()
Measure the execution time of code blocks:
console.time('DataLoading');
await this.httpClient.get('/api/data').toPromise();
console.timeEnd('DataLoading');
// Output: DataLoading: 142.5ms
🏛️ Structured Logging with a LoggerService
For larger projects, console.log() alone isn't enough. A central LoggerService gives you control over log levels, formatting, and output channels:
import { Injectable, isDevMode } from '@angular/core';
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
OFF = 4,
}
@Injectable({ providedIn: 'root' })
export class LoggerService {
private level: LogLevel = isDevMode() ? LogLevel.DEBUG : LogLevel.WARN;
setLevel(level: LogLevel): void {
this.level = level;
}
debug(message: string, ...data: any[]): void {
if (this.level <= LogLevel.DEBUG) {
console.log(`[DEBUG] ${this.timestamp()} ${message}`, ...data);
}
}
info(message: string, ...data: any[]): void {
if (this.level <= LogLevel.INFO) {
console.log(`[INFO] ${this.timestamp()} ${message}`, ...data);
}
}
warn(message: string, ...data: any[]): void {
if (this.level <= LogLevel.WARN) {
console.warn(`[WARN] ${this.timestamp()} ${message}`, ...data);
}
}
error(message: string, ...data: any[]): void {
if (this.level <= LogLevel.ERROR) {
console.error(`[ERROR] ${this.timestamp()} ${message}`, ...data);
}
}
private timestamp(): string {
return new Date().toISOString();
}
}
You then use the service in your components via Dependency Injection:
import { Component, OnInit } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
selector: 'app-orders',
templateUrl: './orders.component.html',
})
export class OrdersComponent implements OnInit {
constructor(private logger: LoggerService) {}
ngOnInit(): void {
this.logger.info('OrdersComponent initialized');
this.loadOrders();
}
loadOrders(): void {
this.logger.debug('Loading orders...');
try {
// HTTP call...
this.logger.info('Orders loaded successfully', { count: 42 });
} catch (error) {
this.logger.error('Error loading orders', error);
}
}
}
The big advantage: In production builds, only warnings and errors are logged, while in dev mode all details are visible.
🌐 Integration with External Logging Services
For production-ready applications, you don't want logs only in the browser console. External services like Sentry and LogRocket help you track errors in real time.
Sentry Integration
Sentry automatically catches errors and groups them intelligently. The integration into Angular is straightforward:
import * as Sentry from '@sentry/angular';
// In your app.config.ts or app.module.ts
Sentry.init({
dsn: 'https://[email protected]/project-id',
integrations: [
Sentry.browserTracingIntegration(),
],
tracesSampleRate: 1.0,
environment: isDevMode() ? 'development' : 'production',
});
You can extend the LoggerService to automatically send errors to Sentry:
error(message: string, ...data: any[]): void {
if (this.level <= LogLevel.ERROR) {
console.error(`[ERROR] ${this.timestamp()} ${message}`, ...data);
Sentry.captureException(data[0] instanceof Error ? data[0] : new Error(message));
}
}
LogRocket Integration
LogRocket records user sessions and links logs to what the user actually saw:
import LogRocket from 'logrocket';
LogRocket.init('your-app-id/project');
// In the LoggerService
error(message: string, ...data: any[]): void {
if (this.level <= LogLevel.ERROR) {
console.error(`[ERROR] ${this.timestamp()} ${message}`, ...data);
LogRocket.captureException(data[0] instanceof Error ? data[0] : new Error(message));
}
}
✅ Best Practices for Logging in Angular
Here are the most important rules you should follow when logging:
1. Use Log Levels Consistently
- DEBUG: Detailed information for development
- INFO: Important business events (login, order, etc.)
- WARN: Unexpected behavior that isn't an error
- ERROR: Real errors that require attention
2. Distinguish Between Production and Development
In development mode you want as much information as possible. In production you should only log what's necessary:
// Already built into the LoggerService:
private level: LogLevel = isDevMode() ? LogLevel.DEBUG : LogLevel.WARN;
This ensures that no sensitive data ends up in the production console and performance isn't affected.
3. Watch Out for Performance
- Avoid excessive logging in loops or frequently called methods
- Use
console.time()to identify performance bottlenecks - Disable debug logs in production via the LoggerService
- Use tree-shaking-friendly imports for external logging libraries
4. Log Structured Data
Instead of individual strings, you should log structured objects – this makes analysis easier:
// ❌ Bad
this.logger.info('User logged in: ' + user.email);
// ✅ Good
this.logger.info('User logged in', { email: user.email, role: user.role, timestamp: Date.now() });
5. Never Log Sensitive Data
Make sure you never write passwords, tokens, or personal data to logs. Use filtering or masking:
// ❌ Never
this.logger.debug('Auth Token:', token);
// ✅ Safe
this.logger.debug('Auth Token:', token.substring(0, 8) + '***');
🎯 Conclusion
Logging in Angular is more than just console.log(). With the right setup, you have a powerful tool for debugging and monitoring:
- A TypeScript decorator automates logging at the class level
- The Console API offers far more than just
.log()– use.table(),.group(), and.time() - A LoggerService brings structure and control over log levels
- External services like Sentry and LogRocket make your logging production-ready
- Best practices ensure your logging stays secure, performant, and useful
Start with the decorator and the LoggerService – you'll quickly notice how much easier debugging becomes! 🚀
Discover more articles
Logging in Angular: Ein mächtiges Werkzeug zur Fehlersuche und Überwachung 🕵️
Logging ist in Angular unverzichtbar. Von TypeScript-Decorators bis zu strukturierten LoggerServices – so debuggst du effizient.
Angular and TailwindCSS: Utility-First CSS Meets Components 🎨
Angular and TailwindCSS are a powerful duo. Here's how to set up Tailwind in your Angular project and use utility-first CSS effectively.
Angular input() for Route Parameters: Ditch ActivatedRoute for Good 🚀
Angular 16+ lets you bind route parameters directly via input() – no more ActivatedRoute boilerplate. Here’s how!