You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by al...@apache.org on 2020/04/18 07:18:17 UTC

[openwhisk-wskdebug] branch console-spinner created (now 91bc110)

This is an automated email from the ASF dual-hosted git repository.

alexkli pushed a change to branch console-spinner
in repository https://gitbox.apache.org/repos/asf/openwhisk-wskdebug.git.


      at 91bc110  await shutdown promise so that Debugger.run() returns only after shutdown

This branch includes the following new commits:

     new ec7f100  wip: better console output using spinners
     new 2ce0267  revamped console ui
     new 91bc110  await shutdown promise so that Debugger.run() returns only after shutdown

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[openwhisk-wskdebug] 01/03: wip: better console output using spinners

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alexkli pushed a commit to branch console-spinner
in repository https://gitbox.apache.org/repos/asf/openwhisk-wskdebug.git

commit ec7f1000834cd54cf43d90a2556e3948217c672d
Author: Alexander Klimetschek <ak...@adobe.com>
AuthorDate: Tue Apr 14 17:05:59 2020 -0700

    wip: better console output using spinners
---
 package-lock.json | 98 +++++++++++++++++++++++++++++++++++++++++++------------
 package.json      |  1 +
 src/debugger.js   | 25 +++++++++++---
 src/invoker.js    |  5 ++-
 src/watcher.js    |  5 ++-
 5 files changed, 108 insertions(+), 26 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index ce21bad..c256a42 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -489,7 +489,6 @@
             "version": "2.4.2",
             "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
             "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-            "dev": true,
             "requires": {
                 "ansi-styles": "^3.2.1",
                 "escape-string-regexp": "^1.0.5",
@@ -500,7 +499,6 @@
                     "version": "3.2.1",
                     "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
                     "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-                    "dev": true,
                     "requires": {
                         "color-convert": "^1.9.0"
                     }
@@ -509,7 +507,6 @@
                     "version": "1.9.3",
                     "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
                     "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-                    "dev": true,
                     "requires": {
                         "color-name": "1.1.3"
                     }
@@ -517,8 +514,7 @@
                 "color-name": {
                     "version": "1.1.3",
                     "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-                    "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
-                    "dev": true
+                    "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
                 }
             }
         },
@@ -559,11 +555,15 @@
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
             "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
-            "dev": true,
             "requires": {
                 "restore-cursor": "^3.1.0"
             }
         },
+        "cli-spinners": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz",
+            "integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w=="
+        },
         "cli-width": {
             "version": "2.2.0",
             "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
@@ -681,6 +681,21 @@
                 "strip-bom": "^4.0.0"
             }
         },
+        "defaults": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
+            "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
+            "requires": {
+                "clone": "^1.0.2"
+            },
+            "dependencies": {
+                "clone": {
+                    "version": "1.0.4",
+                    "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
+                    "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
+                }
+            }
+        },
         "define-properties": {
             "version": "1.1.3",
             "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -762,8 +777,7 @@
         "escape-string-regexp": {
             "version": "1.0.5",
             "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-            "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
-            "dev": true
+            "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
         },
         "eslint": {
             "version": "6.8.0",
@@ -1227,8 +1241,7 @@
         "has-flag": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-            "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
-            "dev": true
+            "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
         },
         "has-symbols": {
             "version": "1.0.1",
@@ -1468,6 +1481,11 @@
                 "is-extglob": "^2.1.1"
             }
         },
+        "is-interactive": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
+            "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="
+        },
         "is-number": {
             "version": "7.0.0",
             "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -1799,7 +1817,6 @@
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
             "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
-            "dev": true,
             "requires": {
                 "chalk": "^2.4.2"
             }
@@ -1821,8 +1838,7 @@
         "mimic-fn": {
             "version": "2.1.0",
             "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-            "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-            "dev": true
+            "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
         },
         "minimatch": {
             "version": "3.0.4",
@@ -2162,8 +2178,7 @@
         "mute-stream": {
             "version": "0.0.8",
             "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
-            "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
-            "dev": true
+            "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
         },
         "natural-compare": {
             "version": "1.4.0",
@@ -2359,7 +2374,6 @@
             "version": "5.1.0",
             "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
             "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
-            "dev": true,
             "requires": {
                 "mimic-fn": "^2.1.0"
             }
@@ -2391,6 +2405,45 @@
             "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.7.tgz",
             "integrity": "sha512-hwZhzGGG/GQ7igxAVFOEun2N4fWul31qE9nfBdCnZGQCB5+L7tN9xZ+94B4aUpLOJx/of3zZs5XsuubayQYQjA=="
         },
+        "ora": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/ora/-/ora-4.0.3.tgz",
+            "integrity": "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg==",
+            "requires": {
+                "chalk": "^3.0.0",
+                "cli-cursor": "^3.1.0",
+                "cli-spinners": "^2.2.0",
+                "is-interactive": "^1.0.0",
+                "log-symbols": "^3.0.0",
+                "mute-stream": "0.0.8",
+                "strip-ansi": "^6.0.0",
+                "wcwidth": "^1.0.1"
+            },
+            "dependencies": {
+                "chalk": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+                    "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+                    "requires": {
+                        "ansi-styles": "^4.1.0",
+                        "supports-color": "^7.1.0"
+                    }
+                },
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                },
+                "supports-color": {
+                    "version": "7.1.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+                    "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+                    "requires": {
+                        "has-flag": "^4.0.0"
+                    }
+                }
+            }
+        },
         "os-tmpdir": {
             "version": "1.0.2",
             "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
@@ -2600,7 +2653,6 @@
             "version": "3.1.0",
             "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
             "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
-            "dev": true,
             "requires": {
                 "onetime": "^5.1.0",
                 "signal-exit": "^3.0.2"
@@ -2678,8 +2730,7 @@
         "signal-exit": {
             "version": "3.0.3",
             "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
-            "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
-            "dev": true
+            "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
         },
         "slice-ansi": {
             "version": "2.1.0",
@@ -2861,7 +2912,6 @@
             "version": "5.5.0",
             "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
             "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-            "dev": true,
             "requires": {
                 "has-flag": "^3.0.0"
             }
@@ -3047,6 +3097,14 @@
             "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
             "dev": true
         },
+        "wcwidth": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+            "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
+            "requires": {
+                "defaults": "^1.0.3"
+            }
+        },
         "whatwg-fetch": {
             "version": "3.0.0",
             "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
diff --git a/package.json b/package.json
index 27004d0..b75166c 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
         "livereload": "^0.9.1",
         "manakin": "^0.5.2",
         "openwhisk": "^3.21.1",
+        "ora": "^4.0.3",
         "pretty-bytes": "^5.3.0",
         "pretty-ms": "^6.0.1",
         "yargs": "^15.3.1"
diff --git a/src/debugger.js b/src/debugger.js
index 879d497..9f1e40d 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -27,6 +27,7 @@ const sleep = require('util').promisify(setTimeout);
 const debug = require('./debug');
 const prettyBytes = require('pretty-bytes');
 const prettyMilliseconds = require('pretty-ms');
+const ora = require('ora');
 
 /**
  * Central component of wskdebug.
@@ -54,25 +55,35 @@ class Debugger {
             console.error(`Error: Could not setup openwhisk client: ${err.message}`);
             process.exit(1);
         }
+        this.spinner = ora({color: "blue"});
+        if (debug.enabled) {
+            // disable spinner from outputting anything since we have all the debug() logs
+            this.spinner.start = () => {};
+            this.spinner.stop = () => {};
+        }
+        this.spinner.start(`Starting`);
     }
 
     async start() {
         this.agentMgr = new AgentMgr(this.argv, this.wsk, this.actionName);
-        this.watcher = new Watcher(this.argv, this.wsk);
+        this.watcher = new Watcher(this.argv, this.wsk, this.spinner);
 
+        this.spinner.start(`Inspecting action`);
         // get the action metadata
         const actionMetadata = await this.agentMgr.peekAction();
         debug("fetched action metadata from openwhisk");
-
         this.wskProps.namespace = actionMetadata.namespace;
-        console.info(`Starting debugger for /${this.wskProps.namespace}/${this.actionName}`);
+
+        this.spinner.stop();
+        console.info(`Debugging /${this.wskProps.namespace}/${this.actionName}`);
 
         // local debug container
-        this.invoker = new OpenWhiskInvoker(this.actionName, actionMetadata, this.argv, this.wskProps, this.wsk);
+        this.invoker = new OpenWhiskInvoker(this.actionName, actionMetadata, this.argv, this.wskProps, this.wsk, this.spinner);
 
         try {
             // run build initially (would be required by starting container)
             if (this.argv.onBuild) {
+                this.spinner.stop();
                 console.info("=> Build:", this.argv.onBuild);
                 spawnSync(this.argv.onBuild, {shell: true, stdio: "inherit"});
             }
@@ -83,6 +94,7 @@ class Debugger {
             // task 1 - start local container
             const containerTask = (async () => {
                 const debugTask = debug.task();
+                this.spinner.start('Starting local container');
 
                 // start container - get it up fast for VSCode to connect within its 10 seconds timeout
                 await this.invoker.startContainer();
@@ -103,6 +115,8 @@ class Debugger {
             const results = await Promise.all([containerTask, openwhiskTask]);
             const actionWithCode = results[1];
 
+            this.spinner.start('Installing agent');
+
             // parallelize slower work using promises again
 
             // task 3 - initialize local container with code
@@ -126,6 +140,7 @@ class Debugger {
             await Promise.all([initTask, agentTask]);
 
             if (this.argv.onStart) {
+                this.spinner.stop();
                 console.log("On start:", this.argv.onStart);
                 spawnSync(this.argv.onStart, {shell: true, stdio: "inherit"});
             }
@@ -133,6 +148,8 @@ class Debugger {
             // start source watching (live reload) if requested
             await this.watcher.start();
 
+            this.spinner.stop();
+
             console.log();
             console.info(`Action     : /${this.wskProps.namespace}/${this.actionName}`);
             if (this.sourcePath) {
diff --git a/src/invoker.js b/src/invoker.js
index eed849a..98bb1d3 100644
--- a/src/invoker.js
+++ b/src/invoker.js
@@ -55,7 +55,7 @@ function resolveValue(value, ...args) {
 }
 
 class OpenWhiskInvoker {
-    constructor(actionName, action, options, wskProps, wsk) {
+    constructor(actionName, action, options, wskProps, wsk, spinner) {
         this.actionName = actionName;
         this.action = action;
 
@@ -80,6 +80,8 @@ class OpenWhiskInvoker {
         this.wskProps = wskProps;
         this.wsk = wsk;
 
+        this.spinner = spinner;
+
         this.containerName = this.asContainerName(`wskdebug-${this.action.name}-${Date.now()}`);
     }
 
@@ -237,6 +239,7 @@ class OpenWhiskInvoker {
 
         this.containerRunning = true;
 
+        this.spinner.stop();
         spawn("docker", ["logs", "-t", "-f", this.name()], {
             stdio: [
                 "inherit", // stdin
diff --git a/src/watcher.js b/src/watcher.js
index dcea5a6..dba16a3 100644
--- a/src/watcher.js
+++ b/src/watcher.js
@@ -23,9 +23,10 @@ const { spawnSync } = require('child_process');
 const debug = require('./debug');
 
 class Watcher {
-    constructor(argv, wsk) {
+    constructor(argv, wsk, spinner) {
         this.argv = argv;
         this.wsk = wsk;
+        this.spinner = spinner;
     }
 
     async start() {
@@ -38,6 +39,8 @@ class Watcher {
              || this.argv.invokeParams
              || this.argv.invokeAction )
         ) {
+            this.spinner.start('Initializing source watching');
+
             this.liveReloadServer = livereload.createServer({
                 port: this.argv.livereloadPort,
                 noListen: !this.argv.livereload,


[openwhisk-wskdebug] 02/03: revamped console ui

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alexkli pushed a commit to branch console-spinner
in repository https://gitbox.apache.org/repos/asf/openwhisk-wskdebug.git

commit 2ce02676b8dfc8eb68f36fbfda2a4c3a465c8e00
Author: Alexander Klimetschek <ak...@adobe.com>
AuthorDate: Sat Apr 18 00:14:28 2020 -0700

    revamped console ui
    
    - added spinner
    - more consistent logging & colors
    - highlight colors
    - central log.js
---
 index.js            |  53 ++++---------
 package-lock.json   | 180 ++++++++++++++++++++++++++++++++++++--------
 package.json        |   3 +-
 src/agentmgr.js     | 138 +++++++++++++---------------------
 src/agents/ngrok.js |  32 ++++----
 src/debug.js        |  31 --------
 src/debugger.js     | 141 +++++++++++++++++-----------------
 src/invoker.js      |  51 ++++---------
 src/log.js          | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/watcher.js      |  25 +++---
 test/test.js        |   2 -
 11 files changed, 544 insertions(+), 325 deletions(-)

diff --git a/index.js b/index.js
index 5f267f7..108f72a 100644
--- a/index.js
+++ b/index.js
@@ -21,32 +21,7 @@ const yargs = require("yargs");
 const Debugger = require("./src/debugger");
 const path = require("path");
 const fs = require("fs");
-const debug = require('./src/debug');
-
-function enableConsoleColors() {
-    // colorful console.error() and co
-    let originalConsole = null;
-    if (!console._logToFile) {
-        originalConsole = {
-            log: console.log,
-            error: console.error,
-            info: console.info,
-            debug: console.debug
-        };
-        // overwrites console.log and co
-        require('manakin').global;
-    }
-    return originalConsole;
-}
-
-function resetConsoleColors(originalConsole) {
-    if (originalConsole) {
-        console.log = originalConsole.log;
-        console.error = originalConsole.error;
-        console.info = originalConsole.info;
-        console.debug = originalConsole.debug;
-    }
-}
+const log = require('./src/log');
 
 function getSupportedKinds() {
     const kinds = [];
@@ -216,6 +191,11 @@ function yargsOptions(yargs) {
         type: "boolean",
         describe: "Verbose output. Logs activation parameters and result"
     });
+    yargs.option("s", {
+        alias: "silent",
+        type: "boolean",
+        describe: "Silent. Only output logs from action container."
+    });
     yargs.version(require("./package.json").version);
 }
 
@@ -275,13 +255,9 @@ function normalizeArgs(argv) {
     }
 }
 
-function printErrorAndExit(err, argv) {
-    console.log();
-    if (argv.verbose) {
-        console.error(err);
-    } else {
-        console.error("Error:", err.message);
-    }
+function printErrorAndExit(err) {
+    log.log();
+    log.exception(err);
     process.exit(1);
 }
 
@@ -297,8 +273,8 @@ function registerExitHandler(dbg) {
 }
 
 async function wskdebug(args, isCommandLine=false) {
-    debug("wskdebug arguments:", args);
-    const originalConsole = enableConsoleColors();
+    log.debug("wskdebug arguments:", args);
+    log.enableConsoleColors();
 
     try {
         const parser = getYargsParser();
@@ -315,6 +291,9 @@ async function wskdebug(args, isCommandLine=false) {
             return;
         }
 
+        log.isVerbose = argv.verbose;
+        log.silent(argv.silent);
+
         try {
             const dbg = new Debugger(argv);
             if (isCommandLine) {
@@ -325,14 +304,14 @@ async function wskdebug(args, isCommandLine=false) {
 
         } catch (e) {
             if (isCommandLine) {
-                printErrorAndExit(e, argv);
+                printErrorAndExit(e);
             } else {
                 throw e;
             }
         }
 
     } finally {
-        resetConsoleColors(originalConsole);
+        log.resetConsoleColors();
     }
 }
 
diff --git a/package-lock.json b/package-lock.json
index c256a42..ae04f84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -185,6 +185,52 @@
                 "@babel/helper-validator-identifier": "^7.9.0",
                 "chalk": "^2.0.0",
                 "js-tokens": "^4.0.0"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "3.2.1",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+                    "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+                    "dev": true,
+                    "requires": {
+                        "color-convert": "^1.9.0"
+                    }
+                },
+                "chalk": {
+                    "version": "2.4.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+                    "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+                    "dev": true,
+                    "requires": {
+                        "ansi-styles": "^3.2.1",
+                        "escape-string-regexp": "^1.0.5",
+                        "supports-color": "^5.3.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "1.9.3",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+                    "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+                    "dev": true,
+                    "requires": {
+                        "color-name": "1.1.3"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.3",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+                    "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+                    "dev": true
+                },
+                "supports-color": {
+                    "version": "5.5.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+                    "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^3.0.0"
+                    }
+                }
             }
         },
         "@babel/parser": {
@@ -486,36 +532,12 @@
             "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
         },
         "chalk": {
-            "version": "2.4.2",
-            "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-            "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
+            "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
             "requires": {
-                "ansi-styles": "^3.2.1",
-                "escape-string-regexp": "^1.0.5",
-                "supports-color": "^5.3.0"
-            },
-            "dependencies": {
-                "ansi-styles": {
-                    "version": "3.2.1",
-                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-                    "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-                    "requires": {
-                        "color-convert": "^1.9.0"
-                    }
-                },
-                "color-convert": {
-                    "version": "1.9.3",
-                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-                    "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-                    "requires": {
-                        "color-name": "1.1.3"
-                    }
-                },
-                "color-name": {
-                    "version": "1.1.3",
-                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-                    "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
-                }
+                "ansi-styles": "^4.1.0",
+                "supports-color": "^7.1.0"
             }
         },
         "chardet": {
@@ -830,6 +852,41 @@
                     "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
                     "dev": true
                 },
+                "ansi-styles": {
+                    "version": "3.2.1",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+                    "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+                    "dev": true,
+                    "requires": {
+                        "color-convert": "^1.9.0"
+                    }
+                },
+                "chalk": {
+                    "version": "2.4.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+                    "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+                    "dev": true,
+                    "requires": {
+                        "ansi-styles": "^3.2.1",
+                        "escape-string-regexp": "^1.0.5",
+                        "supports-color": "^5.3.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "1.9.3",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+                    "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+                    "dev": true,
+                    "requires": {
+                        "color-name": "1.1.3"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.3",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+                    "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+                    "dev": true
+                },
                 "debug": {
                     "version": "4.1.1",
                     "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -847,6 +904,15 @@
                     "requires": {
                         "ansi-regex": "^4.1.0"
                     }
+                },
+                "supports-color": {
+                    "version": "5.5.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+                    "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^3.0.0"
+                    }
                 }
             }
         },
@@ -1819,6 +1885,47 @@
             "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
             "requires": {
                 "chalk": "^2.4.2"
+            },
+            "dependencies": {
+                "ansi-styles": {
+                    "version": "3.2.1",
+                    "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+                    "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+                    "requires": {
+                        "color-convert": "^1.9.0"
+                    }
+                },
+                "chalk": {
+                    "version": "2.4.2",
+                    "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+                    "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+                    "requires": {
+                        "ansi-styles": "^3.2.1",
+                        "escape-string-regexp": "^1.0.5",
+                        "supports-color": "^5.3.0"
+                    }
+                },
+                "color-convert": {
+                    "version": "1.9.3",
+                    "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+                    "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+                    "requires": {
+                        "color-name": "1.1.3"
+                    }
+                },
+                "color-name": {
+                    "version": "1.1.3",
+                    "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+                    "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+                },
+                "supports-color": {
+                    "version": "5.5.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+                    "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+                    "requires": {
+                        "has-flag": "^3.0.0"
+                    }
+                }
             }
         },
         "make-dir": {
@@ -2909,11 +3016,18 @@
             "dev": true
         },
         "supports-color": {
-            "version": "5.5.0",
-            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "version": "7.1.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
+            "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
             "requires": {
-                "has-flag": "^3.0.0"
+                "has-flag": "^4.0.0"
+            },
+            "dependencies": {
+                "has-flag": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+                    "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+                }
             }
         },
         "table": {
diff --git a/package.json b/package.json
index b75166c..3c90c97 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,7 @@
     },
     "scripts": {
         "pretest": "npm install --no-save ngrok",
-        "test": "nyc mocha test/**/*.test.js && test/install/test-npm-install.sh",
+        "test": "WSKDEBUG_SILENT=1 nyc mocha test/**/*.test.js && test/install/test-npm-install.sh",
         "posttest": "eslint .",
         "report-coverage": "nyc report --reporter=json && codecov -f coverage/coverage-final.json"
     },
@@ -42,6 +42,7 @@
         "file": "test/logfile.setup.js"
     },
     "dependencies": {
+        "chalk": "^4.0.0",
         "clone": "^2.1.2",
         "debug": "^4.1.1",
         "fetch-retry": "^3.1.0",
diff --git a/src/agentmgr.js b/src/agentmgr.js
index f96c6fa..3905847 100644
--- a/src/agentmgr.js
+++ b/src/agentmgr.js
@@ -27,8 +27,8 @@ try {
 
 const fs = require('fs-extra');
 const sleep = require('util').promisify(setTimeout);
-const debug = require('./debug');
 const clone = require('clone');
+const log = require('./log');
 
 function getAnnotation(action, key) {
     const a = action.annotations.find(a => a.key === key);
@@ -78,7 +78,7 @@ async function deleteActionIfExists(wsk, name) {
     if (await actionExists(wsk, name)) {
         await wsk.actions.delete(name);
     }
-    debug(`restore: ensured removal of action ${name}`);
+    log.debug(`restore: ensured removal of action ${name}`);
 }
 
 
@@ -99,9 +99,6 @@ class AgentMgr {
      * Fast way to get just the action metadata
      */
     async peekAction() {
-        if (this.argv.verbose) {
-            console.log(`Getting action metadata from OpenWhisk: ${this.actionName}`);
-        }
         let action = await getWskActionWithoutCode(this.wsk, this.actionName);
         if (action === null) {
             throw new Error(`Action not found: ${this.actionName}`);
@@ -126,7 +123,7 @@ class AgentMgr {
                     throw new Error(`Dang! Agent is already installed and action backup is broken (${backupName}).\n\nPlease redeploy your action first before running wskdebug again.`);
 
                 } else {
-                    console.warn("Agent was already installed, but backup is still present. All good.");
+                    log.warn("Agent was already installed, but backup is still present. All good.");
 
                     // need to look at the original action
                     action = backup;
@@ -148,14 +145,10 @@ class AgentMgr {
     }
 
     async readActionWithCode() {
-        if (this.argv.verbose) {
-            console.log(`Fetching action code from OpenWhisk: ${this.actionName}`);
-        }
-
         // user can switch between agents (ngrok or not), hence we need to restore first
         // (better would be to track the agent + its version and avoid a restore, but that's TBD)
         if (this.agentInstalled) {
-            this.actionWithCode = await this.restoreAction();
+            this.actionWithCode = await this.restoreAction(true);
         } else {
             this.actionWithCode = await this.wsk.actions.get(this.actionName);
         }
@@ -167,7 +160,7 @@ class AgentMgr {
         return this.actionWithCode;
     }
 
-    async installAgent(invoker, debugTask) {
+    async installAgent(invoker, debug2) {
         this.agentInstalled = true;
 
         let agentName;
@@ -189,15 +182,17 @@ class AgentMgr {
             // agent using ngrok for forwarding
             agentName = "ngrok";
             agentCode = await this.ngrokAgent.getAgent(agentAction);
-            debugTask("started local ngrok proxy");
+            debug2("started local ngrok proxy");
 
         } else {
             if (this.argv.disableConcurrency) {
                 this.concurrency = false;
+
             } else {
                 this.concurrency = await this.openwhiskSupports("concurrency");
+                debug2(`fetched openwhisk /api/v1/api-docs to detect concurrency`);
                 if (!this.concurrency) {
-                    console.warn("This OpenWhisk does not support action concurrency. Debugging will be a bit slower. Consider using '--ngrok' which might be a faster option.");
+                    log.warn("This OpenWhisk does not support action concurrency. Debugging will be a bit slower. Consider using '--ngrok' which might be a faster option.");
                 }
             }
 
@@ -214,26 +209,18 @@ class AgentMgr {
 
         const backupName = getActionCopyName(this.actionName);
 
-        if (this.argv.verbose) {
-            console.log(`Installing agent in OpenWhisk (${agentName})...`);
-        }
-
         // create copy in case wskdebug gets killed hard
         // do async as this can be slow for larger actions and this is part of the critical startup path
         this.createBackup = (async () => {
-            const debugTask = debug.task();
+            const debug3 = log.newDebug();
 
             await this.wsk.actions.update({
                 name: backupName,
                 action: agentAction
             });
-            debugTask(`created action backup ${backupName}`);
+            debug3(`created action backup ${backupName}`);
         })();
 
-        if (this.argv.verbose) {
-            console.log(`Original action will be backed up at ${backupName}.`);
-        }
-
         if (this.argv.condition) {
             agentAction.parameters.push({
                 key: "$condition",
@@ -246,17 +233,13 @@ class AgentMgr {
         } catch (e) {
             // openwhisk does not support concurrent nodejs actions, try with another
             if (e.statusCode === 400 && e.error && typeof e.error.error === "string" && e.error.error.includes("concurrency")) {
-                console.log(`The Openwhisk server does not support concurrent actions, using alternative agent. Consider using --ngrok for a possibly faster agent.`);
+                log.log(`The Openwhisk server does not support concurrent actions, using alternative agent. Consider using --ngrok for a possibly faster agent.`);
                 this.concurrency = false;
                 agentCode = await this.getPollingActivationDbAgent();
                 await this.pushAgent(agentAction, agentCode, backupName);
             }
         }
-        debugTask(`installed agent '${agentName}' in place of ${this.actionName}`);
-
-        if (this.argv.verbose) {
-            console.log(`Agent installed.`);
-        }
+        debug2(`installed agent type '${agentName}' in place of action '${this.actionName}'`);
     }
 
     stop() {
@@ -276,7 +259,7 @@ class AgentMgr {
         } finally {
             if (this.ngrokAgent) {
                 await this.ngrokAgent.stop();
-                debug("ngrok shut down");
+                log.debug("ngrok shut down");
             }
         }
     }
@@ -302,9 +285,8 @@ class AgentMgr {
                         blocking: true
                     });
 
-                    if (this.argv.verbose) {
-                        process.stdout.write(".");
-                    }
+                    log.verboseWrite(".");
+
                 } else {
                     // poll for the newest activation
                     const since = Date.now();
@@ -339,18 +321,14 @@ class AgentMgr {
                             }
                         }
 
-                        if (this.argv.verbose) {
-                            process.stdout.write(".");
-                        }
+                        log.verboseWrite(".");
 
                         // need to limit load on openwhisk (activation list)
                         await sleep(1000);
                     }
                 }
 
-                if (this.argv.verbose) {
-                    process.stdout.write(".");
-                }
+                log.verboseWrite(".");
 
                 // check for successful response with a new activation
                 if (activation && activation.response) {
@@ -359,13 +337,9 @@ class AgentMgr {
                     // mark this as seen so we don't reinvoke it
                     this.activationsSeen[activation.activationId] = true;
 
-                    if (this.argv.verbose) {
-                        console.log();
-                        console.info(`Activation: ${params.$activationId}`);
-                        console.log(params);
-                    } else {
-                        console.info(`Activation: ${params.$activationId}`);
-                    }
+                    log.verbose(); // because of the .....
+                    log.log();
+                    log.highlight("Activation: ", params.$activationId);
                     return params;
 
                 } else if (activation && activation.activationId) {
@@ -377,7 +351,7 @@ class AgentMgr {
 
                 } else {
                     // unexpected, just log and retry
-                    console.log("Unexpected empty response while waiting for new activations:", activation);
+                    log.log("Unexpected empty response while waiting for new activations:", activation);
                 }
 
             } catch (e) {
@@ -385,30 +359,26 @@ class AgentMgr {
                 const errorCode = getActivationError(e).code;
                 if (errorCode === 42) {
                     // 42 => retry, do nothing here (except logging progress)
-                    if (this.argv.verbose) {
-                        process.stdout.write(".");
-                    }
+                    log.verboseWrite(".");
 
                 } else if (errorCode === 43) {
                     // 43 => graceful shutdown (for unit tests)
-                    console.log("Graceful shutdown requested by agent (only for unit tests)");
+                    log.log("Graceful shutdown requested by agent (only for unit tests)");
                     return null;
 
                 } else if (e.statusCode === 503 && !this.concurrency) {
                     // 503 => openwhisk activation DB likely overloaded with requests, warn, wait a bit and retry
 
-                    if (this.argv.verbose) {
-                        console.log("x");
-                    }
-                    console.warn("Server responded with 503 while looking for new activation records. Consider using --ngrok option.")
+                    log.verbose("x");
+                    log.warn("Server responded with 503 while looking for new activation records. Consider using --ngrok option.")
 
                     await sleep(5000);
 
                 } else {
                     // otherwise log error and abort
-                    console.error();
-                    console.error("Unexpected error while polling agent for activation:");
-                    console.dir(e, { depth: null });
+                    log.error();
+                    log.error("Unexpected error while polling agent for activation:");
+                    log.deepObject(e);
                     throw new Error("Unexpected error while polling agent for activation.");
                 }
             }
@@ -419,10 +389,8 @@ class AgentMgr {
     }
 
     async completeActivation(activationId, result, duration) {
-        console.info(`Completed activation ${activationId} in ${duration/1000.0} sec`);
-        if (this.argv.verbose) {
-            console.log(result);
-        }
+        log.succeed(`Completed activation ${activationId} in ` + log.highlightColor(`${duration/1000.0} sec`));
+        log.verbose("Result:", result);
 
         try {
             result.$activationId = activationId;
@@ -439,10 +407,10 @@ class AgentMgr {
                 // do nothing
             } else if (errorCode === 43) {
                 // 43 => graceful shutdown (for unit tests)
-                console.log("Graceful shutdown requested by agent (only for unit tests)");
+                log.log("Graceful shutdown requested by agent (only for unit tests)");
                 return false;
             } else {
-                console.error("Unexpected error while completing activation:", e);
+                log.error("Unexpected error while completing activation:", e);
             }
         }
         return true;
@@ -450,12 +418,7 @@ class AgentMgr {
 
     // --------------------------------------< restoring >------------------
 
-    async restoreAction() {
-        if (this.argv.verbose) {
-            console.log();
-            console.log(`Restoring action`);
-        }
-
+    async restoreAction(isStartup) {
         const copy = getActionCopyName(this.actionName);
 
         try {
@@ -470,7 +433,7 @@ class AgentMgr {
             } else {
                 // the original was fetched before or was backed up in the copy
                 original = await this.wsk.actions.get(copy)
-                debug("restore: fetched action original from backup copy");
+                log.debug("restore: fetched action original from backup copy");
             }
 
             // copy the backup (copy) to the regular action
@@ -478,31 +441,34 @@ class AgentMgr {
                 name: this.actionName,
                 action: original
             });
-            debug("restore: restored original action");
+            log.debug("restore: restored original action");
 
             if (this.argv.cleanup) {
-                console.log("Removing extra actions due to --cleanup...");
+                if (!isStartup) {
+                    log.log("Removing helper actions due to --cleanup...");
+                }
                 // remove the backup
                 await this.wsk.actions.delete(copy);
-                debug("restore: deleted backup copy");
+                log.debug("restore: deleted backup copy");
 
                 // remove any helpers if they exist
                 await deleteActionIfExists(this.wsk, `${this.actionName}_wskdebug_invoked`);
                 await deleteActionIfExists(this.wsk, `${this.actionName}_wskdebug_completed`);
 
-            } else {
-                console.warn(`Skipping removal of extra actions. Remove using --cleanup if desired:`);
-                console.warn(`- ${copy}`);
-                if (!this.concurrency) {
-                    console.warn(`- ${this.actionName}_wskdebug_invoked`);
-                    console.warn(`- ${this.actionName}_wskdebug_completed`);
+            } else if (!isStartup) {
+                log.log(`Following helper actions are not removed to make shutdown fast. Remove using --cleanup if desired.`);
+                log.log(`- ${log.highlightColor(copy)}`);
+                if (!this.concurrency && !this.ngrokAgent) {
+                    log.log("- " + log.highlightColor(`${this.actionName}_wskdebug_invoked`));
+                    log.log("- " + log.highlightColor(`${this.actionName}_wskdebug_completed`));
                 }
+                log.log();
             }
 
             return original;
 
         } catch (e) {
-            console.error("Error while restoring original action:", e);
+            log.error("Error while restoring original action:", e);
         }
     }
 
@@ -554,7 +520,7 @@ class AgentMgr {
                     { key: "wskdebug", value: true },
                     { key: "description", value: `wskdebug agent. temporarily installed over original action. original action backup at ${backupName}.` }
                 ],
-                parameters: action.parameters
+                parameters: action.parameters || []
             }
         });
     }
@@ -578,7 +544,7 @@ class AgentMgr {
                 ]
             }
         });
-        debug(`created helper action ${actionName}`);
+        log.debug(`created helper action ${actionName}`);
     }
 
     // ----------------------------------------< openwhisk feature detection >-----------------
@@ -593,7 +559,7 @@ class AgentMgr {
                     this.openwhiskVersion = null;
                 }
             } catch (e) {
-                console.warn("Could not retrieve OpenWhisk version:", e.message);
+                log.warn("Could not retrieve OpenWhisk version:", e.message);
                 this.openwhiskVersion = null;
             }
         }
@@ -615,7 +581,7 @@ class AgentMgr {
                         return swagger.definitions.ActionLimits.properties.concurrency;
                     }
                 } catch (e) {
-                    console.warn('Could not read /api/v1/api-docs, setting max action concurrency to 1')
+                    log.warn('Could not read /api/v1/api-docs, setting max action concurrency to 1')
                     return false;
                 }
             }
diff --git a/src/agents/ngrok.js b/src/agents/ngrok.js
index 1ee2a45..9a1b420 100644
--- a/src/agents/ngrok.js
+++ b/src/agents/ngrok.js
@@ -23,6 +23,7 @@ const ngrok = require('ngrok');
 const url = require('url');
 const util = require('util');
 const crypto = require("crypto");
+const log = require('../log');
 
 class NgrokAgent {
     constructor(argv, invoker) {
@@ -31,9 +32,7 @@ class NgrokAgent {
     }
 
     async getAgent(action) {
-        if (this.argv.verbose) {
-            console.log("Setting up ngrok", this.argv.ngrokRegion ? `(region: ${this.argv.ngrokRegion})` : "");
-        }
+        log.verbose("Setting up ngrok", this.argv.ngrokRegion ? `(region: ${this.argv.ngrokRegion})` : "");
 
         // 1. start local server on random port
         this.ngrokServer = http.createServer(this.ngrokHandler.bind(this));
@@ -62,7 +61,9 @@ class NgrokAgent {
             value: this.ngrokAuth
         });
 
-        console.log(`Ngrok forwarding: ${ngrokUrl} => http://localhost:${this.ngrokServerPort} (auth: ${this.ngrokAuth})`);
+        const h = log.highlightColor;
+        log.step(`Ngrok forwarding: ${h(ngrokUrl)} => http://localhost:${h(this.ngrokServerPort)}`);
+        log.debug(`ngrok agent auth key: ${this.ngrokAuth}`)
 
         return fs.readFileSync(`${__dirname}/../../agent/agent-ngrok.js`, {encoding: 'utf8'});
     }
@@ -99,33 +100,28 @@ class NgrokAgent {
             req.on('end', async () => {
                 try {
                     const params = JSON.parse(body);
-                    const id = params.$activationId;
+                    const activationId = params.$activationId;
                     delete params.$activationId;
 
-                    if (this.argv.verbose) {
-                        console.log();
-                        console.info(`Activation: ${id}`);
-                        console.log(params);
-                    } else {
-                        console.info(`Activation: ${id}`);
-                    }
+                    log.verbose(); // because of the .....
+                    log.log();
+                    log.highlight("Activation: ", activationId);
+                    log.verbose("Parameters:", params);
 
                     const startTime = Date.now();
 
-                    const result = await this.invoker.run(params, id);
+                    const result = await this.invoker.run(params, activationId);
 
                     const duration = Date.now() - startTime;
-                    console.info(`Completed activation ${id} in ${duration/1000.0} sec`);
-                    if (this.argv.verbose) {
-                        console.log(result);
-                    }
+                    log.succeed(`Completed activation ${activationId} in ` + log.highlightColor(`${duration/1000.0} sec`));
+                    log.verbose("Result:", result);
 
                     res.statusCode = 200;
                     res.setHeader("Content-Type", "application/json");
                     res.end(JSON.stringify(result));
 
                 } catch (e) {
-                    console.error(e);
+                    log.error(e);
                     res.statusCode = 400;
                     res.end();
                 }
diff --git a/src/debug.js b/src/debug.js
deleted file mode 100644
index 17ac7b9..0000000
--- a/src/debug.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-'use strict';
-
-// common debug() instance for shared time spent measurments (+millis)
-module.exports = require('debug')('wskdebug');
-
-// start a sub debug instance for logging times in parallel promises
-module.exports.task = () => {
-    const debug = require('debug')('wskdebug')
-    // trick to start time measurement from now on without logging an extra line
-    debug.log = () => {};
-    debug();
-    delete debug.log;
-    return debug;
-}
diff --git a/src/debugger.js b/src/debugger.js
index 9f1e40d..3dd127e 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -24,10 +24,17 @@ const Watcher = require('./watcher');
 const openwhisk = require('openwhisk');
 const { spawnSync } = require('child_process');
 const sleep = require('util').promisify(setTimeout);
-const debug = require('./debug');
 const prettyBytes = require('pretty-bytes');
 const prettyMilliseconds = require('pretty-ms');
-const ora = require('ora');
+const log = require('./log');
+
+function prettyMBytes1024(mb) {
+    if (mb > 1024) {
+        return `${mb/1024} GB`;
+    } else {
+        return `${mb} MB`;
+    }
+}
 
 /**
  * Central component of wskdebug.
@@ -35,14 +42,14 @@ const ora = require('ora');
 class Debugger {
     constructor(argv) {
         this.startTime = Date.now();
-        debug("starting debugger");
+        log.debug("starting debugger");
 
         this.argv = argv;
         this.actionName = argv.action;
 
         this.wskProps = wskprops.get();
         if (Object.keys(this.wskProps).length === 0) {
-            console.error(`Error: Missing openwhisk credentials. Found no ~/.wskprops file or WSK_* environment variable.`);
+            log.error(`Error: Missing openwhisk credentials. Found no ~/.wskprops file or WSK_* environment variable.`);
             process.exit(1);
         }
         if (argv.ignoreCerts) {
@@ -52,39 +59,33 @@ class Debugger {
         try {
             this.wsk = openwhisk(this.wskProps);
         } catch (err) {
-            console.error(`Error: Could not setup openwhisk client: ${err.message}`);
+            log.error(`Error: Could not setup openwhisk client: ${err.message}`);
             process.exit(1);
         }
-        this.spinner = ora({color: "blue"});
-        if (debug.enabled) {
-            // disable spinner from outputting anything since we have all the debug() logs
-            this.spinner.start = () => {};
-            this.spinner.stop = () => {};
-        }
-        this.spinner.start(`Starting`);
+
+        const h = log.highlightColor;
+        log.spinner("Debugging " + h(`/_/${this.actionName}`) + " on " + h(this.wskProps.apihost));
     }
 
     async start() {
         this.agentMgr = new AgentMgr(this.argv, this.wsk, this.actionName);
-        this.watcher = new Watcher(this.argv, this.wsk, this.spinner);
+        this.watcher = new Watcher(this.argv, this.wsk);
 
-        this.spinner.start(`Inspecting action`);
         // get the action metadata
-        const actionMetadata = await this.agentMgr.peekAction();
-        debug("fetched action metadata from openwhisk");
-        this.wskProps.namespace = actionMetadata.namespace;
+        this.actionMetadata = await this.agentMgr.peekAction();
+        log.debug("fetched action metadata from openwhisk");
+        this.wskProps.namespace = this.actionMetadata.namespace;
 
-        this.spinner.stop();
-        console.info(`Debugging /${this.wskProps.namespace}/${this.actionName}`);
+        const h = log.highlightColor;
+        log.step("Debugging " + h(`/${this.wskProps.namespace}/${this.actionName}`) + " on " + h(this.wskProps.apihost));
 
         // local debug container
-        this.invoker = new OpenWhiskInvoker(this.actionName, actionMetadata, this.argv, this.wskProps, this.wsk, this.spinner);
+        this.invoker = new OpenWhiskInvoker(this.actionName, this.actionMetadata, this.argv, this.wskProps, this.wsk);
 
         try {
             // run build initially (would be required by starting container)
             if (this.argv.onBuild) {
-                this.spinner.stop();
-                console.info("=> Build:", this.argv.onBuild);
+                log.highlight("On build: ", this.argv.onBuild);
                 spawnSync(this.argv.onBuild, {shell: true, stdio: "inherit"});
             }
             await this.invoker.prepare();
@@ -93,21 +94,21 @@ class Debugger {
 
             // task 1 - start local container
             const containerTask = (async () => {
-                const debugTask = debug.task();
-                this.spinner.start('Starting local container');
+                const debug2 = log.newDebug();
+                log.spinner('Starting local container');
 
                 // start container - get it up fast for VSCode to connect within its 10 seconds timeout
-                await this.invoker.startContainer();
+                await this.invoker.startContainer(debug2);
 
-                debugTask(`started container: ${this.invoker.name()}`);
+                debug2(`started container: ${this.invoker.name()}`);
             })();
 
             // task 2 - fetch action code from openwhisk
             const openwhiskTask = (async () => {
-                const debugTask = debug.task();
+                const debug2 = log.newDebug();
                 const actionWithCode = await this.agentMgr.readActionWithCode();
 
-                debugTask(`got action code (${prettyBytes(actionWithCode.exec.code.length)})`);
+                debug2(`downloaded action code (${prettyBytes(actionWithCode.exec.code.length)})`);
                 return actionWithCode;
             })();
 
@@ -115,56 +116,41 @@ class Debugger {
             const results = await Promise.all([containerTask, openwhiskTask]);
             const actionWithCode = results[1];
 
-            this.spinner.start('Installing agent');
+            log.spinner('Installing agent');
 
             // parallelize slower work using promises again
 
             // task 3 - initialize local container with code
             const initTask = (async () => {
-                const debugTask = debug.task();
+                const debug2 = log.newDebug();
 
                 // /init local container
                 await this.invoker.init(actionWithCode);
 
-                debugTask("installed action on container");
+                debug2("installed action on container");
             })();
 
             // task 4 - install agent in openwhisk
             const agentTask = (async () => {
-                const debugTask = debug.task();
+                const debug2 = log.newDebug();
 
                 // setup agent in openwhisk
-                await this.agentMgr.installAgent(this.invoker, debugTask);
+                await this.agentMgr.installAgent(this.invoker, debug2);
             })();
 
             await Promise.all([initTask, agentTask]);
 
             if (this.argv.onStart) {
-                this.spinner.stop();
-                console.log("On start:", this.argv.onStart);
+                log.highlight("On start: ", this.argv.onStart);
                 spawnSync(this.argv.onStart, {shell: true, stdio: "inherit"});
             }
 
             // start source watching (live reload) if requested
             await this.watcher.start();
 
-            this.spinner.stop();
-
-            console.log();
-            console.info(`Action     : /${this.wskProps.namespace}/${this.actionName}`);
-            if (this.sourcePath) {
-                console.info(`Sources    : ${this.invoker.getSourcePath()}`);
-            }
-            console.info(`Image      : ${this.invoker.getImage()}`);
-            console.info(`OpenWhisk  : ${this.wskProps.apihost}`);
-            console.info(`Container  : ${this.invoker.name()}`);
-            console.info(`Debug type : ${this.invoker.getDebugKind()}`);
-            console.info(`Debug port : localhost:${this.invoker.getPort()}`);
-            if (this.argv.condition) {
-                console.info(`Condition  : ${this.argv.condition}`);
-            }
-            console.log();
-            console.info(`Ready for activations. Started in ${prettyMilliseconds(Date.now() - this.startTime)}. Use CTRL+C to exit`);
+            this.logDetails();
+            const abortMsg = log.isInteractive ? log.highlightColor(" Use CTRL+C to exit.") : "";
+            log.ready(`Ready for activations. Started in ${prettyMilliseconds(Date.now() - this.startTime)}.${abortMsg}`);
 
             this.ready = true;
 
@@ -174,6 +160,30 @@ class Debugger {
         }
     }
 
+    async logDetails() {
+        log.log();
+        log.highlight("Action     : ", `/${this.wskProps.namespace}/${this.actionName}`);
+        if (this.sourcePath) {
+            log.highlight("Sources    : ", `${this.invoker.getSourcePath()}`);
+        }
+        log.highlight("Image      : ", `${this.invoker.getImage()}`);
+        log.highlight("Container  : ", `${this.invoker.name()}`);
+        if (this.actionMetadata.limits) {
+            if (this.actionMetadata.limits.memory) {
+                log.highlight("Memory     : ", `${prettyMBytes1024(this.actionMetadata.limits.memory)}`);
+            }
+            if (this.actionMetadata.limits.timeout) {
+                log.highlight("Timeout    : ", `${prettyMilliseconds(this.actionMetadata.limits.timeout, {verbose:true})}`);
+            }
+        }
+        log.highlight("Debug type : ", `${this.invoker.getDebugKind()}`);
+        log.highlight("Debug port : ", `localhost:${this.invoker.getPort()}`);
+        if (this.argv.condition) {
+            log.highlight("Condition  : ", `${this.argv.condition}`);
+        }
+        log.log();
+    }
+
     async run() {
         return this.runPromise = this._run();
     }
@@ -203,6 +213,7 @@ class Debugger {
 
                     const id = activation.$activationId;
                     delete activation.$activationId;
+                    log.verbose("Parameters:", activation);
 
                     const startTime = Date.now();
 
@@ -256,13 +267,15 @@ class Debugger {
         }
         this.shuttingDown = true;
         const shutdownStart = Date.now();
-        debug("shutting down...");
 
         // only log this if we started properly
         if (this.ready) {
-            console.log();
-            console.log();
-            console.log("Shutting down...");
+            log.log();
+            log.log();
+            log.debug("shutting down...");
+            log.spinner("Shutting down");
+        } else {
+            log.debug("aborting start - shutting down ...");
         }
 
         // need to shutdown everything even if some fail, hence tryCatch() for each
@@ -272,23 +285,23 @@ class Debugger {
         }
         if (this.invoker) {
             await this.tryCatch(this.invoker.stop());
-            debug(`stopped container: ${this.invoker.name()}`);
+            log.debug(`stopped container: ${this.invoker.name()}`);
         }
         if (this.watcher) {
             await this.tryCatch(this.watcher.stop());
-            debug("stopped source file watching");
+            log.debug("stopped source file watching");
         }
 
         // only log this if we started properly
         if (this.ready) {
-            console.log(`Done (shutdown took ${prettyMilliseconds(Date.now() - shutdownStart)})`);
+            log.succeed(`Done. Shutdown in ${prettyMilliseconds(Date.now() - shutdownStart)}.`);
         }
         this.ready = false;
     }
 
     // ------------------------------------------------< utils >-----------------
 
-    async tryCatch(task, message="Error during shutdown:") {
+    async tryCatch(task) {
         try {
             if (typeof task === "function") {
                 task();
@@ -296,13 +309,7 @@ class Debugger {
                 await task;
             }
         } catch (e) {
-            console.log(e);
-            if (this.argv.verbose) {
-                console.error(message);
-                console.error(e);
-            } else {
-                console.error(message, e.message);
-            }
+            log.exception(e, "Error during shutdown:");
         }
     }
 
diff --git a/src/invoker.js b/src/invoker.js
index 98bb1d3..32b9d65 100644
--- a/src/invoker.js
+++ b/src/invoker.js
@@ -21,6 +21,7 @@ const { spawn, execSync } = require('child_process');
 const fetch = require('fetch-retry')(require('isomorphic-fetch'));
 const kinds = require('./kinds/kinds');
 const path = require('path');
+const log = require("./log");
 
 const RUNTIME_PORT = 8080;
 const INIT_RETRY_DELAY_MS = 100;
@@ -31,12 +32,11 @@ const OPENWHISK_DEFAULTS = {
     memory: 256
 };
 
-function execute(cmd, options, verbose) {
+function execute(cmd, options, debug2) {
     cmd = cmd.replace(/\s+/g, ' ');
-    if (verbose) {
-        console.log(cmd);
-    }
     const result = execSync(cmd, options);
+
+    (debug2 || log.debug)(`executed: ${cmd}`);
     if (result) {
         return result.toString().trim();
     } else {
@@ -55,7 +55,7 @@ function resolveValue(value, ...args) {
 }
 
 class OpenWhiskInvoker {
-    constructor(actionName, action, options, wskProps, wsk, spinner) {
+    constructor(actionName, action, options, wskProps, wsk) {
         this.actionName = actionName;
         this.action = action;
 
@@ -65,7 +65,6 @@ class OpenWhiskInvoker {
         this.internalPort = options.internalPort;
         this.command = options.command;
         this.dockerArgs = options.dockerArgs;
-        this.verbose = options.verbose;
 
         // the build path can be separate, if not, same as the source/watch path
         this.sourcePath = options.buildPath || options.sourcePath;
@@ -80,8 +79,6 @@ class OpenWhiskInvoker {
         this.wskProps = wskProps;
         this.wsk = wsk;
 
-        this.spinner = spinner;
-
         this.containerName = this.asContainerName(`wskdebug-${this.action.name}-${Date.now()}`);
     }
 
@@ -109,14 +106,12 @@ class OpenWhiskInvoker {
                 }
                 return runtimes[kind];
 
-            } else if (this.verbose) {
-                console.warn("Could not retrieve runtime images from OpenWhisk, using default image list.");
+            } else {
+                log.warn("Could not retrieve runtime images from OpenWhisk, using default image list.");
             }
 
         } catch (e) {
-            if (this.verbose) {
-                console.warn("Could not retrieve runtime images from OpenWhisk, using default image list.", e.message);
-            }
+            log.warn("Could not retrieve runtime images from OpenWhisk, using default image list.", e.message);
         }
         return kinds.images[kind];
     }
@@ -148,9 +143,7 @@ class OpenWhiskInvoker {
         try {
             this.debug = require(`${__dirname}/kinds/${this.debugKind}/${this.debugKind}`);
         } catch (e) {
-            if (this.verbose) {
-                console.error(`Cannot find debug info for kind ${this.debugKind}:`, e.message);
-            }
+            log.warn(`Cannot find debug info for kind ${this.debugKind}:`, e.message);
             this.debug = {};
         }
 
@@ -177,7 +170,7 @@ class OpenWhiskInvoker {
         // source mounting
         if (this.sourcePath) {
             if (!this.debug.mountAction) {
-                console.warn(`Warning: Sorry, mounting sources not yet supported for: ${kind}.`);
+                log.warn(`Warning: Sorry, mounting sources not yet supported for: ${kind}.`);
                 this.sourcePath = undefined;
             }
         }
@@ -190,8 +183,8 @@ class OpenWhiskInvoker {
         }
     }
 
-    async startContainer() {
-        let showDockerRunOutput = this.verbose;
+    async startContainer(debug2) {
+        let showDockerRunOutput = log.isVerbose;
 
         // quick fail for missing requirements such as docker not running
         await this.checkIfDockerAvailable();
@@ -201,7 +194,7 @@ class OpenWhiskInvoker {
         } catch (e) {
             // make sure the user can see the image download process as part of docker run
             showDockerRunOutput = true;
-            console.log(`
+            log.warn(`
 +------------------------------------------------------------------------------------------+
 | Docker image must be downloaded: ${this.image}
 |                                                                                          |
@@ -215,10 +208,6 @@ class OpenWhiskInvoker {
 `);
         }
 
-        if (this.verbose) {
-            console.log(`Starting local debug container ${this.name()}`);
-        }
-
         execute(
             `docker run
                 -d
@@ -234,12 +223,12 @@ class OpenWhiskInvoker {
             `,
             // live stream view for docker image download output
             { stdio: showDockerRunOutput ? "inherit" : null },
-            this.verbose
+            debug2
         );
 
         this.containerRunning = true;
 
-        this.spinner.stop();
+        log.stopSpinner();
         spawn("docker", ["logs", "-t", "-f", this.name()], {
             stdio: [
                 "inherit", // stdin
@@ -268,16 +257,9 @@ class OpenWhiskInvoker {
     async init(actionWithCode) {
         let action;
         if (this.sourceMountAction) {
-            if (this.verbose) {
-                console.log(`Mounting sources onto local debug container: ${this.sourcePath}`);
-            }
-
             action = this.sourceMountAction;
 
         } else {
-            if (this.verbose) {
-                console.log(`Pushing action code to local debug container: ${this.action.name}`);
-            }
             action = {
                 binary: actionWithCode.exec.binary,
                 main:   actionWithCode.exec.main || "main",
@@ -327,9 +309,6 @@ class OpenWhiskInvoker {
 
     async stop() {
         if (this.containerRunning) {
-            if (this.verbose) {
-                console.log("Stopping local debug container");
-            }
             execute(`docker kill ${this.name()}`);
         }
     }
diff --git a/src/log.js b/src/log.js
new file mode 100644
index 0000000..fc39e25
--- /dev/null
+++ b/src/log.js
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const dbg = require('debug');
+const ora = require('ora');
+const chalk = require('chalk');
+
+const INFO_COLOR_ANSI = 36;
+const infoColor = chalk.ansi(INFO_COLOR_ANSI);
+const highlightColor = chalk.magenta;
+
+const spinner = ora({
+    color: "cyan",
+    stream: process.stdout
+});
+
+const DEBUG_NAMESPACE = "wskdebug"
+const debug = dbg(DEBUG_NAMESPACE);
+
+const noop = () => {};
+
+// no emoji support in windows terminal
+const useEmoji = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color';
+const symbols = useEmoji ? {
+    step: '❯',
+    success: '✔',
+    ready: '🚀'
+} : {
+    step: '-',
+    success: '√',
+    ready: '>'
+};
+
+if (debug.enabled || !spinner.isEnabled) {
+    // disable spinner since we have all the debug() logs saying similar stuff
+    spinner.start = noop;
+    spinner.stop = noop;
+}
+
+let originalConsole = null;
+
+module.exports = {
+
+    isVerbose: false,
+
+    silent: function(silent) {
+        if (silent) {
+            // silent wins
+            this.isVerbose = false;
+            dbg.disable();
+
+            this.log = noop;
+            this.step = noop;
+            this.highlight = noop;
+            this.warn = noop;
+            this.verboseWrite = noop;
+            this.deepObject = noop;
+            spinner.start = noop;
+            spinner.stopAndPersist = noop;
+        }
+    },
+
+    /** Important step message, prefixed with symbol, visible by default. Ends any running spinner(). */
+    step: function(text) {
+        spinner.stopAndPersist({
+            symbol: infoColor(symbols.step),
+            text: spinner.isEnabled ? infoColor(text) : text
+        });
+    },
+
+    highlight: function(text, highlight) {
+        this.step(text + highlightColor(highlight));
+    },
+
+    highlightColor,
+
+    /** Basic log message, visible by default. Ends any running spinner(). */
+    log: function(...args) {
+        spinner.stop();
+        // goes to stdout
+        console.info(...args);
+    },
+
+    /** Warning message, visible by default. Ends any running spinner(). */
+    warn: function(...args) {
+        spinner.stop();
+        // goes to stderr
+        console.warn(...args);
+    },
+
+    /** Error message, visible by default. Ends any running spinner(). */
+    error: function(...args) {
+        spinner.stop();
+        // goes to stderr
+        console.error(...args);
+    },
+
+    verbose: function(...args) {
+        if (this.isVerbose) {
+            this.log(...args);
+        }
+    },
+
+    verboseWrite: function(text) {
+        if (this.isVerbose) {
+            process.stdout.write(text);
+        }
+    },
+
+    exception: function(err, message="Error:") {
+        // stacktrace only in verbose
+        if (this.isVerbose) {
+            this.error(err);
+        } else {
+            this.error(message, err.message);
+        }
+    },
+
+    deepObject: function(obj) {
+        console.dir(obj, { depth: null });
+    },
+
+    // common debug() instance for shared time spent measurments (+millis)
+    debug,
+
+    /**
+     * Create a new "child" debug instance for logging times in parallel promises
+     */
+    newDebug: function() {
+        const debug = dbg(DEBUG_NAMESPACE);
+        // trick to start time measurement from now on without logging an extra line
+        debug.log = () => {};
+        debug();
+        delete debug.log;
+        return debug;
+    },
+
+    /** Start a spinner on the console */
+    spinner: function(text) {
+        spinner.start(infoColor(text) + " ");
+    },
+
+    /** Stop a running spinner().  */
+    stopSpinner: function() {
+        spinner.stop();
+    },
+
+    /** Finish any running spinner and show a log message with a success symbol in front. */
+    succeed: function(text) {
+        spinner.stopAndPersist({
+            symbol: chalk.green(symbols.success),
+            text: infoColor(text)
+        });
+    },
+
+    /** Finish any running spinner and show a log message with a ready symbol in front. */
+    ready: function(text) {
+        spinner.stopAndPersist({
+            symbol: infoColor(symbols.ready),
+            text: infoColor(text)
+        });
+    },
+
+    enableConsoleColors: function() {
+        // colorful console.log() and co
+        if (!console._logToFile) {
+            originalConsole = {
+                log: console.log,
+                error: console.error,
+                info: console.info,
+                debug: console.debug
+            };
+            // overwrites console.*()
+            const manakin = require('manakin').global;
+            manakin.info.color = INFO_COLOR_ANSI;
+
+            // no bright as it might not look good on terminals with white background
+            //manakin.setBright();
+        }
+        return originalConsole;
+    },
+
+    resetConsoleColors: function() {
+        if (originalConsole) {
+            console.log = originalConsole.log;
+            console.error = originalConsole.error;
+            console.info = originalConsole.info;
+            console.debug = originalConsole.debug;
+        }
+    },
+
+    isInteractive: spinner.isEnabled
+};
+
+if (process.env.WSKDEBUG_SILENT) {
+    module.exports.silent(true);
+}
diff --git a/src/watcher.js b/src/watcher.js
index dba16a3..45558b5 100644
--- a/src/watcher.js
+++ b/src/watcher.js
@@ -20,13 +20,12 @@
 const fs = require('fs-extra');
 const livereload = require('livereload');
 const { spawnSync } = require('child_process');
-const debug = require('./debug');
+const log = require('./log');
 
 class Watcher {
-    constructor(argv, wsk, spinner) {
+    constructor(argv, wsk) {
         this.argv = argv;
         this.wsk = wsk;
-        this.spinner = spinner;
     }
 
     async start() {
@@ -39,7 +38,7 @@ class Watcher {
              || this.argv.invokeParams
              || this.argv.invokeAction )
         ) {
-            this.spinner.start('Initializing source watching');
+            log.spinner('Initializing source watching');
 
             this.liveReloadServer = livereload.createServer({
                 port: this.argv.livereloadPort,
@@ -58,9 +57,7 @@ class Watcher {
                 try {
                     let result = [];
 
-                    if (argv.verbose) {
-                        console.log("File modified:", filepath);
-                    }
+                    log.verbose("File modified:", filepath);
 
                     // call original function if we are listening
                     if (argv.livereload) {
@@ -69,13 +66,13 @@ class Watcher {
 
                     // run build command before invoke triggers below
                     if (argv.onBuild) {
-                        console.info("=> Build:", argv.onBuild);
+                        log.highlight("On build: ", argv.onBuild);
                         spawnSync(argv.onBuild, {shell: true, stdio: "inherit"});
                     }
 
                     // run shell command
                     if (argv.onChange) {
-                        console.info("=> Run:", argv.onChange);
+                        log.highlight("On run: ", argv.onChange);
                         spawnSync(argv.onChange, {shell: true, stdio: "inherit"});
                     }
 
@@ -94,22 +91,22 @@ class Watcher {
                             name: action,
                             params: json
                         }).then(response => {
-                            console.info(`=> Invoked action ${action} with params ${argv.invokeParams}: ${response.activationId}`);
+                            log.step(`Invoked action ${action} with params ${argv.invokeParams}: ${response.activationId}`);
                         }).catch(err => {
-                            console.error("Error invoking action:", err);
+                            log.error("Error invoking action:", err);
                         });
                     }
 
                     return result;
                 } catch (e) {
-                    console.error(e);
+                    log.error(e);
                 }
             };
 
             if (this.argv.livereload) {
-                console.info(`LiveReload enabled for ${watch} on port ${this.liveReloadServer.config.port}`);
+                log.log(`LiveReload enabled for ${log.highlightColor(watch)} on port ${this.liveReloadServer.config.port}`);
             }
-            debug("started source file watching");
+            log.debug("started source file watching");
         }
     }
 
diff --git a/test/test.js b/test/test.js
index d81bee1..0aaeed6 100644
--- a/test/test.js
+++ b/test/test.js
@@ -42,9 +42,7 @@ function isDockerInstalled() {
 
 async function beforeEach() {
     process.env.WSK_CONFIG_FILE = path.join(process.cwd(), "test/wskprops");
-    // nock.recorder.rec({ enable_reqheaders_recording: true });
     openwhisk = nock(FAKE_OPENWHISK_SERVER);
-    // openwhisk.log(console.log);
     mockOpenwhiskSwagger(openwhisk);
 
     // save current working dir


[openwhisk-wskdebug] 03/03: await shutdown promise so that Debugger.run() returns only after shutdown

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alexkli pushed a commit to branch console-spinner
in repository https://gitbox.apache.org/repos/asf/openwhisk-wskdebug.git

commit 91bc1101d4185384e409741c69e6579a3de4a0b1
Author: Alexander Klimetschek <ak...@adobe.com>
AuthorDate: Sat Apr 18 00:15:45 2020 -0700

    await shutdown promise so that Debugger.run() returns only after shutdown
---
 src/debugger.js | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/debugger.js b/src/debugger.js
index 3dd127e..e7d250b 100644
--- a/src/debugger.js
+++ b/src/debugger.js
@@ -191,7 +191,6 @@ class Debugger {
     async _run() {
         try {
             this.running = true;
-            this.shuttingDown = false;
 
             // main blocking loop
             // abort if this.running is set to false
@@ -262,10 +261,15 @@ class Debugger {
 
     async shutdown() {
         // avoid duplicate shutdown on CTRL+C
-        if (this.shuttingDown) {
-            return;
+        if (!this.shutdownPromise) {
+            this.shutdownPromise = this._shutdown();
         }
-        this.shuttingDown = true;
+
+        await this.shutdownPromise;
+        delete this.shutdownPromise;
+    }
+
+    async _shutdown() {
         const shutdownStart = Date.now();
 
         // only log this if we started properly