42

In our Angular app (made with Angular CLI) we use several console statements. Is there a global way to detect environment and then display console.log in our components and service only under development?

What I mean by global way - I know we can use something like:

if (!environment.production) {
  console.log(this.reviewTasksList);
}

But by using this code everytime we have to console.log (along with necessary import to get environment variable) our code will become kind of verbose.

I want to know if there is a way to maybe:

  • access the environment in a quicker way
  • Maybe delete all console logs at prod build time

Or the better solution here is to create a logger service and do all the environment check within it?

I don't want my bundle size to be impacted by debug statements and service.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
BlackHoleGalaxy
  • 9,160
  • 17
  • 59
  • 103
  • I suppose this would have to be either a typescript feature (like this, but don't get excited because it doesn't exist https://github.com/Microsoft/TypeScript/issues/3538) or something in AOT compilation to strip out console log (https://stackoverflow.com/questions/42307317/stripping-all-comments-and-console-logs-with-ng-build-prod-possible). I think it's a real shame nothing seems to exist - with preferably different logging levels. – Simon_Weaver Dec 05 '18 at 05:20

12 Answers12

32

This overwrites all console logs with blank function.

if (environment.production) {
  enableProdMode();
  window.console.log = function () { };   // disable any console.log debugging statements in production mode
  // window.console.error = function () { };

}
Taranjit Kang
  • 2,510
  • 3
  • 20
  • 40
20

Alternatively you can use a common service to achieve this

this.loggerService.log(this.reviewTasksList);

where as in your service you can use

log(text: string){
     if (!environment.production) {
       console.log(text)
     }
}
Aravind
  • 40,391
  • 16
  • 91
  • 110
16

You can use isDevMode() or your environment.production to check if the code runs in development or production mode.

I think a logger service would be a good idea and then register a different logger service in providers depending on the mode.

See also https://github.com/angular/angular/pull/14308

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
4

First of all, store console.log, error and debug and then override them with blank function () => {}. This will help if you want to check for any error in production.

if (environment.production) {
    if (window) {
        window['log'] = console.log;
        window['error'] = console.error;
        window['debug'] = console.debug;
    }

    console.log = console.debug = console.error = () => { };
}

To check console in production, write console.log = log; browser console. This will temporarily avail the console.log.

Bhadresh Arya
  • 754
  • 7
  • 6
4

Update: to trace message

It's relatively simple to see the trace of any logs you can just add a boolean property isTraceable and then do something like this.

if (isTraceable) {
    console.trace(message);
}

By default isTraceable can be set false and you can turn it on only when needed.

Original Answer

I have created a solution for this. As mentioned by many others I am using a service to achieve this. The difference in my solution is that you can also specify log levels. Here is my code
This is my log-level.model.ts file you can add it with your other model files.

export const LOG_LEVELS = {
   FATAL: 0,
   ERROR: 1,
   WARN: 2,
   INFO: 3,
   DEBUG: 4,
};

export class LogLevel {
    static readonly DEBUG = LOG_LEVELS.DEBUG;
    static readonly INFO = LOG_LEVELS.INFO;
    static readonly WARN = LOG_LEVELS.WARN;
    static readonly ERROR = LOG_LEVELS.ERROR;
    static readonly FATAL = LOG_LEVELS.FATAL;
}

Now coming to main service this is my log.service.ts file add this with all your other service file and you will be good to go.

import { Injectable } from "@angular/core";
import { LogLevel, LOG_LEVELS } from "../public-api";


@Injectable({
    providedIn: "root"
})
export class LogService {
    currentLog: LogLevel;

    constructor() {
        this.currentLog = LOG_LEVELS.DEBUG;
    }

    setLogLevel(level: LogLevel) {
        this.currentLog = level;
    }

    debug(message: any, param: any = "DEBUG") {
        if (this.currentLog >= LOG_LEVELS.DEBUG) {
            console.log(param, message);
        }
    }

    info(message: any, param: any = "INFO") {
        if (this.currentLog >= LOG_LEVELS.INFO) {
            console.log(param, message);
        }
    }

    warn(message: any, param: any = "WARN") {
        if (this.currentLog >= LOG_LEVELS.WARN) {
            console.warn(param, message);
        }
    }

    error(message: any, param: any = "ERROR") {
        if (this.currentLog >= LOG_LEVELS.ERROR) {
            console.error(param, message);
        }
    }
}

There is just one thing left to do now, controlling the log level. This can be done in app.component.ts file by using following piece of code.

environment.production ? this.log.setLogLevel(LOG_LEVELS.ERROR) : this.log.setLogLevel(LOG_LEVELS.DEBUG);

Make sure to import properly. Also you can change log levels as per your need.

Dhairya Tripathi
  • 197
  • 2
  • 14
3

what if you override console.log if it's not in dev mode ?

 if (! isDevMode()){
   console.log = (...args)=>{}
 }
Milad
  • 27,506
  • 11
  • 76
  • 85
2

You can do this:

export abstract class Logger {

    abstract info(message: string, context?: object);
  ...more methods here...   
}

@Injectable()
export class ConsoleLogger implements Logger {

    constructor( @Inject(CONSOLE) private console: any) {
    }

    info(message: string, context?: object) {
        this.console.info(message, context ? context : '');
    }
}

@Injectable()
export class ServerLoggerService implements Logger {

    constructor(private http: HttpClient) {
    }

    info(message: string, context?: object) {
        this.http.post(...).subscribe();
    }
}

Now to instanciate factory in the module:

export function loggerFactory(console, http) {
  return environment.production ?
         new ServerLoggerService(http) :
         new ConsoleLoggerService(console);
}
Serginho
  • 7,291
  • 2
  • 27
  • 52
1

I am using LoggingService for this purpose:

import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';

export interface ILoggerService {
    log(message?: any, ...optionalParams: any[]): void;
}

@Injectable()
export class LoggerService implements ILoggerService {
    log(message?: any, ...optionalParams: any[]): void {
        if (!environment.production)
            console.log(message, ...optionalParams);
    }
}

My logging method supports everything, that basic console.log supports - multiple args and styling including.

PS: You can implement Console instead of ILoggerService and then override all base methods for your needs.

Andris
  • 3,895
  • 2
  • 24
  • 27
  • The problem with Logging through a `LoggingService` is Chrome console only ever shows the source as the line in the Logging Service as opposed to where you called if from. If you find a solution for this please post it. – ttugates Jan 29 '20 at 13:33
  • @ttugates Does it matter if it in development mode shows that log source is LoggingService!? I think not. Either way log won't be visible in production. – Andris Jan 30 '20 at 12:23
  • But if you want, you can pass to method some context – Andris Jan 30 '20 at 12:23
  • 2
    I find having the source file name and line number of the log call useful for Development. It is a link to Chrome's Sources Tab and always linking to `MyLoggingService` would not be useful. If there is any JS functionality similar to C#'s [`CallerMemberName`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callermembernameattribute?view=netcore-2.2) I would like to know and would use the service. i.e. If the service can pull in / determine context, great I want to use it. If caller has to specify it on every log call, less useful to me. – ttugates Jan 30 '20 at 13:41
  • @ttugates was able to get referenced place by adding this code in log method: `new Error(...optionalParams).stack.split('at ')[2].split(' ')[1]` – Andris Jan 31 '20 at 13:33
0

I've created a solution that can have different settings to control console logs based on environments you are running your application. You can define a setting of your choice in development environment and a different setting for production environment. install @sedeh/smart-service and in your app component inject SmartConsoleService and pass your settings to it. the rest is all done for you. any console log will be treated as per your specification. Checkout Smart-console and let me know if there is anything more need to be included in it.

Masoud
  • 63
  • 8
  • One more thing. Any log raised in zone.js that is done natively is out of JavaScript control and cannot be handled. Examples of it is http CORS console logs or 400, 500, .. error console logs are all done natively and not possible to have a control over them. – Masoud Jan 13 '19 at 03:48
0

I really don't know the better way to do this. In my case... I use

import { isDevMode } from '@angular/core';

and uses:

isDevMode() && console.log('bla bla bla bla');

I don't like so much.. but I didn't find another path to be transparent without write much code. Benefits: can see line and file, from which it was called.

Andris
  • 3,895
  • 2
  • 24
  • 27
Akostha
  • 679
  • 7
  • 8
0

I am using this solution in my projects, it may not be the most elegant, but it is fast and does not require modifying the window.console object.

loggs.ts

import { isDevMode } from "@angular/core";

export const loggs = () => {
  if (isDevMode) {
    window["LOG"] = (...args: any[]) => {
      const date = new Date();
      const minutes =
        date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
      const seconds =
        date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
      const hours = `${date.getHours()}:${minutes}:${seconds}`;
      console.groupCollapsed(hours);
      console.warn.apply(console, args);
      console.groupEnd();
    };
  } else {
    window["LOG"] = () => {};
  }
};

In the root moduele (AppModule)

import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
//Others imports
import { loggs } from "./loggs";

loggs();

And in the last step for any component, service, etc.

Ex: In AppComponent

import { Component, OnInit } from "@angular/core";

declare var LOG: any; //<----HERE

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})

export class AppComponent implements OnInit {

constructor(){}

ngOnInit() {
    LOG("Upsi!");
  }

}

I hope it is useful for someone, greetings!

-1

Change this in your main.ts:

if (environment.production) {
    enableProdMode();
}

To this:

if (environment.production) {
    enableProdMode();
    if (window) {
        window.console.log = () => {};
    }
}

Now when you are in production, your console.logs will be gone. Hope it helps, it will save you a lot of imports ;)

Wolfyr
  • 409
  • 3
  • 11