From 2eb8ee63af0e43f7686bd35855f5f390b13a4737 Mon Sep 17 00:00:00 2001 From: Davide Cavaliere Date: Wed, 11 Feb 2026 11:11:48 +0100 Subject: [PATCH 1/4] =?UTF-8?q?feat(logging):=20enable=20human-readable=20?= =?UTF-8?q?console=20output=20by=20default=20=F0=9F=93=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enable pretty-print by default for better developer experience - Use pino-pretty as direct stream instead of pino.transport() - Simplify timestamp to show time only (HH:MM:ss.l) - Add graceful fallback when pino-pretty is not available Co-Authored-By: Claude Opus 4.5 --- api/utils/log.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/api/utils/log.js b/api/utils/log.js index 94aa25e9f10..74f50a2e828 100644 --- a/api/utils/log.js +++ b/api/utils/log.js @@ -1,5 +1,14 @@ const pino = require('pino'); +// Optional pino-pretty import for human-readable console output +let pinoPretty; +try { + pinoPretty = require('pino-pretty'); +} +catch (e) { + // pino-pretty not available, will use JSON output +} + // Optional OpenTelemetry imports let trace; let context; @@ -207,7 +216,9 @@ class LogManager { this.#prefs = loadLoggingConfig(); this.#prefs.default = this.#prefs.default || 'warn'; this.#deflt = this.#prefs.default || 'error'; - this.#prettyPrint = this.#prefs.prettyPrint || false; + // Pretty-print enabled by default for human-readable console output + // Set config.logging.prettyPrint = false for JSON output (e.g., for log aggregation) + this.#prettyPrint = this.#prefs.prettyPrint !== false; // Initialize OpenTelemetry metrics if available if (metrics) { @@ -306,22 +317,19 @@ class LogManager { } /** - * Gets or creates the pretty transport singleton - * @returns {Object|null} The pretty transport or null + * Gets or creates the pretty stream singleton + * @returns {Object|null} The pretty stream or null */ getPrettyTransport() { - if (!this.#prettyPrint) { + if (!this.#prettyPrint || !pinoPretty) { return null; } if (!this.#prettyTransport) { - this.#prettyTransport = pino.transport({ - target: 'pino-pretty', - options: { - colorize: true, - translateTime: 'SYS:standard', - ignore: 'pid,hostname' - } + this.#prettyTransport = pinoPretty({ + colorize: true, + translateTime: 'SYS:HH:MM:ss.l', + ignore: 'pid,hostname' }); } return this.#prettyTransport; From fc7f9452480ae44d99f68daa93198ef9e8983ad6 Mon Sep 17 00:00:00 2001 From: Davide Cavaliere Date: Wed, 11 Feb 2026 11:17:44 +0100 Subject: [PATCH 2/4] =?UTF-8?q?refactor(plugins):=20use=20debug-js=20for?= =?UTF-8?q?=20verbose=20plugin=20load=20errors=20=F0=9F=94=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add debug package for conditional verbose logging - Show clean one-line error on plugin load failure - Full stack trace only visible with DEBUG=countly:plugins Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 46 ++-------------------------------------- package.json | 1 + plugins/pluginManager.ts | 12 ++++++----- 3 files changed, 10 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d98d6c9f91..8e22b3566c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,6 +105,7 @@ "@types/underscore": "^1.13.0", "@typescript-eslint/eslint-plugin": "^8.54.0", "@typescript-eslint/parser": "^8.54.0", + "debug": "^4.4.3", "docdash": "^2.0.1", "env-cmd": "^10.1.0", "eslint": "^8.56.0", @@ -124,7 +125,7 @@ "typescript": "^5.8.2" }, "engines": { - "node": "^22.0.0 || ^24.0.0" + "node": "^24.0.0" } }, "api/utils/countly-request": { @@ -6397,19 +6398,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -9731,22 +9719,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -11091,20 +11063,6 @@ "node": ">=10.19.0" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/human-interval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/human-interval/-/human-interval-2.0.1.tgz", diff --git a/package.json b/package.json index 7bf83e0db1a..646eb28b9bd 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@types/underscore": "^1.13.0", "@typescript-eslint/eslint-plugin": "^8.54.0", "@typescript-eslint/parser": "^8.54.0", + "debug": "^4.4.3", "docdash": "^2.0.1", "env-cmd": "^10.1.0", "eslint": "^8.56.0", diff --git a/plugins/pluginManager.ts b/plugins/pluginManager.ts index c964cf56641..d8bdc004b20 100644 --- a/plugins/pluginManager.ts +++ b/plugins/pluginManager.ts @@ -191,6 +191,7 @@ const async: any = require('async'); const _: any = require('underscore'); const crypto: typeof import('crypto') = require('crypto'); const BluebirdPromise: any = require('bluebird'); +const debug: any = require('debug')('countly:pluginManager'); const log: LogModule = require('../api/utils/log.js'); const logDbRead: Logger = log('db:read'); const logDbWrite: Logger = log('db:write'); @@ -436,9 +437,9 @@ class PluginManager { } } catch (ex: any) { - console.log('Skipping plugin ' + pluginNames[i] + ' as we could not load it because of errors.'); - console.error(ex.stack); - console.log('Saving this plugin as disabled in db'); + const errorLine = (ex.message || '').split('\n')[0]; + console.log('Skipping plugin ' + pluginNames[i] + ': ' + errorLine); + debug('Plugin %s load error:\n%s', pluginNames[i], ex.stack); } } } @@ -1272,8 +1273,9 @@ class PluginManager { } } catch (ex: any) { - console.log('skipping plugin because of errors:' + pluginNames[i]); - console.error(ex.stack); + const errorLine = (ex.message || '').split('\n')[0]; + console.log('Skipping plugin ' + pluginNames[i] + ': ' + errorLine); + debug('Plugin %s load error:\n%s', pluginNames[i], ex.stack); } } } From d6278117cd601252c73c4f0476bb4cb513e1f0e7 Mon Sep 17 00:00:00 2001 From: Davide Cavaliere Date: Wed, 11 Feb 2026 11:35:49 +0100 Subject: [PATCH 3/4] chore: fix codacy error --- plugins/pluginManager.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/pluginManager.ts b/plugins/pluginManager.ts index d8bdc004b20..ffc4099321a 100644 --- a/plugins/pluginManager.ts +++ b/plugins/pluginManager.ts @@ -191,7 +191,7 @@ const async: any = require('async'); const _: any = require('underscore'); const crypto: typeof import('crypto') = require('crypto'); const BluebirdPromise: any = require('bluebird'); -const debug: any = require('debug')('countly:pluginManager'); +const debug: any = require('debug'); const log: LogModule = require('../api/utils/log.js'); const logDbRead: Logger = log('db:read'); const logDbWrite: Logger = log('db:write'); @@ -200,6 +200,7 @@ const exec: typeof cp.exec = cp.exec; const spawn: typeof cp.spawn = cp.spawn; const configextender: ConfigExtenderFn = require('../api/configextender'); +const d = debug('countly:pluginManager'); // ============================================================ // INTERNAL INTERFACES (used within this module only) // ============================================================ @@ -439,7 +440,7 @@ class PluginManager { catch (ex: any) { const errorLine = (ex.message || '').split('\n')[0]; console.log('Skipping plugin ' + pluginNames[i] + ': ' + errorLine); - debug('Plugin %s load error:\n%s', pluginNames[i], ex.stack); + d('Plugin %s load error:\n%s', pluginNames[i], ex.stack); } } } @@ -1275,7 +1276,7 @@ class PluginManager { catch (ex: any) { const errorLine = (ex.message || '').split('\n')[0]; console.log('Skipping plugin ' + pluginNames[i] + ': ' + errorLine); - debug('Plugin %s load error:\n%s', pluginNames[i], ex.stack); + d('Plugin %s load error:\n%s', pluginNames[i], ex.stack); } } } From bbe1fd5ca50b4cc3d649e5a5c07f3bf433a691b4 Mon Sep 17 00:00:00 2001 From: Davide Cavaliere Date: Fri, 13 Feb 2026 11:08:54 +0100 Subject: [PATCH 4/4] =?UTF-8?q?feat(logging):=20auto-detect=20output=20for?= =?UTF-8?q?mat=20based=20on=20environment=20=F0=9F=96=A5=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pretty-print for human-readable output in terminals and CI, JSON for log aggregation in production (Docker/PM2/pipes). - TTY detected → pretty format - CI env var set → pretty format - Otherwise → JSON for collectors (Loki, ELK, etc.) Explicit config still overrides: `logging.prettyPrint: true|false` Co-Authored-By: Claude Opus 4.5 --- api/utils/log.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/utils/log.js b/api/utils/log.js index 74f50a2e828..02de7b71379 100644 --- a/api/utils/log.js +++ b/api/utils/log.js @@ -216,9 +216,12 @@ class LogManager { this.#prefs = loadLoggingConfig(); this.#prefs.default = this.#prefs.default || 'warn'; this.#deflt = this.#prefs.default || 'error'; - // Pretty-print enabled by default for human-readable console output - // Set config.logging.prettyPrint = false for JSON output (e.g., for log aggregation) - this.#prettyPrint = this.#prefs.prettyPrint !== false; + // Output format determined by: explicit config > environment detection + // - prettyPrint: true → always pretty (human-readable) + // - prettyPrint: false → always JSON (for log aggregation) + // - prettyPrint: unset → auto-detect (pretty in terminal/CI, JSON in Docker/PM2/pipes) + const isInteractive = process.stdout.isTTY || !!process.env.CI; + this.#prettyPrint = this.#prefs.prettyPrint ?? isInteractive; // Initialize OpenTelemetry metrics if available if (metrics) {