diff --git a/api/utils/log.js b/api/utils/log.js index 94aa25e9f10..02de7b71379 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,12 @@ class LogManager { this.#prefs = loadLoggingConfig(); this.#prefs.default = this.#prefs.default || 'warn'; this.#deflt = this.#prefs.default || 'error'; - 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) { @@ -306,22 +320,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; 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..ffc4099321a 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'); const log: LogModule = require('../api/utils/log.js'); const logDbRead: Logger = log('db:read'); const logDbWrite: Logger = log('db:write'); @@ -199,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) // ============================================================ @@ -436,9 +438,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); + d('Plugin %s load error:\n%s', pluginNames[i], ex.stack); } } } @@ -1272,8 +1274,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); + d('Plugin %s load error:\n%s', pluginNames[i], ex.stack); } } }